mirror of https://github.com/SixLabors/ImageSharp
committed by
GitHub
36 changed files with 6440 additions and 1235 deletions
File diff suppressed because it is too large
File diff suppressed because it is too large
@ -1,112 +1,188 @@ |
|||
<# |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ import namespace="System.Linq" #> |
|||
<#@ import namespace="System.Text" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
|
|||
// <auto-generated /> |
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
internal static partial class PorterDuffFunctions |
|||
{ |
|||
<# |
|||
|
|||
void GeneratePixelBlender (string blender) |
|||
{ |
|||
#> |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static TPixel <#=blender#><TPixel>(TPixel backdrop, TPixel source, float amount) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel dest = default; |
|||
dest.PackFromVector4(<#=blender#>(backdrop.ToVector4(), source.ToVector4(), amount)); |
|||
return dest; |
|||
} |
|||
|
|||
<# |
|||
} |
|||
|
|||
void GenerateVectorCompositor(string name, string sourceVar, string destVar, string blendVar) |
|||
{ |
|||
int a_s = sourceVar == "Vector4.Zero" ? 0 : 1; |
|||
int a_b = destVar == "Vector4.Zero" ? 0 : 1; |
|||
int a_x = blendVar == "Vector4.Zero" ? 0 : 1; |
|||
#> |
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
<# if(sourceVar != "Vector4.Zero" ) { #> |
|||
source.W *= opacity; |
|||
<# } #> |
|||
|
|||
// calculate weights |
|||
float xw = backdrop.W * source.W; |
|||
float bw = backdrop.W - xw; |
|||
float sw = source.W - xw; |
|||
|
|||
// calculate final alpha |
|||
float fw = (sw * <#=a_s#>) + (bw * <#=a_b#>) + (xw * <#=a_x#>); |
|||
|
|||
// calculate final value |
|||
Vector4 xform = ((<#=blendVar#> * xw) + (<#=destVar#> * bw) + (<#=sourceVar#> * sw)) / MathF.Max(fw, Constants.Epsilon); |
|||
xform.W = fw; |
|||
|
|||
return Vector4.Lerp(backdrop, xform, opacity); |
|||
} |
|||
|
|||
<# |
|||
} |
|||
GenerateVectorCompositor("Src", "source", "Vector4.Zero", "source"); |
|||
GenerateVectorCompositor("Atop", "Vector4.Zero", "backdrop", "source"); |
|||
GenerateVectorCompositor("Over", "source", "backdrop", "source"); |
|||
GenerateVectorCompositor("In", "Vector4.Zero", "Vector4.Zero", "source"); |
|||
GenerateVectorCompositor("Out", "source", "Vector4.Zero", "Vector4.Zero"); |
|||
GenerateVectorCompositor("Dest", "Vector4.Zero", "backdrop", "backdrop"); |
|||
GenerateVectorCompositor("DestAtop", "source", "Vector4.Zero", "backdrop"); |
|||
GenerateVectorCompositor("DestOver", "source", "backdrop", "backdrop"); |
|||
GenerateVectorCompositor("DestIn", "Vector4.Zero", "Vector4.Zero", "backdrop"); |
|||
GenerateVectorCompositor("DestOut", "Vector4.Zero", "backdrop", "Vector4.Zero"); |
|||
GenerateVectorCompositor("Clear", "Vector4.Zero", "Vector4.Zero", "Vector4.Zero"); |
|||
GenerateVectorCompositor("Xor", "source", "backdrop", "Vector4.Zero"); |
|||
|
|||
|
|||
GeneratePixelBlender("Normal"); |
|||
GeneratePixelBlender("Multiply"); |
|||
GeneratePixelBlender("Add"); |
|||
GeneratePixelBlender("Subtract"); |
|||
GeneratePixelBlender("Screen"); |
|||
GeneratePixelBlender("Darken"); |
|||
GeneratePixelBlender("Lighten"); |
|||
GeneratePixelBlender("Overlay"); |
|||
GeneratePixelBlender("HardLight"); |
|||
|
|||
GeneratePixelBlender("Src"); |
|||
GeneratePixelBlender("Atop"); |
|||
GeneratePixelBlender("Over"); |
|||
GeneratePixelBlender("In"); |
|||
GeneratePixelBlender("Out"); |
|||
GeneratePixelBlender("Dest"); |
|||
GeneratePixelBlender("DestAtop"); |
|||
GeneratePixelBlender("DestOver"); |
|||
GeneratePixelBlender("DestIn"); |
|||
GeneratePixelBlender("DestOut"); |
|||
GeneratePixelBlender("Clear"); |
|||
GeneratePixelBlender("Xor"); |
|||
|
|||
|
|||
#> |
|||
} |
|||
<# |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
#> |
|||
<#@ template debug="false" hostspecific="false" language="C#" #> |
|||
<#@ assembly name="System.Core" #> |
|||
<#@ import namespace="System.Linq" #> |
|||
<#@ import namespace="System.Text" #> |
|||
<#@ import namespace="System.Collections.Generic" #> |
|||
<#@ output extension=".cs" #> |
|||
// Copyright (c) Six Labors and contributors. |
|||
// Licensed under the Apache License, Version 2.0. |
|||
|
|||
// <auto-generated /> |
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
internal static partial class PorterDuffFunctions |
|||
{ |
|||
|
|||
<# void GeneratePixelBlenders(string blender) { #> |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return source; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Atop(backdrop, source, <#=blender#>(backdrop, source)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Over(backdrop, source, <#=blender#>(backdrop, source)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return In(backdrop, source, <#=blender#>(backdrop, source)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Out(backdrop, source); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
return backdrop; |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Atop(source, backdrop, <#=blender#>(source, backdrop)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Over(source, backdrop, <#=blender#>(source, backdrop)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return In(source, backdrop, <#=blender#>(source, backdrop)); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Out(source, backdrop); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Xor(backdrop, source); |
|||
} |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
opacity = opacity.Clamp(0, 1); |
|||
source.W *= opacity; |
|||
|
|||
return Clear(backdrop, source); |
|||
} |
|||
<# } #> |
|||
|
|||
|
|||
<# void GenerateGenericPixelBlender(string blender, string composer) { #> |
|||
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static TPixel <#=blender#><#=composer#><TPixel>(TPixel backdrop, TPixel source, float opacity) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
TPixel dest = default; |
|||
dest.PackFromVector4(<#=blender#><#=composer#>(backdrop.ToVector4(),source.ToVector4(),opacity)); |
|||
return dest; |
|||
} |
|||
|
|||
<# } #> |
|||
|
|||
<# |
|||
|
|||
string[] composers = new []{ |
|||
"Src", |
|||
"SrcAtop", |
|||
"SrcOver", |
|||
"SrcIn", |
|||
"SrcOut", |
|||
"Dest", |
|||
"DestAtop", |
|||
"DestOver", |
|||
"DestIn", |
|||
"DestOut", |
|||
"Clear", |
|||
"Xor", |
|||
}; |
|||
|
|||
string[] blenders = new []{ |
|||
"Normal", |
|||
"Multiply", |
|||
"Add", |
|||
"Subtract", |
|||
"Screen", |
|||
"Darken", |
|||
"Lighten", |
|||
"Overlay", |
|||
"HardLight" |
|||
}; |
|||
|
|||
foreach(var blender in blenders) |
|||
{ |
|||
GeneratePixelBlenders(blender); |
|||
|
|||
foreach(var composer in composers) |
|||
{ |
|||
GenerateGenericPixelBlender(blender,composer); |
|||
} |
|||
} |
|||
|
|||
#> |
|||
} |
|||
} |
|||
@ -1,194 +1,257 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
/// <summary>
|
|||
/// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// These functions are designed to be a general solution for all color cases,
|
|||
/// that is, they take in account the alpha value of both the backdrop
|
|||
/// and source, and there's no need to alpha-premultiply neither the backdrop
|
|||
/// nor the source.
|
|||
/// Note there are faster functions for when the backdrop color is known
|
|||
/// to be opaque
|
|||
/// </remarks>
|
|||
internal static partial class PorterDuffFunctions |
|||
{ |
|||
/// <summary>
|
|||
/// Source over backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source multiplied by backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, backdrop * source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source added to backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Add(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source subtracted from backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Subtract(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Complement of source multiplied by the complement of backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Screen(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Per element, chooses the smallest value of source and backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, Vector4.Min(backdrop, source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Per element, chooses the largest value of source and backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
return Compose(backdrop, source, Vector4.Max(backdrop, source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Overlays source over backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Overlay(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
float cr = OverlayValueFunction(backdrop.X, source.X); |
|||
float cg = OverlayValueFunction(backdrop.Y, source.Y); |
|||
float cb = OverlayValueFunction(backdrop.Z, source.Z); |
|||
|
|||
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Hard light effect
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <param name="opacity">Opacity applied to Source Alpha</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 HardLight(Vector4 backdrop, Vector4 source, float opacity) |
|||
{ |
|||
source.W *= opacity; |
|||
float cr = OverlayValueFunction(source.X, backdrop.X); |
|||
float cg = OverlayValueFunction(source.Y, backdrop.Y); |
|||
float cb = OverlayValueFunction(source.Z, backdrop.Z); |
|||
|
|||
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Helper function for Overlay andHardLight modes
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color element</param>
|
|||
/// <param name="source">Source color element</param>
|
|||
/// <returns>Overlay value</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float OverlayValueFunction(float backdrop, float source) |
|||
{ |
|||
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// General composition function for all modes, with a general solution for alpha channel
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Original Backdrop color</param>
|
|||
/// <param name="source">Original source color</param>
|
|||
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
|
|||
/// <returns>The final color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform) |
|||
{ |
|||
// calculate weights
|
|||
float xw = backdrop.W * source.W; |
|||
float bw = backdrop.W - xw; |
|||
float sw = source.W - xw; |
|||
|
|||
// calculate final alpha
|
|||
float a = xw + bw + sw; |
|||
|
|||
// calculate final value
|
|||
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); |
|||
xform.W = a; |
|||
|
|||
return xform; |
|||
} |
|||
} |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Numerics; |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders |
|||
{ |
|||
/// <summary>
|
|||
/// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// These functions are designed to be a general solution for all color cases,
|
|||
/// that is, they take in account the alpha value of both the backdrop
|
|||
/// and source, and there's no need to alpha-premultiply neither the backdrop
|
|||
/// nor the source.
|
|||
/// Note there are faster functions for when the backdrop color is known
|
|||
/// to be opaque
|
|||
/// </remarks>
|
|||
internal static partial class PorterDuffFunctions |
|||
{ |
|||
/// <summary>
|
|||
/// Source over backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Normal(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source multiplied by backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Multiply(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return backdrop * source; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source added to backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Add(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.Min(Vector4.One, backdrop + source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Source subtracted from backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Subtract(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.Max(Vector4.Zero, backdrop - source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Complement of source multiplied by the complement of backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Screen(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Per element, chooses the smallest value of source and backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Darken(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.Min(backdrop, source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Per element, chooses the largest value of source and backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Lighten(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.Max(backdrop, source); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Overlays source over backdrop
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 Overlay(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
float cr = OverlayValueFunction(backdrop.X, source.X); |
|||
float cg = OverlayValueFunction(backdrop.Y, source.Y); |
|||
float cb = OverlayValueFunction(backdrop.Z, source.Z); |
|||
|
|||
return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Hard light effect
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color</param>
|
|||
/// <param name="source">Source color</param>
|
|||
/// <returns>Output color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
public static Vector4 HardLight(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
float cr = OverlayValueFunction(source.X, backdrop.X); |
|||
float cg = OverlayValueFunction(source.Y, backdrop.Y); |
|||
float cb = OverlayValueFunction(source.Z, backdrop.Z); |
|||
|
|||
return Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Helper function for Overlay andHardLight modes
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Backdrop color element</param>
|
|||
/// <param name="source">Source color element</param>
|
|||
/// <returns>Overlay value</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static float OverlayValueFunction(float backdrop, float source) |
|||
{ |
|||
return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// General composition function for all modes, with a general solution for alpha channel
|
|||
/// </summary>
|
|||
/// <param name="backdrop">Original Backdrop color</param>
|
|||
/// <param name="source">Original source color</param>
|
|||
/// <param name="xform">Desired transformed color, without taking Alpha channel in account</param>
|
|||
/// <returns>The final color</returns>
|
|||
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
|||
private static Vector4 SrcOverReference(Vector4 backdrop, Vector4 source, Vector4 xform) |
|||
{ |
|||
// calculate weights
|
|||
float xw = backdrop.W * source.W; |
|||
float bw = backdrop.W - xw; |
|||
float sw = source.W - xw; |
|||
|
|||
// calculate final alpha
|
|||
float a = xw + bw + sw; |
|||
|
|||
// calculate final value
|
|||
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon); |
|||
xform.W = a; |
|||
|
|||
return xform; |
|||
} |
|||
|
|||
public static Vector4 Over(Vector4 dst, Vector4 src, Vector4 blend) |
|||
{ |
|||
// calculate weights
|
|||
float blendW = dst.W * src.W; |
|||
float dstW = dst.W - blendW; |
|||
float srcW = src.W - blendW; |
|||
|
|||
// calculate final alpha
|
|||
float alpha = dstW + srcW + blendW; |
|||
|
|||
// calculate final color
|
|||
Vector4 color = (dst * dstW) + (src * srcW) + (blend * blendW); |
|||
|
|||
// unpremultiply
|
|||
color /= MathF.Max(alpha, Constants.Epsilon); |
|||
color.W = alpha; |
|||
|
|||
return color; |
|||
} |
|||
|
|||
public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend) |
|||
{ |
|||
// calculate weights
|
|||
float blendW = dst.W * src.W; |
|||
float dstW = dst.W - blendW; |
|||
|
|||
// calculate final alpha
|
|||
float alpha = dstW + blendW; |
|||
|
|||
// calculate final color
|
|||
Vector4 color = (dst * dstW) + (blend * blendW); |
|||
|
|||
// unpremultiply
|
|||
color /= MathF.Max(alpha, Constants.Epsilon); |
|||
color.W = alpha; |
|||
|
|||
return color; |
|||
} |
|||
|
|||
public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend) |
|||
{ |
|||
float alpha = dst.W * src.W; |
|||
|
|||
Vector4 color = src * alpha; // premultiply
|
|||
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
|
|||
color.W = alpha; |
|||
|
|||
return color; |
|||
} |
|||
|
|||
public static Vector4 Out(Vector4 dst, Vector4 src) |
|||
{ |
|||
float alpha = (1 - dst.W) * src.W; |
|||
|
|||
Vector4 color = src * alpha; // premultiply
|
|||
color /= MathF.Max(alpha, Constants.Epsilon); // unpremultiply
|
|||
color.W = alpha; |
|||
|
|||
return color; |
|||
} |
|||
|
|||
public static Vector4 Xor(Vector4 dst, Vector4 src) |
|||
{ |
|||
float srcW = 1 - dst.W; |
|||
float dstW = 1 - src.W; |
|||
|
|||
float alpha = (src.W * srcW) + (dst.W * dstW); |
|||
Vector4 color = (src.W * src * srcW) + (dst.W * dst * dstW); |
|||
|
|||
// unpremultiply
|
|||
color /= MathF.Max(alpha, Constants.Epsilon); |
|||
color.W = alpha; |
|||
|
|||
return color; |
|||
} |
|||
|
|||
private static Vector4 Clear(Vector4 backdrop, Vector4 source) |
|||
{ |
|||
return Vector4.Zero; |
|||
} |
|||
} |
|||
} |
|||
@ -1,153 +1,158 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class SolidFillBlendedShapesTests |
|||
{ |
|||
public static IEnumerable<object[]> modes = |
|||
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x }); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) |
|||
) |
|||
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.Transparent, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = (img.Width / 100); |
|||
int scaleY = (img.Height / 100); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); |
|||
var c = NamedColors<TPixel>.Red.ToVector4(); |
|||
c.W *= 0.5f; |
|||
var pixel = default(TPixel); |
|||
pixel.PackFromVector4(c); |
|||
|
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
pixel, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); ; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = (img.Width / 100); |
|||
int scaleY = (img.Height / 100); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.Black, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|||
|
|||
VerifyImage(provider, mode, img); |
|||
} |
|||
} |
|||
|
|||
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
img.DebugSave( |
|||
provider, |
|||
new { mode }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
var comparer = ImageComparer.TolerantPercentage(0.01f, 3); |
|||
img.CompareFirstFrameToReferenceOutput(comparer, |
|||
provider, |
|||
new { mode }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
using Xunit; |
|||
|
|||
// ReSharper disable InconsistentNaming
|
|||
namespace SixLabors.ImageSharp.Tests.Drawing |
|||
{ |
|||
[GroupOutput("Drawing")] |
|||
public class SolidFillBlendedShapesTests |
|||
{ |
|||
public static IEnumerable<object[]> modes = |
|||
((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))).Select(x => new object[] { x }); |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY) |
|||
) |
|||
.Fill(new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendTransparentEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = img.Width / 100; |
|||
int scaleY = img.Height / 100; |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.Transparent, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendHotPinkRect_3BlendSemiTransparentRedEllipse<TPixel>( |
|||
TestImageProvider<TPixel> provider, |
|||
PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using (Image<TPixel> img = provider.GetImage()) |
|||
{ |
|||
int scaleX = (img.Width / 100); |
|||
int scaleY = (img.Height / 100); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40, 100 * scaleX, 20 * scaleY))); |
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
NamedColors<TPixel>.HotPink, |
|||
new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY))); |
|||
var c = NamedColors<TPixel>.Red.ToVector4(); |
|||
c.W *= 0.5f; |
|||
var pixel = default(TPixel); |
|||
pixel.PackFromVector4(c); |
|||
|
|||
img.Mutate( |
|||
x => x.Fill( |
|||
new GraphicsOptions(true) { BlenderMode = mode }, |
|||
pixel, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY)) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, img); ; |
|||
} |
|||
} |
|||
|
|||
[Theory] |
|||
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)] |
|||
public void _1DarkBlueRect_2BlendBlackEllipse<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
using(Image<TPixel> dstImg = provider.GetImage(), srcImg = provider.GetImage()) |
|||
{ |
|||
int scaleX = (dstImg.Width / 100); |
|||
int scaleY = (dstImg.Height / 100); |
|||
|
|||
dstImg.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.DarkBlue, |
|||
new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY))); |
|||
|
|||
srcImg.Mutate( |
|||
x => x.Fill( |
|||
NamedColors<TPixel>.Black, |
|||
new Shapes.EllipsePolygon(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY))); |
|||
|
|||
dstImg.Mutate( |
|||
x => x.DrawImage(new GraphicsOptions(true) { BlenderMode = mode }, srcImg) |
|||
); |
|||
|
|||
VerifyImage(provider, mode, dstImg); |
|||
} |
|||
} |
|||
|
|||
private static void VerifyImage<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode, Image<TPixel> img) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
img.DebugSave( |
|||
provider, |
|||
new { mode }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
|
|||
var comparer = ImageComparer.TolerantPercentage(0.01f, 3); |
|||
img.CompareFirstFrameToReferenceOutput(comparer, |
|||
provider, |
|||
new { mode }, |
|||
appendPixelTypeToFileName: false, |
|||
appendSourceFileOrDescription: false); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 98fb7e2e4d5935b1c733bd2b206b6145b71ef378 |
|||
Subproject commit 825220cdc4e9d1b4b3b474c63139e18e1cdb800e |
|||
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
Loading…
Reference in new issue