Skip to content

3.14.0

Choose a tag to compare

@matmen matmen released this 04 Nov 20:40
· 8 commits to master since this release
Immutable release. Only release title and notes can be modified.

3.14.0

New

  • Lossless encodeJXL() compresses like libjxl's cjxl -e 3 (effort 3) by default: about 15% smaller files for
    photos (a 1920x1080 photo: 1.17 MB instead of 1.39 MB) and 13% for graphics, within 1-2% of cjxl's sizes. It
    uses a palette of up to 1024 colors where it fits (else palettes of the channels that use few of their values),
    YCoCg and the weighted predictor in contexts of its error, and codes the groups in worker threads
    (JXLEncodeOptions.threads, the same files): the photo takes 0.24 s with 8 threads, 0.65 s with one. effort: 0
    keeps the fast mode, byte for byte what cjxl -e 1 writes (0.11 s for the photo); effort applies to lossless
    compression now too.

Performance

  • Lossy encodeJXL() is faster: a 1920x1080 photo at quality 90 takes 1.6 s instead of 2.3 s at effort 1 and 2.4 s
    instead of 4.8 s at effort 2 on one thread; with 8 threads 0.72 s instead of 0.81 s and 0.95 s instead of 1.46 s
    (3840x2160: 1.6 s instead of 2.0 s and 2.5 s instead of 4.1 s). The 4x8 and AFV transforms are computed directly
    instead of by a 64x64 matrix, the varblock search rounds without Math.round() (slow in V8), patch search skips
    large components after a quick labeling, and the search is split into smaller pieces for the threads. The files stay
    the same, except for rare coefficients the new transforms round differently (2 of 108 test encodes, both the same
    size).

Fixes

  • Invalid options throw a RangeError naming them before any work, instead of failing inside an encoder or being
    ignored:
    • encodeAVIF(): effort other than 0, 1 or 2 failed with a TypeError deep in the encoder, quality and
      alphaQuality outside 0-100 were accepted, and an unknown subsampling silently meant 4:4:4 (now a
      TypeError).
    • encodeWebP(), Animation.encodeWebP() and Animation.webpEncoder() check effort for lossless images too
      (which don't use it).
    • NaN is refused as GIF quality and APNG compression (out of range values are still clamped, like PNG's), as
      dominantColor()'s threshold and as renderSVG()'s size (which threw about the image's width); negative font
      sizes in renderText() (which rendered an empty image); maxPixels that isn't positive in Image.decode() and
      Animation.decode() (which reported every image as too large); fractional loop counts in new Animation().
  • Animation.decode() reports invalid threads and maxPixels as such, not as damaged image data.
  • Image.decode() refuses GIFs with a hint at Animation.decode() instead of "Unsupported image type".
  • SVG feDisplacementMap applies its scale once, like the specification and browsers (it multiplied by it twice, like
    resvg, so at typical scales the whole input was displaced out of the filter region and disappeared), and reads the
    map's colors unpremultiplied.