Browse Source

Fix #16

pull/30/head
James Jackson-South 9 years ago
parent
commit
dc4646eb00
  1. 160
      src/ImageSharp/Colors/ColorTransforms.cs
  2. 253
      src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs
  3. 44
      tests/ImageSharp.Tests/Colors/ColorTransformTests.cs

160
src/ImageSharp/Colors/ColorTransforms.cs

@ -45,6 +45,20 @@ namespace ImageSharp
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>
@ -61,21 +75,7 @@ namespace ImageSharp
/// </returns>
public static Color Multiply(Color backdrop, Color source)
{
if (source == Black)
{
return Black;
}
if (source == White)
{
return backdrop;
}
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 multiply = vb * vs;
multiply.W = vb.W;
Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref multiply));
}
@ -94,21 +94,7 @@ namespace ImageSharp
/// </returns>
public static Color Screen(Color backdrop, Color source)
{
if (source == Black)
{
return backdrop;
}
if (source == White)
{
return White;
}
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 subtract = vb + vs - (vb * vs);
subtract.W = vb.W;
Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref subtract));
}
@ -123,10 +109,8 @@ namespace ImageSharp
/// </returns>
public static Color HardLight(Color backdrop, Color source)
{
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 result = new Vector4(BlendOverlay(vs.X, vb.X), BlendOverlay(vs.Y, vb.Y), BlendOverlay(vs.Z, vb.Z), vb.W);
return new Color(Pack(ref result));
Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref hardlight));
}
/// <summary>
@ -144,10 +128,8 @@ namespace ImageSharp
/// </returns>
public static Color Overlay(Color backdrop, Color source)
{
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 result = new Vector4(BlendOverlay(vb.X, vs.X), BlendOverlay(vb.Y, vs.Y), BlendOverlay(vb.Z, vs.Z), vb.W);
return new Color(Pack(ref result));
Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref overlay));
}
/// <summary>
@ -161,11 +143,8 @@ namespace ImageSharp
/// </returns>
public static Color Darken(Color backdrop, Color source)
{
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 result = Vector4.Min(vb, vs);
result.W = vb.W;
return new Color(Pack(ref result));
Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref darken));
}
/// <summary>
@ -179,11 +158,81 @@ namespace ImageSharp
/// </returns>
public static Color Lighten(Color backdrop, Color source)
{
Vector4 vb = backdrop.ToVector4();
Vector4 vs = source.ToVector4();
Vector4 result = Vector4.Max(vb, vs);
result.W = vb.W;
return new Color(Pack(ref result));
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>
/// <returns>
/// The <see cref="Color"/>.
/// </returns>
public static Color Exclusion(Color backdrop, Color source)
{
Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4());
return new Color(Pack(ref exclusion));
}
/// <summary>
@ -202,18 +251,5 @@ namespace ImageSharp
{
return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount));
}
/// <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)));
}
}
}
}

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;
}
}
}

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

@ -8,7 +8,7 @@ namespace ImageSharp.Tests.Colors
using Xunit;
/// <summary>
/// Tests the color transform algorithms.
/// Tests the color transform algorithms. Test results match the output of CSS equivalents.
/// <see href="https://jsfiddle.net/jamessouth/L1v8r6kh/"/>
/// </summary>
public class ColorTransformTests
@ -23,6 +23,13 @@ namespace ImageSharp.Tests.Colors
/// </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()
{
@ -70,5 +77,40 @@ namespace ImageSharp.Tests.Colors
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