AngusJohnson / Image32

An extensive 2D graphics library written in Delphi Pascal
Boost Software License 1.0
137 stars 31 forks source link

Additional grayscale algorithms and optimzations #105

Closed ahausladen closed 3 weeks ago

ahausladen commented 4 weeks ago

This PR adds two additional grayscale algorithms and optimizes some color operations.

Grayscale

The default TImage32.Grayscale algorithm changes the saturation of the image, but that is not what SVG gray-scaling does (WebBrowsers, Skia). They use an algorithm that uses the perceptive contrast to generate the gray scale image.

This patch doesn't change the default behavior of the Grayscale method, but adds two parameters to it. The first sets the algorithm (Saturation, Linear, Colorimetric) and the second defines the merge amount [0.0-1.0]. The amount parameter doesn't have any effect if "Saturation" is used.

I thought about changing the default to "Linear", which is also faster than "Saturation". That would help SVG images. But if someone already uses it for non-SVG things, it would change the behavior.

Performance optimizations

The functions InvertColors and InvertAlphas use PStaticColor32Array instead of incrementing the pointer.

The AdjustHue, AdjustLuminance and AdjustSaturation functions only calculate the new color if the original color is different from the previous color, giving them a huge speed up for larger same color blocks or fully transparent parts in an image.

ClampByte(Round(...))

ClampByte has two overloads. One for Integer and one for Double. The (Delphi) compiler always calls the Double-overload if you call ClampByte with the result of Round. This happens because Round returns an Int64 and that doesn't fit into an Integer, thus ClampByte(Integer) can't be used and the next "best" overload is the Double-overload.

So we get "Double->Int64->Double->Int64->Byte" instead of "Double->Integer->Byte". To prevent this, all the code that uses ClampByte(Round(...)) is replaced with ClampByte(Integer(Round(...))).

An Int64-overload for ClampByte would have been also possible, but the 32bit compiler would become slower, as it would have to push the Int64 onto the stack instead of using a CPU register for the ClampByte parameter.

Cleanup

The $IF not declared(NativeInt) isn't necessary. All compilers that are supported (Delphi 7+ and FPC) have that datatype. The only problem with Delphi 7-2007 is that NativeInt can't be used with FOR-loops. So this PR also introduces the SizeInt Datatype for Delphi, that FPC already has. It is an "Integer" for Delphi 7-2007 and a "NativeInt" for all newer Delphi versions, so it can be used with FOR-loops.