Skip to content

3.9.0

Latest

Choose a tag to compare

@matmen matmen released this 29 Oct 13:50
· 16 commits to master since this release
Immutable release. Only release title and notes can be modified.

This release renders SVG text, converts images with embedded ICC color profiles to sRGB when decoding, hints glyphs exactly like FreeType, and adds patches to lossy JPEG XL and alpha quality to lossy WebP. SVG rendering also gains blend modes, transform-origin, paint-order and context paints. Like every codec in ImageScript, it's plain TypeScript: no WebAssembly and no native code. There are no breaking API changes, but decoded pixels of images with a non-sRGB ICC profile change (see below).

await Image.renderSVG(svg, { fonts: [await fs.readFile('Inter.ttf')] }); // <text>, <tspan>, <textPath>, ...
await Image.decode(p3Photo); // now converted from Display P3 to sRGB
await Image.decode(p3Photo, { applyColorProfile: false }); // the stored values, as before
await image.encodeJXL({ quality: 90, effort: 2 }); // patches for screenshots and text
await image.encodeWebP({ quality: 80, alphaQuality: 50 });

New

SVG text

Image.renderSVG() renders <text>, <tspan>, <tref> and <textPath> with the fonts passed as SVGOptions.fonts and the ones the SVG embeds with @font-face (TrueType, OpenType and WOFF data: URIs).

  • Font matching like browsers match installed fonts: font-family, then font-stretch, font-style and font-weight by CSS Fonts' algorithm, generic families by the fonts' panose and pitch flags, and missing characters from the other fonts
  • Layout ported from resvg (usvg): chunks shaped as a whole (kerning and ligatures across spans), bidirectional text, x/y/dx/dy/rotate lists, text-anchor, letter-spacing, word-spacing, textLength, baseline-shift, dominant-baseline, text-decoration, text along paths, vertical text, font-variant: small-caps, the font shorthand and xml:space
  • Painting like shapes (gradients, patterns, strokes, paint-order), in clip paths and bounding boxes, with color glyphs (COLR v0/v1, CBDT, sbix, SVG)
  • 359 of resvg's 431 text tests pass, plus 41 of its other tests that contain text. Variable fonts aren't supported yet

ICC color profiles

Decoding converts images with an embedded ICC profile to sRGB, the way browsers display them: PNG (iCCP), JPEG (APP2), WebP (ICCP, animations too), AVIF (colr) and TIFF. Display P3 photos from phones and Adobe RGB or ProPhoto images no longer look washed out.

  • Matrix/TRC profiles (RGB and gray) are applied relative colorimetric. The results are within 1 of LittleCMS's
  • Profiles with only lookup tables, CMYK profiles and sRGB profiles leave the pixels as they are
  • applyColorProfile: false (in Image.decode() and Animation.decode() options) keeps the stored values
  • A 1920x1080 image takes about 35 ms to convert

Patches in lossy JPEG XL

encodeJXL({ quality, effort: 2 }) stores repeated text-like features (glyphs on the flat backgrounds of screenshots) once in a reference frame and adds them back where they occur, like libjxl's patches (cjxl -e 7), ported from it. A page of text takes 62% fewer bytes, screenshots 1-2% fewer. Other images come out the same.

Alpha quality in lossy WebP

encodeWebP({ quality, alphaQuality }) (0-100, default 100; also for animations and Animation.webpEncoder()) keeps fewer alpha levels below 100, with the same alpha values as cwebp's -alpha_q. For example, potato.png's alpha plane takes 1.3 kB at 50 instead of 1.9 kB, and a gradient takes 4.3 kB instead of 16.7 kB.

SVG rendering

  • Blending: mix-blend-mode (all 16 modes and plus-lighter) and isolation: isolate. All 23 of resvg's tests for them pass
  • transform-origin on elements, clip paths, gradients and patterns (21 of 23 tests)
  • paint-order (fill, stroke, markers; 12 of 14 tests)
  • context-fill / context-stroke in markers and <use> (13 of 16 tests)
  • shape-rendering: crispEdges/optimizeSpeed draw without antialiasing, and the root's background-color is used
  • font-size resolves em, ex, percentages and keywords against the parent's size everywhere; font-weight accepts bolder/lighter

Text rendering

Hinting (renderText with hinting) now places every point exactly where FreeType's TrueType interpreter (v35) puts it. This was checked on 18 fonts at 8-48 px and on all 6190 glyphs of DejaVu Sans at 4 sizes. Before, up to 30% of the glyphs of some fonts were off by fractions of a pixel, and a few were off by up to 4 pixels.

Fixes

  • SVG: zero-length dashes at the start of a path get caps (square ones turned along the path)
  • SVG: shapes without a bounding box painted with an objectBoundingBox gradient or pattern use its fallback color
  • SVG: display: none elements no longer count towards group bounding boxes
  • SVG: overflow="inherit" on markers and nested viewports takes the parent's value
  • SVG: path data ends at the first character that isn't part of it, instead of that character being skipped
  • SVG: CR LF and CR line ends read as line feeds, like in XML parsers