-
-
Notifications
You must be signed in to change notification settings - Fork 851
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 #2518 #2519
Fix #2518 #2519
Conversation
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.
Thanks for looking at this. Agreed regarding the indexers.
How did you derive 256 as a multiplier?
@@ -97,19 +98,19 @@ public void Invoke(in RowInterval rows) | |||
* are cleared for each loop iteration, to avoid the repeated allocation for each processed pixel. */ | |||
using IMemoryOwner<Vector4> sourceRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width); | |||
using IMemoryOwner<Vector4> targetRowBuffer = this.configuration.MemoryAllocator.Allocate<Vector4>(this.source.Width); | |||
using IMemoryOwner<float> bins = this.configuration.MemoryAllocator.Allocate<float>(this.levels * 4); | |||
using IMemoryOwner<float> bins = this.configuration.MemoryAllocator.Allocate<float>(this.levels * 256 * 4); |
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.
Shouldn't we be clamping this in the extension method? I'm trying to wrap my head around how this actually works again.
Here's the article it was based upon. https://softwarebydefault.com/2013/06/29/oil-painting-cartoon-filter/ |
Yeah, according to the article we should divide by int currentIntensity = (int)MathF.Round((sourceBlue + sourceGreen + sourceRed) / 3F * this.levels / 255f); Originally: currentIntensity = (int )Math.Round(((double)
(pixelBuffer[calcOffset] +
pixelBuffer[calcOffset + 1] +
pixelBuffer[calcOffset + 2]) / 3.0 *
(levels)) / 255.0); But with this tests are failing and the output looks like a simple blur. Unfortunately I cannot go deeper understanding right now. |
I’ll have a look tonight |
@@ -140,29 +142,29 @@ public void Invoke(in RowInterval rows) | |||
int offsetX = x + fxr; | |||
offsetX = Numerics.Clamp(offsetX, 0, maxX); | |||
|
|||
Vector4 vector = sourceOffsetRow[offsetX].ToVector4(); | |||
Vector4 vector = Vector4.Clamp(sourceOffsetRow[offsetX].ToScaledVector4(), Vector4.Zero, Vector4.One); |
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.
Unfortunately, ToScaledVector4
doesn't guarantee that the vector will be clamped to 0..1
since a pixel implementation is free to return anything. (In fact RgbaVector
doesn't clamp!)
With clamping I was able to undo the buffer overallocation in 99771d6. (Which was in fact ad-hoc since RgbaVector
can hold an arbitrary high value, way above 255.)
I think the PR should be good now if you are also fine with the changes @JimBobSquarePants .
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.
Shouldn’t we fix RgbaVector? ScaledVector is supposed to guarantee 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.
Just had a look at several of our pixel formats. None of the sanitize the range in construction. We should fix that across the board.
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.
None of the sanitize the range in construction
I wonder which are the pixel types where ToScaledVector4()
could go out of range? To me it looks like it can happen only with floating point ones (RgbaVector
, HalfVector2|4
, HalfSingle
...) Do you see any other ones?
Sanitizing in the constructor is insufficient since (1) there are public fields/properties to manipulate the values (2) low-level MemoryMarshal.Cast
-like manipulation is completely normal when working with pixel buffers and it can also push values out of range. If we want a guarantee that ToScaledVector4()
implementations always return values in 0..1
, clipping should happen in ToScaledVector4()
.
However, even if guarantee this in our own pixel types, arbitrary user pixel types can still potentially violate the contract. If it only leads to an IndexOutOfRangeException
, we can blame it on the user. However, if we have unsafe indexing depending on the the values returned from ToScaledVector4()
(like we used to do in OilPaint) we have a serious bug in the processor. We may have a similar problem here:
ImageSharp/src/ImageSharp/Processing/Processors/Normalization/AutoLevelProcessor{TPixel}.cs
Lines 151 to 153 in e81efa3
var vector = Unsafe.Add(ref vectorRef, (uint)x); | |
int luminance = ColorNumerics.GetBT709Luminance(ref vector, levels); | |
float scaledLuminance = Unsafe.Add(ref cdfBase, (uint)luminance) / noOfPixelsMinusCdfMin; |
My recommendation is to revisit these processors before doing anything else.
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.
All of the pixel formats accepting a float
lack sanitation so Short2
, Short4
, and the NormalizedXX
variants would be on the list also.
Scaled vector shouldn't clip. It should normalize the scale of the min max values of the pixel format. The mutability of the pixel formats makes sanitation difficult but as you say, avoiding unnecessary unsafe code turns a trivial exception to a serious one so we should definitely review and fix any processors that could be affected.
Regarding this PR. That clamp is going to hurt performance and should be unnecessary given the correct Vector4
range. I'd say we revert it and merge before continuing to sanitize the processors.
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.
See ac0c1f9. With aéé the confusion this PR started with, it would make sense to squash on merge.
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.
Yeah, this will do for now until we figure out a way to clamp pixel ranges in an efficient manner.
Prerequisites
Description
Applying a (hopefully correct) bin buffer size, and eliminating
Unsafe
usage in a less critical processor we did not attempt to optimize with other safer techniques.Benchmarks
main
PR
I believe the 6% cost is worth the readability and memory safety benefits. We should keep
Unsafe
for the hot path in critical stuff (codecs, resize etc.). If we want to optimize this processor, we can try other techniques first, eg. vectorization.