Skip to content

1.2.0

Latest

Choose a tag to compare

@github-actions github-actions released this 04 Sep 00:14
· 13 commits to main since this release
Immutable release. Only release title and notes can be modified.
08ddc4f

Overview

This release adds rMQR (ISO/IEC 23941) as a third symbology, teaches every decoder to read Kanji mode segments, and introduces an options struct on every generator so new settings no longer need new parameter lists. Encoding and decoding are faster across all symbologies on both x64 (AVX2 / GFNI) and ARM64 (NEON), and the package now ships XML documentation.

Everything is source compatible with v1.1.1. Two APIs are marked [Obsolete] and will be removed in 2.0.0.

Highlights

rMQR support

rMQR is a rectangular symbol that fits banners, cable labels, and other narrow spaces where a square QR code wastes room. All 32 versions (R7x43 - R17x139) are supported for generation and decoding, including image decoding.

using SkiaSharp.QrCode;
using SkiaSharp.QrCode.Image;

// One-liner PNG
var pngBytes = RmQRCodeImageBuilder.GetPngBytes("https://example.com/r/12345", RmQREccLevel.M, size: 512);

// Fix the height and let the width be selected
var data = RmQRCodeGenerator.CreateRmQRCode("https://example.com/r/12345", RmQREccLevel.M, new RmQRCodeGeneratorOptions
{
    Height = RmQRHeight.H9,
    FitStrategy = RmQRFitStrategy.MinimizeArea,
});

// Decode back
if (RmQRCodeDecoder.TryDecode(data, out var text, out var info))
    Console.WriteLine($"{text} ({info.Version})");

Mixed-mode segmentation

By default the whole content is encoded in a single mode, so one lowercase letter pushes an otherwise numeric payload into Byte mode. Opt into segmentation and the content is split into the Numeric / Alphanumeric / Byte runs that cost the fewest bits, which often drops the symbol by a version or more. The result is never larger than the single-mode symbol, and identical when splitting would not help.

var standard = QRCodeGenerator.CreateQrCode(
    "https://example.com/item?id=123456789012345678901234567890",
    ECCLevel.M,
    new QRCodeGeneratorOptions { Segmentation = QRCodeSegmentation.Optimal }); // version 3 instead of 4

var rmqr = RmQRCodeGenerator.CreateRmQRCode(
    "https://example.com/p/1234567890123456",
    RmQREccLevel.M,
    new RmQRCodeGeneratorOptions { Segmentation = RmQRSegmentation.Optimal }); // R15x43 instead of R11x77

Micro QR has MicroQRSegmentation.Optimal as well, respecting each version's available modes.

Generator options

Every generator entry point gained an overload taking an options struct. The parameter list overloads keep their signatures, exceptions, and output; new options are added to the struct from here on. default means "all defaults".

var data = QRCodeGenerator.CreateQrCode("https://example.com", ECCLevel.M, new QRCodeGeneratorOptions
{
    Version = new QRCodeVersionRange(10, 20),   // inclusive range; an int pins a single version
    BoostEccLevel = true,                       // treat ECC as a floor, raise it within the same symbol size
    MaskPattern = 3,                            // pin a data mask instead of automatic selection
    QuietZoneSize = 0,
});
  • Version ranges (QRCodeVersionRange, MicroQRVersionRange) express a pinned version, a range, or a lower bound in one setting. A pinned version that cannot hold the content now throws a message naming the version, ECC level, and mode.
  • ECC boost picks the version for the level you request, then raises the level as far as that version's spare capacity allows without growing the symbol. Recommended when robustness matters more than an exact level, especially with icons. The builder spells it WithErrorCorrectionBoost().
  • Mask pattern pinning reproduces a symbol produced elsewhere byte for byte, and lets you exercise scanners against every pattern.

Kanji mode decoding

All three decoders now read ISO/IEC 18004 Kanji mode segments. Symbols that previously returned UnsupportedContent now decode to text. Encoding is unchanged: the generators still write Japanese text as UTF-8 in Byte mode.

if (QRCodeDecoder.TryDecode(bitmap, out var text, out var info))
    Console.WriteLine(text);
else if (info.Status == QRCodeDecodeStatus.UnmappedCharacter)
    // A Kanji segment used a CP932-only character (NEC row 13); route it to a CP932-capable reader.
    Console.WriteLine("CP932-only content");

The mapping is JIS X 0208, not CP932. The new UnmappedCharacter status is deliberately distinct from UnsupportedContent, so those symbols can be routed elsewhere instead of being silently rewritten.

Module rectangles for vector output

GetModuleRectangles returns the dark modules as merged, disjoint rectangles in module coordinates, typically halving the element count compared to one rectangle per module. Useful for SVG paths and draw-call based graphics APIs.

foreach (var r in qrData.GetModuleRectangles())
    sb.Append(CultureInfo.InvariantCulture, $"M{r.X},{r.Y}h{r.Width}v{r.Height}h{-r.Width}z");

For allocation-free use, size a pooled buffer with GetModuleRectanglesMaxCount() and call TryGetModuleRectangles(Span<ModuleRect>, out int). Available on QRCodeData, MicroQRCodeData, and RmQRCodeData.

Buffer sizing is Try-only

"The content does not fit" is a data-dependent answer, not a defect, so sizing reports it with a bool rather than an exception. TryGetRequiredBufferSize is available on all three generators, and its options parameter is optional.

if (!MicroQRCodeGenerator.TryGetRequiredBufferSize(userInput, MicroQREccLevel.L, out var size))
    return "Content does not fit a Micro QR symbol.";

Invalid arguments still throw, so a configuration mistake is never reported as "content too long".

Migration

Nothing breaks on upgrade. See the Migration Guide for the deprecations and the Kanji behaviour change.

What's Changed

  • [dotnet format] Automated changes by @github-actions[bot] in #356
  • chore(deps): bump the dependencies group with 3 updates by @dependabot[bot] in #357
  • chore(deps): bump actions/stale from 10.4.0 to 11.0.0 in the dependencies group by @dependabot[bot] in #358
  • feat: add rMQR Implementation by @guitarrapc in #359
  • feat: Optimizes RmQR performance by @guitarrapc in #360
  • feat: Improve StandardQR performance by @guitarrapc in #361
  • feat: improve result-object packing and quiet-zone placement by @guitarrapc in #362
  • feat: Improve rMQR Auto fit to table scan by @guitarrapc in #363
  • [dotnet format] Automated changes by @github-actions[bot] in #364
  • feat: improve rMQR image-decode by @guitarrapc in #365
  • feat: rMQR Encode ECI support by @guitarrapc in #366
  • feat: Improve ARM NEON rMQR and related handling by @guitarrapc in #367
  • docs: update benchmarks to latest code base by @guitarrapc in #368
  • fix: rmqr decoder correction cap from Full RS strength to Per-block cap read from a 64-row capacity table, by @guitarrapc in #369
  • feat: Support rMQR segmentation by @guitarrapc in #371
  • feat: add Kanji decode support by @guitarrapc in #372
  • chore(deps): bump actions/attest-build-provenance from 4.1.0 to 4.2.2 in the dependencies group by @dependabot[bot] in #374
  • feat: add TryGetRequiredBufferSize api by @guitarrapc in #373
  • feat: Introduce QRCodeGeneratorOptions by @guitarrapc in #375
  • feat: make buffer sizing Try-only, obsolete parameter-list GetRequiredBufferSize by @guitarrapc in #376
  • feat: add EccBoost is same version but ecc can be upgraded. by @guitarrapc in #377
  • ci: aot analysis by @guitarrapc in #378
  • feat: cut off MaskPlacer when it is possible by @guitarrapc in #379
  • feat: allow specify MaskPattern to use. (StandardQR / MicroQR) by @guitarrapc in #380
  • chore: add negative test for intended 1 module broke, picture approved test by @guitarrapc in #381
  • feat: Add GetModuleRectanglesMaxCount to retrieve merged geometries by @guitarrapc in #382
  • feat: Mixed-mode segmentation to StandardQR and MicroQR by @guitarrapc in #384
  • chore: compression no referenced and should be obsolete by @guitarrapc in #385
  • chore: add xml document to package by @guitarrapc in #386
  • chore: add docs page in playground by @guitarrapc in #387
  • chore: trim xml comments by @guitarrapc in #388

Full Changelog: 1.1.1...1.2.0