Skip to content

zignal 0.11.0

Latest

Choose a tag to compare

@arrufat arrufat released this 14 Oct 15:36

Zignal 0.11.0

This release moves zignal to Zig 0.17.0, runs filters, transforms and codecs in parallel through std.Io, adds vector fonts with text layout, QR codes, CMYK and new animated codecs, and brings JPEG and PNG to or past libjpeg-turbo and libpng speeds.

Highlights

Major Features

  • Zig 0.17.0: the minimum Zig version is now the 0.17.0 release, and zignal itself declares no dependencies.
  • Parallel by default: image filters, transforms, codecs, Matrix products, Pca and ORB take an io: Io and split their work into row bands on it (2.6-4.9x on 8 cores, byte-identical to serial).
  • Vector fonts and text layout: TrueType, CFF OpenType and font collections with kerning, Canvas.drawTextBox with wrapping and alignment, outlines, and an opt-in glyph cache (paragraphs 7.5-17x faster).
  • Codecs: native BMP and GIF readers and writers, JPEG XL and WebP through the system libraries, animated images (Animation.load/save), CMYK/YCCK JPEG decoding, and streaming I/O through std.Io.Reader/Writer.
  • New algorithms: QR code encoding and decoding, MaxLIPO + trust-region global optimization, Chinese Whispers clustering, flood fill, linear solvers and symmetric eigendecomposition, and a recursive Gaussian blur whose cost is independent of sigma.
  • Namespaced API: core types stay flat (Image, Canvas, colors, Matrix, Font, ...), while options, enums and algorithms live under image, canvas, geometry, matrix, font, features, optimization, stats and perlin.

Performance

  • JPEG decoding is at or below libjpeg-turbo, and encoding is rebuilt around MCU rows (4K: 92 to 5 ms on 8 cores).
  • PNG encodes in parallel deflate chunks (4K RGB: 344 to 77 ms).
  • Resize, rotate and warp run separable or tabulated samplers on the pool (4K to 1080p bicubic: 216 to 37 ms serial), and downscales are now anti-aliased.
  • SSIM is separable and vectorized (4K Rgb: 2536 to 82 ms serial, 20 ms on 8 cores).
  • Matrix.gemm is a register-tiled blocked kernel (2048² f32: 26 s to 68 ms on 8 cores).
  • Canvas renders text 2-2.5x and thick curves 2-5x faster with a signed-area coverage rasterizer.

Python Bindings

  • Python 3.11-3.15: Python 3.10 is no longer supported, 3.15 wheels are published, and macOS wheels are arm64 only.
  • Antialiased drawing by default, Font (bitmap and vector) replaces BitmapFont, and draw_text takes a pixel size.
  • New APIs: Cmyk, QR codes, optimize, chinese_whispers_clustering, flood fill, text boxes and BMP/GIF I/O.
  • The GIL is released around every image filter, so filters run concurrently with other Python threads.

Note: this release has many breaking changes (the io: Io parameter, argument order, namespaced names, canvas.DrawOptions, unmanaged types, blur validation). See CHANGELOG.md and bindings/python/CHANGELOG.md for the full list.

What's Changed

  • build(python): use translate-c for python headers by @arrufat in #344
  • fix(matrix): guard query ops and deinit on poisoned chains by @arrufat in #345
  • refactor(matrix): split Matrix(T) chain into Chain(T) by @arrufat in #346
  • feat(image): add BMP support by @arrufat in #348
  • feat(gif): add decoder, encoder, and animated image support by @arrufat in #349
  • fix(gif/lzw): correct code size growth logic by @arrufat in #350
  • fix(terminal): fix aspect scaling and improve capability detection by @arrufat in #351
  • refactor(codecs): move image codecs to src/codecs/ by @arrufat in #352
  • chore: update zig version to 0.17.0-dev.304 by @arrufat in #353
  • perf(features): optimize FAST and refactor ORB by @arrufat in #354
  • refactor(convolution): extract u8 scaling and clamping helpers by @arrufat in #355
  • refactor(image): optimize binary morphology and kernel init by @arrufat in #356
  • refactor(cli): standardize logs and unify common utilities by @arrufat in #357
  • refactor: rename nearest_neighbor to nearest by @arrufat in #358
  • refactor(image)!: standardize argument order for image methods by @arrufat in #359
  • refactor(matrix): simplify SVD options by removing redundant with_u by @arrufat in #360
  • feat(matrix): add in-place ops and sumRows/sumCols by @arrufat in #361
  • refactor(geometry): clean up and optimize Point by @arrufat in #362
  • refactor(pca): optimize SIMD and simplify fit logic by @arrufat in #363
  • refactor(python): simplify color object creation by @arrufat in #364
  • feat(image): add color support to braille display by @arrufat in #365
  • build: bump minimum zig version to 0.17.0-dev.813 by @arrufat in #366
  • refactor(matrix): unify SVD and optimize operations by @arrufat in #367
  • feat(optimization): add MaxLIPO global optimizer by @arrufat in #368
  • feat(image): add flood fill algorithm by @arrufat in #369
  • refactor(optimization): optimize memory usage and performance by @arrufat in #370
  • refactor(optimization): clean up and simplify codebase by @arrufat in #372
  • refactor(matrix): extract cholesky factorization by @arrufat in #373
  • feat: add QR code encoding and decoding by @arrufat in #374
  • QR follow-ups: generic decode and SMatrix.solve by @arrufat in #375
  • feat(matrix): add linear system solver using LU factorization by @arrufat in #376
  • feat(python): add Matrix.solve method by @arrufat in #377
  • feat(colormaps): add inferno colormap by @arrufat in #378
  • feat(cli): add pipeline command and short option support by @arrufat in #379
  • feat(iterm2): add inline image protocol support by @arrufat in #380
  • refactor!(terminal): group encoders under terminal namespace by @arrufat in #381
  • feat(jpeg): harden progressive decoding and speed up hot paths by @arrufat in #382
  • feat(png): support truncated files and optimize defiltering by @arrufat in #383
  • feat(examples): add image codec playground by @arrufat in #384
  • refactor: update enum builtins for zig 0.17.0-dev by @arrufat in #385
  • refactor(font): deduplicate gzip I/O and optimize font handling by @arrufat in #386
  • refactor(font): optimize glyph lookup and PCF bitmap parsing by @arrufat in #387
  • refactor(image): extract convolvePlanes helper for multi-channel data by @arrufat in #388
  • refactor(border): centralize getPixel function by @arrufat in #389
  • refactor(convolution): extract quantizeWeight helper function by @arrufat in #390
  • perf(image): optimize convolution hot paths by @arrufat in #391
  • perf(image): close the gap with OpenCV on core filters by @arrufat in #392
  • refactor(image): post-review cleanup of the filter perf pass by @arrufat in #393
  • perf(image): apply the follow-ups deferred from #393 by @arrufat in #394
  • refactor(image): optimize box blur sliding sums and clean up filters by @arrufat in #395
  • refactor(terminal): optimize graphics encoders and clean up detection by @arrufat in #396
  • refactor(terminal): single round-trip sixel detection and probe policy cleanup by @arrufat in #397
  • refactor: migrate off deprecated std.meta field functions by @arrufat in #398
  • build: simplify and streamline build.zig by @arrufat in #399
  • feat(canvas): add DrawOptions struct with blending support by @arrufat in #400
  • feat(canvas): direction-independent antialiasing for polygon fills by @arrufat in #401
  • refactor(canvas): consolidate pixel writes behind Paint and tidy polygon rasterizer by @arrufat in #402
  • TrueType fonts: VectorFont, Font union, nonzero glyph fills, Python Font class by @arrufat in #403
  • feat(font): CFF OpenType (.otf) support in VectorFont by @arrufat in #404
  • feat(font): font collections, seac accents and CFF2 default instances by @arrufat in #405
  • feat(canvas): text boxes with wrap, alignment and spacing, outlined text by @arrufat in #406
  • Text layout follow-ups: linear wrap, mask halos, round joins, enum registry by @arrufat in #407
  • Font size cap, band-limited diagonal lines, thin-stroke parity by @arrufat in #408
  • Glyph cache for vector text by @arrufat in #409
  • Lay text blocks out once by @arrufat in #410
  • Blit bitmap halos through blitMask by @arrufat in #411
  • One edge sweep for the fast and even-odd soft fills by @arrufat in #412
  • CI: publish to TestPyPI on releases and manual runs only by @arrufat in #413
  • Band-limit ring and arc outlines by @arrufat in #414
  • One antialiased box filler for thick axis-aligned lines and rectangle outlines by @arrufat in #416
  • Docs: variable fonts render their default instance, use a static instance by @arrufat in #417
  • refactor(canvas): dedup fills, arcs, strokes and text paths by @arrufat in #418
  • refactor(font): one glyph map, one bitmap pen, shared loaders and searches by @arrufat in #419
  • perf(font): interpret CFF glyphs once, index GPOS pair lookups at load by @arrufat in #420
  • refactor: create files through the cwd whatever the path by @arrufat in #423
  • fix(canvas): never read untouched cells of the area accumulator by @arrufat in #428
  • refactor(canvas): one rasterizer for rings, arcs and pies by @arrufat in #426
  • refactor(font): one way to measure text by @arrufat in #425
  • refactor(font): one glyph table for bitmap fonts by @arrufat in #427
  • feat(python): antialiased drawing by default by @arrufat in #424
  • Convolution cleanup and a few speedups by @arrufat in #429
  • fix(numerics): skewness/kurtosis, perlin normalisation, matrix pivoting by @arrufat in #430
  • fix(jpeg): restart intervals, header validation, Adobe RGB by @arrufat in #431
  • fix: harden canvas, codecs, fonts and CLI against bad input by @arrufat in #432
  • chore: remove dead code by @arrufat in #433
  • refactor(image): allocator before out in out-param methods by @arrufat in #434
  • fix(transforms): extract/insert use the half-open rect like crop by @arrufat in #435
  • perf(image): run filters over row bands on the caller's Io by @arrufat in #437
  • feat(image): opt-in recursive Gaussian blur by @arrufat in #438
  • perf(image): band the channel split/merge and the order-statistic filters by @arrufat in #440
  • perf(image): band the vertical border rows, vectorize the Sobel magnitude by @arrufat in #441
  • fix(image): support 2-D convolution kernels larger than 7x7 by @arrufat in #443
  • perf(image): transpose 16x16 blocks in the IIR Gaussian row pass by @arrufat in #444
  • feat(python): release the GIL around image filters by @arrufat in #446
  • perf(image): run the resampling transforms in row bands on the caller's Io by @arrufat in #447
  • perf(matrix): run gemm in row bands on the caller's Io by @arrufat in #448
  • perf(image): separable two-pass u8 plane resize by @arrufat in #449
  • perf(image): sample the geometric transforms through a per-call Sampler by @arrufat in #450
  • perf(jpeg): stream baseline decode one MCU row at a time by @arrufat in #451
  • perf(jpeg): vectorize the render path by @arrufat in #453
  • perf(jpeg): check-free Huffman decoding with a 10-bit combined lookup by @arrufat in #454
  • perf(jpeg): 16-bit two-block inverse DCT by @arrufat in #456
  • perf(jpeg): rebuild the encoder around MCU rows and vectors by @arrufat in #457
  • fix(codecs): narrow vector lanes to bytes without the saturating @intcast by @arrufat in #459
  • build: run the tests as one binary and right-size the thread-pool tests by @arrufat in #458
  • perf(image): convolve struct pixels interleaved in the separable passes by @arrufat in #460
  • perf(image): run the recursive Gaussian interleaved for struct pixels by @arrufat in #461
  • perf(image): separable resize for f32 planes and interleaved struct pixels by @arrufat in #463
  • perf(image): convert, flips and insert run in row bands on the caller's Io by @arrufat in #464
  • perf(jpeg): decode restart-interval segments in parallel on the caller's Io by @arrufat in #465
  • feat(jpeg): write one restart marker per MCU row by default by @arrufat in #466
  • perf(jpeg): encode the scan in parallel across restart intervals by @arrufat in #468
  • fix(image): raise the comptime branch quota for wide IIR lanes by @arrufat in #470
  • perf(png): filter and deflate the rows in parallel chunks by @arrufat in #469
  • refactor: share the band plumbing and trim the duplicated helpers by @arrufat in #471
  • perf: resize views separably, pack the transposed gemm panel, band the rest by @arrufat in #472
  • perf(image): resample through a per-band ring instead of a full accumulator plane by @arrufat in #473
  • refactor(image): take an Options struct in ImagePyramid.init and reuse the blur scratch by @arrufat in #474
  • refactor(image): make HoughTransform unmanaged by @arrufat in #475
  • ci: run the test workflow once per pull request by @arrufat in #479
  • refactor(geometry): make ConvexHull unmanaged by @arrufat in #477
  • refactor(jpeg): make JpegState unmanaged by @arrufat in #478
  • refactor(features): make Tracer unmanaged by @arrufat in #476
  • refactor(image): share the kernel, lerp and plane plumbing in interpolation by @arrufat in #480
  • perf(image): resize any pixel type through the axis tap tables by @arrufat in #481
  • perf(image): gather nearest resize rows through a column table by @arrufat in #482
  • perf(image): write flips, invert and equalize straight into an output image by @arrufat in #483
  • feat(image): let rotate keep the input size with RotateSize.crop by @arrufat in #484
  • feat(clustering): add Chinese Whispers clustering for embeddings by @arrufat in #485
  • fix(clustering): widen CSR offsets to usize and document the strict threshold by @arrufat in #486
  • refactor(optimization): fold stop criteria into GlobalOptimizer.Options by @arrufat in #487
  • feat(codecs): JPEG XL and WebP through runtime-loaded system libraries by @arrufat in #488
  • feat(image): load animated GIF, WebP and JPEG XL through AnimatedImage by @arrufat in #489
  • feat(codecs): encode animated JPEG XL by @arrufat in #490
  • feat(codecs): encode animated WebP by @arrufat in #491
  • feat(codecs): store only the changed region of JPEG XL animation frames by @arrufat in #492
  • feat(codecs): store only the changed region of GIF animation frames by @arrufat in #493
  • refactor(codecs): stream every codec through std.Io.Reader / std.Io.Writer by @arrufat in #494
  • perf(gif): decode only the first frame for still loads by @arrufat in #495
  • refactor(codecs): share GIF block writers, one JPEG baseline writer by @arrufat in #496
  • refactor(api)!: namespace the public API following std conventions by @arrufat in #497
  • refactor(dynlib): move the runtime library loader out of codecs by @arrufat in #498
  • test: add -Dtest-filter and name tests "Subject: behavior" by @arrufat in #499
  • perf(metrics)!: separable, vectorized, band-parallel SSIM by @arrufat in #500
  • perf(metrics)!: vectorized, band-parallel psnr and meanPixelError by @arrufat in #501
  • refactor(cli): parse, help and argument counts once in main by @arrufat in #502
  • fix(image)!: validate blur parameters in the library by @arrufat in #503
  • fix(cli): address review findings across subcommands by @arrufat in #504
  • fix(image): anti-alias downscales in resize by @arrufat in #505
  • fix(python): work around aro crash on CPython's stdatomic backend by @arrufat in #506
  • build: migrate to Zig 0.17.0 by @arrufat in #507
  • fix: CI failures after the Zig 0.17.0 migration by @arrufat in #508
  • feat: CMYK color space and CMYK/YCCK JPEG decoding by @arrufat in #509
  • build: move the Python bindings into their own package by @arrufat in #510
  • build(python): support Python 3.15, drop 3.10 and Intel macOS by @arrufat in #511

Full Changelog: 0.10.0...0.11.0