Browse Source

fix blending to allow non rectangualar regions to antialiase

Added a sample to resize and added rounded corners to an image
af/merge-core
Scott Williams 9 years ago
parent
commit
b76c21e7ee
  1. 1
      .gitignore
  2. 19
      ImageSharp.sln
  3. 12
      samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj
  4. 63
      samples/AvatarWithRoundedCorner/Program.cs
  5. 3
      samples/AvatarWithRoundedCorner/fb.jpg
  6. 36
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  7. 15
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  8. 34
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  9. 54
      tests/ImageSharp.Tests/Drawing/BlendedShapes.cs

1
.gitignore

@ -217,3 +217,4 @@ artifacts/
#CodeCoverage
**/CodeCoverage/*
docs/
/samples/AvatarWithRoundedCorner/output

19
ImageSharp.sln

@ -1,7 +1,7 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.26403.7
VisualStudioVersion = 15.0.26430.6
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{C317F1B1-D75E-4C6D-83EB-80367343E0D7}"
ProjectSection(SolutionItems) = preProject
@ -45,6 +45,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ImageSharp.Benchmarks", "te
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageSharp.Sandbox46", "tests\ImageSharp.Sandbox46\ImageSharp.Sandbox46.csproj", "{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{7CC6D57E-B916-43B8-B315-A0BB92F260A2}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AvatarWithRoundedCorner", "samples\AvatarWithRoundedCorner\AvatarWithRoundedCorner.csproj", "{844FC582-4E78-4371-847D-EFD4D1103578}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -127,6 +131,18 @@ Global
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x64.Build.0 = Release|Any CPU
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x86.ActiveCfg = Release|Any CPU
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB}.Release|x86.Build.0 = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|Any CPU.Build.0 = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.ActiveCfg = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x64.Build.0 = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.ActiveCfg = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Debug|x86.Build.0 = Debug|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.ActiveCfg = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|Any CPU.Build.0 = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.ActiveCfg = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x64.Build.0 = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.ActiveCfg = Release|Any CPU
{844FC582-4E78-4371-847D-EFD4D1103578}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -139,5 +155,6 @@ Global
{EA3000E9-2A91-4EC4-8A68-E566DEBDC4F6} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{2BF743D8-2A06-412D-96D7-F448F00C5EA5} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{96188137-5FA6-4924-AB6E-4EFF79C6E0BB} = {56801022-D71A-4FBE-BC5B-CBA08E2284EC}
{844FC582-4E78-4371-847D-EFD4D1103578} = {7CC6D57E-B916-43B8-B315-A0BB92F260A2}
EndGlobalSection
EndGlobal

12
samples/AvatarWithRoundedCorner/AvatarWithRoundedCorner.csproj

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp1.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\ImageSharp.Drawing\ImageSharp.Drawing.csproj" />
</ItemGroup>
</Project>

63
samples/AvatarWithRoundedCorner/Program.cs

@ -0,0 +1,63 @@

namespace AvatarWithRoundedCorner
{
using System;
using System.Numerics;
using ImageSharp;
using SixLabors.Shapes;
class Program
{
static void Main(string[] args)
{
using (var image = Image.Load("fb.jpg"))
{
image.Resize(new ImageSharp.Processing.ResizeOptions
{
Size = new ImageSharp.Size(200, 200),
Mode = ImageSharp.Processing.ResizeMode.Crop
});
ApplyRoundedCourners(image, 30);
System.IO.Directory.CreateDirectory("output");
image.Save("output/fb.png");
}
}
public static void ApplyRoundedCourners(Image<Rgba32> img, float cornerRadius)
{
var corners = BuildCorners(img.Width, img.Height, cornerRadius);
// now we have our corners time to draw them
img.Fill(Rgba32.Transparent, corners, new GraphicsOptions(true)
{
BlenderMode = ImageSharp.PixelFormats.PixelBlenderMode.Src // enforces that any part of this shape that has color is punched out of the background
});
}
public static IPathCollection BuildCorners(int imageWidth, int imageHeight, float cornerRadius)
{
// first create a square
var rect = new SixLabors.Shapes.Rectangle(-0.5f, -0.5f, cornerRadius, cornerRadius);
// then cut out of the square a circle so we are left with a corner
var cornerToptLeft = rect.Clip(new SixLabors.Shapes.Ellipse(cornerRadius-0.5f, cornerRadius - 0.5f, cornerRadius));
// corner is now a corner shape positions top left
//lets make 3 more positioned correctly, we cando that by translating the orgional artound the center of the image
var center = new Vector2(imageWidth / 2, imageHeight / 2);
var angle = Math.PI / 2f;
float rightPos = imageWidth - cornerToptLeft.Bounds.Width +1;
float bottomPos = imageHeight - cornerToptLeft.Bounds.Height + 1;
// move it across the widthof the image - the width of the shape
var cornerTopRight = cornerToptLeft.RotateDegree(90).Translate(rightPos, 0);
var cornerBottomLeft = cornerToptLeft.RotateDegree(-90).Translate(0, bottomPos);
var cornerBottomRight = cornerToptLeft.RotateDegree(180).Translate(rightPos, bottomPos);
return new PathCollection(cornerToptLeft, cornerBottomLeft, cornerTopRight, cornerBottomRight);
}
}
}

3
samples/AvatarWithRoundedCorner/fb.jpg

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:93bb4d6281dc1e845db57e836e0dca30b7a4062e81044efb27ad4d8b1a33130c
size 15787

36
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs

@ -16,79 +16,79 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Src(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(Vector4.Zero, source, source);
source.W *= opacity;
return Compose(Vector4.Zero, source, source).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Atop(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(backdrop, Vector4.Zero, source);
return Compose(backdrop, Vector4.Zero, source).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Over(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, source);
source.W *= opacity;
return Compose(backdrop, source, source).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(Vector4.Zero, Vector4.Zero, source);
return Compose(Vector4.Zero, Vector4.Zero, source).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(Vector4.Zero, source, Vector4.Zero);
source.W *= opacity;
return Compose(Vector4.Zero, source, Vector4.Zero).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Dest(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(backdrop, Vector4.Zero, backdrop);
return Compose(backdrop, Vector4.Zero, backdrop).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(Vector4.Zero, source, backdrop);
source.W *= opacity;
return Compose(Vector4.Zero, source, backdrop).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 DestOver(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, backdrop);
source.W *= opacity;
return Compose(backdrop, source, backdrop).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 DestIn(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(Vector4.Zero, Vector4.Zero, backdrop);
return Compose(Vector4.Zero, Vector4.Zero, backdrop).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 DestOut(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(backdrop, Vector4.Zero, Vector4.Zero);
return Compose(backdrop, Vector4.Zero, Vector4.Zero).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Clear(Vector4 backdrop, Vector4 source, float opacity)
{
return Compose(Vector4.Zero, Vector4.Zero, Vector4.Zero);
return Compose(Vector4.Zero, Vector4.Zero, Vector4.Zero).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, Vector4.Zero);
source.W *= opacity;
return Compose(backdrop, source, Vector4.Zero).Blend(backdrop, opacity);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]

15
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt

@ -48,15 +48,12 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 <#=name#>(Vector4 backdrop, Vector4 source, float opacity)
{
<#
if(sourceVar != "Vector4.Zero")
{
#>
<#=sourceVar#>.W *= opacity;
<#
}
#>
return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>);
<# if(sourceVar != "Vector4.Zero") {
#>
<#=sourceVar#>.W *= opacity;
<#
} #>
return Compose(<#=destVar#>, <#=sourceVar#>, <#=blendVar#>).Blend(backdrop, opacity);
}
<#

34
src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs

@ -31,8 +31,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Normal(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, source);
return Over(backdrop, source, opacity);
}
/// <summary>
@ -45,8 +44,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Multiply(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, backdrop * source);
return Compose(backdrop, source, backdrop * source).Blend(backdrop, opacity);
}
/// <summary>
@ -59,8 +57,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[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));
return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)).Blend(backdrop, opacity);
}
/// <summary>
@ -73,8 +70,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Substract(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source));
return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)).Blend(backdrop, opacity);
}
/// <summary>
@ -87,8 +83,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[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)));
return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))).Blend(backdrop, opacity);
}
/// <summary>
@ -101,8 +96,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Darken(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, Vector4.Min(backdrop, source));
return Compose(backdrop, source, Vector4.Min(backdrop, source)).Blend(backdrop, opacity);
}
/// <summary>
@ -115,8 +109,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Lighten(Vector4 backdrop, Vector4 source, float opacity)
{
source.W *= opacity;
return Compose(backdrop, source, Vector4.Max(backdrop, source));
return Compose(backdrop, source, Vector4.Max(backdrop, source)).Blend(backdrop, opacity);
}
/// <summary>
@ -134,7 +127,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders
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)));
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))).Blend(backdrop, opacity);
}
/// <summary>
@ -147,12 +140,11 @@ namespace ImageSharp.PixelFormats.PixelBlenders
[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)));
return Compose(backdrop, source, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))).Blend(backdrop, opacity);
}
/// <summary>
@ -186,10 +178,16 @@ namespace ImageSharp.PixelFormats.PixelBlenders
float a = xw + bw + sw;
// calculate final value
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a;
xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / MathF.Max(a, Constants.Epsilon);
xform.W = a;
return xform;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Blend(this Vector4 source, Vector4 backdrop, float opacity)
{
return Vector4.Lerp(backdrop, source, opacity);
}
}
}

54
tests/ImageSharp.Tests/Drawing/BlendedShapes.cs

@ -18,14 +18,16 @@ namespace ImageSharp.Tests.Drawing
.Select(x=> new object[] { x });
[Theory]
[WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0, 40, 100, 20));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true)
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});
@ -34,18 +36,20 @@ namespace ImageSharp.Tests.Drawing
}
[Theory]
[WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0, 40, 100, 20));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true)
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0* scaleX, 40 * scaleY, 100 * scaleX, 20 * scaleY));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});
img.Fill(NamedColors<TPixel>.Transparent, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true)
img.Fill(NamedColors<TPixel>.Transparent, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});
@ -53,16 +57,17 @@ namespace ImageSharp.Tests.Drawing
}
}
[Theory]
[WithBlankImages(nameof(modes), 100, 100, PixelTypes.Rgba32)]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_transparent50Percent<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0, 40, 100, 20));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20, 0, 30, 100), new ImageSharp.GraphicsOptions(true)
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40, 100 * scaleX, 20* scaleY));
img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});
@ -71,7 +76,32 @@ namespace ImageSharp.Tests.Drawing
TPixel pixel = default(TPixel);
pixel.PackFromVector4(c);
img.Fill(pixel, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true)
img.Fill(pixel, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});
img.DebugSave(provider, new { mode });
}
}
[Theory]
[WithBlankImages(nameof(modes), 250, 250, PixelTypes.Rgba32)]
public void DrawBlendedValues_doldidEllips<TPixel>(TestImageProvider<TPixel> provider, PixelBlenderMode mode)
where TPixel : struct, IPixel<TPixel>
{
using (var img = provider.GetImage())
{
var scaleX = (img.Width / 100);
var scaleY = (img.Height / 100);
img.Fill(NamedColors<TPixel>.DarkBlue, new Rectangle(0 * scaleX, 40* scaleY, 100 * scaleX, 20 * scaleY));
//img.Fill(NamedColors<TPixel>.HotPink, new Rectangle(20 * scaleX, 0 * scaleY, 30 * scaleX, 100 * scaleY), new ImageSharp.GraphicsOptions(true)
//{
// BlenderMode = mode
//});
img.Fill(NamedColors<TPixel>.Black, new SixLabors.Shapes.Ellipse(40 * scaleX, 50 * scaleY, 50 * scaleX, 50 * scaleY), new ImageSharp.GraphicsOptions(true)
{
BlenderMode = mode
});

Loading…
Cancel
Save