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

Png - Preserve Pixel Format for Non-generic decode. #1861

Merged
merged 15 commits into from
Dec 14, 2021

Conversation

JimBobSquarePants
Copy link
Member

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

Fixes #1813 by determining the most appropriate pixel format to decode to based upon the detected color type and bit depth.

I could have avoided the identify/reset/decode route and gained a little more performance but Identify is so quick anyway it seemed like the simplest approach.

@JimBobSquarePants JimBobSquarePants added enhancement formats:png breaking Signifies a binary breaking change. labels Nov 30, 2021
@JimBobSquarePants JimBobSquarePants added this to the 2.0.0 milestone Nov 30, 2021
@codecov
Copy link

codecov bot commented Nov 30, 2021

Codecov Report

Merging #1861 (f6b8383) into master (f45e184) will decrease coverage by 0%.
The diff coverage is 86%.

Impacted file tree graph

@@          Coverage Diff           @@
##           master   #1861   +/-   ##
======================================
- Coverage      87%     87%   -1%     
======================================
  Files         957     957           
  Lines       50391   50474   +83     
  Branches     6264    6291   +27     
======================================
+ Hits        44180   44252   +72     
- Misses       5180    5185    +5     
- Partials     1031    1037    +6     
Flag Coverage Δ
unittests 87% <86%> (-1%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
src/ImageSharp/Formats/Png/PngDecoderCore.cs 90% <83%> (-1%) ⬇️
src/ImageSharp/Formats/Png/PngDecoder.cs 87% <88%> (+4%) ⬆️

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 82e8c0d...f6b8383. Read the comment docs.

@JimBobSquarePants JimBobSquarePants requested a review from a team December 1, 2021 12:13
@JimBobSquarePants JimBobSquarePants changed the title Png - Preserve Pixel Format for Non-generic decode. WIP - Png - Preserve Pixel Format for Non-generic decode. Dec 1, 2021
@JimBobSquarePants JimBobSquarePants marked this pull request as draft December 1, 2021 12:51
@JimBobSquarePants JimBobSquarePants marked this pull request as ready for review December 1, 2021 13:37
@JimBobSquarePants JimBobSquarePants changed the title WIP - Png - Preserve Pixel Format for Non-generic decode. Png - Preserve Pixel Format for Non-generic decode. Dec 1, 2021
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.

but Identify is so quick anyway it seemed like the simplest approach.

I'm not sure if it's always true. There is some unnecessary IO cost reading the stream twice when the file is large and/or there are *Text chunks not being skipped on ignoreMetadata. This may hurt scalability, and it doesn't seem to be very hard to fix:

We can introduce a field in PngDecoderCore that asks to skip everything except the absolute minimum needed to determine the pixel type (Header + Transparency?), and stop when the essential chunks are processed or when a Data chunk is detected.

Later we can improve the IImageDecoderInternals infrastructure to support these scenarios, but that's far beyond the essential feature work we need for Png.

@JimBobSquarePants
Copy link
Member Author

JimBobSquarePants commented Dec 7, 2021

I'm not sure if it's always true. There is some unnecessary IO cost reading the stream twice when the file is large and/or there are *Text chunks not being skipped on ignoreMetadata. This may hurt scalability, and it doesn't seem to be very hard to fix:

I've changed the behavior of the chunk reader to avoid reading data from anything other than the header and transparency chunks when required. This also allows skipping the entire chunk. It also now stops as soon as we have the info we require.

It's a little hacky but should be fast enough for now.

this.SkipChunkDataAndCrc(chunk);
break;
}

this.ReadGammaChunk(pngMetadata, chunk.Data.GetSpan());
break;
case PngChunkType.Data:
this.SkipChunkDataAndCrc(chunk);
Copy link
Member

Choose a reason for hiding this comment

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

Spec says:

When present, the tRNS chunk must precede the first IDAT chunk, and must follow the PLTE chunk, if any.

It means that if we reach Data, we are free to quit the loop. Forwarding the file position beyond the data chunk may have IO overhead we can easily avoid here.

this.paletteAlpha = alpha;
this.AssignTransparentMarkers(alpha, pngMetadata);

if (this.colorMetadataOnly && this.isHeaderChunkReached)
Copy link
Member

@antonfirsov antonfirsov Dec 13, 2021

Choose a reason for hiding this comment

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

According to the spec, IDAT IHDR is always the first, so - in theory - we don't need to track if we reached it. I wonder if there are malformed PNG-s in the wild which do not follow this rule.

@brianpopow any thoughts?

Copy link
Collaborator

Choose a reason for hiding this comment

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

You mean IHDR is always first I guess? I will go through my collection of "weird and broken images" to check if i can find any which has not IHDR first.

Copy link
Member

Choose a reason for hiding this comment

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

You mean IHDR is always first I guess?

Yes, sorry for the typo.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I could not find any png which do not start with IHDR. Nonetheless I am still in favor of keeping it as it is, just to be sure, because in theory such png can be created.

if (this.colorMetadataOnly && this.isHeaderChunkReached)
{
// Quick exit
this.isEndChunkReached = true;
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should just break out of the loop instead of abusing this flag?

Copy link
Member Author

Choose a reason for hiding this comment

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

goto baby; goto.

@JimBobSquarePants
Copy link
Member Author

@antonfirsov @brianpopow I'll just follow the strict specification here and optimize accordingly. The worst that can happen is that they decode to the incorrect pixel format.

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.

LGTM. I think it's possible to achieve the same without goto will do it in a separate cleanup PR.

@JimBobSquarePants
Copy link
Member Author

LGTM. I think it's possible to achieve the same without goto will do it in a separate cleanup PR.

Whole thing needs a cleanup. We should be skipping all chunks we don't currently read on decode but are instead reading the data.

@JimBobSquarePants JimBobSquarePants merged commit ecd3db2 into master Dec 14, 2021
@JimBobSquarePants JimBobSquarePants deleted the js/png-decode-to-type branch December 14, 2021 12:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking Signifies a binary breaking change. enhancement formats:png
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decode PNG to source stream's pixel type
4 participants