Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ and the packages use [Semantic Versioning](https://semver.org/). Finding IDs suc
Version 3.0 fixes a large number of interop bugs and deliberately breaks APIs whose concept was wrong.
Read the [migration guide](doc/articles/migration-3.0.md) before upgrading.

## [3.19.0]

### Added
- `WicFrameInfo.HasResolution` in Dapplo.Windows.Imaging: true when the file itself stores a resolution, false when `DpiX` / `DpiY` are only the default of the decoder. WIC can't say "unknown", every decoder fills in its own default (96 for a PNG without pHYs, 72 for a WebP without EXIF), so "the file says 72" (macOS screenshots, camera JPEGs) couldn't be told apart from "the file says nothing". Per container: PNG pHYs with the unit meter, JPEG JFIF density in inch or cm or EXIF XResolution, TIFF/DNG XResolution, JPEG XR WIDTH_RESOLUTION, BMP pixels per meter other than 0, GIF/ICO/DDS never, other containers an EXIF IFD with XResolution; a ResolutionUnit of 1 (none) or a pHYs/JFIF unit 0 (aspect ratio only) is no resolution. When the resolution is only in EXIF, `DpiX` / `DpiY` are now the EXIF values: the WIC JPEG decoder reported 96 for a JPEG with only EXIF XResolution 72. It is filled by `GetFrameInfo` and every `Decode` overload from the frame which is open anyway; only PNG (when WIC doesn't expose pHYs), JPEG XR and BMP read a few bytes of the file, the stream position is restored.
- `WicEncodeOptions.Palette` (up to 256 colors as 0xAARRGGBB) and the indexed pixel formats `WicPixelFormats.Format1bppIndexed` .. `Format8bppIndexed`: indexed pixels are encoded with that palette (GIF, PNG, BMP, TIFF), other pixels get the nearest color of the palette and the encoder is asked for an 8 bit indexed format.
- `WicEncodeOptions.Dither`: error diffusion when the pixels are reduced to fewer colors than they have.

### Changed
- GIF without a palette in the options: pixels with at most 256 colors and no partly transparent pixel get exactly these colors as palette, so decoding gives the same pixels; the pixels are mapped to the palette by Dapplo.Windows.Imaging, the WIC format converter picked a neighbor of an exact color. Before, WIC always chose the palette (`IWICPalette::InitializeFromBitmap`), which could change colors; it still does for more colors.

## [3.18.0]

### Added
Expand Down
33 changes: 30 additions & 3 deletions doc/articles/imaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,14 @@ The pixel formats are WIC GUIDs, `WicPixelFormats` has the common ones: `Format3
ImageSharp `Bgra32`), `Format32bppPbgra`, `Format24bppBgr` (GDI+ `Format24bppRgb`, ImageSharp `Bgr24`),
`Format32bppRgba` (ImageSharp `Rgba32`), and `Format64bppRgbaHalf` and `Format128bppRgbaFloat` for HDR. When the encoder
wants another pixel format (e.g. JPEG has no alpha), WIC converts the pixels while it reads them;
`WicEncodeResult.PixelFormat` is the format which was written. For an indexed format (GIF) a palette is made from the
pixels, with a transparent color when the source has alpha.
`WicEncodeResult.PixelFormat` is the format which was written.

For an indexed format (GIF) a palette is made from the pixels: with at most 256 colors (and no partly transparent
pixel) exactly these colors, so the GIF has the same pixels; otherwise WIC chooses the colors, with
`WicEncodeOptions.Dither` using error diffusion. `WicEncodeOptions.Palette` (up to 256 colors as `0xAARRGGBB`, alpha 0
is the transparent color) sets the palette yourself: pixels in `WicPixelFormats.Format8bppIndexed` (or 1, 2, 4 bpp)
are indexes into it, other pixels get the nearest color and the encoder is asked for an indexed format, so a PNG gets a
palette too.

When the pixels aren't in one buffer (ImageSharp can split a large image), the other overload takes a callback. The
encoder pulls the rows it needs, no copy of the image is made.
Expand Down Expand Up @@ -125,7 +131,8 @@ try
WicFrameInfo info = WicImage.Decode(source, WicPixelFormats.Format32bppBgra, (WicFrameInfo frame, out int stride) =>
{
bitmap = new Bitmap(frame.Width, frame.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution((float)frame.DpiX, (float)frame.DpiY);
// The resolution of the file; when it stores none, WIC reports the default of the decoder (96 or 72), use your own
bitmap.SetResolution(frame.HasResolution ? (float)frame.DpiX : 96, frame.HasResolution ? (float)frame.DpiY : 96);
bitmapData = bitmap.LockBits(new Rectangle(0, 0, frame.Width, frame.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
stride = bitmapData.Stride;
return bitmapData.Scan0;
Expand All @@ -144,6 +151,26 @@ With `applyOrientation: true` the EXIF orientation is applied (rotated and flipp
`WicFrameInfo.Width` and `Height` are then those of the upright image. A third overload hands the rows to a
`WicRowConsumer`, in bands from a pooled buffer.

### Resolution

WIC always reports a resolution: when the file stores none, each decoder fills in its own default (e.g. 96 for
PNG, 72 for WebP). `WicFrameInfo.HasResolution` tells the two apart, it is true only when the file itself stores a
resolution:

| Container | Stored resolution |
|---|---|
| PNG | a pHYs chunk with the unit meter (unit 0 is only an aspect ratio) |
| JPEG | JFIF density with the unit inch or cm, or EXIF XResolution with a ResolutionUnit other than none |
| TIFF, DNG | XResolution with a ResolutionUnit other than none (no ResolutionUnit means inch) |
| JPEG XR | WIDTH_RESOLUTION of the container |
| BMP | pixels per meter other than 0 |
| GIF, ICO, DDS | never, GIF only has an aspect ratio |
| others (WebP, HEIF, AVIF, JPEG XL, RAW, ...) | an EXIF IFD with XResolution and a ResolutionUnit other than none |

The metadata of the frame is used, the bytes of the file are read only for a PNG whose pHYs WIC doesn't expose, JPEG XR
and BMP. When the resolution is only in EXIF, `DpiX` / `DpiY` are the EXIF values: the JPEG decoder of WIC reports its
default (96) for a JPEG with only EXIF XResolution.

## When there is no codec

When WIC has no codec for the container format or doesn't recognize the stream, a `WicCodecNotFoundException` (a
Expand Down
3 changes: 2 additions & 1 deletion src/Dapplo.Windows.Example.DocSamples/ImagingSamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ public static Bitmap DecodeIntoBitmap(Stream source)
WicFrameInfo info = WicImage.Decode(source, WicPixelFormats.Format32bppBgra, (WicFrameInfo frame, out int stride) =>
{
bitmap = new Bitmap(frame.Width, frame.Height, PixelFormat.Format32bppArgb);
bitmap.SetResolution((float)frame.DpiX, (float)frame.DpiY);
// The resolution of the file; when it stores none, WIC reports the default of the decoder (96 or 72), use your own
bitmap.SetResolution(frame.HasResolution ? (float)frame.DpiX : 96, frame.HasResolution ? (float)frame.DpiY : 96);
bitmapData = bitmap.LockBits(new Rectangle(0, 0, frame.Width, frame.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
stride = bitmapData.Stride;
return bitmapData.Scan0;
Expand Down
Loading