Skip to content
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

Merged
merged 38 commits into from
Jun 23, 2018
Merged

Add 16 bit png support #613

merged 38 commits into from
Jun 23, 2018

Conversation

JimBobSquarePants
Copy link
Member

@JimBobSquarePants JimBobSquarePants commented Jun 12, 2018

Prerequisites

  • I have written a descriptive pull-request title
  • I have verified that there are no overlapping pull-requests open
  • I have verified that I am following matches the existing coding patterns and practice as demonstrated in the repository. These follow strict Stylecop rules 👮.
  • I have provided test coverage for my change (where applicable)

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:

  • Refactor Rgba64 to use StructLayout.Sequential and add R,G,B,A fields.
  • Add To/From Rgba64 methods to IPixel
  • Use the new methods to read/write the pixel data. Conversion will be automatic to other formats via scaling.
  • Add tests

Additionally I would like to:

  • Create a new Rgb48 pixel format

Potentially we could create new pixel formats for greyscale and greyscale + alpha data but that could happen post launch if the workload becomes too much.

@codecov
Copy link

codecov bot commented Jun 12, 2018

Codecov Report

Merging #613 into master will increase coverage by 0.17%.
The diff coverage is 88.54%.

Impacted file tree graph

@@            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
Impacted Files Coverage Δ
...geSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs 100% <ø> (ø) ⬆️
tests/ImageSharp.Tests/TestFile.cs 88.23% <ø> (ø) ⬆️
....Tests/TestUtilities/Tests/TestEnvironmentTests.cs 63.63% <ø> (ø) ⬆️
tests/ImageSharp.Tests/TestImages.cs 100% <ø> (ø) ⬆️
...s/ImageSharp.Tests/Formats/Jpg/JpegDecoderTests.cs 48.64% <100%> (ø) ⬆️
src/ImageSharp/Common/Helpers/ImageMaths.cs 83.87% <100%> (ø) ⬆️
src/ImageSharp/PixelFormats/Byte4.cs 100% <100%> (ø) ⬆️
src/ImageSharp/PixelFormats/RgbaVector.cs 95.67% <100%> (+1.58%) ⬆️
tests/ImageSharp.Tests/PixelFormats/Alpha8Tests.cs 100% <100%> (ø) ⬆️
...s/ReferenceCodecs/SystemDrawingReferenceEncoder.cs 100% <100%> (ø) ⬆️
... and 82 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d715eb1...46df59f. Read the comment docs.

@JimBobSquarePants
Copy link
Member Author

JimBobSquarePants commented Jun 13, 2018

@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.

I need to figure out a way to set both the bit depth and the color type when encoding without complex sanitation to ensure that the values are correct.

e.g A 2 color palette image is never going to be a 16 bit image.

I'm stuck figuring it out.

Scratch that, enums to the rescue!

@JimBobSquarePants
Copy link
Member Author

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.

@tocsoft tocsoft added this to the 1.0.0-beta5 milestone Jun 17, 2018
@antonfirsov
Copy link
Member

@JimBobSquarePants will this resolve #285?

@JimBobSquarePants
Copy link
Member Author

@antonfirsov Yup, as long as the image pixel format is of high enough resolution then the accuracy is maintained. We’ll have Rgb48 and Rgba64 now to make that easier.

@JimBobSquarePants
Copy link
Member Author

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 Rgba64 and I had to get rid of the System.Drawing comparer for pngs as it was losing accuracy on decode.

Anyway, it works, we now have full 16 bit support.

Copy link
Member

@antonfirsov antonfirsov left a 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());
Copy link
Member

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.

public void ToRgba32(ref Rgba32 dest)
{
Vector4 vector = this.ToVector4() * 255F;
dest.R = (byte)MathF.Round(vector.X);
Copy link
Member

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.

Copy link
Member Author

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...

Copy link
Member Author

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);
Copy link
Member

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?

Copy link
Member Author

@JimBobSquarePants JimBobSquarePants Jun 19, 2018

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());
Copy link
Member

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.

Copy link
Member Author

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);
Copy link
Member

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?

Copy link
Member Author

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)
Copy link
Member

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 😄

Copy link
Member Author

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.

@antonfirsov
Copy link
Member

antonfirsov commented Jun 18, 2018

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.

@antonfirsov
Copy link
Member

@dlemstra is there an easy way to dump raw pixel data from libpng output?

@dlemstra
Copy link
Member

@antonfirsov You can probably do this with Magick.NET (Q16) by loading an image and getting the pixels of that image.

@JimBobSquarePants
Copy link
Member Author

@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 Rgba32 as our pixel format might actually preserve the same data.

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!

@antonfirsov
Copy link
Member

antonfirsov commented Jun 19, 2018

@dlemstra do I get the full Rgba64 value from 16bit png images? Is it possible without writing C++ code (eg. using Magick.NET)? (Ohh .. yes)
Any hint/link for start?

@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.

Decode_64Bpp_Rgba64_rgb-16-alpha.png is fine, it just looked like memory garbage to me 😄

JimBobSquarePants and others added 3 commits June 19, 2018 23:40
# Conflicts:
#	tests/ImageSharp.Tests/Drawing/Text/DrawTextOnImageTests.cs
@JimBobSquarePants
Copy link
Member Author

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 Rgba64 but it could be something different.

Perhaps we should use different comparers for 16 bit and 8 bit pixels formats?

e.g.

FontShapesAreRenderedCorrectly_LargeText<Rgba32>(provider: Solid2480x3508_(255,255,255,25

SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison.ImageDifferenceIsOverThresholdException : Image difference is over threshold!
Report ImageFrame 0: 
Total difference: 0.1764%
[Δ(1542,1542,1542,0) @ (819,6)];
[Δ(-1542,-1542,-1542,0) @ (822,6)];
[Δ(1285,1285,1285,0) @ (832,6)];
[Δ(2570,2570,2570,0) @ (833,6)];
[Δ(1542,1542,1542,0) @ (834,6)]...

@antonfirsov
Copy link
Member

@JimBobSquarePants I'll have a look tomorrow!

@JimBobSquarePants
Copy link
Member Author

@antonfirsov No need to look over the ImageComprarer it's correct. There was something, not quite right about the text drawing reference images that made them different. I recreated them and everything works.

@antonfirsov
Copy link
Member

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.
I want to do a second review however in the next few days, hope I can do it by Saturday night.

@JimBobSquarePants JimBobSquarePants merged commit 0d179d6 into master Jun 23, 2018
@JimBobSquarePants JimBobSquarePants deleted the js/16-bit-pngs branch June 23, 2018 15:44
@JimBobSquarePants
Copy link
Member Author

Merging this, it's solid and blocking other work.

}

/// <inheritdoc />
public override string ToString() => this.ToVector4().ToString();
Copy link
Member

@antonfirsov antonfirsov Jun 24, 2018

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.

Copy link
Member

@antonfirsov antonfirsov left a 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);
Copy link
Member

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);
Copy link
Member

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);
Copy link
Member

@antonfirsov antonfirsov Jun 24, 2018

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?

antonfirsov pushed a commit to antonfirsov/ImageSharp that referenced this pull request Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants