Browse Source

Merge pull request #686 from vpenades/master

Ext.DrawImage generalization to support multiple formats. (Experimental)
pull/692/merge
James Jackson-South 8 years ago
committed by GitHub
parent
commit
bf1227cb7f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 210
      src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs
  2. 53
      src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs
  3. 8166
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs
  4. 44
      src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt
  5. 838
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.cs
  6. 67
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.Generated.tt
  7. 31
      src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs
  8. 120
      src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs
  9. 58
      tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs

210
src/ImageSharp.Drawing/Processing/DrawImageExtensions.cs

@ -1,121 +1,137 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The blending mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing.Processors.Drawing;
using SixLabors.Primitives;
namespace SixLabors.ImageSharp.Processing
{
/// <summary>
/// Adds extensions that allow the drawing of images to the <see cref="Image{TPixel}"/> type.
/// </summary>
public static class DrawImageExtensions
{
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The blending mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, PixelColorBlendingMode colorBlending, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="colorBlending">The color blending mode.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, colorBlending, alphaComposition, opacity));
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, colorBlending, alphaComposition, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, GraphicsOptions options)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, Point.Empty, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, GraphicsOptions.Default.ColorBlendingMode, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlending, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, colorBlending, GraphicsOptions.Default.AlphaCompositionMode, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlending">The color blending to apply.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="alphaComposition">The alpha composition mode.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, colorBlending, alphaComposition, opacity));
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlending, PixelAlphaCompositionMode alphaComposition, float opacity)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, colorBlending, alphaComposition, opacity));
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// <summary>
/// Draws the given image together with the current one by blending their pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <typeparam name="TPixelDst">The pixel format of the destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format of the source image.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
}
/// <param name="options">The options containing the blend mode and opacity.</param>
/// <returns>The <see cref="Image{TPixelDst}"/>.</returns>
public static IImageProcessingContext<TPixelDst> DrawImage<TPixelDst, TPixelSrc>(this IImageProcessingContext<TPixelDst> source, Image<TPixelSrc> image, Point location, GraphicsOptions options)
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
=> source.ApplyProcessor(new DrawImageProcessor<TPixelDst, TPixelSrc>(image, location, options.ColorBlendingMode, options.AlphaCompositionMode, options.BlendPercentage));
}
}

53
src/ImageSharp.Drawing/Processing/Processors/Drawing/DrawImageProcessor.cs

@ -15,32 +15,34 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Combines two images together by blending the pixels.
/// </summary>
/// <typeparam name="TPixel">The pixel format.</typeparam>
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst>
where TPixelDst : struct, IPixel<TPixelDst>
where TPixelSrc : struct, IPixel<TPixelSrc>
{
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
public DrawImageProcessor(Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity)
{
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity));
this.Image = image;
this.Opacity = opacity;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode);
this.Location = location;
}
/// <summary>
/// Gets the image to blend
/// </summary>
public Image<TPixel> Image { get; }
public Image<TPixelSrc> Image { get; }
/// <summary>
/// Gets the opacity of the image to blend
@ -50,7 +52,7 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
/// <summary>
/// Gets the pixel blender
/// </summary>
public PixelBlender<TPixel> Blender { get; }
public PixelBlender<TPixelDst> Blender { get; }
/// <summary>
/// Gets the location to draw the blended image
@ -58,10 +60,10 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
public Point Location { get; }
/// <inheritdoc/>
protected override void OnFrameApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration)
{
Image<TPixel> targetImage = this.Image;
PixelBlender<TPixel> blender = this.Blender;
Image<TPixelSrc> targetImage = this.Image;
PixelBlender<TPixelDst> blender = this.Blender;
int locationY = this.Location.Y;
// Align start/end positions.
@ -76,23 +78,18 @@ namespace SixLabors.ImageSharp.Processing.Processors.Drawing
int width = maxX - minX;
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
using (IMemoryOwner<float> amount = memoryAllocator.Allocate<float>(width))
{
amount.GetSpan().Fill(this.Opacity);
ParallelFor.WithConfiguration(
minY,
maxY,
configuration,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend(memoryAllocator, background, background, foreground, amount.GetSpan());
});
}
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator;
ParallelFor.WithConfiguration(
minY,
maxY,
configuration,
y =>
{
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity);
});
}
}
}

8166
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.cs

File diff suppressed because it is too large

44
src/ImageSharp/PixelFormats/PixelBlenders/DefaultPixelBlenders.Generated.tt

@ -12,9 +12,11 @@
// Licensed under the Apache License, Version 2.0.
// <auto-generated />
using System;
using System.Numerics;
using System.Buffers;
using System;
using System.Numerics;
using System.Buffers;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
@ -78,32 +80,28 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
/// <inheritdoc />
public override TPixel Blend(TPixel background, TPixel source, float amount)
{
return PorterDuffFunctions.<#=blender_composer#>(background, source, amount);
TPixel dest = default;
dest.PackFromScaledVector4(PorterDuffFunctions.<#=blender_composer#>(background.ToScaledVector4(), source.ToScaledVector4(), amount.Clamp(0, 1)));
return dest;
}
/// <inheritdoc />
public override void Blend(MemoryAllocator memoryManager, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount)
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount)
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
amount = amount.Clamp(0, 1);
for (int i = 0; i < destination.Length; i++)
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixel>.Instance.ToVector4(source, sourceSpan, destination.Length);
for (int i = 0; i < destination.Length; i++)
{
destinationSpan[i] = PorterDuffFunctions.<#=blender_composer#>(backgroundSpan[i], sourceSpan[i], amount[i]);
}
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount);
}
}
PixelOperations<TPixel>.Instance.PackFromVector4(destinationSpan, destination, destination.Length);
}
/// <inheritdoc />
protected override void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount)
{
for (int i = 0; i < destination.Length; i++)
{
destination[i] = PorterDuffFunctions.<#=blender_composer#>(background[i], source[i], amount[i].Clamp(0, 1));
}
}
}

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

File diff suppressed because it is too large

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

@ -13,6 +13,11 @@
// <auto-generated />
<#
// Note use of MethodImplOptions.NoInlining. We have tests that are failing on certain architectures when
// AggresiveInlining is used. Confirmed on Intel i7-6600U in 64bit.
#>
using System;
using System.Numerics;
using System.Runtime.CompilerServices;
@ -24,107 +29,96 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
<# void GeneratePixelBlenders(string blender) { #>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Src(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return source;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcAtop(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Atop(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOver(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Over(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcIn(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return In(backdrop, source, <#=blender#>(backdrop, source));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>SrcOut(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Out(backdrop, source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Dest(Vector4 backdrop, Vector4 source, float opacity)
{
return backdrop;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestAtop(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Atop(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOver(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Over(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestIn(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return In(source, backdrop, <#=blender#>(source, backdrop));
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>DestOut(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Out(source, backdrop);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Xor(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Xor(backdrop, source);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[MethodImpl(MethodImplOptions.NoInlining)]
public static Vector4 <#=blender#>Clear(Vector4 backdrop, Vector4 source, float opacity)
{
opacity = opacity.Clamp(0, 1);
source.W *= opacity;
source.W *= opacity;
return Clear(backdrop, source);
}
@ -137,8 +131,9 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
public static TPixel <#=blender#><#=composer#><TPixel>(TPixel backdrop, TPixel source, float opacity)
where TPixel : struct, IPixel<TPixel>
{
opacity = opacity.Clamp(0, 1);
TPixel dest = default;
dest.PackFromVector4(<#=blender#><#=composer#>(backdrop.ToVector4(),source.ToVector4(),opacity));
dest.PackFromScaledVector4(<#=blender#><#=composer#>(backdrop.ToScaledVector4(), source.ToScaledVector4(), opacity));
return dest;
}
@ -175,7 +170,7 @@ string[] blenders = new []{
foreach(var blender in blenders)
{
GeneratePixelBlenders(blender);
GeneratePixelBlenders(blender);
foreach(var composer in composers)
{

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

@ -8,7 +8,7 @@ using System.Runtime.CompilerServices;
namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
{
/// <summary>
/// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model.
/// Collection of Porter Duff Color Blending and Alpha Composition Functions.
/// </summary>
/// <remarks>
/// These functions are designed to be a general solution for all color cases,
@ -148,31 +148,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
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
@ -193,6 +169,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Atop(Vector4 dst, Vector4 src, Vector4 blend)
{
// calculate weights
@ -212,6 +189,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 In(Vector4 dst, Vector4 src, Vector4 blend)
{
float alpha = dst.W * src.W;
@ -223,6 +201,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Out(Vector4 dst, Vector4 src)
{
float alpha = (1 - dst.W) * src.W;
@ -234,6 +213,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static Vector4 Xor(Vector4 dst, Vector4 src)
{
float srcW = 1 - dst.W;
@ -249,6 +229,7 @@ namespace SixLabors.ImageSharp.PixelFormats.PixelBlenders
return color;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static Vector4 Clear(Vector4 backdrop, Vector4 source)
{
return Vector4.Zero;

120
src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs

@ -2,6 +2,9 @@
// Licensed under the Apache License, Version 2.0.
using System;
using System.Buffers;
using System.Numerics;
using SixLabors.ImageSharp.Memory;
using SixLabors.Memory;
namespace SixLabors.ImageSharp.PixelFormats
@ -23,19 +26,114 @@ namespace SixLabors.ImageSharp.PixelFormats
/// At amount = 0, "from" is returned, at amount = 1, "to" is returned.
/// </param>
/// <returns>The final pixel value after composition</returns>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 pixels together.
/// </summary>
/// <param name="memoryAllocator">The <see cref="MemoryAllocator"/></param>
/// <param name="destination">The destination span.</param>
/// <param name="background">The background span.</param>
/// <param name="source">The source span.</param>
public abstract TPixel Blend(TPixel background, TPixel source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</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>
public abstract void Blend(MemoryAllocator memoryAllocator, Span<TPixel> destination, Span<TPixel> background, Span<TPixel> source, Span<float> amount);
/// </param>
protected abstract void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, float amount);
/// <summary>
/// Blend 2 rows together.
/// </summary>
/// <param name="destination">destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values 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>
protected abstract void BlendFunction(Span<Vector4> destination, ReadOnlySpan<Vector4> background, ReadOnlySpan<Vector4> source, ReadOnlySpan<float> amount);
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values 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>
public void Blend(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixel> source, ReadOnlySpan<float> amount)
{
this.Blend<TPixel>(memoryManager, destination, background, source, amount);
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</param>
/// <param name="amount">
/// A span with values 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>
public void Blend<TPixelSrc>(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixelSrc> source, ReadOnlySpan<float> amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeGreaterThanOrEqualTo(amount.Length, destination.Length, nameof(amount.Length));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(source, sourceSpan, destination.Length);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length);
}
}
/// <summary>
/// Blends 2 rows together
/// </summary>
/// <typeparam name="TPixelSrc">the pixel format of the source span</typeparam>
/// <param name="memoryManager">memory manager to use internally</param>
/// <param name="destination">the destination span</param>
/// <param name="background">the background span</param>
/// <param name="source">the source span</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>
public void Blend<TPixelSrc>(MemoryAllocator memoryManager, Span<TPixel> destination, ReadOnlySpan<TPixel> background, ReadOnlySpan<TPixelSrc> source, float amount)
where TPixelSrc : struct, IPixel<TPixelSrc>
{
Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length));
Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length));
Guard.MustBeBetweenOrEqualTo(amount, 0, 1, nameof(amount));
using (IMemoryOwner<Vector4> buffer = memoryManager.Allocate<Vector4>(destination.Length * 3))
{
Span<Vector4> destinationSpan = buffer.Slice(0, destination.Length);
Span<Vector4> backgroundSpan = buffer.Slice(destination.Length, destination.Length);
Span<Vector4> sourceSpan = buffer.Slice(destination.Length * 2, destination.Length);
PixelOperations<TPixel>.Instance.ToScaledVector4(background, backgroundSpan, destination.Length);
PixelOperations<TPixelSrc>.Instance.ToScaledVector4(source, sourceSpan, destination.Length);
this.BlendFunction(destinationSpan, backgroundSpan, sourceSpan, amount);
PixelOperations<TPixel>.Instance.PackFromScaledVector4(destinationSpan, destination, destination.Length);
}
}
}
}

58
tests/ImageSharp.Tests/PixelFormats/PixelOperationsTests.Blender.cs

@ -34,7 +34,7 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.AddSrcOver), PixelColorBlendingMode.Add },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.SubtractSrcOver), PixelColorBlendingMode.Subtract },
{ new TestPixel<RgbaVector>(), typeof(DefaultPixelBlenders<RgbaVector>.MultiplySrcOver), PixelColorBlendingMode.Multiply },
};
};
[Theory]
[MemberData(nameof(BlenderMappings))]
@ -43,6 +43,62 @@ namespace SixLabors.ImageSharp.Tests.PixelFormats
{
PixelBlender<TPixel> blender = PixelOperations<TPixel>.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver);
Assert.IsType(type, blender);
}
public static TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32> ColorBlendingExpectedResults = new TheoryData<Rgba32, Rgba32, float, PixelColorBlendingMode, Rgba32>()
{
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Normal, Rgba32.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Screen, new Rgba32(0xFFEEE7FF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.HardLight, new Rgba32(0xFFC62D32) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Overlay, new Rgba32(0xFFDDCEFF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Darken, new Rgba32(0xFF701919) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Lighten, new Rgba32(0xFFE1E4FF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Add, new Rgba32(0xFFFFFDFF) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Subtract, new Rgba32(0xFF71CBE6) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelColorBlendingMode.Multiply, new Rgba32(0xFF631619) },
};
[Theory]
[MemberData(nameof(ColorBlendingExpectedResults))]
public void TestColorBlendingModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelColorBlendingMode mode, Rgba32 expectedResult)
{
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(mode, PixelAlphaCompositionMode.SrcOver);
Rgba32 actualResult = blender.Blend(backdrop, source, opacity);
// var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults
Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4());
}
public static TheoryData<Rgba32, Rgba32, float, PixelAlphaCompositionMode, Rgba32> AlphaCompositionExpectedResults = new TheoryData<Rgba32, Rgba32, float, PixelAlphaCompositionMode, Rgba32>()
{
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Clear, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Xor, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Dest, Rgba32.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestAtop, Rgba32.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestIn, Rgba32.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOut, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.DestOver, Rgba32.MistyRose },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.Src, Rgba32.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcAtop, Rgba32.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcIn, Rgba32.MidnightBlue },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOut, new Rgba32(0) },
{ Rgba32.MistyRose, Rgba32.MidnightBlue, 1, PixelAlphaCompositionMode.SrcOver, Rgba32.MidnightBlue },
};
[Theory]
[MemberData(nameof(AlphaCompositionExpectedResults))]
public void TestAlphaCompositionModes(Rgba32 backdrop, Rgba32 source, float opacity, PixelAlphaCompositionMode mode, Rgba32 expectedResult)
{
PixelBlender<Rgba32> blender = PixelOperations<Rgba32>.Instance.GetPixelBlender(PixelColorBlendingMode.Normal, mode);
Rgba32 actualResult = blender.Blend(backdrop, source, opacity);
// var str = actualResult.Rgba.ToString("X8"); // used to extract expectedResults
Assert.Equal(actualResult.ToVector4(), expectedResult.ToVector4());
}
}
}

Loading…
Cancel
Save