# Extract images from PDF files in C#

> List every image a PDF page draws in .NET, with placement and resolution, save JPEGs as stored and decode the rest to PNG. Pure C#, MIT licensed.

Available since 1.1.

Find the images on a page, where they land and at what resolution, and save them as files.

## Short answer

`page.GetImages()` lists the images a page draws, in drawing order, with their placement.
Save a JPEG as stored with `GetEncodedData()`, or call `Decode()` to get 8 bit gray or RGB pixels
and save them with `SavePng`.

```csharp
int n = 0;
foreach (PdfPageImage image in images)
{
    n++;
    if (image.Encoding == "DCTDecode")
    {
        File.WriteAllBytes($"image-{n}.jpg", image.GetEncodedData());   // the JPEG as stored
    }
    else if (image.Decode() is PdfBitmap bitmap)
    {
        bitmap.SavePng($"image-{n}.png");                                // 8 bit gray or RGB pixels
        Console.WriteLine($"image-{n}.png: {bitmap.Format}, {bitmap.Width}x{bitmap.Height}");
    }
}
```

## What a page draws

`GetImages()` walks the page content: image XObjects, inside form XObjects too, and inline
images. The same XObject drawn twice appears twice, once per placement.

```csharp
PdfDocument doc = PdfDocument.Open("manual.pdf");
IReadOnlyList<PdfPageImage> images = doc.Pages[0].GetImages();
foreach (PdfPageImage image in images)
{
    Console.WriteLine(
        $"{image.Width}x{image.Height}, {image.Encoding ?? "raw samples"}, " +
        $"{image.HorizontalResolution:0} dpi, at {image.Bounds.Left:0},{image.Bounds.Bottom:0}");
}
```

```text
300x100, raw samples, 225 dpi, at 72,770
640x400, DCTDecode, 288 dpi, at 72,22
```

| `PdfPageImage` | Meaning |
|---|---|
| `Width`, `Height` | Size in samples |
| `BitsPerComponent` | 1, 2, 4, 8 or 16 (1 for a mask, 8 for a JPEG) |
| `Encoding` | `DCTDecode`, `JPXDecode`, `JBIG2Decode` or `CCITTFaxDecode`, or null for plain samples |
| `Bounds` | The smallest rectangle around the image on the page, in points from the bottom left, before `/Rotate` |
| `Transform` | The matrix that maps the image's unit square to the page |
| `HorizontalResolution`, `VerticalResolution` | Dots per inch, as drawn |
| `IsInline`, `IsMask` | An inline image (BI ... EI), a stencil mask |
| `Dictionary`, `Stream` | The image dictionary and XObject, for anything else |

## Encoded data

`GetEncodedData()` returns the data with general compression such as Flate undone:

- a JPEG file for `DCTDecode`, as stored, with no re-encoding,
- a JPEG 2000 codestream for `JPXDecode`,
- the raw samples when `Encoding` is null.

## Decoded pixels

`Decode()` returns a `PdfBitmap`: rows top to bottom, `Format` `Gray8` or `Rgb24`, with `Width`,
`Height`, `Stride` and `Pixels`. `SavePng(path)`, `SavePng(stream)` and `ToPng()` write it as PNG.
It returns null for an unknown encoding or a color space it does not support.

| Input | Decoded |
|---|---|
| Raw samples | 1 to 16 bits per component |
| JPEG | Inverse DCT, upsampling and color conversion as libjpeg does them, within two steps per sample |
| JPEG 2000 | JP2 files and raw codestreams (ITU-T T.800 part 1): every progression order, precincts, layers, tiles, ROI, the 5/3 and 9/7 wavelets, RCT and ICT, 1 to 16 bit components, JP2 palettes and channel definitions. Lossless images decode bit exact, lossy ones within one step of OpenJPEG |
| CCITT fax | Group 3 one and two dimensional, Group 4, bit exact with libtiff |
| JBIG2 | Generic, refinement, text and halftone regions, with JBIG2Globals |
| Color spaces | DeviceGray, DeviceRGB, DeviceCMYK, CalGray, CalRGB, ICCBased, Indexed, Lab, Separation and DeviceN |

The Decode array is applied, and stencil masks come back black where they paint. For a JPEG 2000
image, the color space in the image dictionary wins; without one, the JP2 header's applies, as
ISO 32000-1, 7.4.9 says.

## Damaged and hostile files

Decoding is bounded by the image's size and the document's decode limit. Damaged or oversized
data raises `PdfFormatException`; damaged page content is read as far as it goes.

## Limits

- Soft masks and transparency are not applied: an image with an alpha channel decodes without
  it, and neither is a JPEG 2000 image's own alpha (SMaskInData).
- JPEG 2000 ICC profiles count only for their number of components, and HTJ2K (JPEG 2000 part
  15) and part 2 extensions are not supported.
- Separation and DeviceN colors other than a Type 2 tint transform are approximated as gray.
- Rendering whole pages to images is out of scope.

## Related

- [Extract text](https://www.jadev-corp.com/guides/extract-text/)
- [OCR](https://www.jadev-corp.com/guides/ocr/): decoded scans feed the recognizer
- [Images](https://www.jadev-corp.com/guides/images/): place images on pages
- API: [PdfPageImage](https://www.jadev-corp.com/api/pdfnetstack/pdfpageimage/), [PdfBitmap](https://www.jadev-corp.com/api/pdfnetstack/pdfbitmap/), [PdfPage](https://www.jadev-corp.com/api/pdfnetstack/pdfpage/)

## Are JPEG images re-encoded when extracted?

No. GetEncodedData returns a DCTDecode image as the JPEG file stored in the PDF, byte for byte once general compression is undone.

## Which image encodings can be decoded to pixels?

Raw samples of 1 to 16 bits, JPEG, JPEG 2000, CCITT fax (Group 3 and 4) and JBIG2.

Source: https://www.jadev-corp.com/pdfnetstack/guides/extract-images/
