Cracking the Code: Working with PDF Images in Modern C++
Images in PDFs fall into two categories: the page rendered as an image, and the raster images embedded within the page's content. Working with either type programmatically requires a different approach, and the Adobe PDF Library's Modern C++ API provides clean, well-structured tools for both. This tutorial covers five image workflows: converting PDF pages to image files, extracting embedded images from page content, importing external images into a PDF, resampling images to reduce file size, and embedding ICC color profiles for color-managed output.
All samples are from the Modern C++ samples repository on GitHub in the Images folder. The Modern C++ API uses standard C++17 conventions throughout, including RAII initialization, range-based iteration, and standard exception handling.
Who Is This For?
This tutorial is written for C++ developers building systems that need to process PDF images at scale. That includes document conversion pipelines that produce image derivatives of PDF pages, digital asset management systems that extract and catalog embedded images, print production workflows that require color-managed TIFF output, web applications that generate PDF thumbnails or previews, and archival systems that need to optimize embedded image resolution before storage.
Sample 1: Converting PDF Pages to Image Files
When to Use This
Use DocToImages when you need to render PDF pages as standalone image files. Common applications include generating document thumbnails for a web interface, producing page previews for a document management system, creating print-quality rasters for review workflows, and converting PDF pages to TIFF for archival systems that do not accept PDF.
What the Code Does
DocToImages is a full-featured command-line utility that accepts a wide range of options for format, resolution, page range, compression, and rendering behavior. The core of the conversion is the PageImageParams structure combined with a call to Page::get_image. First, set up the rendering parameters:
PageImageParams pip;
pip.set_page_draw_flags(DrawFlags::UseAnnotFaces | DrawFlags::DoLazyErase);
pip.set_smoothing(options.smoothing);
pip.set_horizontal_resolution(options.hres); // e.g., 300.0
pip.set_vertical_resolution(options.vres);
Then render each page and save:
Page pg = doc.get_page(page_index);
Rect page_rect = pg.get_crop_box();
Image page_image = pg.get_image(page_rect, pip);
ImageSaveParams isp;
isp.set_compression(CompressionCode::LZW); // For TIFF
page_image.save(output_path, ImageType::TIFF, isp);
The page_rect determines which part of the PDF page is rendered. get_crop_box() is the most common choice and returns the visible page area. Other options include get_media_box() for the full physical page, get_trim_box() for the trim-to area in print production, and get_bleed_box() for pre-press bleed margins.
Supported Output Formats
DocToImages supports TIFF, JPEG, PNG, BMP, and GIF. TIFF with LZW compression is the standard choice for print-quality output. PNG is preferred for web previews where lossless quality matters. JPEG is appropriate for thumbnails and display images where file size is the priority. The sample also supports multipage TIFF output by collecting page images into an ImageCollection and saving once at the end.
Key Parameters
Resolution defaults to 300 DPI, which is appropriate for print-quality output. For web thumbnails, 72 or 96 DPI reduces file size significantly. The smoothing option (SmoothFlags::Text, SmoothFlags::LineArt, or the combined all flag) applies anti-aliasing to the rendered output and noticeably improves quality for low-resolution output. The asprinted flag adds DrawFlags::IsPrinting to the draw flags, which activates print-specific rendering behavior in the PDF content.
Expected Output
One image file per page (or one multipage TIFF if the multi option is set), named using the base document name with a page counter suffix. A 5-page document converted to PNG at 150 DPI produces five PNG files, one per page.
Sample: doc_to_images.cpp on GitHub
Sample 2: Extracting Embedded Images from a PDF
When to Use This
Use ImageExtraction when you need to retrieve the raster images that are embedded within a PDF's content, as distinct from rendering the page itself as an image. Pulling product photos from a catalog PDF, extracting diagrams from a technical document, and harvesting images for a digital asset management system are typical use cases. Note that vector graphics are not exported by this sample, only raster images.
What the Code Does
The sample traverses the page content tree using try_as to identify Image elements. When one is found, it is saved directly at its native resolution and also saved at a resampled 500 DPI version. The traversal is recursive because images can be nested inside Container, Group, and Form elements:
static void extract_images(Content& content) {
for (int i = 0; i < content.get_num_elements(); ++i) {
auto elem = content.get_element(i);
if (auto* img = elem->try_as<Image>()) {
img->save("ImageExtraction-extract-out" + std::to_string(next_index) + ".png",
ImageType::PNG);
Image resampled = img->change_resolution(500);
resampled.save("ImageExtraction-extract-Resolution-500-out" +
std::to_string(next_index) + ".png", ImageType::PNG);
++next_index;
}
else if (auto* grp = elem->try_as<Group>()) {
auto sub = grp->get_content();
if (sub) extract_images(*sub);
}
// Similarly for Container and Form types
}
}
The try_as pattern is idiomatic Modern C++ and avoids the unsafe casts required by the classic API. If the element is not the requested type, try_as returns nullptr and the branch is skipped cleanly.
The change_resolution(500) call demonstrates that extracted images can be immediately resampled before saving, which is useful when the source PDF contains very high-resolution images that need to be normalized to a target DPI for downstream processing.
Expected Output
Two PNG files per image found in the document: one at the original embedded resolution and one at 500 DPI. The output files are numbered sequentially starting from 0.
Sample: image_extraction.cpp on GitHub
Sample 3: Importing an External Image into a PDF
When to Use This
Use ImageImport when you need to add an external image file to a PDF document. This is useful for programmatically assembling PDFs from image assets, stamping a logo or signature image onto existing documents, building image-heavy PDFs from a collection of raster files, and creating new PDF documents where the content is primarily or entirely image-based.
What the Code Does
The sample creates a new Document, loads an image from a file path, creates a page sized to fit the image with a one-inch margin on all sides, positions the image on the page, and saves the result. The image placement uses PDF points (72 points = 1 inch):
Document doc;
Image new_image(input_path, doc); // Associate the image with the document
// Create a page 1 inch larger than the image on all sides
Matrix img_matrix = new_image.get_matrix();
Rect page_rect{0, 0, img_matrix.a + 144, img_matrix.d + 144};
Page doc_page = doc.create_page(Document::before_first_page, page_rect);
// Center the image (72 points = 1 inch margin)
new_image.translate(72, 72);
Content content = doc_page.get_content();
content.add_element(new_image);
doc_page.update_content();
doc.save(SaveFlags::Full, output_path);
The get_matrix() call returns the image's transformation matrix. The a and d fields of a PDF matrix are the horizontal and vertical scaling factors, which for an unrotated image correspond to the image's width and height in points. Adding 144 (2 inches at 72 points/inch) to each dimension creates a one-inch border on each side.
update_content() must be called after modifying the page content, or the changes will not be written to the document structure. Forgetting this step is a common source of empty output pages.
Expected Output
A new PDF file named ImageImport-out1.pdf containing a single page with the imported image centered on a white background with one-inch margins.
Sample: image_import.cpp on GitHub
Sample 4: Resampling Images to Reduce File Size
When to Use This
Use ImageResampling when a PDF contains embedded images at a resolution higher than needed for the intended output. A PDF prepared for print may contain images at 600 DPI or higher. If that PDF is being archived or delivered for screen viewing, those high-resolution images are unnecessary overhead. Resampling reduces image resolution and file size while keeping the document visually correct at the target viewing resolution.
What the Code Does
The sample traverses the page content tree (using the same recursive pattern as ImageExtraction), finds each Image element, calls change_resolution to produce a resampled version at 400 DPI, and replaces the original in the content tree. The replacement uses add_element followed by remove_element to swap positions:
if (auto* img_ptr = elem->try_as<Image>()) {
Image new_img = img_ptr->change_resolution(400);
content.add_element(new_img, i - 1); // Insert resampled at position i
content.remove_element(i + 1); // Remove original from i+1
++num_replaced;
}
After all images on a page are replaced, update_content() is called on the page to commit the changes. The final save uses SaveFlags::CollectGarbage in addition to SaveFlags::Full, which removes the now-unreferenced original image data from the output file and is what actually produces the smaller file size:
doc.save(SaveFlags::Full | SaveFlags::CollectGarbage, output_path);
Omitting SaveFlags::CollectGarbage will produce an output file that contains both the original and resampled image data, which defeats the purpose of resampling. Always include it when the goal is file size reduction.
Expected Output
A new PDF named ImageResampling-out.pdf with all raster images resampled to 400 DPI. The visual appearance of the document is unchanged at normal viewing zoom levels. File size reduction varies depending on the original image resolutions but can be substantial for print-optimized source documents.
Sample: image_resampling.cpp on GitHub
Sample 5: Embedding ICC Color Profiles
When to Use This
Use ImageEmbedICCProfile when rendering PDF pages to image files for color-critical applications such as print production, color proofing, or archival systems where accurate color reproduction is required. An ICC profile defines the color characteristics of the output device. Rendering with a matching profile ensures that colors in the output image match what the device will actually produce.
What the Code Does
The sample renders each page of a PDF to a CMYK TIFF using a specified ICC profile. Four output files are generated per page, one for each of the four standard rendering intents defined in the ICC specification. Rendering intent controls how out-of-gamut colors in the PDF are mapped to the output color space:
PageImageParams pip;
pip.set_icc_profile_custom_path(profile_path);
pip.set_image_color_space(NamedColorSpace::device_cmyk());
pip.set_render_intent(RenderIntent::Saturation);
Image img_sat = pg.get_image(pg.get_crop_box(), pip);
img_sat.save("ImageEmbedICCProfile-out_sat0.tif", ImageType::TIFF, isp);
pip.set_render_intent(RenderIntent::Perceptual);
Image img_per = pg.get_image(pg.get_crop_box(), pip);
img_per.save("ImageEmbedICCProfile-out_per0.tif", ImageType::TIFF, isp);
// AbsColorimetric and RelColorimetric variants follow the same pattern
Understanding Rendering Intents
Perceptual is the most common choice for photographs and continuous-tone images. It compresses the source gamut to fit the output gamut while preserving relative relationships between colors. RelColorimetric maps colors that fall within the output gamut exactly and clips those that do not. AbsColorimetric is similar but also adjusts for the white point of the output medium, which matters for proofing. Saturation maximizes color vividness and is used for business graphics and charts rather than photographic content.
The sample uses a CMYK ICC profile (Probev1_ICCv2.icc), which is appropriate for offset printing workflows. Substituting an sRGB or AdobeRGB profile with DeviceRGB color space produces RGB TIFF output suitable for digital publishing.
Expected Output
Four TIFF files per page, named with suffixes _sat, _abs, _per, and _rel to identify the rendering intent used. All four are CMYK TIFFs with LZW compression, suitable for color comparison and print proofing.
Sample: image_embed_icc_profile.cpp on GitHub
Additional Image Capabilities
The Modern C++ Images folder includes additional samples not covered in this tutorial. DrawToBitmap renders pages to in-memory bitmap buffers rather than files, which is useful for server-side thumbnail generation. DrawSeparations and GetSeparatedImages handle color separation workflows for commercial print. OutputPreview simulates how a page will look with specific colorants turned off, which is a standard pre-press proofing operation. All are available in the Modern C++ samples repository.
Get Started
The Modern C++ image samples and the full Adobe PDF Library SDK are available through a free trial. Get started today!