-
-
Notifications
You must be signed in to change notification settings - Fork 852
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
Conversation
…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 Report
@@ 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
Continue to review full report at Codecov.
|
} | ||
|
||
/// <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)); |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Indeed....
Coverage test fails again? WTF, it was fine yesterday |
@vpenades Coverage changes aren't failing, there's differences against the reference files. |
@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. 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. |
On it... |
For some reason, FillRegionProcessor and DrawTextProcessor where overshooting opacity over 1
Added missing AgressiveInlinings.
Finally! |
There was a problem hiding this 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) |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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))); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
replaced *Vector with *ScaledVector conversion methods
@@ -16,93 +17,93 @@ internal static partial class PorterDuffFunctions | |||
|
|||
|
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] | |||
[MethodImpl(MethodImplOptions.NoInlining)] |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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...
I'm happy to merge this now unless anyone objects. |
Ext.DrawImage generalization to support multiple formats. (Experimental)
With this pull request I wanted to achieve:
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