Browse Source

Enhance Blend to use point and Size Fix #14

pull/33/head
James Jackson-South 9 years ago
parent
commit
7f473cb0cd
  1. 1
      Settings.StyleCop
  2. 22
      src/ImageSharp/Colors/Vector4BlendTransforms.cs
  3. 23
      src/ImageSharp/Filters/Overlays/Blend.cs
  4. 84
      src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs
  5. 5
      src/ImageSharp/Numerics/Rectangle.cs
  6. 9
      tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs

1
Settings.StyleCop

@ -35,6 +35,7 @@
<Value>Paeth</Value>
<Value>th</Value>
<Value>desensitivity</Value>
<Value>premultiplied</Value>
</CollectionProperty>
</GlobalSettings>
<Analyzers>

22
src/ImageSharp/Colors/Vector4BlendTransforms.cs

@ -185,6 +185,28 @@ namespace ImageSharp
return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W);
}
/// <summary>
/// Linearly interpolates from one vector to another based on the given weighting.
/// The two vectors are premultiplied by their W component before operating.
/// </summary>
/// <param name="backdrop">The backdrop vector.</param>
/// <param name="source">The source vector.</param>
/// <param name="amount">
/// A value between 0 and 1 indicating the weight of the second source vector.
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>
/// The <see cref="Vector4"/>
/// </returns>
public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount)
{
amount = amount.Clamp(0, 1);
backdrop = backdrop * new Vector4(backdrop.X, backdrop.Y, backdrop.Z, 1) * backdrop.W;
source = source * new Vector4(source.X, source.Y, source.Z, 1) * source.W;
return Vector4.Lerp(backdrop, source, amount) / new Vector4(source.W, source.W, source.W, 1);
}
/// <summary>
/// Multiplies or screens the color component, depending on the component value.
/// </summary>

23
src/ImageSharp/Filters/Overlays/Blend.cs

@ -21,11 +21,11 @@ namespace ImageSharp
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <returns>The <see cref="Image{TColor, TPacked}"/>.</returns>
public static Image<TColor, TPacked> Blend<TColor, TPacked>(this Image<TColor, TPacked> source, ImageBase<TColor, TPacked> image, int percent = 50)
public static Image<TColor, TPacked> Blend<TColor, TPacked>(this Image<TColor, TPacked> source, Image<TColor, TPacked> image, int percent = 50)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return Blend(source, image, percent, source.Bounds);
return Blend(source, image, percent, default(Size), default(Point));
}
/// <summary>
@ -36,15 +36,24 @@ namespace ImageSharp
/// <typeparam name="TColor">The pixel format.</typeparam>
/// <typeparam name="TPacked">The packed format. <example>uint, long, float.</example></typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 100.</param>
/// <param name="rectangle">
/// The <see cref="Rectangle"/> structure that specifies the portion of the image object to alter.
/// </param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TColor, TPacked}"/>.</returns>
public static Image<TColor, TPacked> Blend<TColor, TPacked>(this Image<TColor, TPacked> source, ImageBase<TColor, TPacked> image, int percent, Rectangle rectangle)
public static Image<TColor, TPacked> Blend<TColor, TPacked>(this Image<TColor, TPacked> source, Image<TColor, TPacked> image, int percent, Size size, Point location)
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
return source.Process(rectangle, new BlendProcessor<TColor, TPacked>(image, percent));
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
return source.Process(source.Bounds, new BlendProcessor<TColor, TPacked>(image, size, location, percent));
}
}
}

84
src/ImageSharp/Filters/Processors/Overlays/BlendProcessor.cs

@ -18,58 +18,60 @@ namespace ImageSharp.Processors
where TColor : struct, IPackedPixel<TPacked>
where TPacked : struct
{
/// <summary>
/// The image to blend.
/// </summary>
private readonly ImageBase<TColor, TPacked> blend;
/// <summary>
/// Initializes a new instance of the <see cref="BlendProcessor{TColor,TPacked}"/> class.
/// </summary>
/// <param name="image">
/// The image to blend with the currently processing image.
/// Disposal of this image is the responsibility of the developer.
/// </param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="alpha">The opacity of the image to blend. Between 0 and 100.</param>
public BlendProcessor(ImageBase<TColor, TPacked> image, int alpha = 100)
public BlendProcessor(Image<TColor, TPacked> image, Size size, Point location, int alpha = 100)
{
Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha));
this.blend = image;
this.Value = alpha;
this.Image = image;
this.Size = size;
this.Alpha = alpha;
this.Location = location;
}
/// <summary>
/// Gets the image to blend.
/// </summary>
public Image<TColor, TPacked> Image { get; private set; }
/// <summary>
/// Gets the alpha percentage value.
/// </summary>
public int Value { get; }
public int Alpha { get; }
/// <summary>
/// Gets the size to draw the blended image.
/// </summary>
public Size Size { get; }
/// <summary>
/// Gets the location to draw the blended image.
/// </summary>
public Point Location { get; }
/// <inheritdoc/>
protected override void Apply(ImageBase<TColor, TPacked> source, Rectangle sourceRectangle, int startY, int endY)
{
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Rectangle bounds = this.blend.Bounds;
// Align start/end positions.
int minX = Math.Max(0, startX);
int maxX = Math.Min(source.Width, endX);
int minY = Math.Max(0, startY);
int maxY = Math.Min(source.Height, endY);
// Reset offset if necessary.
if (minX > 0)
if (this.Image.Bounds.Size != this.Size)
{
startX = 0;
this.Image = this.Image.Resize(this.Size.Width, this.Size.Height);
}
if (minY > 0)
{
startY = 0;
}
// Align start/end positions.
Rectangle bounds = this.Image.Bounds;
int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
int minY = Math.Max(this.Location.Y, startY);
int maxY = Math.Min(this.Location.Y + bounds.Height, endY);
float alpha = this.Value / 100F;
float alpha = this.Alpha / 100F;
using (PixelAccessor<TColor, TPacked> toBlendPixels = this.blend.Lock())
using (PixelAccessor<TColor, TPacked> toBlendPixels = this.Image.Lock())
using (PixelAccessor<TColor, TPacked> sourcePixels = source.Lock())
{
Parallel.For(
@ -78,26 +80,20 @@ namespace ImageSharp.Processors
this.ParallelOptions,
y =>
{
int offsetY = y - startY;
for (int x = minX; x < maxX; x++)
{
int offsetX = x - startX;
Vector4 color = sourcePixels[offsetX, offsetY].ToVector4();
Vector4 color = sourcePixels[x, y].ToVector4();
Vector4 blendedColor = toBlendPixels[x - minX, y - minY].ToVector4();
if (bounds.Contains(offsetX, offsetY))
if (blendedColor.W > 0)
{
Vector4 blendedColor = toBlendPixels[offsetX, offsetY].ToVector4();
if (blendedColor.W > 0)
{
// Lerping colors is dependent on the alpha of the blended color
color = Vector4.Lerp(color, blendedColor, alpha > 0 ? alpha : blendedColor.W);
}
// Lerping colors is dependent on the alpha of the blended color
color = Vector4BlendTransforms.PremultipliedLerp(color, blendedColor, alpha);
}
TColor packed = default(TColor);
packed.PackFromVector4(color);
sourcePixels[offsetX, offsetY] = packed;
sourcePixels[x, y] = packed;
}
});
}

5
src/ImageSharp/Numerics/Rectangle.cs

@ -127,6 +127,11 @@ namespace ImageSharp
}
}
/// <summary>
/// Gets the size of this <see cref="Rectangle"/>.
/// </summary>
public Size Size => new Size(this.Width, this.Height);
/// <summary>
/// Gets a value indicating whether this <see cref="Rectangle"/> is empty.
/// </summary>

9
tests/ImageSharp.Tests/Processors/Filters/BlendTest.cs

@ -6,6 +6,7 @@
namespace ImageSharp.Tests
{
using System.IO;
using System.Linq;
using Xunit;
@ -16,7 +17,9 @@ namespace ImageSharp.Tests
{
string path = CreateOutputDirectory("Blend");
Image blend;
Image blend;// = new Image(400, 400);
// blend.BackgroundColor(Color.RebeccaPurple);
using (FileStream stream = File.OpenRead(TestImages.Bmp.Car))
{
blend = new Image(stream);
@ -28,8 +31,8 @@ namespace ImageSharp.Tests
using (FileStream output = File.OpenWrite($"{path}/{file.FileName}"))
{
image.Blend(blend)
.Save(output);
image.Blend(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4))
.Save(output);
}
}
}

Loading…
Cancel
Save