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

Adds support for encoding 8-bit bitmaps #906

Merged
merged 11 commits into from
May 12, 2019

Conversation

brianpopow
Copy link
Collaborator

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 PR add the support for encoding 8-Bit Bitmaps.

@brianpopow brianpopow changed the title Adds support for encoding 8-bit bitmaps WIP: Adds support for encoding 8-bit bitmaps May 7, 2019
@codecov
Copy link

codecov bot commented May 9, 2019

Codecov Report

Merging #906 into master will decrease coverage by 0.08%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #906      +/-   ##
==========================================
- Coverage   89.55%   89.46%   -0.09%     
==========================================
  Files        1031     1031              
  Lines       46115    46137      +22     
  Branches     3261     3264       +3     
==========================================
- Hits        41297    41278      -19     
- Misses       4108     4111       +3     
- Partials      710      748      +38
Impacted Files Coverage Δ
tests/ImageSharp.Tests/TestImages.cs 100% <ø> (ø) ⬆️
src/ImageSharp/Formats/Bmp/BmpEncoder.cs 100% <ø> (ø) ⬆️
.../Processors/Quantization/QuantizedFrame{TPixel}.cs 94.11% <ø> (ø) ⬆️
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs 94.4% <100%> (+1.19%) ⬆️
...ts/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs 98.24% <100%> (ø) ⬆️
...xelFormats/PixelOperations{TPixel}.PixelBenders.cs 89.9% <0%> (-10.1%) ⬇️
...Processing/Processors/Transforms/TransformUtils.cs 87.64% <0%> (-5.62%) ⬇️
src/ImageSharp/Common/Helpers/ImageMaths.cs 81.81% <0%> (-5.2%) ⬇️
...ocessing/Processors/Dithering/ErrorDiffuserBase.cs 93.75% <0%> (-3.13%) ⬇️
...ormats/Jpeg/Components/Decoder/QualityEvaluator.cs 94.04% <0%> (-2.39%) ⬇️
... and 18 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 89f3de4...6ce4efe. Read the comment docs.

@codecov
Copy link

codecov bot commented May 9, 2019

Codecov Report

Merging #906 into master will increase coverage by 0.01%.
The diff coverage is 100%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master     #906      +/-   ##
==========================================
+ Coverage    89.6%   89.61%   +0.01%     
==========================================
  Files        1060     1060              
  Lines       46542    46586      +44     
  Branches     3268     3272       +4     
==========================================
+ Hits        41703    41750      +47     
+ Misses       4128     4125       -3     
  Partials      711      711
Impacted Files Coverage Δ
tests/ImageSharp.Tests/TestImages.cs 100% <ø> (ø) ⬆️
.../Processors/Quantization/QuantizedFrame{TPixel}.cs 94.11% <ø> (ø) ⬆️
src/ImageSharp/Formats/Bmp/BmpDecoderCore.cs 86.97% <100%> (+0.02%) ⬆️
...cessing/Processors/Quantization/OctreeQuantizer.cs 100% <100%> (+11.11%) ⬆️
...ts/ImageSharp.Tests/Formats/Bmp/BmpEncoderTests.cs 98.63% <100%> (+0.38%) ⬆️
src/ImageSharp/Formats/Bmp/BmpEncoder.cs 100% <100%> (ø) ⬆️
src/ImageSharp/Formats/Bmp/BmpEncoderCore.cs 94.48% <100%> (+1.28%) ⬆️
...geSharp/PixelFormats/PixelImplementations/Gray8.cs 81.63% <0%> (+2.04%) ⬆️

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 25b3f75...b1d2947. Read the comment docs.

@brianpopow
Copy link
Collaborator Author

@JimBobSquarePants: i have some trouble with unit tests failing for net472 and net462. I cannot not reproduce it locally.
My dotnet info output looks like this:

> dotnet --info
.NET Core SDK (gemäß "global.json"):
 Version:   2.2.203
 Commit:    e5bab63eca

Laufzeitumgebung:
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.203\

Host (useful for support):
  Version: 2.2.4
  Commit:  f95848e524

As far as i can tell, this matches the appveyor configuration which is failing. Do you have an idea how i can reproduce this issue? The tests work fine with net462 and net472 when i run them locally with:

dotnet test --filter "FullyQualifiedName=SixLabors.ImageSharp.Tests.Formats.Bmp.BmpEncoderTests.Encode_8Bit_WithV4Header_Works" tests/ImageSharp.Tests/ImageSharp.Tests.csproj -c "Release" -f "net472"

@antonfirsov
Copy link
Member

@brianpopow I think hardware-specific floating point inaccuracies are trolling us again.

Which PixelOperations<TPixel>.Instance operation do the failing tests trigger in BmpEncoder? What is TPixel? Is the operation implemented using Vector4 for the given pixel type?

@brianpopow
Copy link
Collaborator Author

@antonfirsov TPixel is Gray8. Im using the OctreeFrameQuantizer to downscale the number of colors from 32 bit to 8 bit, it uses Vector3 in ConstructPalette(). The quantization is the main part of encoding 8 bit bitmaps, so i suspect the issue is maybe related to it, but i cannot really tell how. Especially because im unable to reproduce it.

@antonfirsov
Copy link
Member

@brianpopow for quantization, inconsistencies across platforms are acceptable. You just need to pass a tolerant comparer inEncode_8Bit_WithV3Header_Works and Encode_8Bit_WithV4Header_Works when pixel type is Gray8 (maybe splitting the theories further?).
The reported error is 0.0064%, so I would suggest to use ImageComparer.TolerantPercentage(0.01f).

@brianpopow
Copy link
Collaborator Author

Ok i have changed the test for the one gray image to have a tolerance of 0.01f, as Anton suggested. I think this is now ready for review.

The OctreeFrameQuantizer works pretty good btw. Here is an example output with just 256 different colors (converted to png, because github does not like bmp):

8bit

@brianpopow brianpopow changed the title WIP: Adds support for encoding 8-bit bitmaps Adds support for encoding 8-bit bitmaps May 11, 2019
@@ -1022,7 +1022,8 @@ private void ReadInfoHeader()
this.bmpMetadata.InfoHeaderType = infoHeaderType;

// We can only encode at these bit rates so far.
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel16)
if (bitsPerPixel.Equals((short)BmpBitsPerPixel.Pixel8)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there other options?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, 4 bit and 1 bit

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.

A few minor change requests and questions, otherwise looks very good!

where TPixel : struct, IPixel<TPixel>
{
#if NETCOREAPP2_1
Span<byte> colorPalette = stackalloc byte[ColorPaletteSize8Bit];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1024 byte is too much for stackallock, we can remove this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the limit? I can never find an explicit instruction.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've read something in Joe Duffy's original material suggesting 128 bytes, based on their benchmarks.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok. I use 257 bytes in the HuffmanTable but I'm gonna leave that as-is.

#endif

var quantizer = new OctreeQuantizer(dither: true, maxColors: 256);
QuantizedFrame<TPixel> quantized = quantizer.CreateFrameQuantizer<TPixel>(this.configuration).QuantizeFrame(image);
Copy link
Member

@antonfirsov antonfirsov May 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FrameQuantizer<T> and QuantizedFrame<T> are IDisposable!

Copy link
Member

@antonfirsov antonfirsov May 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also:
IQuantizer should be configuratble by the user as an Encoder parameter, like we do it in PngEncoder!

var color = default(Rgba32);
foreach (TPixel quantizedColor in quantized.Palette)
{
quantizedColor.ToRgba32(ref color);
Copy link
Member

@antonfirsov antonfirsov May 11, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be a stupid idea and lack of understanding, but:
What if we convert to Rgba32 first, and quantize in Rgba32 space?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, in Octree we're working in the Rgba32 space then converting to/from so I see where you are coming from. However, by converting before quantization we end up allocating a new buffer to house the image. That's probably a lot more costly.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally forgot that it will lead to another allocation, so let's keep it for now.

(An idea is starting to formulate in my mind, but of course it would break IQuantizer API again 😄.)

#if NETCOREAPP2_1
Span<byte> colorPalette = stackalloc byte[ColorPaletteSize8Bit];
#else
byte[] colorPalette = new byte[ColorPaletteSize8Bit];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to use MemoryAllocator.AllocateManagedByteBuffer() for this size.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't actually need the #ifdef anymore. It's supported in latest c# versions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean replacing GC array allocation.

where TPixel : struct, IPixel<TPixel> => TestBmpEncoderCore(provider, bitsPerPixel, supportTransparency: true);

[Theory]
[WithFile(Bit8Gs, PixelTypes.Gray8, BmpBitsPerPixel.Pixel8)]
public void Encode_8BitGray_WithV3Header_Works<TPixel>(TestImageProvider<TPixel> provider, BmpBitsPerPixel bitsPerPixel)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't say enough times, how much I ❤️ this kind of extensive and explanatory test coverage! This is an important enabler for us to do optimizations and refactors.
For #907 stuff I'm literally spending days to add missing tests, so I can change the code safely.

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.

Thanks!
I'm merging, to resolve conflicts with #908.

@antonfirsov antonfirsov merged commit 2461da8 into SixLabors:master May 12, 2019
@antonfirsov
Copy link
Member

@brianpopow I've been thinking on the quantizer inaccuracies. Do we really need execute the quantization when when TPixel is Gray8? Can't we just use the 255 colors of Gray8 as a palette to get rid of the inconsistent behavior?

@brianpopow
Copy link
Collaborator Author

@antonfirsov no you are right, when its Gray8, we do not actually need the quantization. I will change that.

antonfirsov added a commit to antonfirsov/ImageSharp that referenced this pull request Nov 11, 2019
…coding

Adds support for encoding 8-bit bitmaps
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.

3 participants