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

Support High Precision TPixel formats via Color - Boxed #1801

Merged
merged 14 commits into from
Nov 9, 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

An alternative to #1797 that uses a boxed IPixel representation to hold any Color created via new Color.FromPixel<TPixel>(TPixel pixel) method or via Color(Vector4). This allows supporting all the pixel formats in the library without loss of precision.

See SixLabors/ImageSharp.Drawing#165

CC. @tocsoft @antonfirsov

@JimBobSquarePants JimBobSquarePants added this to the 2.0.0 milestone Nov 1, 2021
@JimBobSquarePants JimBobSquarePants requested a review from a team November 1, 2021 11:45
@codecov
Copy link

codecov bot commented Nov 1, 2021

Codecov Report

Merging #1801 (7c7c8cf) into master (d8d2616) will increase coverage by 0.18%.
The diff coverage is 75.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1801      +/-   ##
==========================================
+ Coverage   87.12%   87.31%   +0.18%     
==========================================
  Files         936      936              
  Lines       47944    48015      +71     
  Branches     6018     6034      +16     
==========================================
+ Hits        41771    41923     +152     
+ Misses       5176     5092      -84     
- Partials      997     1000       +3     
Flag Coverage Δ
unittests 87.31% <75.00%> (+0.18%) ⬆️

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

Impacted Files Coverage Δ
src/ImageSharp/Color/Color.Conversions.cs 68.85% <68.85%> (-17.82%) ⬇️
src/ImageSharp/Color/Color.cs 91.66% <85.71%> (-4.08%) ⬇️
.../ImageSharp/Formats/Webp/Lossless/LosslessUtils.cs 97.54% <0.00%> (+8.85%) ⬆️
...ageSharp/Formats/Webp/Lossless/PredictorEncoder.cs 98.10% <0.00%> (+9.07%) ⬆️

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 d8d2616...7c7c8cf. Read the comment docs.

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.

We should avoid boxing when not necessary, and ensure equality correctness, otherwise looks good!

[MethodImpl(InliningOptions.ShortMethod)]
public static Color FromPixel<TPixel>(TPixel pixel)
where TPixel : unmanaged, IPixel<TPixel>
=> new(pixel);
Copy link
Member

Choose a reason for hiding this comment

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

I'm surprised we didn't have this method before. We should avoid boxing in case user provides a typical pixel like Rgba32, which I would expect to be the more common than advanced scenarios like SixLabors/ImageSharp.Drawing#165

I realized that we don't need to add new stuff to PixelOperations<T> unless we see users complaining about the default heuristic, which is very unlikely.

Suggested change
=> new(pixel);
=> Unsafe.SizeOf<TPixel> > sizeof(Rgba64) ? new(pixel) : new(pixel.ToRgba64());

Copy link
Member Author

Choose a reason for hiding this comment

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

Does sizeof work here? Does the compiler see that as a constant?

Copy link
Member

Choose a reason for hiding this comment

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

Does the compiler see that as a constant?

As far as I'm aware it should, since it's not a generic argument, however we need to mark the method unsafe too which is not in my recommendation.

Copy link
Member Author

@JimBobSquarePants JimBobSquarePants Nov 3, 2021

Choose a reason for hiding this comment

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

Just remembered why I wrote it this way. ToRgba64 doesn't exist on IPixel<TSelf>;

Probably why the method doesn't already exist also.

Copy link
Member

@antonfirsov antonfirsov Nov 3, 2021

Choose a reason for hiding this comment

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

ToRgba64 doesn't exist on IPixel<TSelf>;

I missed that. We can implement this as following:

[MethodImpl(InliningOptions.ShortMethod)]
public unsafe static Color FromPixel<TPixel>(TPixel pixel)
    where TPixel : unmanaged, IPixel<TPixel>
{
    // Avoid boxing in case we can convert to Rgba64 safely and efficently
    if (typeof(TPixel) == typeof(Rgba64))
    {
        Rgba64 p = (Rgba64)(object)pixel;
        return new(p);
    }
    else if (typeof(TPixel) == typeof(Rgb48))
    {
        Rgb48 p = (Rgb48)(object)pixel;
        return new(new Rgba64(p.R, p.G, p.B, ushort.MaxValue)); // or add a new Color(Rgb48) constructor
    }
    else if (typeof(TPixel) == typeof(L16))
    {
        Rgb48 p = (L16)(object)pixel;
        return new(new Rgba64(p.PackedValue, p.PackedValue, p.PackedValue, ushort.MaxValue)); // or add a new Color(L16) constructor
    }
    else if (Unsafe.SizeOf(TPixel) <= sizeof(Rgba32))
    {
        return new(pixel.ToRgba32());
    }
    else
    {
        return new(pixel);
    }
}

The JIT will optimize away the unnecessary branches and the boxing from the (Rgba64)(object)pixel conversion.

Update: Also special-case L16

Copy link
Member Author

Choose a reason for hiding this comment

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

Cool, will do.

Copy link
Member Author

Choose a reason for hiding this comment

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

La32 also.

Comment on lines +265 to +268
for (int i = 0; i < source.Length; i++)
{
destination[i] = source[i].ToPixel<TPixel>();
}
Copy link
Member

Choose a reason for hiding this comment

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

Just a note that it's possible to detect the common boxedHighPrecisionPixel == null case with SIMD and bulk convert, if we really want to.

Copy link
Member Author

Choose a reason for hiding this comment

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

Wasn’t aware of that. SIMD is still very much a Google event for me

Copy link
Member

Choose a reason for hiding this comment

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

object is basically a pointer so it's unsafe convertible to long on 64 bits. We can check 4 Colors with bitwise masks, if all of them are NULL we can shuffle stuff so the data fields are together, then bulk convert them.

Super unsure if it's worth the trouble.

Copy link
Member Author

Choose a reason for hiding this comment

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

Super unsure if it's worth the trouble.

Yeah, not attempting that just now. Not worth the trouble.

src/ImageSharp/Color/Color.cs Outdated Show resolved Hide resolved
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.

Product code LGTM. Let's extend the coverage a bit, and it is good to merge!

We also need tests for the IPixel path for Equality_WhenTrue and Equality_WhenFalse.

tests/ImageSharp.Tests/Color/ColorTests.CastTo.cs Outdated Show resolved Hide resolved
@JimBobSquarePants JimBobSquarePants merged commit a53ebaa into master Nov 9, 2021
@JimBobSquarePants JimBobSquarePants deleted the js/HighPresisionColorBoxed branch November 9, 2021 03:44
@calrsom
Copy link

calrsom commented Nov 18, 2021

Thanks to everyone who helped with this! 🎉

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