Skip to content

Images & vectors

Raster images

PNG and JPEG are decoded everywhere; WebP / HEIF where the platform supports them.

image(bytes = imageBytes, width = 300.dp, contentScale = ContentScale.Fit)

// Square crop:
image(bytes = imageBytes, width = 200.dp, height = 200.dp, contentScale = ContentScale.Crop)

// Width given, height derived from intrinsic aspect ratio:
image(bytes = imageBytes, width = 480.dp)

// Intrinsic pixel size (1px -> 1pt):
image(bytes = imageBytes)

ContentScale options: Fit, Crop, FillBounds (stretches to the destination box).

Downscaling

image(..., allowDownScale = true) is the default. The platform decoder subsamples raster bytes so they roughly match the rendered size at 200 DPI before drawing — this keeps heap and PDF size sane when consumers paste 4000-px photos into 200-pt thumbnails. Pass allowDownScale = false for archival / print workflows where every original pixel must survive.

image(bytes = imageBytes, width = 240.dp, height = 160.dp, allowDownScale = false)

Decode budget

Independently of allowDownScale, every backend caps how many pixels a decode may allocate, based on the image's declared header dimensions — read before any pixel memory is touched. The default ceiling is 50 megapixels (PdfImagePolicy.DEFAULT_MAX_DECODE_PIXELS).

An image over the ceiling is sub-sampled down to fit, not dropped — inSampleSize on Android, ImageReadParam.setSourceSubsampling on the JVM, CGImageSourceCreateThumbnailAtIndex on iOS — and the reduction is reported through PdfLog. This is what stops a dimension bomb: a few dozen bytes of PNG header can claim 50 000 × 50 000 px, and without a pre-decode bound the platform decoder obliges with a multi-gigabyte allocation and takes the process with it.

Raise the ceiling once at startup when a document legitimately carries very large imagery — A0 at 300 DPI is ~139 MP, well over the untrusted default:

PdfImagePolicy.maxDecodePixels = 200_000_000L

Web (Wasm) refuses instead of sampling

The pure-Kotlin writer embeds encoded streams verbatim and owns no decoder, so it has nothing to sample with. An over-budget image is skipped there with a PdfLog warning rather than handed on to the reader. Raising maxDecodePixels lets it through on that target too.

Accessibility alt text

Pass altText to carry an accessibility description into backends that write tagged structure (the JVM/Desktop backend records it as the image's /Alt):

image(bytes = chartBytes, width = 300.dp, altText = "Q1 revenue bar chart")

Compose Resources users

Pass a typed Res.drawable.* reference straight in: image(Res.drawable.cover_photo, width = 480.dp) from the pdfkmp-compose-resources module (inside pdfAsync { }).

Vector / SVG

Both Android <vector> XML and W3C <svg> are accepted by VectorImage.parse(...). Vectors stay vector inside the PDF — no rasterisation, sharp at any zoom.

val star = VectorImage.parse(starXml) // parse once, reuse many times

vector(image = star, width = 64.dp)
vector(image = star, width = 64.dp, tint = PdfColor.Red)              // override fill
vector(image = star, width = 64.dp, strokeMode = VectorStrokeMode.Disabled)

// Inline parsing for one-offs:
vector(xml = """<svg ...>...</svg>""", width = 48.dp)

Support matrix

Category Supported
Fills solid, linear & radial gradients
Paths M/L/Q/C/Z + elliptical arcs (A/a)
SVG shapes <rect> (incl. rounded), <circle>, <ellipse>, <line>, <polyline>, <polygon>
Transforms <g transform="translate/rotate/scale">, nested <g>
Styling inline style="", opacity attributes
Colours rgb(), ~20 named colours, hex
Coordinates viewBox offsets

Compose Resources users

Skip the manual VectorImage.parse(...) and pass a typed reference directly: vector(Res.drawable.logo, width = 64.dp, tint = PdfColor.Blue) (inside pdfAsync { }).

Free-form vector drawing (freeDraw)

When the primitives don't cover a shape, freeDraw(width, height) { path { … } } lets you author paths in a local (0, 0)(width, height) coordinate space that scales into the node's final rectangle. The pen model matches any 2D canvas — moveTo / lineTo / quadTo / cubicTo / rect / close — and each path can fill (solid or gradient) and / or stroke:

freeDraw(width = 60.dp, height = 60.dp) {
    path(fill = PdfColor(1f, 0.8f, 0.2f), strokeColor = PdfColor.Black, strokeWidth = 2f) {
        moveTo(30f, 4f); lineTo(56f, 52f); lineTo(4f, 52f); close()
    }
    path(fill = PdfColor.Black) { rect(27f, 20f, 6f, 18f); rect(27f, 42f, 6f, 6f) }
}

See also

  • QR, barcodes & charts — built on freeDraw.
  • Decorations & effects — circles, ellipses, gradients.
  • Samples.withImage(), Samples.slicedImage(), Samples.imageDownscale(), Samples.vectorShowcase(), Samples.vectorAdvanced().