Images

edit

PdfImage.Load reads an image file, and DrawImage draws it in a rectangle of the page:

public static void DrawImage(string imagePath, string outputPath)
{
    // A JPEG file is embedded as it is; another format (PNG, WebP...) is decoded, with its transparency
    var image = PdfImage.Load(imagePath);

    var document = new PdfDocument();
    var builder = document.Pages.Add(new PdfSize(300, 150)).CreateContentBuilder()
        .SetFillRgb(0.9, 0.93, 0.97).Rectangle(0, 0, 300, 150).Fill();

    // The image is stretched to the rectangle it is drawn in: compute the width that keeps its aspect ratio
    var height = 120.0;
    var width = height * image.Width / image.Height;
    builder.DrawImage(image, 15, 15, width, height)
        .DrawImage(image, 30 + width, 15, width / 2, height / 2); // drawn again: the file stores it once

    document.Save(outputPath);
}

A landscape picture with rounded transparent corners on a light blue background, and a smaller copy of it

  • The image fills the rectangle it is drawn in: compute the width or the height from Width and Height, its size in pixels, to keep its aspect ratio.
  • An image drawn several times, on one page or on several, is stored once in the file.
  • A PdfImage does not belong to a document and is thread-safe: each document that uses it gets its own copy.

Where images come from

MethodStores
PdfImage.Load(path), PdfImage.Load(bytes)A JPEG file as it is; any other format Meziantou.Framework.Imaging reads (PNG, WebP, GIF, TIFF...) decoded, as with FromImage
PdfImage.FromJpeg(bytes)A JPEG file as it is (gray, YCbCr or CMYK, 8 bits per sample), without decoding it
PdfImage.FromImage(image)The first frame of an Image of Meziantou.Framework.Imaging, whose pixels are copied
PdfImage.FromImageAsJpeg(image, encoder)The first frame of an Image, compressed as JPEG: see Photos
  • A JPEG file is kept as it is: it is not decoded and compressed again, so no quality is lost.
  • The other images are stored as gray or RGB samples of 8 or 16 bits, compressed without loss. The save applies the PNG predictors to the samples when they make them smaller, as they do for most photos and gradients. Their transparent pixels become a soft mask (HasSoftMask), so the page shows through them as in the example.
  • The ICC profile of an image is kept, so that its colors are shown as they were meant.
  • FromImage throws ArgumentException for an image with floating-point pixels: convert it to an integer pixel format first.

To resize, crop or convert an image before drawing it, process it with Meziantou.Framework.Imaging, then pass it to FromImage. A large photo drawn small still stores all its pixels: resize it to the resolution the page needs.

Photos

The samples of a photo compressed without loss are large. FromImageAsJpeg compresses the image as JPEG instead, with the settings of a JpegEncoder (quality, chroma subsampling, progressive coding):

private static void DrawPhotoAsJpeg(Image photo, string outputPath)
{
    // A photo is much smaller as JPEG than as samples compressed without loss; its transparency stays a soft mask
    var image = PdfImage.FromImageAsJpeg(photo, new Imaging.Formats.JpegEncoder { Quality = 85 });

    var document = new PdfDocument();
    document.Pages.Add(new PdfSize(300, 150)).CreateContentBuilder().DrawImage(image, 10, 10, 128, 128);
    document.Save(outputPath);
}
  • JPEG loses details, and blurs sharp edges: keep FromImage for drawings, screenshots and text.
  • The samples of 16 bits are rounded to 8 bits, the precision of JPEG in PDF.
  • The transparent pixels still become a soft mask, compressed without loss, and the ICC profile of the image is kept.
  • A JPEG file you already have is better drawn with Load or FromJpeg: it is kept as it is, without a second loss.

Black and white images

A scan, a fax or a line drawing has one bit per pixel. FromBilevel stores it in one bit per pixel with a compression made for such images, which is much smaller than the gray samples of FromImage:

// A 16 x 2 image, one bit per pixel: a set bit is a black pixel
byte[] pixels = [0b1111_0000, 0b0000_1111, 0b1010_1010, 0b0101_0101];
var image = PdfImage.FromBilevel(pixels, width: 16, height: 2, PdfBilevelCompression.CcittGroup4);

var document = new PdfDocument();
document.Pages.Add(PdfSize.A4).CreateContentBuilder().DrawImage(image, 50, 700, 160, 20);
var data = document.ToByteArray();

The pixels are the rows of the image from top to bottom, each of (width + 7) / 8 bytes, the most significant bit of a byte first: a set bit is a black pixel. The bits after the last pixel of a row are ignored.

PdfBilevelCompressionStores
CcittGroup4 (default)The Group 4 coding of fax machines and scanners (CCITTFaxDecode), which every reader decodes
Jbig2JBIG2 arithmetic coding (JBIG2Decode, PDF 1.4): usually smaller, especially for dithered images and halftones
FlateThe rows of bits compressed with FlateDecode
  • Every compression is lossless: a reader shows the pixels of the image. The JBIG2 image is one generic region; the symbol coding of text, which some encoders use to replace similar glyphs by one another, is not used.
  • The image is compressed when FromBilevel is called. Group 4 coding supports images of up to 1048576 pixels per row.