---
title: "Extract images from a PDF in C#"
url: https://recursiveai.org/onepdf/cookbook/extract-images/
description: "Extract images from a PDF in C#: JPEGs byte for byte, everything else as PNG with transparency, JPEG 2000 and CMYK included. Runnable code."
version: "1.15.0"
---

# Extract images from a PDF in C#

Run each page through a small class that overrides `DrawImage`: OnePDF calls it once for every image the page draws. A JPEG in a PDF is a complete JPEG file, so its bytes can be saved as they are; every other image is raw pixels, which the program writes as PNG with OneImage. It runs on a USGS fact sheet, a public PDF with JPEG 2000 photos, CMYK maps and transparent icons.

```shell
dotnet new console -o PdfImages && cd PdfImages
dotnet add package RecursiveAI.OnePdf --version 1.15.0
dotnet add package RecursiveAI.OneImage.Pdf --version 1.10.4
curl -LO https://pubs.usgs.gov/fs/2016/3020/fs20163020.pdf
```

Put this in `Program.cs` and run `dotnet run`:

```csharp
using System.Runtime.InteropServices;
using OneImage;
using OneImage.Pdf;
using OnePdf;

// OnePDF decodes Flate, LZW, CCITT and the other PDF filters.
// This adds JPEG, JPEG 2000 and JBIG2.
PdfImageCodecs.Register(PdfImageDecoderRegistry.Default);

using PdfDocument doc = PdfDocument.Open("fs20163020.pdf");
var saver = new ImageSaver("images");
foreach (PdfPage page in doc.Pages)
{
    saver.PageNumber = page.Index + 1;
    // runs the page and calls DrawImage for every image it draws
    page.Interpret(saver);
}
Console.WriteLine($"{saver.Jpegs} saved as .jpg (original bytes), "
                + $"{saver.Pngs} as .png, {saver.Skipped} skipped");

sealed class ImageSaver(string folder) : PdfDevice
{
    // an image drawn on many pages is saved once
    readonly HashSet<PdfStream> _seen = [];
    public int PageNumber { get; set; }
    public int Jpegs { get; private set; }
    public int Pngs { get; private set; }
    public int Skipped { get; private set; }

    public override void DrawImage(in PdfImage image, in PdfGraphicsState state)
    {
        if (image.Stream is { } stream && !_seen.Add(stream)) return;
        var encoded = new PdfImageOptions { PreferEncoded = true };
        PdfImageData? data = image.GetData(encoded);
        // a stencil is a shape painted in one color
        if (data is null || data.IsStencil) return;
        Directory.CreateDirectory(folder);
        int n = Jpegs + Pngs + Skipped + 1;
        string path = Path.Combine(folder, $"page{PageNumber}-{n}");

        // A gray or RGB JPEG with no mask and no /Decode is a complete JPEG file.
        if (data.EncodedFilter == "DCTDecode" && data.Alpha is null
            && data.Semantics.Decode is null
            && data.Semantics.ColorSpace?.ComponentCount is 1 or 3)
        {
            File.WriteAllBytes(path + ".jpg", data.EncodedData.ToArray());
            Jpegs++;
            return;
        }

        // decoded: 8-bit samples in their final color space, mask as alpha
        data = image.GetData();
        if (data is not { IsDecoded: true }
            || data.ColorSpace!.Kind == PdfColorSpaceKind.Lab)
        {
            Skipped++;
            return;
        }
        ToImage(data).Save(path + ".png");
        Pngs++;
    }

    static Image ToImage(PdfImageData data)
    {
        ReadOnlySpan<byte> s = data.Samples.Span;
        int w = data.Width, h = data.Height;
        PdfColorSpace cs = data.ColorSpace!;
        Image img = cs.ComponentCount switch
        {
            1 => Image.FromPixels(MemoryMarshal.Cast<byte, Gray8>(s), w, h),
            3 => Image.FromPixels(MemoryMarshal.Cast<byte, Rgb24>(s), w, h),
            _ => Image.FromPixels(MemoryMarshal.Cast<byte, Cmyk32>(s), w, h),
        };
        if (!cs.IccProfile.IsEmpty)
            // keep its profile
            img = img.AssignProfile(ColorProfile.FromIcc(cs.IccProfile.Span));
        if (cs.ComponentCount == 4)
            img = img.ConvertColor(ColorProfile.Srgb);   // PNG has no CMYK
        if (data.Alpha is { } alpha)
        {
            GrayAlpha8[] values =
                [.. alpha.Values.ToArray().Select(a => new GrayAlpha8(0, a))];
            var mask = Image.FromPixels<GrayAlpha8>(values, alpha.Width, alpha.Height);
            if (alpha.Width != w || alpha.Height != h)
                // a mask may have its own resolution
                mask = mask.Resize(w, h, Fit.Stretch);
            img = img.AddAlpha().Composite(mask, alpha: AlphaComposition.DestIn);
        }
        return img;
    }
}
```

```text
5 saved as .jpg (original bytes), 38 as .png, 0 skipped
```

The `images` folder now holds every distinct image of the six pages. Poppler’s `pdfimages -list` counts the same 43 image objects in the file, plus the soft masks that the program applies as transparency instead of saving separately.

## What each part handles

- **Duplicates.** `image.Stream` is the image object, so a logo drawn on every page is saved once. Inline images have no stream and are saved each time.
- **JPEGs as they are.** The bytes are only a faithful file when the PDF adds nothing on top: no mask, no `/Decode` array, gray or RGB. CMYK JPEGs can carry an inverting `/Decode`, so they go through the decoder.
- **Decoded pixels.** `GetData()` applies every PDF rule first: palettes, spot colors, `/Decode` arrays and color-key masks.
- **Color.** An ICC-based image keeps its profile in the PNG. CMYK is converted to sRGB. Lab images are skipped here.
- **Transparency.** `data.Alpha` is the soft mask, color-key mask or JPEG 2000 alpha, stretched to the image’s size when it has its own resolution.

## Limits

- **Images, not pages.** This saves the pictures embedded in the file. To get a picture of a whole page, with its text and drawings, render it with [OneImage](https://recursiveai.org/oneimage/).
- **Codecs.** Without `RecursiveAI.OneImage.Pdf`, OnePDF still returns JPEG bytes and decodes Flate and the other PDF filters, but JPEG 2000 and JBIG2 images come back undecoded, and you need your own PNG encoder.
- **Size limits.** An image’s declared size is checked against `PdfLimits` before any memory is allocated, and `GetData` throws `PdfLimitException` above it. That matters for PDFs users upload.
- **Stencil masks** (one-bit shapes painted in a single color, such as the black layer of some scans) are skipped.

To put an image into a PDF instead, see [Add an image to a PDF](https://recursiveai.org/onepdf/cookbook/add-image-to-pdf/). The drawing calls a device receives are in [Display-list text format](https://recursiveai.org/onepdf/docs/display-list-format/).
