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

Fix run-length encode compression implementation mistake in tga encoder #2172

Merged
merged 2 commits into from
Jul 30, 2022

Conversation

l1nxy
Copy link
Contributor

@l1nxy l1nxy commented Jul 13, 2022

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

According to the Tga format spec page 24:

Run-length Packets should never encode pixels from more than one scan line. Even if the end of one
scan line and the beginning of the next contain pixels of the same value, the two should be encoded
as separate packets. In other words, Run-length Packets should not wrap from one line to another.
This scheme allows software to create and use a scan line table for rapid, random access of
individual lines. Scan line tables are discussed in further detail in the Extension Area section of this
document.

It should not cross multiple lines when using Run-length encode compression. Some libraries can not fix the rle mistake when importing images saved by ImageSharp, and will throw an exception.

@CLAassistant
Copy link

CLAassistant commented Jul 13, 2022

CLA assistant check
All committers have signed the CLA.

@l1nxy l1nxy changed the title Fix run-length encode compression mistake in tga encoder. Fix run-length encode compression implementation mistake in tga encoder. Jul 13, 2022
@l1nxy l1nxy changed the title Fix run-length encode compression implementation mistake in tga encoder. Fix run-length encode compression implementation mistake in tga encoder Jul 13, 2022
@brianpopow
Copy link
Collaborator

@l1nxy thanks for providing a fix for this.

Some questions I have:

  • Can you provide a test image where this error happens?
  • Does ImageMagick detect this error? If so, we should add a test for this case (we do use ImageMagick as a ReferenceDecoder for unit tests).

@l1nxy
Copy link
Contributor Author

l1nxy commented Jul 13, 2022

@l1nxy thanks for providing a fix for this.

Some questions I have:

  • Can you provide a test image where this error happens?
  • Does ImageMagick detect this error? If so, we should add a test for this case (we do use ImageMagick as a ReferenceDecoder for unit tests).

ImageMagick thinks the old cross multiple scan line format and new format which do not cross multiple scan lines all of those are correct, and I was find this microsoft/DirectXTex#251.
Maybe should i add a new option to control this?

@l1nxy
Copy link
Contributor Author

l1nxy commented Jul 13, 2022

I will close this pr and make a new one, which should add a new option to control which RLE compression is selected.

@l1nxy l1nxy closed this Jul 13, 2022
@l1nxy l1nxy deleted the fix-tga-rle-encoder branch July 13, 2022 09:57
@brianpopow
Copy link
Collaborator

I will close this pr and make a new one, which should add a new option to control which RLE compression is selected.

Mhm, I dont think this is a good idea. Why would someone want to have an option to write a invalid tga? If its against the spec, we should not do it.

@l1nxy l1nxy restored the fix-tga-rle-encoder branch July 13, 2022 10:15
@l1nxy
Copy link
Contributor Author

l1nxy commented Jul 13, 2022

I will close this pr and make a new one, which should add a new option to control which RLE compression is selected.

Mhm, I dont think this is a good idea. Why would someone want to have an option to write a invalid tga? If its against the spec, we should not do it.

But the legacy format is used widely, ImageMagick and Many picture viewers can detect the legacy format.
What do you think about it?

@l1nxy l1nxy reopened this Jul 13, 2022
@brianpopow
Copy link
Collaborator

brianpopow commented Jul 13, 2022

I will close this pr and make a new one, which should add a new option to control which RLE compression is selected.

Mhm, I dont think this is a good idea. Why would someone want to have an option to write a invalid tga? If its against the spec, we should not do it.

But the legacy format is used widely, ImageMagick and Many picture viewers can detect the legacy format. What do you think about it?

I think its ok, if we can decode such image, but I dont think we should encode such tga which do not follow the spec. Can we actually decode such images? You have not provided a test image yet.

edit: Also note you can add commits to this PR without the need to re-open a new one.

@l1nxy
Copy link
Contributor Author

l1nxy commented Jul 13, 2022

I will close this pr and make a new one, which should add a new option to control which RLE compression is selected.

Mhm, I dont think this is a good idea. Why would someone want to have an option to write a invalid tga? If its against the spec, we should not do it.

But the legacy format is used widely, ImageMagick and Many picture viewers can detect the legacy format. What do you think about it?

I think its ok, if we can decode such image, but I dont think we should encode such tga which do not follow the spec. Can we actually decode such images? You have not provided a test image yet.

edit: Also note you can add commits to this PR without the need to re-open a new one.

OK, thank you very much. i will provide some test pic later. and test the encoder.

@JimBobSquarePants
Copy link
Member

Just to confirm.

We should aim to be able to decode both old and new specification.
We should only encode to the latest specification.

@brianpopow
Copy link
Collaborator

Just to confirm.

We should aim to be able to decode both old and new specification.
We should only encode to the latest specification.

Yes, that is what I would suggest.

@l1nxy
Copy link
Contributor Author

l1nxy commented Jul 18, 2022

There is the test_pic.zip.
I downloaded the GitHub logo and translated it to TGA format using ImageMagick, Then, I wrote a demo to read the TGA file by DirectxTex, which can be read correctly.

And I wrote another demo that used ImageSharp to read this TGA file and save it directly to another TGA file which named Github_legacy.tga, It can not be read by DirectxTex which means ImageSharp generated a legacy TGA format file.

Repeat this procedure using modified code, It can be read by DirectxTex correctly.

The TGA decoder can read these three files, so the decoder implementation is correct, But there is a wired thing is the image size generated by ImageSharp bigger than the file generated by ImageMagick.

Copy link
Collaborator

@brianpopow brianpopow left a comment

Choose a reason for hiding this comment

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

I would like two unit tests to be added to make sure it works now as it should. (I would do it by myself, but for some weird reason gitlfs does not let me push to this PR)

  1. One test for decoding the legacy format (even though we can decode it already, a test to make sure would be nice).
    Add the following to TgaDecoderTests.cs
[Theory]
[WithFile(Github_RLE_legacy, PixelTypes.Rgba32)]
public void TgaDecoder_CanDecode_LegacyFormat<TPixel>(TestImageProvider<TPixel> provider)
    where TPixel : unmanaged, IPixel<TPixel>
{
    using (Image<TPixel> image = provider.GetImage(TgaDecoder))
    {
        image.DebugSave(provider);
        ImageComparingUtils.CompareWithReferenceDecoder(provider, image);
    }
}

and the Github_RLE_legacy to TestImages.cs.

  1. Make sure the encoded bytes now matches the expected size. Add to TgaEncoderTests.cs
// Run length encoded pixels should not exceed row boundaries.
// https://github.com/SixLabors/ImageSharp/pull/2172
[Theory]
[MemberData(nameof(TgaBitsPerPixelFiles))]
public void TgaEncoder_RunLengthDoesNotCrossRowBoundaries(string imagePath, TgaBitsPerPixel bmpBitsPerPixel)
{
    var options = new TgaEncoder() { Compression = TgaCompression.RunLength };

    var testFile = TestFile.Create(imagePath);
    using (Image<Rgba32> input = testFile.CreateRgba32Image())
    {
        using (var memStream = new MemoryStream())
        {
            input.Save(memStream, options);
            // TODO assert the current encoded bytes match.
        }
    }
}

bool firstRow = true;
TPixel startPixel = pixels[xStart, yStart];
for (int y = yStart; y < pixels.Height; y++)
TPixel startPixel = pixels[xStart, yPos];
Copy link
Collaborator

Choose a reason for hiding this comment

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

Instead of using the indexer, you could use DangerousGetRowSpan, which should be faster. Like this:

Span<TPixel> pixelRow = pixels.DangerousGetRowSpan(yPos).Slice(xStart);

@JimBobSquarePants
Copy link
Member

I would do it by myself, but for some weird reason gitlfs does not let me push to this PR

There's a whole heap of issues related to GitHub, Git LFS and forks. Looks like they're finally on to figuring out a cause though which is ace.

git-lfs/git-lfs#5001

I use a workaround for pushing to forks when there is no LFS files to be added but that won't help in this case.

@brianpopow
Copy link
Collaborator

But there is a wired thing is the image size generated by ImageSharp bigger than the file generated by ImageMagick.

I had some more time looking into this. The size difference currently fort the given testimage is:

  • ImageSharp (main): 50.2 kB
  • ImageMagick: 35.6 kB

The reason for that is, that we always write Run-Length Packets. If consecutive pixels are not the same, this will add an additional byte overhead to each pixel data. This will add up alot, if there are not many pixels equal. (note: the test image looks good for RLE, but it has transparency).

So to fix this, we need to change this to not always write RLE packets.
This does not have to be part of this PR and can be a follow up PR.

@brianpopow
Copy link
Collaborator

I will go ahead and merge this. Code itself looks good, just tests are missing. I will do a follow up PR to add tests and fix the image size issue, since I do not know how to work around the git lfs issues

@JimBobSquarePants
Copy link
Member

Code itself looks good

It's still using the indexer to access pixels which will be slow. Best make sure your follow up fixes that.

@brianpopow brianpopow merged commit 7dacf4f into SixLabors:main Jul 30, 2022
@brianpopow brianpopow mentioned this pull request Aug 3, 2022
4 tasks
brianpopow added a commit that referenced this pull request Aug 4, 2022
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