Pop PDF
Extract

Pages as Images

Pdf::extractAsImages() rasterizes the pages of an existing PDF into standalone image files, one per page, and returns the paths it wrote. Install the imagick extension with a PDF delegate before you call it — that extension is what does the rasterizing.

Extracting Every Page#

Two arguments — the PDF to read and the directory to write into:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages');

Given a three-page invoice.pdf, that writes invoice-01.jpg, invoice-02.jpg and invoice-03.jpg into pages/ and returns their full paths in an array keyed by 1-based page number:

PHP
[
    1 => '/path/to/pages/invoice-01.jpg',
    2 => '/path/to/pages/invoice-02.jpg',
    3 => '/path/to/pages/invoice-03.jpg',
]

The key is the page number in the source document:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages');

foreach ($images as $pageNumber => $image) {
    echo $pageNumber . ': ' . basename($image) . "\n";
}

The name in front of the page number is the source file's name without its extension, so files from several PDFs extracted into one directory stay grouped by document. Create the destination directory before the call and make it writable — the method writes into it rather than creating it.

To know how many files are coming before any are written, Build\Image\Extractor::countPages() counts a document's pages directly:

PHP
use Pop\Pdf\Build\Image\Extractor;

$extractor = new Extractor();
echo $extractor->countPages(__DIR__ . '/invoice.pdf');

That prints 3. Building an Extractor is also the cheapest way to check imagick is present, since that's where the extension is looked for.

Formats and Resolution#

The third and fourth arguments are the output format and the resolution it's rasterized at, defaulting to jpg at 300 DPI:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 150);
  • six formats are supported — jpg, jpeg, png, webp, tiff and tif — matched case-insensitively, and the format sets the extension on every file written
  • resolution decides the pixel dimensions: a US Letter page comes out 2550 by 3300 pixels at the default 300 DPI, 1275 by 1650 at 150
  • JPEG and WebP write at quality 90 rather than the encoder's own default, because the usual destination for an extracted page is OCR, where compression artifacts along text edges cost more than the bytes they save; PNG and TIFF are lossless already and are written as they come

Reading a page's own size back is a matter of getimagesize() on the returned path:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300);

[$width, $height] = getimagesize($images[1]);
echo $width . 'x' . $height;

Naming the Output Files#

The fifth argument, $filenameFormat, is a sprintf() string that receives the source file's name without its extension as argument 1 and the 1-based page number as argument 2; the extension is appended afterward, so the format string covers the stem only. It defaults to '%1$s-%2$02d', which is where invoice-01 comes from.

Dropping the stem gives files named for the page alone:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300, filenameFormat: 'page-%2$02d');

That writes page-01.png, page-02.png and page-03.png. Changing the padding and separator gives the unpadded form:

PHP
use Pop\Pdf\Pdf;

$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300, filenameFormat: 'page_%2$d');

That writes page_1.png, page_2.png and page_3.png. Both positional references stay available in any order and with any padding, so '%1$s_page_%2$03d' writes invoice_page_001.png.

Selecting Pages#

$pages and $pageLimit are the last two arguments, and they sit behind $filenameFormat, so pass them by name:

PHP
use Pop\Pdf\Pdf;

$firstTwo    = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300, pageLimit: 2);
$oneAndThree = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300, pages: [1, 3]);
  • $pages takes an array of 1-based page numbers, or a single number on its own — pages: 2 and pages: [2] mean the same thing; $pageLimit takes the first n pages of the document
  • an explicit $pages list wins outright: passing pages: [3] alongside pageLimit: 1 writes page 3, not page 1
  • filtering keeps the source document's numbering — pages: [1, 3] returns the keys 1 and 3 and writes invoice-01.png and invoice-03.png, so a result key is always the page it came from, whatever selection produced it; that's the opposite of getImageOnlyPages(), whose array is keyed from zero
  • a page number past the end of the document drops out of the selection, and a $pageLimit larger than the document extracts every page, so the count of the result tells you how much of a requested range the document actually held
PHP
use Pop\Pdf\Pdf;

$requested = [1, 9];
$images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'png', resolution: 300, pages: $requested);

echo count($images) . ' of ' . count($requested) . ' requested pages written';

Extracting for OCR#

Pdf::isImageOnlyDocument() reports whether a document is scanned images rather than real text, and gates this call naturally: when it returns true, the pages have to go through OCR instead of text extraction.

PHP
use Pop\Pdf\Pdf;

$file = __DIR__ . '/invoice.pdf';

if (Pdf::isImageOnlyDocument($file)) {
    foreach (Pdf::extractAsImages($file, __DIR__ . '/pages', format: 'png', resolution: 300) as $pageNumber => $image) {
        // hand $image to an OCR engine, attribute the result to $pageNumber
    }
}

Because the result is keyed by source page number, whatever an OCR engine returns can be attributed straight back to the page it came from. See Image-Only Pages for the per-page classification behind that check.

Errors#

Four conditions raise Pop\Pdf\Build\Exception, each one a thing to have in place before the call:

  • Install the imagick extension, which is what does the rasterizing.
  • Pass a $file that exists on disk.
  • Pass a $location that is an existing, writable directory.
  • Pass one of the six supported formats.

Each message names what it found:

PHP
use Pop\Pdf\Pdf;
use Pop\Pdf\Build\Exception;

try {
    $images = Pdf::extractAsImages(__DIR__ . '/invoice.pdf', __DIR__ . '/pages', format: 'bmp');
} catch (Exception $e) {
    echo $e->getMessage();
}

That prints Error: The format 'bmp' is not supported.

See Also#

  • Image-Only Pages — deciding whether a document needs OCR at all
  • Extracting Text — pulling the text layer out of a document that has one
  • Importing — reading an existing PDF back in as pages you can edit