Skip to content

List Images

Inventorying the images in a document means parsing each page’s content stream and looking at what it draws, because an image can appear in more than one way. This is also how you find the XObject name of a particular image, which is what remove an image watermark needs.

If you want the pixel data plus where each image lands on the page, use image extraction instead. The extractor.ExtractPageImages route returns decoded images with position, size and rotation. Parsing the content stream yourself is for when you want the raw properties: which filter, which colorspace, how many bits per component.

The two forms an image takes

FormIn the content streamHow to reach it
XObject image/Name Doresources.GetXObjectImageByName(name)
Inline imageBI ... ID ... EIthe *contentstream.ContentStreamInlineImage parameter of the BI operand

Both need handling. Inline images are meant for small bitmaps and carry their data directly in the stream, so they are invisible to anything that only looks at the resource dictionary.

A third case is not an image at all but contains them: Do can also invoke a form XObject, whose content stream draws its own images against its own resource dictionary. Recursing into those is the part most naive scans miss.

Doing it

parser := contentstream.NewContentStreamParser(contents)
operations, err := parser.Parse()
if err != nil {
    return err
}

for _, op := range *operations {
    if op.Operand != "Do" || len(op.Params) != 1 {
        continue
    }
    name := op.Params[0].(*core.PdfObjectName)

    if _, xtype := resources.GetXObjectByName(*name); xtype == model.XObjectTypeImage {
        ximg, err := resources.GetXObjectImageByName(*name)
        if err != nil {
            return err
        }
        fmt.Printf("%s: %dx%d, %s\n", *name, *ximg.Width, *ximg.Height,
            ximg.ColorSpace.String())
    }
}

Get the page’s content with page.GetAllContentStreams(), which concatenates a /Contents array into one string so the parse sees the page as a viewer does.

GetXObjectByName returns the stream and an XObjectType, one of XObjectTypeImage, XObjectTypeForm, XObjectTypePS, XObjectTypeUnknown or XObjectTypeUndefined. Undefined covers a missing entry and a malformed one alike, so a check for == XObjectTypeImage is the safe test rather than a check against form.

Recursing into a form XObject means switching resource dictionaries. Names are scoped to a resource dictionary, so /Im0 inside a form is not the page’s /Im0. Use the form’s own Resources when it has one and fall back to the page’s when it does not.

Limitations

ximg.Width, ximg.Height and ximg.BitsPerComponent are pointers and are dereferenced directly in the example. They are required entries for an image XObject, but a corrupt file that omits them will panic rather than return an error.

Colorspace names come back from ColorSpace.String(). For an ICCBased colorspace that is the wrapper name, not the underlying device space, so a JPEG embedded with its ICC profile intact reports differently from the same JPEG without one.

The walk covers page content streams and form XObjects. Images reachable only from somewhere else, an annotation appearance stream, a tiling pattern, a soft mask, a Type 3 font glyph procedure, are not visited.

An XObject drawn several times on one page is reported once. That is deliberate, since the resource entry is one image, but it means the count is of distinct images rather than of draws.

Run the example

The example prints a block per page listing each image with its filter, dimensions, color component count, colorspace and bit depth, then a summary of how many times each filter and colorspace appeared across all the files given. listImagesInContentStream does the work and is the function that recurses into form XObjects. It accepts several input paths and accumulates the summary over all of them.

git clone https://github.com/unidoc/unipdf-examples.git
cd unipdf-examples/image
go run pdf_list_images.go input.pdf

If this is your first time using UniPDF, follow the getting started guide to create an API key and set up your development environment.

View the full source
Last updated on