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 support for decoding jpeg's with arithmetic coding #2073

Merged
merged 12 commits into from
May 1, 2022

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 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 adds support for decoding jpeg's with arithmetic coding.

Copy link
Contributor

@gfoidl gfoidl left a comment

Choose a reason for hiding this comment

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

I always enjoy reading such fine code.
At the same time I'm sorry for so much comments.

return;
}

int blockCol = (mcuCol * h) + x;
Copy link
Contributor

Choose a reason for hiding this comment

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

You could hoist mcuCol * h before the loop for (int x ..., as it's invariant for this loop and I don't expect JIT to hoist it for us (especially on older runtimes).


this.DecodeBlockBaseline(
component,
ref Unsafe.Add(ref blockRef, blockCol),
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
ref Unsafe.Add(ref blockRef, blockCol),
ref Unsafe.Add(ref blockRef, (uint)blockCol),

to avoid the sign-extending move.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@gfoidl I dont understand the suggestion: How can I use uint here, when Unsafe.Add expects int?

Copy link
Contributor

Choose a reason for hiding this comment

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

Here it's for the overload Unsafe.Add<T>(ref T source, nuint elementOffset) and uses the implicit conversion from uint -> nuint.
So the long form of the suggestion is

Suggested change
ref Unsafe.Add(ref blockRef, blockCol),
ref Unsafe.Add(ref blockRef, (nuint)(uint)blockCol),

but as the compiler does the second conversion for us, I've shortened it to

Suggested change
ref Unsafe.Add(ref blockRef, blockCol),
ref Unsafe.Add(ref blockRef, (uint)blockCol),

In essence, and to avoid the movsxd (the sign extending move), we need to convert the int to any of IntPtr / nint or UIntPtr / nuint. As first step here is to prove to the JIT that there's only positive numbers by the uint-cast, which elides the movsxd emitting.
The actual target type (nint or nuint respectively there IntPtr base variants) doesn't really matter then.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Thank you for the explanation. Now I see why it does not work: The uint overload is only available on .net6.0, so cant use that here.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, sorry, didn't check that. The use (nint)(uint) for the cast.

v = -v;
}

Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Unsafe.Add(ref destinationRef, ZigZag.TransposingOrder[k]) = (short)v;
Unsafe.Add(ref destinationRef, (uint)ZigZag.TransposingOrder[k]) = (short)v;


namespace SixLabors.ImageSharp.Formats.Jpeg.Components.Decoder
{
internal class ArithmeticStatistics
Copy link
Contributor

@gfoidl gfoidl Mar 25, 2022

Choose a reason for hiding this comment

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

Can this be written as

using System.Diagnostics;
using System.Runtime.CompilerServices;

internal unsafe struct ArithmeticStatistics
{
    private fixed byte statistics[256];

    public ArithmeticStatistics(bool dc, int identifier)
    {
        this.IsDcStatistics = dc;
        this.Identifier = identifier;
    }

    public bool IsDcStatistics { get; private set; }

    public int Identifier { get; private set; }

    public ref byte GetReference() => ref this.statistics[0];

    public ref byte GetReference(int offset)
    {
        Debug.Assert(offset < 256);
        return ref this.statistics[(uint)offset];
    }

    public void Reset()
    {
        Unsafe.InitBlockUnaligned(ref this.GetReference(), 0x00, (uint)(this.IsDcStatistics ? 64 : 256));
    }
}

?

  • avoids the additional allocation for each stats-object
  • downside: size is fixed to 256, so potentially the list will be larger
  • codegen is better than with the class especially for GetReference and Reset (which are the hottest methods here?)

/// <summary>
/// Gets the component id.
/// </summary>
public byte Id { get; }
Copy link
Contributor

Choose a reason for hiding this comment

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

The existing interface members don't have the access modifier specified.
It's not strictly needed, but C# allows this (since C# 8 I think).

At least it should be consistent withing this file.

@@ -1055,18 +1127,18 @@ private void ProcessStartOfFrameMarker(BufferedReadStream stream, int remaining,
// 2 byte: Width
int frameWidth = (this.temp[3] << 8) | this.temp[4];

// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that)
// Validate: width/height > 0 (they are upper-bounded by 2 byte max value so no need to check that).
if (frameHeight == 0 || frameWidth == 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
if (frameHeight == 0 || frameWidth == 0)
if ((frameHeight | frameWidth) == 0)

produces less code and avoids a branch.

Note: current JIT will do this optimizaiton for use, but older runtimes miss that optimization.

@br3aker
Copy link
Contributor

br3aker commented Mar 25, 2022

I know that I'm asking for too much but can we postpone this PR from merging for a bit?
There's so much virtual types existing in current jpeg decoder and new ones from this PR which already led to explicit downcasting. I'm working on the downscaling decoding for jpeg which would also need some sort of a virtuality for both the huffman decoder and component post processing which combined can cause a mess IMO.

I'll push some fixes on your branch if you don't mind.

EDIT:
There's actually a lot of other fixes I've planned to do during refactoring PR so I'll fix everything later, sorry for interfering.

@brianpopow
Copy link
Collaborator Author

@gfoidl thanks for your review, its always appreciated!

@br3aker:

I know that I'm asking for too much but can we postpone this PR from merging for a bit?

Its not a problem, there is no need to rush this PR.

I'll push some fixes on your branch if you don't mind.

I would not mind, if you push directly to this branch, your help is very welcome, but I dont think you have permission to do so.
I am pretty sure its somehow possible to give you those write access to this branch, but I did not see how at the moment.
You will have to create a regular PR with this branch as the target branch instead of main.

@br3aker
Copy link
Contributor

br3aker commented Mar 25, 2022

Code itself is very good, it's just those tiny little spots I'd want to fix but they do not block merging so it's okay. I will create a PR with general refactoring of the decoder part.

@JimBobSquarePants JimBobSquarePants added this to the 3.0.0 milestone Mar 27, 2022
@brianpopow brianpopow requested a review from a team April 21, 2022 10:17
Copy link
Contributor

@br3aker br3aker left a comment

Choose a reason for hiding this comment

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

Looks good overall but please revert component type to JpegComponent in JpegDecoderCore.

Comment on lines +103 to +111
/// <summary>
/// The arithmetic decoding tables.
/// </summary>
private List<ArithmeticDecodingTable> arithmeticDecodingTables;

/// <summary>
/// The restart interval.
/// </summary>
private int? resetInterval;
Copy link
Contributor

@br3aker br3aker Apr 21, 2022

Choose a reason for hiding this comment

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

I don't like that this arithmetic coding specific property is leaked in the 'master' class, same goes for reset interval which should only be used inside scanDecoder but it's a broad question for some further refactoring - I'll work on it right after scaled decoding PR.

public JpegComponent[] Components { get; set; }
public IJpegComponent[] Components { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

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

As far as I remember, IJpegComponent interface is used in tests for libjpeg output comparison but I think we shouldn't use this interface everywhere - this leads to virtual calls for every component field getter .

@br3aker
Copy link
Contributor

br3aker commented May 1, 2022

@brianpopow hi! Anything blocking this PR from being merged?

@br3aker br3aker mentioned this pull request May 1, 2022
9 tasks
@brianpopow
Copy link
Collaborator Author

@brianpopow hi! Anything blocking this PR from being merged?

@br3aker: I think this can be merged now.
@JimBobSquarePants do you have any objection on merging this?

@JimBobSquarePants
Copy link
Member

Nope. Rock on

@brianpopow brianpopow merged commit 3e7b2c9 into main May 1, 2022
@brianpopow brianpopow deleted the bp/arithmeticcoding branch May 1, 2022 10:54
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.

None yet

4 participants