Browse Source

Merge remote-tracking branch 'refs/remotes/origin/blending'

pull/30/head
James Jackson-South 9 years ago
parent
commit
c388ad05d4
  1. 13
      src/ImageSharp/Colors/Color.cs
  2. 2
      src/ImageSharp/Colors/ColorDefinitions.cs
  3. 225
      src/ImageSharp/Colors/ColorTransforms.cs
  4. 253
      src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs
  5. 4
      tests/ImageSharp.Tests/Colors/ColorTests.cs
  6. 116
      tests/ImageSharp.Tests/Colors/ColorTransformTests.cs

13
src/ImageSharp/Colors/Color.cs

@ -32,7 +32,7 @@ namespace ImageSharp
/// <summary>
/// The half vector value.
/// </summary>
private static readonly Vector4 Half = new Vector4(0.5f);
private static readonly Vector4 Half = new Vector4(0.5F);
/// <summary>
/// The packed value.
@ -107,6 +107,17 @@ namespace ImageSharp
this.packedValue = Pack(ref vector);
}
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> struct.
/// </summary>
/// <param name="packed">
/// The packed value.
/// </param>
public Color(uint packed)
{
this.packedValue = packed;
}
/// <summary>
/// Gets or sets the red component.
/// </summary>

2
src/ImageSharp/Colors/ColorDefinitions.cs

@ -581,7 +581,7 @@ namespace ImageSharp
public static readonly Color Purple = new Color(128, 0, 128, 255);
/// <summary>
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #0.
/// Represents a <see cref="Color"/> matching the W3C definition that has an hex value of #663399.
/// </summary>
public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255);

225
src/ImageSharp/Colors/ColorTransforms.cs

@ -5,7 +5,6 @@
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
@ -18,6 +17,48 @@ namespace ImageSharp
/// </remarks>
public partial struct Color
{
/// <summary>
/// Adds the second color to the first.
/// </summary>
/// <param name="left">The first source color.</param>
/// <param name="right">The second source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color operator +(Color left, Color right)
{
Vector4 add = left.ToVector4() + right.ToVector4();
return new Color(Pack(ref add));
}
/// <summary>
/// Subtracts the second color from the first.
/// </summary>
/// <param name="left">The first source color.</param>
/// <param name="right">The second source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color operator -(Color left, Color right)
{
Vector4 sub = left.ToVector4() - right.ToVector4();
return new Color(Pack(ref sub));
}
/// <summary>
/// The blending formula simply selects the source color.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Normal(Color backdrop, Color source)
{
Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref normal));
}
/// <summary>
/// Blends two colors by multiplication.
/// <remarks>
@ -27,31 +68,171 @@ namespace ImageSharp
/// original color.
/// </remarks>
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Multiply(Color backdrop, Color source)
{
Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref multiply));
}
/// <summary>
/// Multiplies the complements of the backdrop and source color values, then complements the result.
/// <remarks>
/// The result color is always at least as light as either of the two constituent colors. Screening any
/// color with white produces white; screening with black leaves the original color unchanged.
/// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen.
/// </remarks>
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Screen(Color backdrop, Color source)
{
Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref subtract));
}
/// <summary>
/// Multiplies or screens the colors, depending on the source color value. The effect is similar to
/// shining a harsh spotlight on the backdrop.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color HardLight(Color backdrop, Color source)
{
Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref hardlight));
}
/// <summary>
/// Multiplies or screens the colors, depending on the backdrop color value.
/// <remarks>
/// Source colors overlay the backdrop while preserving its highlights and shadows.
/// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness
/// of the backdrop.
/// </remarks>
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Overlay(Color backdrop, Color source)
{
Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref overlay));
}
/// <summary>
/// Selects the darker of the backdrop and source colors.
/// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Darken(Color backdrop, Color source)
{
Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref darken));
}
/// <summary>
/// Selects the lighter of the backdrop and source colors.
/// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Lighten(Color backdrop, Color source)
{
Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref lighten));
}
/// <summary>
/// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining
/// a diffused spotlight on the backdrop.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color SoftLight(Color backdrop, Color source)
{
Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref softlight));
}
/// <summary>
/// Brightens the backdrop color to reflect the source color. Painting with black produces no changes.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color ColorDodge(Color backdrop, Color source)
{
Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref dodge));
}
/// <summary>
/// Darkens the backdrop color to reflect the source color. Painting with white produces no change.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color ColorBurn(Color backdrop, Color source)
{
Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref burn));
}
/// <summary>
/// Subtracts the darker of the two constituent colors from the lighter color.
/// Painting with white inverts the backdrop color; painting with black produces no change.
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Difference(Color backdrop, Color source)
{
Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref difference));
}
/// <summary>
/// Produces an effect similar to that of the <see cref="Difference"/> mode but lower in contrast. Painting with white
/// inverts the backdrop color; painting with black produces no change
/// </summary>
/// <param name="backdrop">The backdrop color.</param>
/// <param name="source">The source color.</param>
/// <param name="destination">The destination color.</param>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Multiply(Color source, Color destination)
public static Color Exclusion(Color backdrop, Color source)
{
if (destination == Color.Black)
{
return Color.Black;
}
if (destination == Color.White)
{
return source;
}
// TODO: This will use less memory than using Vector4
// but we should test speed vs memory to see which is best balance.
byte r = (byte)(source.R * destination.R).Clamp(0, 255);
byte g = (byte)(source.G * destination.G).Clamp(0, 255);
byte b = (byte)(source.B * destination.B).Clamp(0, 255);
byte a = (byte)(source.A * destination.A).Clamp(0, 255);
return new Color(r, g, b, a);
Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref exclusion));
}
/// <summary>
@ -71,4 +252,4 @@ namespace ImageSharp
return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount));
}
}
}
}

253
src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs

@ -0,0 +1,253 @@
// <copyright file="Vector4BlendTransforms.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp
{
using System;
using System.Numerics;
/// <summary>
/// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification.
/// <see href="https://www.w3.org/TR/compositing-1/"/>
/// </summary>
public class Vector4BlendTransforms
{
/// <summary>
/// The epsilon for comparing floating point numbers.
/// </summary>
private const float Epsilon = 0.001F;
/// <summary>
/// The blending formula simply selects the source vector.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Normal(Vector4 backdrop, Vector4 source)
{
return new Vector4(source.X, source.Y, source.Z, source.W);
}
/// <summary>
/// Blends two vectors by multiplication.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Multiply(Vector4 backdrop, Vector4 source)
{
Vector4 multiply = backdrop * source;
multiply.W = backdrop.W;
return multiply;
}
/// <summary>
/// Multiplies the complements of the backdrop and source vector values, then complements the result.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Screen(Vector4 backdrop, Vector4 source)
{
Vector4 subtract = backdrop + source - (backdrop * source);
subtract.W = backdrop.W;
return subtract;
}
/// <summary>
/// Multiplies or screens the colors, depending on the source vector value.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 HardLight(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendOverlay(source.X, backdrop.X), BlendOverlay(source.Y, backdrop.Y), BlendOverlay(source.Z, backdrop.Z), backdrop.W);
}
/// <summary>
/// Multiplies or screens the vectors, depending on the backdrop vector value.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Overlay(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendOverlay(backdrop.X, source.X), BlendOverlay(backdrop.Y, source.Y), BlendOverlay(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Selects the minimum of the backdrop and source vectors.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Darken(Vector4 backdrop, Vector4 source)
{
Vector4 result = Vector4.Min(backdrop, source);
result.W = backdrop.W;
return result;
}
/// <summary>
/// Selects the max of the backdrop and source vector.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Lighten(Vector4 backdrop, Vector4 source)
{
Vector4 result = Vector4.Max(backdrop, source);
result.W = backdrop.W;
return result;
}
/// <summary>
/// Selects the maximum or minimum of the vectors, depending on the source vector value.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 SoftLight(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendSoftLight(backdrop.X, source.X), BlendSoftLight(backdrop.Y, source.Y), BlendSoftLight(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Increases the backdrop vector to reflect the source vector.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Dodge(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendDodge(backdrop.X, source.X), BlendDodge(backdrop.Y, source.Y), BlendDodge(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Decreases the backdrop vector to reflect the source vector.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Burn(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendBurn(backdrop.X, source.X), BlendBurn(backdrop.Y, source.Y), BlendBurn(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Subtracts the minimum of the two constituent vectors from the maximum vector.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Difference(Vector4 backdrop, Vector4 source)
{
Vector4 result = Vector4.Abs(backdrop - source);
result.W = backdrop.W;
return result;
}
/// <summary>
/// Produces an effect similar to that of the <see cref="Difference"/> mode but lower in magnitude.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <returns>
/// The <see cref="Vector4"/>.
/// </returns>
public static Vector4 Exclusion(Vector4 backdrop, Vector4 source)
{
return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Multiplies or screens the color component, depending on the component value.
/// </summary>
/// <param name="b">The backdrop component.</param>
/// <param name="s">The source component.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float BlendOverlay(float b, float s)
{
return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s)));
}
/// <summary>
/// Darkens or lightens the backdrop component, depending on the source component value.
/// </summary>
/// <param name="b">The backdrop component.</param>
/// <param name="s">The source component.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float BlendSoftLight(float b, float s)
{
return s <= .5F ? (2F * b * s + b * b * (1F - 2F * s)) : (float)(Math.Sqrt(b) * (2F * s - 1F) + 2F * b * (1F - s));
}
/// <summary>
/// Brightens the backdrop component to reflect the source component.
/// </summary>
/// <param name="b">The backdrop component.</param>
/// <param name="s">The source component.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float BlendDodge(float b, float s)
{
return Math.Abs(s - 1F) < Epsilon ? s : Math.Min(b / (1F - s), 1F);
}
/// <summary>
/// Darkens the backdrop component to reflect the source component.
/// </summary>
/// <param name="b">The backdrop component.</param>
/// <param name="s">The source component.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float BlendBurn(float b, float s)
{
return Math.Abs(s) < Epsilon ? s : Math.Max(1F - ((1F - b) / s), 0F);
}
/// <summary>
/// Darkens the backdrop component to reflect the source component.
/// </summary>
/// <param name="b">The backdrop component.</param>
/// <param name="s">The source component.</param>
/// <returns>
/// The <see cref="float"/>.
/// </returns>
private static float BlendExclusion(float b, float s)
{
return b + s - 2F * b * s;
}
}
}

4
tests/ImageSharp.Tests/Colors/ColorTests.cs

@ -3,11 +3,11 @@
// Licensed under the Apache License, Version 2.0.
// </copyright>
using System.Numerics;
namespace ImageSharp.Tests
{
using System;
using System.Numerics;
using Xunit;
/// <summary>

116
tests/ImageSharp.Tests/Colors/ColorTransformTests.cs

@ -0,0 +1,116 @@
// <copyright file="ColorTransformTests.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageSharp.Tests.Colors
{
using Xunit;
/// <summary>
/// Tests the color transform algorithms. Test results match the output of CSS equivalents.
/// <see href="https://jsfiddle.net/jamessouth/L1v8r6kh/"/>
/// </summary>
public class ColorTransformTests
{
/// <summary>
/// Orange backdrop
/// </summary>
private static readonly Color Backdrop = new Color(204, 102, 0);
/// <summary>
/// Blue source
/// </summary>
private static readonly Color Source = new Color(0, 102, 153);
[Fact]
public void Normal()
{
Color normal = Color.Normal(Backdrop, Source);
Assert.True(normal == Source);
}
[Fact]
public void Multiply()
{
Assert.True(Color.Multiply(Backdrop, Color.Black) == Color.Black);
Assert.True(Color.Multiply(Backdrop, Color.White) == Backdrop);
Color multiply = Color.Multiply(Backdrop, Source);
Assert.True(multiply == new Color(0, 41, 0));
}
[Fact]
public void Screen()
{
Assert.True(Color.Screen(Backdrop, Color.Black) == Backdrop);
Assert.True(Color.Screen(Backdrop, Color.White) == Color.White);
Color screen = Color.Screen(Backdrop, Source);
Assert.True(screen == new Color(204, 163, 153));
}
[Fact]
public void HardLight()
{
Color hardLight = Color.HardLight(Backdrop, Source);
Assert.True(hardLight == new Color(0, 82, 51));
}
[Fact]
public void Overlay()
{
Color overlay = Color.Overlay(Backdrop, Source);
Assert.True(overlay == new Color(153, 82, 0));
}
[Fact]
public void Darken()
{
Color darken = Color.Darken(Backdrop, Source);
Assert.True(darken == new Color(0, 102, 0));
}
[Fact]
public void Lighten()
{
Color lighten = Color.Lighten(Backdrop, Source);
Assert.True(lighten == new Color(204, 102, 153));
}
[Fact]
public void SoftLight()
{
Color softLight = Color.SoftLight(Backdrop, Source);
Assert.True(softLight == new Color(163, 90, 0));
}
[Fact]
public void ColorDodge()
{
Color colorDodge = Color.ColorDodge(Backdrop, Source);
Assert.True(colorDodge == new Color(204, 170, 0));
}
[Fact]
public void ColorBurn()
{
Color colorBurn = Color.ColorBurn(Backdrop, Source);
Assert.True(colorBurn == new Color(0, 0, 0));
}
[Fact]
public void Difference()
{
Color difference = Color.Difference(Backdrop, Source);
Assert.True(difference == new Color(204, 0, 153));
}
[Fact]
public void Exclusion()
{
Color exclusion = Color.Exclusion(Backdrop, Source);
Assert.True(exclusion == new Color(204, 122, 153));
}
}
}
Loading…
Cancel
Save