-
-
Notifications
You must be signed in to change notification settings - Fork 854
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add 16 bit png support #613
Conversation
Codecov Report
@@ Coverage Diff @@
## master #613 +/- ##
=========================================
+ Coverage 88.63% 88.8% +0.17%
=========================================
Files 884 886 +2
Lines 37032 37960 +928
Branches 2670 2723 +53
=========================================
+ Hits 32822 33711 +889
- Misses 3415 3448 +33
- Partials 795 801 +6
Continue to review full report at Codecov.
|
@antonfirsov @tocsoft @dlemstra and anyone else reading this. I've got the fundamentals in place for reading 16 bit png's but need some help on the writing side.
e.g A 2 color palette image is never going to be a 16 bit image.
Scratch that, enums to the rescue! |
Failing tests are due to me changing png encoder grayscale conversion algorithm to ITU-R recommendation 709 matching the libpng implementation. Will reference images tomorrow and add specific bitdepth reference images plus tests. |
@JimBobSquarePants will this resolve #285? |
@antonfirsov Yup, as long as the image pixel format is of high enough resolution then the accuracy is maintained. We’ll have |
This is ready to review now. Sorry about the size of this chaps but it turned out adding two new pixel formats with associated operations was a big job touching everywhere. I had a hell of a time with image comparison as I had to switch to comparing using Anyway, it works, we now have full 16 bit support. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The output seems visually OK (except Decode_64Bpp_Rgba64_rgb-16-alpha.png maybe?), but I'm afraid we should rethink the testing method. Either I'm missing something, or all png tests are now testing the decoder against itself!
Suggestion:
- Keep the System.Drawing decoder as reference everywhere
- Use tolerant comparison for 64bit and 48bit (where System.Drawing output is inaccurate)
- Do a CRC verification on the result against a hard-coded value to ensure the match is exact (might be worth to produce the expected CRC with an external tool if possible)
@@ -310,6 +310,22 @@ public void ToBgra32(ref Bgra32 dest) | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public Argb32 ToArgb32() => this; | |||
|
|||
/// <inheritdoc/> | |||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
public void PackFromRgb48(Rgb48 source) => this.PackFromScaledVector4(source.ToScaledVector4()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should probably consider generating these default implementations later.
src/ImageSharp/PixelFormats/Rgb48.cs
Outdated
public void ToRgba32(ref Rgba32 dest) | ||
{ | ||
Vector4 vector = this.ToVector4() * 255F; | ||
dest.R = (byte)MathF.Round(vector.X); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hope we can do this later more efficiently without float-packing.
I guess doing just dest.R = (byte)(this.R / 255)
will lead to rounding errors.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe.... We'd probably have to scale up and down via shifting to preserve accuracy though...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Check out the new method using offset + shift I nicked from libpng. Much better!
@@ -20,74 +20,94 @@ public class PngDecoderTests | |||
{ | |||
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; | |||
|
|||
// This should be exact but for some reason it fails in some build environments. | |||
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.0001F, 26); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it travis? Can't we make it environment specific?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems to be both CI environments, well variants anyway.
// System.Drawing on Windows can decode 48bit and 64bit pngs but | ||
// it doesn't preserve the accuracy we require for comparison. | ||
// This makes CompareToOriginal method non-useful. | ||
configuration.Configure(new PngConfigurationModule()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should find a different testing method for those bit depths then, otherwise we are testing PngDecoder
against itself in all our test cases, loosing the point of the regression testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup, very much agree.
{ | ||
image.CompareToOriginal(provider, ImageComparer.Exact); | ||
image.VerifyEncoder(provider, "png", null, encoder, customComparer: ImageComparer.Exact); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me it looks like you are testing the decoders result against itself, running it one another time inside VerifyEncoder()
. Or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, this is useless, but it was the only way I could do any testing just now by round tripping. We need a better reference encoder for png
@@ -105,12 +106,12 @@ public TolerantImageComparer(float imageThreshold, int perPixelManhattanThreshol | |||
} | |||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
private static int GetManhattanDistanceInRgbaSpace(ref Rgba32 a, ref Rgba32 b) | |||
private static int GetManhattanDistanceInRgbaSpace(ref Rgba64 a, ref Rgba64 b) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 seems good, but it might be worth to have another look tomorrow. All our tests depend on this stuff 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Defo would like a second opinion on this. I had to change some tolerances, I'm assuming code round actually hid differences before.
We can even skip the CRC point in this PR, adding an issue for it. The point is that if our 64bpp/48bpp output is most likely fine, and we are sure we do not break existing code/test logic, that is an increment compared to what we currently have in the main branch. |
@dlemstra is there an easy way to dump raw pixel data from libpng output? |
@antonfirsov You can probably do this with Magick.NET (Q16) by loading an image and getting the pixels of that image. |
@antonfirsov The CRC won't tell us enough to determine things are correct, only that the compressed data integrity is ok so we can ignore that. I'll revert the reference decoder changes though and do some cleanup there and I'll use tolerance for the 16 bit ones. Using I also just double checked the rgb-16-alpha.png image using BeyondCompare and it's an exact match so that's ok. @dlemstra If you could rig something in the tests to bridge between an Magick.NET image and ours that would be amazing! |
@dlemstra do I get the full Rgba64 value from 16bit png images? @JimBobSquarePants 👍 for the revert, we should not risk our existing test coverage. If we could load a raw pixel data dump, and do a comparison against it, then we are good with the 16bit stuff! I'll see what I can do tonight.
|
# Conflicts: # tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
Hey @antonfirsov Can you do me a biggie and have a look at the image comparison code? The new font tests are failing with some pretty big differences after the merge. It could be rounding errors creeping in during conversion to/from Perhaps we should use different comparers for 16 bit and 8 bit pixels formats? e.g.
|
@JimBobSquarePants I'll have a look tomorrow! |
@antonfirsov No need to look over the |
Good news! I won't be able do make progress with Magick.NET testing now, so I think it's fine to merge this as-is + add an issue for the 16bit testing topic. |
Merging this, it's solid and blocking other work. |
} | ||
|
||
/// <inheritdoc /> | ||
public override string ToString() => this.ToVector4().ToString(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"({R},{G},{B})"
is much better for debugging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry for being late with my review, doing it post-merge now.
Have a few findings + a few worrying anomalies around comparison logic.
{ | ||
image.CompareToOriginal(provider, ImageComparer.Exact); | ||
image.VerifyEncoder(provider, "png", null, encoder, customComparer: ValidatorComparer); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two calls to DebugSave()
and CompareToOriginal()
would more straightforward and readable in a decoder test. ( (VerifyEncoder()
does the same though.)
@@ -20,79 +20,105 @@ public class PngDecoderTests | |||
{ | |||
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32 | Tests.PixelTypes.RgbaVector | Tests.PixelTypes.Argb32; | |||
|
|||
// TODO: Cannot use exact comparer since System.Drawing doesn't preserve more than 32bits. | |||
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.1302F, 2134); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Even if S.D decoder is inaccurate, and the output seems visually perfect, this high level of tolerance seems worrying for me. It might be a sign that something is wrong somewhere (maybe the comparison logic). What is the percentage difference if you lower the threshold?
@@ -17,7 +17,7 @@ public class ResizeTests : FileTestBase | |||
{ | |||
public static readonly string[] CommonTestImages = { TestImages.Png.CalliphoraPartial }; | |||
|
|||
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.005f); | |||
private static readonly ImageComparer ValidatorComparer = ImageComparer.TolerantPercentage(0.069F); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do we need this dramatic (~10x) increase in tolerance?
Add 16 bit png support
Prerequisites
Description
This WIP PR will add proper 16 bit png support to the library. Fixes #610
Currently we support decoding from 16 Rgb and Rgba pngs but we cheat while doing it by removing the least significant bits and preserving only 8 bits of data. To do this properly we need to be able to use
Rgba64
to losslessly read and write the pixel data.My plan is as follows:
Rgba64
to useStructLayout.Sequential
and addR,G,B,A
fields.Rgba64
methods toIPixel
Additionally I would like to:
Rgb48
pixel formatPotentially we could create new pixel formats for greyscale and greyscale + alpha data but that could happen post launch if the workload becomes too much.