Skip to content

Releases

2.0.0-preview.8

2.0.0-preview.8 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 05 Nov 06:10
Immutable release. Only release title and notes can be modified.
8a60d3d

Summary

FeatherQR now reads every code in an image, of any symbology, in one call. It writes and reads binary payloads with ECI, and the data types report their ECC level and mask. Micro QR and rMQR gain the multi-symbol reads Standard QR already had. The net8.0 target is removed (.NET 8 and 9 reaching end of support on 2026-11-10).

Key changes

  • Read every symbol of any symbology at once. SymbolDecoder reads Standard QR, Micro QR and rMQR codes from one image, and each result carries Symbology, Text and Corners. Micro QR and rMQR also gain DecodeSymbols and DecodeImageSymbols.

    using FeatherQR;
    using FeatherQR.SkiaSharp;
    
    await using var image = File.OpenRead("label.png");
    foreach (var symbol in await SymbolDecoder.DecodeSymbolsAsync(image))
    {
        var corner = symbol.Corners.TopLeft;
        Console.WriteLine($"{symbol.Symbology}: {symbol.Text} at ({corner.X}, {corner.Y})");
    }
  • Binary payloads and ECI. The generators take ReadOnlySpan<byte>, and TryDecodeBytes / TryDecodeImageBytes return the bytes with no character set applied. Standard QR and rMQR take an ECI assignment, which comes back as EciAssignment. The data types report EccLevel, and all but rMQR also report MaskPattern.

    using FeatherQR;
    
    byte[] payload = ...;
    var qr = QRCodeGenerator.Create(payload, QREccLevel.M);                         // no ECI header
    var tagged = QRCodeGenerator.Create(payload, QREccLevel.M, eciAssignment: 899); // declared as binary data
    
    if (QRCodeDecoder.TryDecodeBytes(tagged, out var bytes, out var info))
    {
        Console.WriteLine($"{bytes.Length} bytes, ECI {info.EciAssignment}"); // ECI 899
    }
    Console.WriteLine($"level {qr.EccLevel}, mask {qr.MaskPattern}");

Breaking changes

See the migration guide for details.

  • No net8.0 build. Apps on .NET 8 and 9 now use the netstandard2.1 build. It has the same API and output but no hardware intrinsics. Move to .NET 10 to get the SIMD paths back.
  • The empty-matrix constructors are no longer public. This covers new QRCodeData(int, int), new MicroQRCodeData(MicroQRVersion, int) and new RmQRCodeData(RmQRVersion, int). The restore constructors now throw InvalidDataException unless the format information is one a generator writes.
  • QRCodeImageBuilder rejects WithErrorCorrection and WithEciMode on a builder made from QRCodeData. They used to be ignored silently. Now they throw InvalidOperationException.
  • A width or height below one throws ArgumentOutOfRangeException naming that parameter, in TryDecodeImage and DecodeImageSymbols. It used to be an ArgumentException naming luminance.
  • Empty content written as default, [] or a null literal is ambiguous (CS0121) before C# 13. This is because of the new bytes overloads. On Micro QR it is ambiguous even with options. Pass "" or ReadOnlySpan<byte>.Empty to choose one.

What's Changed

Full Changelog: 2.0.0-preview.7...2.0.0-preview.8

2.0.0-preview.7

2.0.0-preview.7 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 26 Oct 19:06
Immutable release. Only release title and notes can be modified.
a6813de

Summary

This preview adds async stream I/O to FeatherQR.SkiaSharp, reads an encoded image straight from a Stream, and reads every QR code in an image in one call. Standard QR now throws instead of silently rewriting text that ISO-8859-1 cannot hold. Small-version encodes are faster on ARM64, and so is the scalar path on every platform.

Key changes

  • Async writes. Every method that writes an image to a Stream has an ...Async twin: SaveToAsync and SaveToSvgAsync on the builders, and the static SavePngAsync and SaveSvgAsync on each of the three builders. Under a UI SynchronizationContext the render moves off the caller's thread; an ASP.NET Core request pays no extra hop.
using FeatherQR;
using FeatherQR.SkiaSharp;

// ASP.NET Core: write the PNG into the response body without blocking a thread.
app.MapGet("/qr", async (string text, HttpResponse response, CancellationToken ct) =>
{
    response.ContentType = "image/png";
    await QRCodeImageBuilder.SavePngAsync(text, response.Body, cancellationToken: ct);
});

// Builder form, same options as before.
await new QRCodeImageBuilder(text).WithSize(512).SaveToAsync(stream, ct);
  • Stream reads. Decode(Stream) and DecodeAsync(Stream) on the three image decoders read an encoded PNG, JPEG or WebP and return a result instead of out parameters: QRCodeDecodeResult, MicroQRCodeDecodeResult and RmQRCodeDecodeResult, each with Text, Info and Success. With C# 14 they are reachable on QRCodeDecoder, MicroQRCodeDecoder and RmQRCodeDecoder; on older language versions call them on QRCodeImageDecoder and its siblings.
// Read an uploaded image (Request.Body, IFormFile.OpenReadStream(), a FileStream ...).
var result = await QRCodeDecoder.DecodeAsync(stream, ct);
if (result.Success)
    Console.WriteLine($"{result.Text} (version {result.Info.Version})");
  • Every QR code in an image. DecodeSymbols returns each Standard QR code found, once, with its text and corners. From an SKBitmap or a stream in FeatherQR.SkiaSharp, and from luminance in the core as DecodeImageSymbols, in an array form and a span form that allocates only the texts. On a photograph of a sheet of 60 codes it reads all 60 in one call.
// From a bitmap or a stream.
QRCodeDecodeResult[] codes = QRCodeDecoder.DecodeSymbols(bitmap);
codes = await QRCodeDecoder.DecodeSymbolsAsync(stream, ct);

foreach (var code in codes)
    Console.WriteLine($"{code.Text} at {code.Info.Corners.TopLeft}");

// From grayscale pixels, into a buffer you own: returns how many were written.
Span<QRCodeDecodeResult> room = new QRCodeDecodeResult[8];
int count = QRCodeDecoder.DecodeImageSymbols(luminance, width, height, room);
  • Read limits. A stream read refuses an image over 64 MiB of encoded bytes or 16,777,216 pixels (4,096 × 4,096) by its header, before any pixel is allocated, with an InvalidDataException that names the limit. Pass ImageStreamReadLimits to raise or lower them.
var limits = new ImageStreamReadLimits(maxBytes: 128 * 1024 * 1024, maxPixels: 48_000_000);
var result = await QRCodeDecoder.DecodeAsync(stream, limits, ct);
  • Performance. On ARM64, mask selection for Standard QR versions 1-11 uses the 128-bit lane-per-pattern scorer, so small-version encodes on Apple silicon are much closer to x64. The scalar path (hardware intrinsics off, .NET Framework) encodes about 15 % faster, and UTF-8 handling allocates less on every target.
  • Playground. The decode panel can read every QR code in an image and outline each one, and the generator has a Photograph effect to preview a printed-and-photographed symbol.

Breaking change

  • EciMode.Iso8859_1 over text it cannot hold throws. QRCodeGenerator.Create, CreateStructuredAppend and TryGetRequiredBufferSize throw ArgumentException (naming eciMode) when the text has a character above U+00FF, as RmQRCodeGenerator already did. Earlier versions wrote a look-alike (ž as z, “ as ") or ? on .NET 8+, and the low byte on netstandard, so the symbol read back as different text. Text that ISO-8859-1 holds is written as before. Use EciMode.Utf8, or EciMode.Default to let the library choose. Details in the migration guide.

What's Changed

  • chore: add wider encode corpus benchmarks by @guitarrapc in #479
  • feat: Improve macOS ARM64 by discard NEON but use Vector128 optimized implementation by @guitarrapc in #480
  • feat: Improve scalar mask scoring and UTF-8 handling, and reject incompatible ISO-8859-1 input. by @guitarrapc in #481
  • feat: Async API for GUI/Web stream I/O by @guitarrapc in #483

Full Changelog: 2.0.0-preview.6...2.0.0-preview.7

2.0.0-preview.6

2.0.0-preview.6 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 20 Oct 17:32
Immutable release. Only release title and notes can be modified.
09bbc48

Summary

This preview makes Optimal segmentation the default, adds FNC1 to Standard QR and rMQR, and introduces a new package, FeatherQR.Gs1, for GS1 QR Code and GS1 Digital Link data. Standard QR also reads camera photographs far more reliably. The generator options changed shape, so update FeatherQR and FeatherQR.SkiaSharp together.

Key changes

  • Optimal segmentation is the default. Mixed content such as URLs with numeric tails, order numbers and labels now gets the smallest symbol without opting in. Optimal encoding was made faster first.
using FeatherQR;
using FeatherQR.SkiaSharp;

var url = "https://example.com/item?id=123456789012345678901234567890";

// Default: Optimal splits the text into Byte + Numeric runs.
var optimal = QRCodeGenerator.Create(url, QREccLevel.M);   // version 3

// Single: one segment in one mode, as in 1.x and earlier previews.
var single = QRCodeGenerator.Create(url, QREccLevel.M,
    new QRCodeGeneratorOptions { Segmentation = QRSegmentation.Single });   // version 4

// Options are settable now, so you can also assign after construction.
var options = new QRCodeGeneratorOptions();
options.Segmentation = QRSegmentation.Single;

// Image builders take the same setting.
var png = new QRCodeImageBuilder(url)
    .WithSegmentation(QRSegmentation.Single)
    .ToByteArray();

// RmQRCodeGeneratorOptions and MicroQRCodeGeneratorOptions work the same way, with RmQRSegmentation and MicroQRSegmentation.
  • FNC1 encode and decode for Standard QR and rMQR, in first position (GS1) and second position (AIM application indicator). Set it with the new Fnc1 option (Fnc1Mode.FirstPosition or Fnc1Mode.SecondPosition(...)). Decoders report it on QRCodeDecodeInfo.Fnc1 and RmQRCodeDecodeInfo.Fnc1. Output without FNC1 is unchanged.
  • New package: FeatherQR.Gs1.
    • QRCodeGenerator.CreateGs1 and CreateGs1DigitalLink, plus the rMQR equivalents, write a GS1 QR Code or a Digital Link URI from (AI) text or an element string. Invalid data throws Gs1FormatException, which reports where the problem is.
    • Gs1ElementString validates element strings and converts between element strings, (AI) text and Digital Link URIs. TryRead turns decoded text into (AI) text. The span forms allocate nothing.
    • The AI table is generated from the GS1 Barcode Syntax Dictionary, release 2026-01-27 (Apache-2.0).
    • The generator methods are C# 14 extension members. On C# 12 and 13, call them on Gs1QRCodeGenerator and Gs1RmQRCodeGenerator.
dotnet add package FeatherQR.Gs1 --prerelease
using FeatherQR;
using FeatherQR.Gs1;
using FeatherQR.SkiaSharp;

// GS1 QR Code: an element string behind FNC1 in first position.
var gs1 = QRCodeGenerator.CreateGs1("(01)09520123456788(17)251231(10)ABC123", QREccLevel.M);
File.WriteAllBytes("label.png", new QRCodeImageBuilder(gs1).ToByteArray());

// GS1 Digital Link: https://id.gs1.org/01/09520123456788/10/ABC123?17=251231
var link = QRCodeGenerator.CreateGs1DigitalLink("(01)09520123456788(17)251231(10)ABC123", QREccLevel.M);

// Your own domain instead of id.gs1.org: https://brand.example/01/09520123456788
var branded = QRCodeGenerator.CreateGs1DigitalLink("(01)09520123456788", QREccLevel.M, stem: "https://brand.example");

// Invalid data throws Gs1FormatException, which says what is wrong and where.
try
{
    QRCodeGenerator.CreateGs1("(01)09520123456789", QREccLevel.M);
}
catch (Gs1FormatException ex)
{
    Console.WriteLine(ex.Message); // InvalidCheckDigit at 17 (1 character): the check digit is wrong.
}
  • Much better photo decoding for Standard QR. On BoofCV's 536 test photographs the decoder now reads 457, up from 322. zxing-cpp reads 392 of them. Photographs also decode faster on x64.
  • Shift_JIS text decodes. Byte segments under ECI 20, or with no ECI header and Shift_JIS bytes, now return Japanese text.
  • Playground supports GS1 QR Code, GS1 Digital Link and AIM formats, and shows the FNC1 mode and GS1 data when decoding.

Breaking changes

See the migration guide for details and code samples.

  • Optimal is the default, and the enum values swapped. In QRSegmentation, MicroQRSegmentation and RmQRSegmentation, Optimal is now 0 and Single is 1. A call that passes no segmentation can produce a smaller symbol with a different bit stream, rMQR can pick different dimensions, and content that used to overflow can now fit. An integer stored from an earlier release reads as the other value. To keep the old output, set Segmentation = QRSegmentation.Single or call .WithSegmentation(QRSegmentation.Single) on an image builder.
  • Generator options use set instead of init. QRCodeGeneratorOptions, MicroQRCodeGeneratorOptions and RmQRCodeGeneratorOptions are now mutable record structs. The constructors added in preview.3 are gone, so write new QRCodeGeneratorOptions { QuietZoneSize = 2 }. This is binary breaking: an assembly compiled against an earlier preview throws MissingMethodException. Recompile, and update FeatherQR and FeatherQR.SkiaSharp together.
  • Some symbols decode differently. FNC1 symbols now decode, where Standard QR returned UnsupportedContent and rMQR returned InvalidBitstream. Byte segments under ECI 20 decode instead of returning UnsupportedContent. Byte segments without an ECI header can now read as Shift_JIS, so a few ISO-8859-1 and Windows-1252 texts read differently.
  • Micro QR writes UTF-8 for a few ISO-8859-1 texts whose bytes would otherwise read back as different text, such as é. These texts can need a larger version or no longer fit M4.

What's Changed

Full Changelog: 2.0.0-preview.5...2.0.0-preview.6

2.0.0-preview.5

2.0.0-preview.5 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 09 Oct 20:10
Immutable release. Only release title and notes can be modified.
f4b74de

Summary

This preview makes Standard QR encoding about three times faster from version 19 up, and 4 to 7.5 times faster on .NET Framework. Every symbol is byte-identical to preview.4, and the public API is unchanged. Standard QR now checks its arguments up front, as Micro QR and rMQR already did.

Key changes

Faster Standard QR encoding. Mask selection, which took most of an encode, was rewritten. Payload writing, Reed-Solomon and the quiet zone also got faster. Against preview.4:

  • .NET 10: 2.7 to 3.4 times faster from version 19 up, and 1.1 to 1.3 times at versions 1 to 10.
  • x64 without AVX, the 128-bit path a default NativeAOT publish takes: 1.7 to 2.5 times from version 19 up.
  • .NET Framework 4.8: 4 to 7.5 times from version 19 up.
  • Micro QR and rMQR encoding: 1.1 to 1.25 times.

Other improvements

  • Micro QR matrix decoding no longer branches on each module. M2 and M3 symbols that change from call to call decode 2.3 to 2.8 times faster, and a NativeAOT build now runs close to the JIT (Apple M2).
  • CreateStructuredAppend no longer slows down on text with long runs of U+FEFF. A 15,000-character set with runs of 200 takes 1.2 ms instead of 2.1 ms.
  • Image decoding no longer takes most cross-shaped marks for finder patterns. Read results on our test sets are unchanged.
  • The README now covers NativeAOT on x64. A default publish runs the 128-bit paths and can take up to twice as long as the JIT. Targeting x86-64-v3 brings it close to JIT speed.

Breaking changes

  • Standard QR checks its arguments up front. QRCodeGenerator.Create, CreateStructuredAppend and TryGetRequiredBufferSize throw ArgumentOutOfRangeException for an undefined QREccLevel or EciMode, or for a quiet zone above 10,000 modules. Before, these failed deep inside, wrote an unreadable symbol or overflowed. The QRCodeData constructors apply the same bound, and also refuse a negative quiet zone or a version outside 1 to 40. See the migration guide.

What's Changed

  • fix: validation and allocation before throw by @guitarrapc in #446
  • feat: Improve Large QR Ecc table lookup by linear search to lookups and stack buffers by @guitarrapc in #447
  • feat: Improve Mask evaluation penalty 3 by @guitarrapc in #448
  • feat: Improve encoding speed when pinning mask by @guitarrapc in #450
  • chore: cross benchmark and improve microqr codeword extraction by @guitarrapc in #449
  • feat: Improve Encode pipeline by remove duplicated analysis by @guitarrapc in #451
  • feat: Module Placer Mask Tranposed over v12 with vectorize row/col rules by @guitarrapc in #452
  • chore: add benchmark ci by @guitarrapc in #454
  • feat: Improve Payload writers by @guitarrapc in #455
  • feat: Improve StandardQR Encode (no Span) with use Vector pack by @guitarrapc in #457
  • chore(deps): bump golang.org/x/text from 0.3.7 to 0.3.8 in /tools/CrossLanguageBenchmark/go in the go_modules group across 1 directory by @dependabot[bot] in #456
  • feat: Improve RS by handling multiple blocks at once, code by @guitarrapc in #459
  • feaet: improve scalar mask selection for .NET Framework by @guitarrapc in #458
  • feat: Improve Quietzone gaps clear line over 16 bytes by @guitarrapc in #460
  • fix: the lane walk's exit moves with the waiting lanes by @guitarrapc in #461

Full Changelog: 2.0.0-preview.4...2.0.0-preview.5

2.0.0-preview.4

2.0.0-preview.4 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 02 Oct 13:24
Immutable release. Only release title and notes can be modified.
4f4fd4b

Summary

This preview adds Kanji-mode encoding to all three symbologies through a new AllowKanji option, off by default. It also makes encoding and decoding faster in builds that cannot use AVX2, such as a default NativeAOT publish and Blazor WebAssembly.

Coming from 1.x? The migration guide covers every 2.0.0 preview.

Key changes

Kanji mode for Japanese text

Standard QR, Micro QR and rMQR can now write Japanese text in Kanji mode: 13 bits a character and no ECI header, where UTF-8 takes 24 bits a character plus an ECI header. A symbol holds about 1.85 times as many characters, so the same text often fits a smaller version.

var kanji = new QRCodeGeneratorOptions { AllowKanji = true };

QRCodeGenerator.Create("日本語のテキスト", QREccLevel.M);        // UTF-8, version 2
QRCodeGenerator.Create("日本語のテキスト", QREccLevel.M, kanji); // Kanji, version 1

// Text with ASCII needs Optimal, which mixes Kanji and ASCII runs when that gives a smaller symbol
QRCodeGenerator.Create("東京タワー333m", QREccLevel.M, kanji with { Segmentation = QRSegmentation.Optimal }); // version 1

MicroQRCodeGenerator.Create("こんにちは", MicroQREccLevel.L, new MicroQRCodeGeneratorOptions { AllowKanji = true }); // M3 instead of M4
  • Off by default, and without it the output is byte-identical to preview.3. Not every reader handles Kanji mode: in our tests the iPhone Camera read it, but the built-in QR scanner and Google Lens on Android 17 did not. Turn it on when you know your readers or need the capacity.
  • Text is written in Kanji mode only when every character is in JIS X 0208. One character outside it, such as an emoji, ① or halfwidth katakana, keeps the whole text in UTF-8. So does an explicit EciMode or Utf8Bom.
  • TryGetRequiredBufferSize and CreateStructuredAppend follow the option.

Faster on NativeAOT and WebAssembly

A default NativeAOT publish on x64 cannot use AVX2, and WebAssembly had little SIMD code, so much of the library ran scalar there. Those builds, and x64 CPUs without AVX, now have 128-bit SIMD paths for encoding and decoding. Decoding gains the most. No code change is needed.

Image decoding

  • Standard QR, Micro QR and rMQR now share one image decode pipeline. Read results across our synthetic and real-image test sets are unchanged from preview.3.
  • Retrying with a larger buffer is cheap for Micro QR and rMQR images. When the destination is too short, the decoder now stops at the symbol it read and reports DestinationTooSmall with that symbol's version, where it used to keep searching the image.
Span<char> buffer = stackalloc char[64];
if (!RmQRCodeDecoder.TryDecodeImage(luminance, width, height, buffer, out var written, out var info)
    && info.Status == DecodeStatus.DestinationTooSmall)
{
    buffer = new char[RmQRCodeDecoder.GetMaxDecodedLength(info.Version)];
    RmQRCodeDecoder.TryDecodeImage(luminance, width, height, buffer, out written, out info);
}

Breaking changes

  • A failed matrix decode reports no characters written. The module-matrix TryDecode span overloads of all three decoders now set charsWritten to 0 whenever they return false, as the image overloads already did. Before, a symbol that failed partway could report the characters read before the failure. See the migration guide.

What's Changed

  • chore(deps): bump the dependencies group with 2 updates by @dependabot[bot] in #441
  • feat: Share the image decode pipeline across Standard QR, Micro QR and rMQR by @guitarrapc in #442
  • feat: add Kanji encoding support by @guitarrapc in #443
  • feat: Shared decode pipeline for MicroQR and rMQR by @guitarrapc in #444
  • feat: add 128bit SIMD fallback for NativeAOT default, WASM and non-Avx CPU by @guitarrapc in #445

Full Changelog: 2.0.0-preview.3...2.0.0-preview.4

2.0.0-preview.3

2.0.0-preview.3 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Sep 15:00
Immutable release. Only release title and notes can be modified.
b72bbcb

Summary

This preview finishes the 2.0.0 API cleanup. Every public name follows one naming rule, the members deprecated in 1.2.0 are gone, and results and options are immutable. It also adds Structured Append encoding and decoding for Standard QR, reports where a decoded symbol sits in the image, and reads many more real-world captures. The next preview adds Kanji-mode encoding.

Coming from 1.x? The package split and namespace changes shipped in 2.0.0-preview.2. The migration guide covers both previews.

Key changes

Image decoding reads far more real-world captures

All three symbologies now read images that failed before: crisp symbols down to one pixel per module, anti-aliased or blurred edges, uneven lighting, and strong perspective or two-axis tilt. Standard QR also reads bowed prints and finders printed too thin or too thick, and Micro QR reports fewer false reads. Finder search and thresholding are faster on both x64 and ARM64.

Structured Append (Standard QR)

Standard QR now both creates and reads Structured Append sets, where one message is split across up to 16 symbols.

QRCodeGenerator.CreateStructuredAppend splits text that does not fit in one symbol into symbols of the same version. The version range you allow decides how many symbols it uses. On decode, each symbol reports its index, count and parity through QRCodeDecodeInfo.StructuredAppend instead of failing with UnsupportedContent.

QRCodeData[] set = QRCodeGenerator.CreateStructuredAppend(longText, QREccLevel.M,
    new QRCodeGeneratorOptions { Version = QRVersionRange.AtMost(10) });

Symbol corners in the decode result

For a successful image decode, the decode info of all three symbologies reports Corners: the four outer corners of the symbol in image pixel coordinates. They are named in the symbol's own frame, so they also tell you its rotation and whether the capture is mirrored.

Breaking changes

  • API renaming. A prefix names the symbology (QR, MicroQR, RmQR), and no prefix means all three. The migration guide has a replacement script.
Before After
ECCLevel QREccLevel
QRCodeSegmentation QRSegmentation
QRCodeVersionRange QRVersionRange
QRCodeDecodeStatus DecodeStatus
QRCodeGenerator.CreateQrCode QRCodeGenerator.Create
MicroQRCodeGenerator.CreateMicroQRCode MicroQRCodeGenerator.Create
RmQRCodeGenerator.CreateRmQRCode RmQRCodeGenerator.Create
QRCodeCalculatedSize.QrSize, MicroQRCodeCalculatedSize.QrSize .Size
QRCodeGeneratorOptions.Utf8BOM .Utf8Bom
QRCodeRenderer SymbolRenderer
QRCodeImageBuilderBase<TSelf> SymbolImageBuilderBase<TSelf>
QRCodeExtensions SKCanvasExtensions
  • Recompile if you call an options constructor. The constructors of QRCodeGeneratorOptions, MicroQRCodeGeneratorOptions and RmQRCodeGeneratorOptions gain an optional allowKanji parameter. Source stays compatible, but binaries built against preview.3 that call them need a rebuild. Object initializers, the form the docs use, are not affected.
  • Removals. Generators take an options struct only, GetRequiredBufferSize is replaced by TryGetRequiredBufferSize, and the string overloads are gone. A string still converts to ReadOnlySpan<char>, except on netstandard2.0 below C# 14.
  • Immutable, sealed types. QRCodeCalculatedSize is built by the library, IconData is init-only, and GradientOptions compares by value. The data types and image builders are sealed.
  • Rendering can change existing images. Module styling no longer reaches the finder patterns, a non-square canvas or render area fits the symbol instead of stretching it, padding takes the background colour instead of transparency, and custom FinderPatternShapes draw only the dark modules.
  • Colours are values. WithColors takes two required colours, with new WithCodeColor, WithBackgroundColor and WithClearColor setters. SymbolRenderer.Render takes SKColor instead of SKColor?.
  • Decode behavior. Structured Append symbols decode instead of returning UnsupportedContent.

What's Changed

  • chore: public api check for future library split by @guitarrapc in #389
  • chore: Split Core packages and rename library by @guitarrapc in #394
  • chore: replace Old SkiaSharp.QrCode to FeatherQR by @guitarrapc in #395
  • feat(breaking): remove old API and rename Public API by @guitarrapc in #396
  • Terminal feat: report symbol corners from image decode by @guitarrapc in #397
  • fix: Keep finder patterns readable when modules are styled by @guitarrapc in #399
  • feat(breaking): fit the symbol to the canvas instead of stretching it by @guitarrapc in #400
  • fix: stop stretch with SymbolRenderer.Render, but change to keep square and fit it. by @guitarrapc in #401
  • feat: WithColor/WithModuleShape/WithFinderPatternShape to be not optional, added WithCodeColorWithBackgroundColor, WithClearColor by @guitarrapc in #402
  • fix: svg rendering lost finder pattern when alpha has been set by @guitarrapc in #403
  • feat: Stop passing null for Render method colour value by @guitarrapc in #404
  • chore: enable deterministics build by @guitarrapc in #405
  • chore: add missing overload tests by @guitarrapc in #406
  • fix: refuse a non-finite corner radius in the three rounded shapes by @guitarrapc in #408
  • fix: builder setter cross effect on tests by @guitarrapc in #407
  • feat: add StructuredAppend Decoder/Encoder by @guitarrapc in #409
  • Fixes three image-decoder defects by @guitarrapc in #410
  • feat: Improve Structured Append NEON handling by @guitarrapc in #411
  • feat: increase image decode accuracy for clean images by @guitarrapc in #412
  • fix: Cannot decode StandardQR v19-H195px / v27L 255px pattern by @guitarrapc in #413
  • feat: increase image decode accuracy for anti-alias & 1px/module image, crisp renders at fractional low density by @guitarrapc in #414
  • feat: Improve decode performance for Structured Append and also MicroQR/rMQR by @guitarrapc in #415
  • feat: Improve Otsu image decode histogram with 256bit register mask count & improve scalar path by @guitarrapc in #416
  • feat: improve inversed-polarityretry speed by reuse otsu histogram by @guitarrapc in #418
  • chore: add sweap fixture images of Realm zxing cpp samples by @guitarrapc in #417
  • feat: improve decoding of large Standard QR symbols by vectorizing the piecewise mesh sampler by @guitarrapc in #420
  • chore: add sweap fixture images of Realm zxing cpp samples by @guitarrapc in #421
  • feat: Improve Decode finder for AMD64/ARM64 & Otsu hisogram for ARM64 by @guitarrapc in #422
  • feat: improve accuracy for the threshold under uneven lighting by @guitarrapc in #423
  • feat: Improve Micro QR and rMQR failure path by @guitarrapc in #424
  • feat: Improve Micro QR false results by @guitarrapc in #425
  • feat: Improve decode accuracy for grid position difference by @guitarrapc in #426
  • feat: Improve decode accuracy for distorted finder frame by @guitarrapc in #427
  • feat: improve over 30% distorted images failure. Expand column approval to perspective view by @guitarrapc in #428
  • feat: Improve decoding two-axis perspective images, a tilt image mis-reading by @guitarrapc in #429
  • refactor: remove dead code and wrong comments by @guitarrapc in #430
  • chore: rMQR keep the anisotropic grid, record the perspective gate measurement, correct the non-square edge by @guitarrapc in #431
  • feat: change StandardQR decoder order from always mesh to use mesh only when right-bottom alignment couldt't anchored by @guitarrapc in #432
  • chore: add tests by @guitarrapc in #433
  • feat: Improve decoding Thin or thick rings StandardQR by @guitarrapc in #434
  • feat: Improve strongly keystoned rMQR by tracing its timing-pattern perimeter by @guitarrapc in #436
  • chore: analyse AOT SIMD capability by @guitarrapc in #435
  • feat: Improve decoding for the four interior nodes are searched wh...
Read more

2.0.0-preview.2

2.0.0-preview.2 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Sep 15:03
Immutable release. Only release title and notes can be modified.
0d68c9d

Summary

The repository is now guitarrapc/FeatherQR, matching the package name. The library code is unchanged from 2.0.0-preview.1.

Key changes

Breaking changes

None since 2.0.0-preview.1. Coming from 1.x, see the 2.0.0-preview.1 notes and the migration guide.

What's Changed

Full Changelog: 1.2.0...2.0.0-preview.2

2.0.0-preview.1

2.0.0-preview.1 Pre-release
Pre-release

Choose a tag to compare

@github-actions github-actions released this 28 Sep 15:03
Immutable release. Only release title and notes can be modified.
f330820

Summary

The first 2.0.0 preview splits the library into a dependency-free core and a SkiaSharp rendering package, and renames it to FeatherQR. Encoding and decoding behave exactly as in 1.2.0, and type names are unchanged; the work is in using lines and, if you want it, the package reference. The API cleanup announced in 1.2.0 comes in a later preview.

See the migration guide for details.

Key changes

Three packages, with a core that needs no SkiaSharp

Package Contents
FeatherQR Generators, decoders, data types, options. No dependencies on .NET 8+
FeatherQR.SkiaSharp Image builders, renderer, SKCanvas extensions, icons and shapes, SKBitmap decoding
SkiaSharp.QrCode No code. A metapackage that resolves FeatherQR.SkiaSharp

A project that only needs module matrices, or decodes from another image library through the luminance overloads, can reference FeatherQR alone and no longer carries the SkiaSharp native library.

The SkiaSharp.QrCode install line keeps working

An existing <PackageReference Include="SkiaSharp.QrCode" /> restores FeatherQR.SkiaSharp and FeatherQR transitively. Switch the reference to FeatherQR.SkiaSharp when convenient. All three packages ship at the same version.

Breaking changes

  • Namespaces. SkiaSharp.QrCode → FeatherQR, and SkiaSharp.QrCode.Image → FeatherQR.SkiaSharp.
  • TryDecode(SKBitmap) moved. The bitmap overloads are now extension members in FeatherQR.SkiaSharp. QRCodeImageDecoder.TryDecode(bitmap, …) works on every language version (likewise MicroQRCodeImageDecoder and RmQRCodeImageDecoder). The 1.x spelling QRCodeDecoder.TryDecode(bitmap, …) still compiles on C# 14.

What's Changed

Full Changelog: 1.2.0...2.0.0-preview.1

1.2.0

Choose a tag to compare

@github-actions github-actions released this 04 Sep 00:14
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: https://github.com/guitarrapc/SkiaSharp.QrCode/compare/1.1.1.....

Read more

1.1.1

Choose a tag to compare

@github-actions github-actions released this 05 Sep 07:23
Immutable release. Only release title and notes can be modified.

What's Changed

  • fix: FinderPattern filled by outer ring dark colour when using transparent bg by @guitarrapc in #355

Full Changelog: 1.1.0...1.1.1