Skip to content

Add Dapplo.Windows.Imaging: WIC codec discovery, encode/decode and metadata - #81

Merged
Lakritzator merged 7 commits into
masterfrom
feature/wic-codec-discovery
Nov 2, 2026
Merged

Lakritzator merged 7 commits into
masterfrom
feature/wic-codec-discovery

Conversation

@Lakritzator

Copy link
Copy Markdown
Member

Requested by Robin · project thread

Why

Greenshot needs to know which formats WIC can read and write. The registry key of the WIC decoder category (HKCR\CLSID\{7ED96837-...}\Instance) lists only 2 of 14 decoders on Windows 11 26300, because the built-in and the packaged (Store) codecs aren't in it. Going through WPF for loading and saving also can't reach every codec (JPEG XL "no imaging component", a NullReferenceException in the HEIF encoder). This adds a new package that uses WIC directly, with no WPF, no System.Drawing and no other pixel library.

What

New package Dapplo.Windows.Imaging (net480 and net10.0-windows, references Com and Common):

  • Discovery: WicCodecs.GetDecoders(refresh) / GetEncoders(refresh) use IWICImagingFactory::CreateComponentEnumerator. WicCodecInfo reports kind, CLSID, container format, vendor, friendly name, author, version, extensions (lower case, with the dot), MIME types and signing status. A codec that can't be read is skipped and logged.
  • Encode: WicImage.Encode takes either a pointer and stride (read through a Dapplo IWICBitmapSource, so the image isn't copied) or a WicRowProvider callback (the encoder pulls the rows it needs). Options are ImageQuality, Lossless and any encoder option by name; options an encoder doesn't have are skipped. GetEncoderOptions lists what an encoder supports. WIC converts the pixel format when needed, and indexed formats (GIF) get a palette.
  • Decode: GetFrameInfo, and Decode into caller memory, into memory supplied once the size is known (one pass, e.g. a locked Bitmap), or to a WicRowConsumer. applyOrientation applies the EXIF orientation via IWICBitmapFlipRotator.
  • No codec: WicCodecNotFoundException (a COMException) for WINCODEC_ERR_COMPONENTNOTFOUND and WINCODEC_ERR_COMPONENTINITIALIZEFAILURE. If a stream or callback throws, that exception reaches the caller unchanged.
  • Metadata: this is a generic layer over the query language.
    • WicMetadata holds all items with friendly paths (/app1/ifd/exif/...; the tag paths work as aliases too), plus TryGet, TryGetRational, Orientation, the ICC ColorProfile, and EXIF/XMP/IPTC RawBlocks.
    • WicMetadataWriter has Set, Remove, SetColorProfile and AddRawBlock. When the encoder doesn't accept something, it is reported in WicEncodeResult.MetadataIssues and doesn't fail the encode.
    • PROPVARIANT mapping covers strings (LPSTR/LPWSTR), integers, floats, bool, rationals, FILETIME, GUID, blobs/vectors and nested blocks. WicMetadataPaths gives the paths for each container.
  • Streams are .NET Streams through a small IStream adapter. It works on part of a larger stream, and non-seekable streams go through a MemoryStream. COM objects are created per call and released deterministically, and calls work from STA and MTA threads.

Interop: every vtable was checked against wincodec.idl / wincodec.h / wincodecsdk.h / ocidl.h from the Windows SDK. Docs: doc/articles/imaging.md with compiled samples (ImagingSamples), plus the codec/path matrix, README, CHANGELOG [3.18.0] and version.json 3.18.

Tests

On the CI runner (Windows Server 2025), 0 tests fail on net10 and net48.

  • Discovery, including STA and MTA.
  • PNG/TIFF round trips with both encode overloads and both decode overloads.
  • 24bpp at width 63 with a padded stride.
  • JPEG XR lossless.
  • JPEG quality applied.
  • Streams that can't seek, and offsets inside a larger stream.
  • A row callback that throws.
  • Unknown container or data gives WicCodecNotFoundException.
  • Metadata paths, policies and ICC for JPEG/TIFF/PNG/JPEG XR.
  • All 8 orientations.
  • Unsupported queries are reported.
  • Raw EXIF/XMP carry-over.
  • GIF with transparency.

The runner has the HEIF and JPEG XL codecs registered, but their encoders fail with 0x88982F8B there. So JpegXl_LosslessRoundTrip and Heif_EncodeDecode are skipped on CI. Please run those two on a machine where the encoders work.

Follow-up (not in this PR)

Greenshot: reference the package, replace WpfFileFormatHandler.RetrieveDecoders() with WicCodecs.GetDecoders(), load and save through WicImage, and add the DLL to core-files.iss.


Generated by Claude Code

WicCodecs.GetDecoders/GetEncoders ask WIC (IWICImagingFactory::CreateComponentEnumerator)
for the codecs, which also reports the built-in and packaged (Store) codecs the registry
category key doesn't list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qu4NGxUZizEKzrG8d2DGY
…es for indexed formats, orientation 5 and 7

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qu4NGxUZizEKzrG8d2DGY
…coder wrote it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017qu4NGxUZizEKzrG8d2DGY
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@Lakritzator Lakritzator self-assigned this Nov 2, 2026
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 41442041037

Coverage increased (+2.4%) to 54.23%

Details

  • Coverage increased (+2.4%) from the base build.
  • Patch coverage: 290 uncovered changes across 14 files (1011 of 1301 lines covered, 77.71%).
  • No coverage regressions found.

Uncovered Changes

Top 10 Files by Coverage Impact Changed Covered %
src/Dapplo.Windows.Imaging/Interop/PropVariant.cs 183 75 40.98%
src/Dapplo.Windows.Imaging/WicImage.cs 326 281 86.2%
src/Dapplo.Windows.Imaging/Internal/ManagedIStream.cs 102 66 64.71%
src/Dapplo.Windows.Imaging/Internal/WicMetadataReading.cs 174 148 85.06%
src/Dapplo.Windows.Imaging/WicMetadataWriter.cs 124 101 81.45%
src/Dapplo.Windows.Imaging/Internal/WicPixelSource.cs 65 51 78.46%
src/Dapplo.Windows.Imaging/WicCodecs.cs 70 58 82.86%
src/Dapplo.Windows.Imaging/WicMetadata.cs 51 43 84.31%
src/Dapplo.Windows.Imaging/WicRational.cs 24 18 75.0%
src/Dapplo.Windows.Imaging/WicFrameInfo.cs 20 16 80.0%
Total (22 files) 1301 1011 77.71%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 11410
Covered Lines: 6393
Line Coverage: 56.03%
Relevant Branches: 6272
Covered Branches: 3196
Branch Coverage: 50.96%
Branches in Coverage %: Yes
Coverage Strength: 1038.49 hits per line

💛 - Coveralls

Copy link
Copy Markdown
Member Author

The build check on c02fdaf failed on net10 in two UiAutomationScrollerTests (ClosedWindow_EndsLoops, Wpf_HasNoWin32ScrollBar) with TimeoutException: The test window WpfScrollTestWindow didn't start. That isn't this PR's: the diff doesn't touch UI Automation or the WPF test window, net48 passed in the same job, and the other build run on the same commit passed. I re-ran the failed job once.


Generated by Claude Code

@Lakritzator
Lakritzator merged commit 844128b into master Nov 2, 2026
16 of 18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants