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

Ext.DrawImage generalization to support multiple formats. (Experimental) #686

Merged
merged 14 commits into from
Sep 11, 2018

Conversation

vpenades
Copy link
Contributor

@vpenades vpenades commented Aug 27, 2018

With this pull request I wanted to achieve:

  • Allow to draw images with a format different than the destination image (API breaking change)
    now you can do: image<Rgba>.Mutate(x => x.DrawImage( Image<argb> ) );

About the implementation: Given that image drawing works with rows, and before drawing, both source and destination rows are converted to Vector4, the whole operation is done at no cost. This allows drawing images with different formats, without requiring a conversion.

  • Faster rendering: for image drawing, the "amount" opacity was expanded to a row, which consumed memory, etc. I've made an alternative path that passes the amount directly (the old one is also there)

  • Refactored PixelBlender class to move the bulk of the previously generated code to the main class. With this change a lot less code is generated.

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)

…r two loop variations.

Generalised DrawingImage methods to support drawing images with a format different than the destination image at no cost.
…l blenders. Pixel blenders are performance critical, so every tiny performance improvement counts.
@codecov
Copy link

codecov bot commented Aug 29, 2018

Codecov Report

Merging #686 into master will decrease coverage by 0.79%.
The diff coverage is 94.44%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master     #686     +/-   ##
=========================================
- Coverage   89.36%   88.56%   -0.8%     
=========================================
  Files         878      879      +1     
  Lines       39249    38772    -477     
  Branches     2557     2665    +108     
=========================================
- Hits        35074    34340    -734     
- Misses       3485     3742    +257     
  Partials      690      690
Impacted Files Coverage Δ
.../PixelFormats/PixelBlenders/PorterDuffFunctions.cs 100% <ø> (+12.5%) ⬆️
...ts/PixelBlenders/DefaultPixelBlenders.Generated.cs 77.77% <ø> (-16.5%) ⬇️
...ats/PixelBlenders/PorterDuffFunctions.Generated.cs 38.02% <ø> (-14.83%) ⬇️
...rc/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs 100% <100%> (ø)
...Tests/PixelFormats/PixelOperationsTests.Blender.cs 100% <100%> (ø) ⬆️
...rocessing/Processors/Drawing/DrawImageProcessor.cs 100% <100%> (ø) ⬆️
...ageSharp.Drawing/Processing/DrawImageExtensions.cs 37.5% <37.5%> (ø) ⬆️

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 0e6fef0...c596fb1. Read the comment docs.

}

/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount.Clamp(0,1));
Copy link
Member

Choose a reason for hiding this comment

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

we should clamp outside the loop

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Indeed....

@vpenades
Copy link
Contributor Author

Coverage test fails again? WTF, it was fine yesterday

@JimBobSquarePants
Copy link
Member

@vpenades Coverage changes aren't failing, there's differences against the reference files.

@JimBobSquarePants
Copy link
Member

@vpenades Can you have a look at the generated output from these changes compared the master?

We've got differences appearing on NET 4.7.1 and NET 4.6.2 in 64 bit mode.
https://ci.appveyor.com/project/six-labors/imagesharp/build/1.0.0-PullRequest00686001835

It's likely minor but best to cast your eye over to ensure nothing has gone wrong. If you're happy with the output please open a new PR against our reference images repository.

@vpenades
Copy link
Contributor Author

vpenades commented Sep 6, 2018

On it...

@vpenades
Copy link
Contributor Author

Finally!

Copy link
Member

@JimBobSquarePants JimBobSquarePants left a comment

Choose a reason for hiding this comment

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

Those vector conversion changes will need to be updated plus I'd like to remove any per-pixel clamping if possible.

scanlineDirty = true;
}

if (scanline[startX] > 1)
Copy link
Member

Choose a reason for hiding this comment

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

What do these new constraining checks do?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I had the DebugGuard set, FillRegionProcessor was producing some opacities of value 1.02xxx... the algorythms there probably need to be reviewed to ensure that opacity values are always in range. But since I don't fully understand what these functions do, I just added a clamp there.

Now that the clamps are back into the pixels, these checks could be removed...

#if DEBUG
for (int i = 0; i < scanline.Length; i++)
{
Guard.MustBeBetweenOrEqualTo(scanline[i], 0, 1, nameof(scanline));
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to throw?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, that one is no longer neccessary if the pixels do the clamp, I'll remove it now

@@ -27,44 +27,39 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity.Clamp(0,1);
Copy link
Member

Choose a reason for hiding this comment

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

Are these called by per-row operations or are they purely for per-pixel operations?

Copy link
Member

Choose a reason for hiding this comment

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

If they are called from bulk we shouldn't be clamping per pixel.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are being clamped per pixel. that's why I wanted to remove them and replace them with DebugGuards.

But by using DebugGuards, several tests fail due to out of range values, which prevents the PR to pass the coverage tests, etc.

So, for now, I preffer to go back to Clamp, and solve any potential out of range issues in future PRs

Copy link
Member

Choose a reason for hiding this comment

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

I think it'll be best to fix clamping here while we are working on it.

In the internal method PorterDuffFunctions.NormalSrc(Vector4 backdrop, Vector4 source, float opacity) the opacity is clamped before being used as a multiplier.

As far as I can see that method is only called by.

PorterDuffFunctions.NormalSrc<TPixel>(TPixel backdrop, TPixel source, float opacity)

NormalSrc.Blend(TPixel background, TPixel source, float amount)

NormalSrc.BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)

NormalSrc.BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)

The clamping should take place in the caller methods not the callee, same for the other operations. Only the last method taking the ReadOnlySpan<float> amount requires per-pixel clamping of the values but that seems like something we could do with Vector<float> in the caller in supported architectures.

Copy link
Member

Choose a reason for hiding this comment

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

Thankfully all the changes required can be done in the T4 templates.

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 happy to make the changes if you like?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sure, go for it!, it's just lunch time here! 😄

{
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity);
Copy link
Member

Choose a reason for hiding this comment

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

I couldn't find the callee Blend method in the diff but it's imperative now that if we are mixing pixel formats and conversion to/from Vector4 needs to used the scaled conversion methods.

@@ -78,32 +80,27 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
return PorterDuffFunctions.<#=blender_composer#>(background, source, amount);
TPixel dest = default;
dest.PackFromVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToVector4(),source.ToVector4(),amount.Clamp(0,1)));
Copy link
Member

Choose a reason for hiding this comment

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

I think this is what I was referring to before. Should be ToScaledVector4() with the reverse PackFromScaledVector4() on repacking.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ahhh, okey, change it now

@@ -16,93 +17,93 @@ internal static partial class PorterDuffFunctions



[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
Copy link
Member

Choose a reason for hiding this comment

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

@vpenades @antonfirsov Confirmed that changing this fixes tests in 64bit on Net Framework 4.7.1 and 4.6.2

We'll need to figure out how to create a reduced sample to report.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So that's the thing that's been driving me crazy...

@JimBobSquarePants
Copy link
Member

I'm happy to merge this now unless anyone objects.

@JimBobSquarePants JimBobSquarePants merged commit bf1227c into SixLabors:master Sep 11, 2018
antonfirsov pushed a commit to antonfirsov/ImageSharp that referenced this pull request Nov 11, 2019
Ext.DrawImage generalization to support multiple formats. (Experimental)
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