From a3a69824dd8c6ac44847794ad219a97fede7db98 Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Fri, 28 Apr 2017 21:13:29 +0200 Subject: [PATCH 01/28] Added Pixel effect mode enumeration for Porter-Duff composition modes --- .../PixelFormats/PixelTransformMode.cs | 58 ++++ .../PixelTransformModeExtensions.cs | 56 ++++ .../PixelFormats/PorterDuffFunctions.cs | 267 ++++++++++++++++++ 3 files changed, 381 insertions(+) create mode 100644 src/ImageSharp/PixelFormats/PixelTransformMode.cs create mode 100644 src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs create mode 100644 src/ImageSharp/PixelFormats/PorterDuffFunctions.cs diff --git a/src/ImageSharp/PixelFormats/PixelTransformMode.cs b/src/ImageSharp/PixelFormats/PixelTransformMode.cs new file mode 100644 index 0000000000..7ac7eb0299 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelTransformMode.cs @@ -0,0 +1,58 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + /// + /// Porter Duff Blending composition modes + /// + public enum PixelTransformMode + { + /// + /// Default blending mode, also known as "Normal" or "Alpha Blending" + /// + Normal, + + /// + /// Backdrop + Source + /// + Multiply, + + /// + /// Backdrop + Source + /// + Add, + + /// + /// Backdrop - Source + /// + Substract, + + /// + /// Screen effect + /// + Screen, + + /// + /// Darken effect + /// + Darken, + + /// + /// Lighten effect + /// + Lighten, + + /// + /// Overlay effect + /// + Overlay, + + /// + /// Hard light effect + /// + HardLight + } +} diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs new file mode 100644 index 0000000000..1992244620 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Collections.Generic; + using System.Text; + using PixelFormats; + + /// + /// Extensions to retrieve the appropiate pixel transformation functions for + /// + public static class PixelTransformModeExtensions + { + /// + /// Gets a pixel transformation function + /// + /// The pixel format used for both Backdrop and Source + /// The Duff Porter mode + /// A function that transforms a Backdrop and Source colors into a final color + public static Func GetPixelFunction(this PixelTransformMode mode) + where TPixel : IPixel + { + return mode.GetPixelFunction(); + } + + /// + /// Gets a pixel transformation function + /// + /// The pixel format used for Backdrop and Output + /// The pixel format used for Source + /// The Duff Porter mode + /// A function that transforms a Backdrop and Source colors into a final color + public static Func GetPixelFunction(this PixelTransformMode mode) + where TBckPixel : IPixel + where TSrcPixel : IPixel + { + switch (mode) + { + case PixelTransformMode.Normal: return PorterDuffFunctions.NormalBlendFunction; + case PixelTransformMode.Multiply: return PorterDuffFunctions.MultiplyFunction; + case PixelTransformMode.Add: return PorterDuffFunctions.AddFunction; + case PixelTransformMode.Screen: return PorterDuffFunctions.ScreenFunction; + case PixelTransformMode.Darken: return PorterDuffFunctions.DarkenFunction; + case PixelTransformMode.Lighten: return PorterDuffFunctions.LightenFunction; + case PixelTransformMode.Overlay: return PorterDuffFunctions.OverlayFunction; + case PixelTransformMode.HardLight: return PorterDuffFunctions.HardLightFunction; + + default: throw new NotImplementedException(nameof(mode)); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs new file mode 100644 index 0000000000..0e9b985e81 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs @@ -0,0 +1,267 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions + /// + /// Backdrop Pixel Format + /// Source Pixel Format + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class PorterDuffFunctions + where TBckPixel : IPixel + where TsrcPixel : IPixel + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel NormalBlendFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, l); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel MultiplyFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, b * l); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel AddFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Min(Vector4.One, b + l)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel SubstractFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Max(Vector4.Zero, b - l)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel ScreenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l))); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel DarkenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Min(b, l)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel LightenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + return Compose(b, l, Vector4.Max(b, l)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel OverlayFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + float cr = OverlayValueFunction(b.X, l.X); + float cg = OverlayValueFunction(b.Y, l.Y); + float cb = OverlayValueFunction(b.Z, l.Z); + + return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + public static TBckPixel HardLightFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + { + Vector4 l = source.ToVector4(); + l.W *= opacity; + if (l.W == 0) + { + return backdrop; + } + + Vector4 b = backdrop.ToVector4(); + + float cr = OverlayValueFunction(l.X, b.X); + float cg = OverlayValueFunction(l.Y, b.Y); + float cb = OverlayValueFunction(l.Z, b.Z); + + return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + } + + /// + /// General composition function for all modes, with a general solution for alpha channel + /// + /// Original backgrop color + /// Original source color + /// Desired transformed color, without taking Alpha channel in account + /// The final color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TBckPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) + { + System.Diagnostics.Debug.Assert(source.W > 0, nameof(source) + " Alpha must be non zero"); + + // 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)) / a; + xform.W = a; + + TBckPixel packed = default(TBckPixel); + packed.PackFromVector4(xform); + + return packed; + } + } +} \ No newline at end of file From bbaa8898e5fa26b03438ab683d0cbfe5007d4c0e Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Fri, 28 Apr 2017 21:14:13 +0200 Subject: [PATCH 02/28] Added Image drawing functions to use general porter duff modes --- src/ImageSharp.Drawing/DrawImage.cs | 50 ++++++++- .../Processors/DrawImageEffectProcessor.cs | 102 ++++++++++++++++++ 2 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 6a4f49337c..a7f2bf7011 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -4,7 +4,8 @@ // namespace ImageSharp -{ +{ + using System; using Drawing.Processors; using ImageSharp.PixelFormats; @@ -52,6 +53,53 @@ namespace ImageSharp source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); return source; + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// Pixel function effect to apply on every pixel + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, PixelTransformMode mode, int percent, Size size, Point location) + where TPixel : struct, IPixel + { + Func pixelFunc = mode.GetPixelFunction(); + + return DrawImage(source, image, pixelFunc, percent, size, location); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// Pixel function effect to apply on every pixel + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, Func pixelFunc, int percent, Size size, Point location) + where TPixel : struct, IPixel + { + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + source.ApplyProcessor(new DrawImageEffectProcessor(image, size, location, pixelFunc, percent), source.Bounds); + return source; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs new file mode 100644 index 0000000000..4b57be53ec --- /dev/null +++ b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs @@ -0,0 +1,102 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format. + internal class DrawImageEffectProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The image to blend with the currently processing image. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// Pixel function effect to apply on every pixel + /// The opacity of the image to blend. Between 0 and 100. + public DrawImageEffectProcessor(Image image, Size size, Point location, Func pixelFunction, int alpha = 100) + { + Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + this.Image = image; + this.PixelFunction = pixelFunction; + this.Size = size; + this.Location = location; + this.Alpha = alpha; + } + + /// + /// Gets the image to blend. + /// + public Image Image { get; private set; } + + /// + /// Gets The function effect to apply on a per pixel basis + /// + public Func PixelFunction { get; private set; } + + /// + /// Gets the alpha percentage value. + /// + public int Alpha { get; } + + /// + /// Gets the size to draw the blended image. + /// + public Size Size { get; } + + /// + /// Gets the location to draw the blended image. + /// + public Point Location { get; } + + /// + protected override void OnApply(ImageBase target, Rectangle sourceRectangle) + { + if (this.Image.Bounds.Size != this.Size) + { + // should Resize be moved to core? + this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); + } + + // 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, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + + float alpha = this.Alpha / 100F; + + using (PixelAccessor sourcePixels = this.Image.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + for (int x = minX; x < maxX; x++) + { + TPixel targetColor = targetPixels[x, y]; + TPixel sourceColor = sourcePixels[x - minX, y - minY]; + + targetPixels[x, y] = this.PixelFunction(targetColor, sourceColor, alpha); + } + }); + } + } + } +} \ No newline at end of file From c442cb445aedab0d23af2a5ecc0b10132e983a2d Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Fri, 28 Apr 2017 22:28:46 +0200 Subject: [PATCH 03/28] fixed stylecop --- src/ImageSharp.Drawing/DrawImage.cs | 188 +++++++++--------- .../Processors/DrawImageEffectProcessor.cs | 174 ++++++++-------- 2 files changed, 181 insertions(+), 181 deletions(-) diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index a7f2bf7011..52c275595b 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -1,105 +1,105 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp { using System; - using Drawing.Processors; - using ImageSharp.PixelFormats; - - /// - /// Extension methods for the type. - /// - public static partial class ImageExtensions - { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The . - public static Image Blend(this Image source, Image image, int percent = 50) - where TPixel : struct, IPixel - { - return DrawImage(source, image, percent, default(Size), default(Point)); - } - - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) - where TPixel : struct, IPixel - { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); - return source; + using Drawing.Processors; + using ImageSharp.PixelFormats; + + /// + /// Extension methods for the type. + /// + public static partial class ImageExtensions + { + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The . + public static Image Blend(this Image source, Image image, int percent = 50) + where TPixel : struct, IPixel + { + return DrawImage(source, image, percent, default(Size), default(Point)); } - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. /// The image to blend with the currently processing image. - /// Pixel function effect to apply on every pixel - /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, PixelTransformMode mode, int percent, Size size, Point location) - where TPixel : struct, IPixel - { + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) + where TPixel : struct, IPixel + { + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); + return source; + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// Pixel function effect to apply on every pixel + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, PixelTransformMode mode, int percent, Size size, Point location) + where TPixel : struct, IPixel + { Func pixelFunc = mode.GetPixelFunction(); - return DrawImage(source, image, pixelFunc, percent, size, location); + return DrawImage(source, image, pixelFunc, percent, size, location); } - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The image this method extends. + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. /// The image to blend with the currently processing image. - /// Pixel function effect to apply on every pixel - /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// The . - public static Image DrawImage(this Image source, Image image, Func pixelFunc, int percent, Size size, Point location) - where TPixel : struct, IPixel - { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } - - source.ApplyProcessor(new DrawImageEffectProcessor(image, size, location, pixelFunc, percent), source.Bounds); - return source; - } - } + /// Pixel function effect to apply on every pixel + /// The pixel format. + /// The opacity of the image image to blend. Must be between 0 and 100. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, Func pixelFunc, int percent, Size size, Point location) + where TPixel : struct, IPixel + { + if (size == default(Size)) + { + size = new Size(image.Width, image.Height); + } + + if (location == default(Point)) + { + location = Point.Empty; + } + + source.ApplyProcessor(new DrawImageEffectProcessor(image, size, location, pixelFunc, percent), source.Bounds); + return source; + } + } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs index 4b57be53ec..4ef8800f92 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs @@ -1,102 +1,102 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - using ImageSharp.PixelFormats; - using ImageSharp.Processing; - - /// - /// Combines two images together by blending the pixels. - /// - /// The pixel format. - internal class DrawImageEffectProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The image to blend with the currently processing image. - /// The size to draw the blended image. +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Drawing.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + using ImageSharp.PixelFormats; + using ImageSharp.Processing; + + /// + /// Combines two images together by blending the pixels. + /// + /// The pixel format. + internal class DrawImageEffectProcessor : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The image to blend with the currently processing image. + /// The size to draw the blended image. /// The location to draw the blended image. /// Pixel function effect to apply on every pixel - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageEffectProcessor(Image image, Size size, Point location, Func pixelFunction, int alpha = 100) + /// The opacity of the image to blend. Between 0 and 100. + public DrawImageEffectProcessor(Image image, Size size, Point location, Func pixelFunction, int alpha = 100) { - Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); this.Image = image; - this.PixelFunction = pixelFunction; - this.Size = size; + this.PixelFunction = pixelFunction; + this.Size = size; this.Location = location; - this.Alpha = alpha; - } - - /// - /// Gets the image to blend. - /// + this.Alpha = alpha; + } + + /// + /// Gets the image to blend. + /// public Image Image { get; private set; } /// /// Gets The function effect to apply on a per pixel basis /// - public Func PixelFunction { get; private set; } - - /// - /// Gets the alpha percentage value. - /// - public int Alpha { get; } - - /// - /// Gets the size to draw the blended image. - /// - public Size Size { get; } - - /// - /// Gets the location to draw the blended image. - /// - public Point Location { get; } - - /// - protected override void OnApply(ImageBase target, Rectangle sourceRectangle) - { - if (this.Image.Bounds.Size != this.Size) - { - // should Resize be moved to core? - this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); - } - - // 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, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - - float alpha = this.Alpha / 100F; - - using (PixelAccessor sourcePixels = this.Image.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + public Func PixelFunction { get; private set; } + + /// + /// Gets the alpha percentage value. + /// + public int Alpha { get; } + + /// + /// Gets the size to draw the blended image. + /// + public Size Size { get; } + + /// + /// Gets the location to draw the blended image. + /// + public Point Location { get; } + + /// + protected override void OnApply(ImageBase target, Rectangle sourceRectangle) + { + if (this.Image.Bounds.Size != this.Size) + { + // should Resize be moved to core? + this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); + } + + // 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, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + + float alpha = this.Alpha / 100F; + + using (PixelAccessor sourcePixels = this.Image.Lock()) + using (PixelAccessor targetPixels = target.Lock()) + { + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + for (int x = minX; x < maxX; x++) { TPixel targetColor = targetPixels[x, y]; TPixel sourceColor = sourcePixels[x - minX, y - minY]; - targetPixels[x, y] = this.PixelFunction(targetColor, sourceColor, alpha); - } - }); - } - } - } + targetPixels[x, y] = this.PixelFunction(targetColor, sourceColor, alpha); + } + }); + } + } + } } \ No newline at end of file From 3b100bb979d54c98bac234787a989bf282ee37b0 Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Fri, 28 Apr 2017 22:29:09 +0200 Subject: [PATCH 04/28] fixed headers --- src/ImageSharp/PixelFormats/PixelTransformMode.cs | 2 +- src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs | 2 +- src/ImageSharp/PixelFormats/PorterDuffFunctions.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ImageSharp/PixelFormats/PixelTransformMode.cs b/src/ImageSharp/PixelFormats/PixelTransformMode.cs index 7ac7eb0299..159b275158 100644 --- a/src/ImageSharp/PixelFormats/PixelTransformMode.cs +++ b/src/ImageSharp/PixelFormats/PixelTransformMode.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs index 1992244620..50cda63827 100644 --- a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs +++ b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs index 0e9b985e81..f3717f8f8f 100644 --- a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // From 106e93626f5f6413e0b3a823c391c89068c0a472 Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Fri, 28 Apr 2017 23:36:52 +0200 Subject: [PATCH 05/28] Replaced assert with DebugGuard --- src/ImageSharp/PixelFormats/PorterDuffFunctions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs index f3717f8f8f..1c9f6b8425 100644 --- a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs @@ -244,7 +244,7 @@ namespace ImageSharp.PixelFormats [MethodImpl(MethodImplOptions.AggressiveInlining)] private static TBckPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) { - System.Diagnostics.Debug.Assert(source.W > 0, nameof(source) + " Alpha must be non zero"); + DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); // calculate weights float xw = backdrop.W * source.W; From 8742a3ccff7f739d7df16ab72376ad9b009df751 Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Sat, 29 Apr 2017 00:24:59 +0200 Subject: [PATCH 06/28] Added missing function --- src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs index 50cda63827..4ecddfded0 100644 --- a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs +++ b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs @@ -43,6 +43,7 @@ namespace ImageSharp case PixelTransformMode.Normal: return PorterDuffFunctions.NormalBlendFunction; case PixelTransformMode.Multiply: return PorterDuffFunctions.MultiplyFunction; case PixelTransformMode.Add: return PorterDuffFunctions.AddFunction; + case PixelTransformMode.Substract: return PorterDuffFunctions.SubstractFunction; case PixelTransformMode.Screen: return PorterDuffFunctions.ScreenFunction; case PixelTransformMode.Darken: return PorterDuffFunctions.DarkenFunction; case PixelTransformMode.Lighten: return PorterDuffFunctions.LightenFunction; From 3a542eec74dd3f4bfa3b1a63018db6c98bccc31a Mon Sep 17 00:00:00 2001 From: Vicente Penades Date: Sat, 29 Apr 2017 00:25:28 +0200 Subject: [PATCH 07/28] Added a DrawImage Effect test --- .../Drawing/DrawImageEffectTest.cs | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs new file mode 100644 index 0000000000..78f0a08702 --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.IO; + using ImageSharp.PixelFormats; + using Xunit; + + public class DrawImageEffectTest : FileTestBase + { + [Fact] + public void ImageShouldApplyDrawImageFilter() + { + string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect"); + + PixelTransformMode[] modes = (PixelTransformMode[])System.Enum.GetValues(typeof(PixelTransformMode)); + + using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) + { + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) + { + foreach (PixelTransformMode mode in modes) + { + using (FileStream output = File.OpenWrite($"{path}/{mode}.{file.FileName}")) + { + Size size = new Size(image.Width / 2, image.Height / 2); + Point loc = new Point(image.Width / 4, image.Height / 4); + + image.DrawImage(blend, mode, 75, size, loc).Save(output); + } + } + } + } + } + } + } +} \ No newline at end of file From 0d8eb2032c3009126373af983ed6a5f6978fae91 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 29 Apr 2017 23:01:51 +0100 Subject: [PATCH 08/28] initial blender implementations --- src/ImageSharp.Drawing/Brushes/IBrush.cs | 3 +- .../Brushes/ImageBrush{TPixel}.cs | 66 ++++----- .../Brushes/PatternBrush{TPixel}.cs | 43 +++--- .../Brushes/Processors/BrushApplicator.cs | 53 +++---- .../Brushes/RecolorBrush{TPixel}.cs | 55 +++----- .../Brushes/SolidBrush{TPixel}.cs | 83 ++++++----- src/ImageSharp.Drawing/FillRegion.cs | 16 ++- src/ImageSharp.Drawing/GraphicsOptions.cs | 56 ++++++-- src/ImageSharp.Drawing/Pens/IPen.cs | 3 +- src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs | 15 +- .../Processors/DrawPathProcessor.cs | 2 +- .../Processors/FillProcessor.cs | 7 +- .../Processors/FillRegionProcessor.cs | 133 +++++++++--------- src/ImageSharp/Formats/Png/PngDecoderCore.cs | 4 +- src/ImageSharp/Image/PixelAccessor{TPixel}.cs | 2 +- src/ImageSharp/PixelFormats/Alpha8.cs | 2 +- src/ImageSharp/PixelFormats/Argb32.cs | 2 +- src/ImageSharp/PixelFormats/Bgr565.cs | 2 +- src/ImageSharp/PixelFormats/Bgra4444.cs | 2 +- src/ImageSharp/PixelFormats/Bgra5551.cs | 2 +- src/ImageSharp/PixelFormats/Byte4.cs | 2 +- src/ImageSharp/PixelFormats/HalfSingle.cs | 2 +- src/ImageSharp/PixelFormats/HalfVector2.cs | 2 +- src/ImageSharp/PixelFormats/HalfVector4.cs | 2 +- src/ImageSharp/PixelFormats/IPixel.cs | 8 +- .../PixelFormats/NormalizedByte2.cs | 2 +- .../PixelFormats/NormalizedByte4.cs | 2 +- .../PixelFormats/NormalizedShort2.cs | 2 +- .../PixelFormats/NormalizedShort4.cs | 2 +- .../PixelFormats/PixelBlenderMode.cs | 83 +++++++++++ .../DefaultBurnPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultDarkenPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultDifferencePixelBlender{TPixel}.cs | 42 ++++++ .../DefaultDodgePixelBlender{TPixel}.cs | 42 ++++++ .../DefaultExclusionPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultHardLightPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultLightenPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultMultiplyPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultNormalPixelBlender{TPixel}.cs | 37 +++++ .../DefaultOverlayPixelBlender{TPixel}.cs | 42 ++++++ ...ltPremultipliedLerpPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultScreenPixelBlender{TPixel}.cs | 42 ++++++ .../DefaultSoftLightPixelBlender{TPixel}.cs | 42 ++++++ .../PixelFormats/PixelBlender{TPixel}.cs | 39 +++++ .../PixelOperations{TPixel}.PixelBenders.cs | 125 ++++++++++++++++ ...{TPixel}.cs => PixelOperations{TPixel}.cs} | 20 +-- src/ImageSharp/PixelFormats/Rg32.cs | 2 +- src/ImageSharp/PixelFormats/Rgba1010102.cs | 2 +- ...perations.cs => Rgba32.PixelOperations.cs} | 6 +- src/ImageSharp/PixelFormats/Rgba32.cs | 2 +- src/ImageSharp/PixelFormats/Rgba64.cs | 2 +- ...tions.cs => RgbaVector.PixelOperations.cs} | 4 +- src/ImageSharp/PixelFormats/RgbaVector.cs | 2 +- src/ImageSharp/PixelFormats/Short2.cs | 2 +- src/ImageSharp/PixelFormats/Short4.cs | 2 +- .../PixelFormats/Vector4BlendTransforms.cs | 2 +- .../ColorMatrix/PolaroidProcessor.cs | 2 +- .../Processors/Overlays/GlowProcessor.cs | 40 +++--- .../Overlays/GlowProcessorParallel.cs | 100 +++++++++++++ .../Processors/Transforms/ResizeProcessor.cs | 2 +- .../Color/Bulk/PackFromXyzw.cs | 4 +- .../Color/Bulk/ToVector4.cs | 4 +- .../ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs | 4 +- .../Color/Bulk/ToXyzw.cs | 4 +- tests/ImageSharp.Benchmarks/Samplers/Glow.cs | 48 +++++++ ...ationsTests.cs => PixelOperationsTests.cs} | 12 +- .../ImageSharp.Tests/ImageSharp.Tests.csproj | 6 + .../PixelFormats/PixelOperations.cs | 53 +++++++ .../Processors/Filters/GlowTest.cs | 2 +- .../TestUtilities/TestPixel.cs | 62 ++++++++ 70 files changed, 1430 insertions(+), 322 deletions(-) create mode 100644 src/ImageSharp/PixelFormats/PixelBlenderMode.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs create mode 100644 src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs rename src/ImageSharp/PixelFormats/{BulkPixelOperations{TPixel}.cs => PixelOperations{TPixel}.cs} (91%) rename src/ImageSharp/PixelFormats/{Rgba32.BulkOperations.cs => Rgba32.PixelOperations.cs} (97%) rename src/ImageSharp/PixelFormats/{RgbaVector.BulkOperations.cs => RgbaVector.PixelOperations.cs} (81%) create mode 100644 src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs create mode 100644 tests/ImageSharp.Benchmarks/Samplers/Glow.cs rename tests/ImageSharp.Tests/Colors/{BulkPixelOperationsTests.cs => PixelOperationsTests.cs} (96%) create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/TestPixel.cs diff --git a/src/ImageSharp.Drawing/Brushes/IBrush.cs b/src/ImageSharp.Drawing/Brushes/IBrush.cs index e16f220288..9534c7a882 100644 --- a/src/ImageSharp.Drawing/Brushes/IBrush.cs +++ b/src/ImageSharp.Drawing/Brushes/IBrush.cs @@ -24,6 +24,7 @@ namespace ImageSharp.Drawing /// /// The pixel source. /// The region the brush will be applied to. + /// The graphic options /// /// The brush applicator for this brush /// @@ -31,6 +32,6 @@ namespace ImageSharp.Drawing /// The when being applied to things like shapes would usually be the /// bounding box of the shape not necessarily the bounds of the whole image /// - BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region); + BrushApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 3e2da040fd..5b1409fe2d 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -31,9 +31,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new ImageBrushApplicator(sourcePixels, this.image, region); + return new ImageBrushApplicator(sourcePixels, this.image, region, options); } /// @@ -57,9 +57,14 @@ namespace ImageSharp.Drawing.Brushes private readonly int xLength; /// - /// The offset. + /// The Y offset. /// - private readonly Vector2 offset; + private readonly int offsetY; + + /// + /// The X offset. + /// + private readonly int offsetX; /// /// Initializes a new instance of the class. @@ -70,16 +75,18 @@ namespace ImageSharp.Drawing.Brushes /// /// The region. /// + /// The options /// /// The sourcePixels. /// - public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region) - : base(sourcePixels) + public ImageBrushApplicator(PixelAccessor sourcePixels, IImageBase image, RectangleF region, GraphicsOptions options) + : base(sourcePixels, options) { this.source = image.Lock(); this.xLength = image.Width; this.yLength = image.Height; - this.offset = new Vector2(MathF.Max(MathF.Floor(region.Top), 0), MathF.Max(MathF.Floor(region.Left), 0)); + this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); + this.offsetX = (int)MathF.Max(MathF.Floor(region.Left), 0); } /// @@ -94,13 +101,8 @@ namespace ImageSharp.Drawing.Brushes { get { - Vector2 point = new Vector2(x, y); - - // Offset the requested pixel by the value in the rectangle (the shapes position) - point = point - this.offset; - int srcX = (int)point.X % this.xLength; - int srcY = (int)point.Y % this.yLength; - + int srcX = (x - this.offsetX) % this.xLength; + int srcY = (y - this.offsetY) % this.yLength; return this.source[srcX, srcY]; } } @@ -112,33 +114,27 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + // create a span for colors + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); + int sourceY = (y - this.offsetY) % this.yLength; + int offsetX = x - this.offsetX; + BufferSpan sourceRow = this.source.GetRowSpan(sourceY); - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = this[targetX, targetY].ToVector4(); + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + int sourceX = (i + offsetX) % this.xLength; + TPixel pixel = sourceRow[sourceX]; + overlay[i] = pixel; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index ad37f4d289..4aebe00fb6 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -90,9 +90,9 @@ namespace ImageSharp.Drawing.Brushes } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector); + return new PatternBrushApplicator(sourcePixels, this.pattern, this.patternVector, options); } /// @@ -112,8 +112,9 @@ namespace ImageSharp.Drawing.Brushes /// The sourcePixels. /// The pattern. /// The patternVector. - public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector) - : base(sourcePixels) + /// The options + public PatternBrushApplicator(PixelAccessor sourcePixels, Fast2DArray pattern, Fast2DArray patternVector, GraphicsOptions options) + : base(sourcePixels, options) { this.pattern = pattern; this.patternVector = patternVector; @@ -146,34 +147,22 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + int patternY = y % this.pattern.Height; + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; - // 2d array index at row/column - Vector4 sourceVector = this.patternVector[targetY % this.patternVector.Height, targetX % this.patternVector.Width]; - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + int patternX = (x + i) % this.pattern.Width; + overlay[i] = this.pattern[y, x]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 5dd6dad76d..59cb0820a4 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -21,16 +21,31 @@ namespace ImageSharp.Drawing.Processors /// Initializes a new instance of the class. /// /// The target. - internal BrushApplicator(PixelAccessor target) + /// The options. + internal BrushApplicator(PixelAccessor target, GraphicsOptions options) { this.Target = target; + + this.Options = options; + + this.Blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); } + /// + /// Gets the blendder + /// + internal PixelBlender Blender { get; } + /// /// Gets the destinaion /// protected PixelAccessor Target { get; } + /// + /// Gets the blend percentage + /// + protected GraphicsOptions Options { get; private set; } + /// /// Gets the color for a single pixel. /// @@ -45,39 +60,27 @@ namespace ImageSharp.Drawing.Processors /// /// Applies the opactiy weighting for each pixel in a scanline to the target based on the pattern contained in the brush. /// - /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. - /// The number of pixels effected by this scanline. - /// The offset fromthe begining of the opacity data starts. + /// The a collection of opacity values between 0 and 1 to be merged with the brushed color value before being applied to the target. /// The x position in the target pixel space that the start of the scanline data corresponds to. /// The y position in the target pixel space that whole scanline corresponds to. /// scanlineBuffer will be > scanlineWidth but provide and offset in case we want to share a larger buffer across runs. - internal virtual void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal virtual void Apply(BufferSpan scanline, int x, int y) { - DebugGuard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) + if (this.Options.BlendPercentage < 1) { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = this[targetX, targetY].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; } + + overlay[i] = this[x + i, y]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 7c192b2d3c..b8b2499ff7 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -54,9 +54,9 @@ namespace ImageSharp.Drawing.Brushes public TPixel TargeTPixel { get; } /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold); + return new RecolorBrushApplicator(sourcePixels, this.SourceColor, this.TargeTPixel, this.Threshold, options); } /// @@ -86,8 +86,9 @@ namespace ImageSharp.Drawing.Brushes /// Color of the source. /// Color of the target. /// The threshold . - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold) - : base(sourcePixels) + /// The options + public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold, GraphicsOptions options) + : base(sourcePixels, options) { this.sourceColor = sourceColor.ToVector4(); this.targeTPixel = targeTPixel.ToVector4(); @@ -136,42 +137,24 @@ namespace ImageSharp.Drawing.Brushes } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); - - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) + using (Buffer overlay = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - - Vector4 sourceVector = backgroundVector; - float distance = Vector4.DistanceSquared(sourceVector, this.sourceColor); - if (distance <= this.threshold) - { - float lerpAmount = (this.threshold - distance) / this.threshold; - sourceVector = Vector4BlendTransforms.PremultipliedLerp( - sourceVector, - this.targeTPixel, - lerpAmount); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } - } + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + + int offsetX = x + i; + + // no doubt this one can be optermised further but I can't imagine its + // actually being used and can probably be removed/interalised for now + overlay[i] = this[offsetX, y]; } + + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index 634a2b70de..f97266c77d 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -39,34 +39,61 @@ namespace ImageSharp.Drawing.Brushes public TPixel Color => this.color; /// - public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - return new SolidBrushApplicator(sourcePixels, this.color); + if (options.BlendPercentage < 0) + { + return new SolidBrushApplicator(sourcePixels, this.color, options); + } + else + { + return new SolidNoBlendBrushApplicator(sourcePixels, this.color, options); + } } /// /// The solid brush applicator. /// - private class SolidBrushApplicator : BrushApplicator + private class SolidNoBlendBrushApplicator : SolidBrushApplicator { - /// - /// The solid color. - /// - private readonly TPixel color; - private readonly Vector4 colorVector; + public SolidNoBlendBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) + : base(sourcePixels, color, options) + { + } + internal override void Apply(BufferSpan scanline, int x, int y) + { + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); + this.Blender.Compose(destinationRow, destinationRow, this.Colors, scanline); + } + } + + /// + /// The solid brush applicator. + /// + private class SolidBrushApplicator : BrushApplicator + { /// /// Initializes a new instance of the class. /// /// The color. + /// The options /// The sourcePixels. - public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color) - : base(sourcePixels) + public SolidBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) + : base(sourcePixels, options) { - this.color = color; - this.colorVector = color.ToVector4(); + this.Colors = new Buffer(sourcePixels.Width); + for (int i = 0; i < this.Colors.Length; i++) + { + this.Colors[i] = color; + } } + /// + /// Gets the colors. + /// + protected Buffer Colors { get; } + /// /// Gets the color for a single pixel. /// @@ -75,41 +102,27 @@ namespace ImageSharp.Drawing.Brushes /// /// The color /// - internal override TPixel this[int x, int y] => this.color; + internal override TPixel this[int x, int y] => this.Colors[x]; /// public override void Dispose() { - // noop + this.Colors.Dispose(); } /// - internal override void Apply(float[] scanlineBuffer, int scanlineWidth, int offset, int x, int y) + internal override void Apply(BufferSpan scanline, int x, int y) { - Guard.MustBeGreaterThanOrEqualTo(scanlineBuffer.Length, offset + scanlineWidth, nameof(scanlineWidth)); + BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - using (Buffer buffer = new Buffer(scanlineBuffer)) + using (Buffer amountBuffer = new Buffer(scanline.Length)) { - BufferSpan slice = buffer.Slice(offset); - - for (int xPos = 0; xPos < scanlineWidth; xPos++) + for (int i = 0; i < scanline.Length; i++) { - int targetX = xPos + x; - int targetY = y; - - float opacity = slice[xPos]; - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = this.Target[targetX, targetY].ToVector4(); - Vector4 sourceVector = this.colorVector; - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - this.Target[targetX, targetY] = packed; - } + amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; } + + this.Blender.Compose(destinationRow, destinationRow, this.Colors, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/FillRegion.cs b/src/ImageSharp.Drawing/FillRegion.cs index f29c37a67f..b3ee2ed996 100644 --- a/src/ImageSharp.Drawing/FillRegion.cs +++ b/src/ImageSharp.Drawing/FillRegion.cs @@ -15,6 +15,20 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Flood fills the image with the specified brush. + /// + /// The type of the color. + /// The image this method extends. + /// The details how to fill the region of interest. + /// The graphics options. + /// The . + public static Image Fill(this Image source, IBrush brush, GraphicsOptions options) + where TPixel : struct, IPixel + { + return source.Apply(new FillProcessor(brush, options)); + } + /// /// Flood fills the image with the specified brush. /// @@ -25,7 +39,7 @@ namespace ImageSharp public static Image Fill(this Image source, IBrush brush) where TPixel : struct, IPixel { - return source.Apply(new FillProcessor(brush)); + return source.Fill(brush, GraphicsOptions.Default); } /// diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp.Drawing/GraphicsOptions.cs index a21617eadf..2ceb654d3a 100644 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ b/src/ImageSharp.Drawing/GraphicsOptions.cs @@ -5,6 +5,8 @@ namespace ImageSharp.Drawing { + using ImageSharp.PixelFormats; + /// /// Options for influencing the drawing functions. /// @@ -15,24 +17,60 @@ namespace ImageSharp.Drawing /// public static readonly GraphicsOptions Default = new GraphicsOptions(true); + private float? blendPercentage; + + private int? antialiasSubpixelDepth; + + private bool? antialias; + + private PixelBlenderMode blenderMode; + /// - /// Whether antialiasing should be applied. + /// Initializes a new instance of the struct. /// - public bool Antialias; + /// If set to true [enable antialiasing]. + public GraphicsOptions(bool enableAntialiasing) + { + this.blenderMode = PixelBlenderMode.Default; + this.blendPercentage = 1; + this.antialiasSubpixelDepth = 16; + this.antialias = enableAntialiasing; + } /// - /// The number of subpixels to use while rendering with antialiasing enabled. + /// Gets or sets a value indicating whether antialiasing should be applied. /// - public int AntialiasSubpixelDepth; + public bool Antialias + { + get => this.antialias ?? true; + set => this.antialias = value; + } /// - /// Initializes a new instance of the struct. + /// Gets or sets a value indicating the number of subpixels to use while rendering with antialiasing enabled. /// - /// If set to true [enable antialiasing]. - public GraphicsOptions(bool enableAntialiasing) + public int AntialiasSubpixelDepth + { + get => this.antialiasSubpixelDepth ?? 16; + set => this.antialiasSubpixelDepth = value; + } + + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public float BlendPercentage + { + get => this.blendPercentage ?? 1; + set => this.blendPercentage = value; + } + + /// + /// Gets or sets a value indicating the blending percentage to apply to the drawing operation + /// + public PixelBlenderMode BlenderMode { - this.Antialias = enableAntialiasing; - this.AntialiasSubpixelDepth = 16; + get => this.blenderMode; + set => this.blenderMode = value; } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/Pens/IPen.cs b/src/ImageSharp.Drawing/Pens/IPen.cs index 31a609c8e0..d488dbfb0a 100644 --- a/src/ImageSharp.Drawing/Pens/IPen.cs +++ b/src/ImageSharp.Drawing/Pens/IPen.cs @@ -20,12 +20,13 @@ namespace ImageSharp.Drawing.Pens /// /// The pixel source. /// The region the pen will be applied to. + /// The currently active graphic options. /// /// Returns a the applicator for the pen. /// /// /// The when being applied to things like shapes would usually be the bounding box of the shape not necessarily the shape of the whole image. /// - PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region); + PenApplicator CreateApplicator(PixelAccessor pixelSource, RectangleF region, GraphicsOptions options); } } diff --git a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs index f49d03cbca..1da50e0d6c 100644 --- a/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs +++ b/src/ImageSharp.Drawing/Pens/Pen{TPixel}.cs @@ -103,6 +103,7 @@ namespace ImageSharp.Drawing.Pens /// /// The source pixels. /// The region the pen will be applied to. + /// The Graphics options /// /// Returns a the applicator for the pen. /// @@ -110,16 +111,16 @@ namespace ImageSharp.Drawing.Pens /// The when being applied to things like shapes would ussually be the /// bounding box of the shape not necorserrally the shape of the whole image /// - public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region) + public PenApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { if (this.pattern == null || this.pattern.Length < 2) { // if there is only one item in the pattern then 100% of it will // be solid so use the quicker applicator - return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width); + return new SolidPenApplicator(sourcePixels, this.Brush, region, this.Width, options); } - return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern); + return new PatternPenApplicator(sourcePixels, this.Brush, region, this.Width, this.pattern, options); } private class SolidPenApplicator : PenApplicator @@ -127,9 +128,9 @@ namespace ImageSharp.Drawing.Pens private readonly BrushApplicator brush; private readonly float halfWidth; - public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width) + public SolidPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region); + this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; this.RequiredRegion = RectangleF.Outset(region, width); } @@ -170,9 +171,9 @@ namespace ImageSharp.Drawing.Pens private readonly float[] pattern; private readonly float totalLength; - public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern) + public PatternPenApplicator(PixelAccessor sourcePixels, IBrush brush, RectangleF region, float width, float[] pattern, GraphicsOptions options) { - this.brush = brush.CreateApplicator(sourcePixels, region); + this.brush = brush.CreateApplicator(sourcePixels, region, options); this.halfWidth = width / 2; this.totalLength = 0; diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 62e366d2ae..124722cc09 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -55,7 +55,7 @@ namespace ImageSharp.Drawing.Processors protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { using (PixelAccessor sourcePixels = source.Lock()) - using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds)) + using (PenApplicator applicator = this.Pen.CreateApplicator(sourcePixels, this.Path.Bounds, this.Options)) { Rectangle rect = RectangleF.Ceiling(applicator.RequiredRegion); diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index ca2dc99824..0634a06a3e 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -24,14 +24,17 @@ namespace ImageSharp.Drawing.Processors /// The brush. /// private readonly IBrush brush; + private readonly GraphicsOptions options; /// /// Initializes a new instance of the class. /// /// The brush to source pixel colors from. - public FillProcessor(IBrush brush) + /// The options + public FillProcessor(IBrush brush, GraphicsOptions options) { this.brush = brush; + this.options = options; } /// @@ -63,7 +66,7 @@ namespace ImageSharp.Drawing.Processors // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle)) + using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) { Parallel.For( minY, diff --git a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs index af1e6fa895..323214fb8d 100644 --- a/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillRegionProcessor.cs @@ -88,104 +88,105 @@ namespace ImageSharp.Drawing.Processors } using (PixelAccessor sourcePixels = source.Lock()) - using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect)) + using (BrushApplicator applicator = this.Brush.CreateApplicator(sourcePixels, rect, this.Options)) { float[] buffer = arrayPool.Rent(maxIntersections); int scanlineWidth = maxX - minX; - float[] scanline = ArrayPool.Shared.Rent(scanlineWidth); - try + using (Buffer scanline = new Buffer(scanlineWidth)) { - bool scanlineDirty = true; - for (int y = minY; y < maxY; y++) + try { - if (scanlineDirty) + bool scanlineDirty = true; + for (int y = minY; y < maxY; y++) { - // clear the buffer - for (int x = 0; x < scanlineWidth; x++) + if (scanlineDirty) { - scanline[x] = 0; - } - - scanlineDirty = false; - } + // clear the buffer + for (int x = 0; x < scanlineWidth; x++) + { + scanline[x] = 0; + } - float subpixelFraction = 1f / subpixelCount; - float subpixelFractionPoint = subpixelFraction / subpixelCount; - for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) - { - int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); - if (pointsFound == 0) - { - // nothing on this line skip - continue; + scanlineDirty = false; } - QuickSort(buffer, pointsFound); - - for (int point = 0; point < pointsFound; point += 2) + float subpixelFraction = 1f / subpixelCount; + float subpixelFractionPoint = subpixelFraction / subpixelCount; + for (float subPixel = (float)y; subPixel < y + 1; subPixel += subpixelFraction) { - // points will be paired up - float scanStart = buffer[point] - minX; - float scanEnd = buffer[point + 1] - minX; - int startX = (int)MathF.Floor(scanStart); - int endX = (int)MathF.Floor(scanEnd); + int pointsFound = region.Scan(subPixel, buffer, maxIntersections, 0); + if (pointsFound == 0) + { + // nothing on this line skip + continue; + } - if (startX >= 0 && startX < scanline.Length) + QuickSort(buffer, pointsFound); + + for (int point = 0; point < pointsFound; point += 2) { - for (float x = scanStart; x < startX + 1; x += subpixelFraction) + // points will be paired up + float scanStart = buffer[point] - minX; + float scanEnd = buffer[point + 1] - minX; + int startX = (int)MathF.Floor(scanStart); + int endX = (int)MathF.Floor(scanEnd); + + if (startX >= 0 && startX < scanline.Length) { - scanline[startX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = scanStart; x < startX + 1; x += subpixelFraction) + { + scanline[startX] += subpixelFractionPoint; + scanlineDirty = true; + } } - } - if (endX >= 0 && endX < scanline.Length) - { - for (float x = endX; x < scanEnd; x += subpixelFraction) + if (endX >= 0 && endX < scanline.Length) { - scanline[endX] += subpixelFractionPoint; - scanlineDirty = true; + for (float x = endX; x < scanEnd; x += subpixelFraction) + { + scanline[endX] += subpixelFractionPoint; + scanlineDirty = true; + } } - } - int nextX = startX + 1; - endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge - if (nextX >= 0) - { - for (int x = nextX; x < endX; x++) + int nextX = startX + 1; + endX = Math.Min(endX, scanline.Length); // reduce to end to the right edge + if (nextX >= 0) { - scanline[x] += subpixelFraction; - scanlineDirty = true; + for (int x = nextX; x < endX; x++) + { + scanline[x] += subpixelFraction; + scanlineDirty = true; + } } } } - } - if (scanlineDirty) - { - if (!this.Options.Antialias) + if (scanlineDirty) { - for (int x = 0; x < scanlineWidth; x++) + if (!this.Options.Antialias) { - if (scanline[x] > 0.5) - { - scanline[x] = 1; - } - else + for (int x = 0; x < scanlineWidth; x++) { - scanline[x] = 0; + if (scanline[x] > 0.5) + { + scanline[x] = 1; + } + else + { + scanline[x] = 0; + } } } - } - applicator.Apply(scanline, scanlineWidth, 0, minX, y); + applicator.Apply(scanline, minX, y); + } } } - } - finally - { - arrayPool.Return(buffer); - ArrayPool.Shared.Return(scanline); + finally + { + arrayPool.Return(buffer); + } } } } diff --git a/src/ImageSharp/Formats/Png/PngDecoderCore.cs b/src/ImageSharp/Formats/Png/PngDecoderCore.cs index 05ba5ee60c..904aa1ff6e 100644 --- a/src/ImageSharp/Formats/Png/PngDecoderCore.cs +++ b/src/ImageSharp/Formats/Png/PngDecoderCore.cs @@ -623,13 +623,13 @@ namespace ImageSharp.Formats case PngColorType.Rgb: - BulkPixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzBytes(scanlineBuffer, pixelBuffer, this.header.Width); break; case PngColorType.RgbWithAlpha: - BulkPixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); + PixelOperations.Instance.PackFromXyzwBytes(scanlineBuffer, pixelBuffer, this.header.Width); break; } diff --git a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs index 3c67638869..1e46a672e8 100644 --- a/src/ImageSharp/Image/PixelAccessor{TPixel}.cs +++ b/src/ImageSharp/Image/PixelAccessor{TPixel}.cs @@ -116,7 +116,7 @@ namespace ImageSharp /// BufferSpan IBuffer2D.Span => this.pixelBuffer; - private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + private static PixelOperations Operations => PixelOperations.Instance; /// /// Gets or sets the pixel at the specified position. diff --git a/src/ImageSharp/PixelFormats/Alpha8.cs b/src/ImageSharp/PixelFormats/Alpha8.cs index 5e2fff5d02..5a6eebf405 100644 --- a/src/ImageSharp/PixelFormats/Alpha8.cs +++ b/src/ImageSharp/PixelFormats/Alpha8.cs @@ -60,7 +60,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Argb32.cs b/src/ImageSharp/PixelFormats/Argb32.cs index a6db505bbc..2798c4c5ad 100644 --- a/src/ImageSharp/PixelFormats/Argb32.cs +++ b/src/ImageSharp/PixelFormats/Argb32.cs @@ -221,7 +221,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Bgr565.cs b/src/ImageSharp/PixelFormats/Bgr565.cs index a3d8cbcf28..78442a3cef 100644 --- a/src/ImageSharp/PixelFormats/Bgr565.cs +++ b/src/ImageSharp/PixelFormats/Bgr565.cs @@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/Bgra4444.cs b/src/ImageSharp/PixelFormats/Bgra4444.cs index 8d4e9b4f47..9138e25ca5 100644 --- a/src/ImageSharp/PixelFormats/Bgra4444.cs +++ b/src/ImageSharp/PixelFormats/Bgra4444.cs @@ -68,7 +68,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Bgra5551.cs b/src/ImageSharp/PixelFormats/Bgra5551.cs index 8ae1cd4304..9ff571243e 100644 --- a/src/ImageSharp/PixelFormats/Bgra5551.cs +++ b/src/ImageSharp/PixelFormats/Bgra5551.cs @@ -69,7 +69,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Byte4.cs b/src/ImageSharp/PixelFormats/Byte4.cs index acb73dd1c0..b27952ce4d 100644 --- a/src/ImageSharp/PixelFormats/Byte4.cs +++ b/src/ImageSharp/PixelFormats/Byte4.cs @@ -71,7 +71,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/HalfSingle.cs b/src/ImageSharp/PixelFormats/HalfSingle.cs index 893667fc78..2a686f5356 100644 --- a/src/ImageSharp/PixelFormats/HalfSingle.cs +++ b/src/ImageSharp/PixelFormats/HalfSingle.cs @@ -73,7 +73,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/HalfVector2.cs b/src/ImageSharp/PixelFormats/HalfVector2.cs index bd3cda55d0..ef9676b562 100644 --- a/src/ImageSharp/PixelFormats/HalfVector2.cs +++ b/src/ImageSharp/PixelFormats/HalfVector2.cs @@ -83,7 +83,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/HalfVector4.cs b/src/ImageSharp/PixelFormats/HalfVector4.cs index 03e4326b70..8b284509f7 100644 --- a/src/ImageSharp/PixelFormats/HalfVector4.cs +++ b/src/ImageSharp/PixelFormats/HalfVector4.cs @@ -86,7 +86,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/IPixel.cs b/src/ImageSharp/PixelFormats/IPixel.cs index 43fe81e953..9a8d9730a2 100644 --- a/src/ImageSharp/PixelFormats/IPixel.cs +++ b/src/ImageSharp/PixelFormats/IPixel.cs @@ -16,11 +16,11 @@ namespace ImageSharp.PixelFormats where TSelf : struct, IPixel { /// - /// Creates a instance for this pixel type. - /// This method is not intended to be consumed directly. Use instead. + /// Creates a instance for this pixel type. + /// This method is not intended to be consumed directly. Use instead. /// - /// The instance. - BulkPixelOperations CreateBulkOperations(); + /// The instance. + PixelOperations CreateBulkOperations(); } /// diff --git a/src/ImageSharp/PixelFormats/NormalizedByte2.cs b/src/ImageSharp/PixelFormats/NormalizedByte2.cs index dab113ae78..c5b6eff6bb 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte2.cs @@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/NormalizedByte4.cs b/src/ImageSharp/PixelFormats/NormalizedByte4.cs index 0cb5c756b6..9e36766601 100644 --- a/src/ImageSharp/PixelFormats/NormalizedByte4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedByte4.cs @@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/NormalizedShort2.cs b/src/ImageSharp/PixelFormats/NormalizedShort2.cs index 86d80cbad5..01a2d99547 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort2.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort2.cs @@ -88,7 +88,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/NormalizedShort4.cs b/src/ImageSharp/PixelFormats/NormalizedShort4.cs index 8512d41316..3e4535fdf1 100644 --- a/src/ImageSharp/PixelFormats/NormalizedShort4.cs +++ b/src/ImageSharp/PixelFormats/NormalizedShort4.cs @@ -90,7 +90,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs new file mode 100644 index 0000000000..c189fefdcc --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -0,0 +1,83 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using System; + using System.Collections.Generic; + using System.Text; + + /// + /// The various blending modes. + /// + public enum PixelBlenderMode + { + /// + /// The default composition mode. + /// + /// uses PremultipliedLerpTransform + Default = 0, + + /// + /// Normal transform. + /// + Normal, + + /// + /// Multiply Transform. + /// + Multiply, + + /// + /// Screen Transform. + /// + Screen, + + /// + /// HardLight Transform. + /// + HardLight, + + /// + /// Overlay Transform. + /// + Overlay, + + /// + /// Darken Transform. + /// + Darken, + + /// + /// Lighten Transform. + /// + Lighten, + + /// + /// SoftLight Transform. + /// + SoftLight, + + /// + /// Dodge Transform. + /// + Dodge, + + /// + /// Burn Transform. + /// + Burn, + + /// + /// Difference Transform. + /// + Difference, + + /// + /// Exclusion Transform. + /// + Exclusion + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..f7a71e6a16 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultBurnPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Burn(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Burn(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..521ae7bf1b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultDarkenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Darken(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Darken(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs new file mode 100644 index 0000000000..08e1d36dd9 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultDifferencePixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Difference(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Difference(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs new file mode 100644 index 0000000000..cd2c4e4b81 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultDodgePixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Dodge(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Dodge(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..817fe82c76 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultExclusionPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Exclusion(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Exclusion(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..41c1005f6b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultHardLightPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.HardLight(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.HardLight(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..6de12372ba --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultLightenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Lighten(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Lighten(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..8240c2b873 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultMultiplyPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Multiply(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Multiply(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..cc58498b34 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs @@ -0,0 +1,37 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultNormalPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + return source; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = source[i]; + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..0425436084 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultOverlayPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Overlay(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Overlay(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..32ab087965 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultPremultipliedLerpPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.PremultipliedLerp(background.ToVector4(), source.ToVector4(), amount); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.PremultipliedLerp(background[i].ToVector4(), source[i].ToVector4(), amount[i]); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..be578af81b --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultScreenPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.Screen(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.Screen(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs new file mode 100644 index 0000000000..357e084c9d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs @@ -0,0 +1,42 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System; + using System.Numerics; + using ImageSharp.PixelFormats; + + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal class DefaultSoftLightPixelBlender : PixelBlender + where TPixel : struct, IPixel + { + /// + public override TPixel Compose(TPixel background, TPixel source, float amount) + { + Vector4 result = Vector4BlendTransforms.SoftLight(background.ToVector4(), source.ToVector4()); + TPixel resultPixel = default(TPixel); + resultPixel.PackFromVector4(result); + return resultPixel; + } + + /// + public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + Vector4 result = Vector4BlendTransforms.SoftLight(background[i].ToVector4(), source[i].ToVector4()); + destination[i].PackFromVector4(result); + } + } + } +} diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs new file mode 100644 index 0000000000..ee0c67396d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -0,0 +1,39 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + /// + /// Abstract base class for calling pixel composition functions + /// + /// The type of the pixel + internal abstract class PixelBlender + where TPixel : struct, IPixel + { + /// + /// Composes 2 pixels together. + /// + /// The background color. + /// The source color. + /// + /// 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. + /// + /// The final pixel value after composition + public abstract TPixel Compose(TPixel background, TPixel source, float amount); + + /// + /// Composes 2 pixels together. + /// + /// The destination span. + /// The background span. + /// The source span. + /// + /// 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. + /// + public abstract void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount); + } +} diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs new file mode 100644 index 0000000000..5ab3449f2d --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -0,0 +1,125 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats +{ + using System.Numerics; + using System.Runtime.CompilerServices; + using ImageSharp.PixelFormats.PixelBlenders; + +#pragma warning disable CS1710 // XML comment has a duplicate typeparam tag + /// + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . + /// + /// The pixel format. + public partial class PixelOperations +#pragma warning restore CS1710 // XML comment has a duplicate typeparam tag + where TPixel : struct, IPixel + { + /// + /// Gets the NormalBlender. + /// + private PixelBlender normalBlender = new DefaultNormalPixelBlender(); + + /// + /// Gets the MultiplyBlender. + /// + private PixelBlender multiplyBlender = new DefaultMultiplyPixelBlender(); + + /// + /// Gets the ScreenBlender. + /// + private PixelBlender screenBlender = new DefaultScreenPixelBlender(); + + /// + /// Gets the HardLightBlender. + /// + private PixelBlender hardLightBlender = new DefaultHardLightPixelBlender(); + + /// + /// Gets the OverlayBlender. + /// + private PixelBlender overlayBlender = new DefaultOverlayPixelBlender(); + + /// + /// Gets the DarkenBlender. + /// + private PixelBlender darkenBlender = new DefaultDarkenPixelBlender(); + + /// + /// Gets the LightenBlender. + /// + private PixelBlender lightenBlender = new DefaultLightenPixelBlender(); + + /// + /// Gets the SoftLightBlender. + /// + private PixelBlender softLightBlender = new DefaultSoftLightPixelBlender(); + + /// + /// Gets the DodgeBlender. + /// + private PixelBlender dodgeBlender = new DefaultDodgePixelBlender(); + + /// + /// Gets the BurnBlender. + /// + private PixelBlender burnBlender = new DefaultBurnPixelBlender(); + + /// + /// Gets the DifferenceBlender. + /// + private PixelBlender differenceBlender = new DefaultDifferencePixelBlender(); + + /// + /// Gets the DifferenceBlender. + /// + private PixelBlender exclusionBlender = new DefaultExclusionPixelBlender(); + + /// + /// Gets the PremultipliedLerpBlender. + /// + private PixelBlender premultipliedLerpBlender = new DefaultPremultipliedLerpPixelBlender(); + + /// + /// Find an instance of the pixel blender. + /// + /// The blending mode to apply + /// A . + internal virtual PixelBlender GetPixelBlender(PixelBlenderMode mode) + { + switch (mode) + { + case PixelBlenderMode.Normal: + return this.normalBlender; + case PixelBlenderMode.Multiply: + return this.multiplyBlender; + case PixelBlenderMode.Screen: + return this.screenBlender; + case PixelBlenderMode.HardLight: + return this.hardLightBlender; + case PixelBlenderMode.Overlay: + return this.overlayBlender; + case PixelBlenderMode.Darken: + return this.darkenBlender; + case PixelBlenderMode.Lighten: + return this.lightenBlender; + case PixelBlenderMode.SoftLight: + return this.softLightBlender; + case PixelBlenderMode.Dodge: + return this.dodgeBlender; + case PixelBlenderMode.Burn: + return this.burnBlender; + case PixelBlenderMode.Difference: + return this.differenceBlender; + case PixelBlenderMode.Exclusion: + return this.exclusionBlender; + default: + return this.premultipliedLerpBlender; + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs similarity index 91% rename from src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs rename to src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 9a3d266c13..11ff422221 100644 --- a/src/ImageSharp/PixelFormats/BulkPixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -8,18 +8,20 @@ namespace ImageSharp.PixelFormats using System.Numerics; using System.Runtime.CompilerServices; - /// - /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations - /// for pixel buffers of type . - /// - /// The pixel format. - public class BulkPixelOperations +#pragma warning disable CS1710 // XML comment has a duplicate typeparam tag + /// + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . + /// + /// The pixel format. + public partial class PixelOperations +#pragma warning restore CS1710 // XML comment has a duplicate typeparam tag where TPixel : struct, IPixel { /// - /// Gets the global instance for the pixel type + /// Gets the global instance for the pixel type /// - public static BulkPixelOperations Instance { get; } = default(TPixel).CreateBulkOperations(); + public static PixelOperations Instance { get; } = default(TPixel).CreateBulkOperations(); /// /// Bulk version of diff --git a/src/ImageSharp/PixelFormats/Rg32.cs b/src/ImageSharp/PixelFormats/Rg32.cs index a4bfc5823e..4d1f0fecfd 100644 --- a/src/ImageSharp/PixelFormats/Rg32.cs +++ b/src/ImageSharp/PixelFormats/Rg32.cs @@ -74,7 +74,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// /// Expands the packed representation into a . diff --git a/src/ImageSharp/PixelFormats/Rgba1010102.cs b/src/ImageSharp/PixelFormats/Rgba1010102.cs index cfd60f410e..96f7af7732 100644 --- a/src/ImageSharp/PixelFormats/Rgba1010102.cs +++ b/src/ImageSharp/PixelFormats/Rgba1010102.cs @@ -77,7 +77,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs similarity index 97% rename from src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs rename to src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs index df21cdc701..ff284e625d 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.BulkOperations.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.PixelOperations.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -16,9 +16,9 @@ namespace ImageSharp.PixelFormats public partial struct Rgba32 { /// - /// implementation optimized for . + /// implementation optimized for . /// - internal class BulkOperations : BulkPixelOperations + internal class PixelOperations : PixelOperations { /// /// SIMD optimized bulk implementation of diff --git a/src/ImageSharp/PixelFormats/Rgba32.cs b/src/ImageSharp/PixelFormats/Rgba32.cs index d668be76fd..0d59269108 100644 --- a/src/ImageSharp/PixelFormats/Rgba32.cs +++ b/src/ImageSharp/PixelFormats/Rgba32.cs @@ -202,7 +202,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Rgba64.cs b/src/ImageSharp/PixelFormats/Rgba64.cs index 20bba9f41e..b7ff143a95 100644 --- a/src/ImageSharp/PixelFormats/Rgba64.cs +++ b/src/ImageSharp/PixelFormats/Rgba64.cs @@ -76,7 +76,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs b/src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs similarity index 81% rename from src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs rename to src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs index 9dcfc9074d..da0900c115 100644 --- a/src/ImageSharp/PixelFormats/RgbaVector.BulkOperations.cs +++ b/src/ImageSharp/PixelFormats/RgbaVector.PixelOperations.cs @@ -13,9 +13,9 @@ namespace ImageSharp.PixelFormats public partial struct RgbaVector { /// - /// implementation optimized for . + /// implementation optimized for . /// - internal class BulkOperations : BulkPixelOperations + internal class PixelOperations : PixelOperations { /// internal override unsafe void ToVector4(BufferSpan sourceColors, BufferSpan destVectors, int count) diff --git a/src/ImageSharp/PixelFormats/RgbaVector.cs b/src/ImageSharp/PixelFormats/RgbaVector.cs index c59e932590..25eaa97117 100644 --- a/src/ImageSharp/PixelFormats/RgbaVector.cs +++ b/src/ImageSharp/PixelFormats/RgbaVector.cs @@ -208,7 +208,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new RgbaVector.BulkOperations(); + public PixelOperations CreateBulkOperations() => new RgbaVector.PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Short2.cs b/src/ImageSharp/PixelFormats/Short2.cs index 60fbb5b35e..d681e27b78 100644 --- a/src/ImageSharp/PixelFormats/Short2.cs +++ b/src/ImageSharp/PixelFormats/Short2.cs @@ -89,7 +89,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Short4.cs b/src/ImageSharp/PixelFormats/Short4.cs index 65ce51eb21..f58949a720 100644 --- a/src/ImageSharp/PixelFormats/Short4.cs +++ b/src/ImageSharp/PixelFormats/Short4.cs @@ -91,7 +91,7 @@ namespace ImageSharp.PixelFormats } /// - public BulkPixelOperations CreateBulkOperations() => new BulkPixelOperations(); + public PixelOperations CreateBulkOperations() => new PixelOperations(); /// [MethodImpl(MethodImplOptions.AggressiveInlining)] diff --git a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs b/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs index 87c7a289ed..f066e9a816 100644 --- a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs +++ b/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs @@ -11,7 +11,7 @@ namespace ImageSharp.PixelFormats /// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification. /// /// - public class Vector4BlendTransforms + internal class Vector4BlendTransforms { /// /// The blending formula simply selects the source vector. diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index ccc3c00608..74185f11f4 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Processing.Processors protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); - new GlowProcessor(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new GlowProcessorParallel(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index 0493782560..fba3fbb8ed 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -65,25 +65,31 @@ namespace ImageSharp.Processing.Processors startY = 0; } + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) + using (Buffer amounts = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); - sourcePixels[offsetX, offsetY] = packed; - } - }); + for (int i = 0; i < width; i++) + { + rowColors[i] = glowColor; + } + + // TODO move GraphicOptions into core so all processes can use it. + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Default); + for (int y = minY; y < maxY; y++) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2((i + offsetX), offsetY)); + amounts[i] = 1 - (.95F * (distance / maxDistance)); + } + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + blender.Compose(destination, destination, rowColors, amounts); + } } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs new file mode 100644 index 0000000000..6c2b8cfc64 --- /dev/null +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs @@ -0,0 +1,100 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Processing.Processors +{ + using System; + using System.Numerics; + using System.Threading.Tasks; + + using ImageSharp.PixelFormats; + + /// + /// An that applies a radial glow effect an . + /// + /// The pixel format. + internal class GlowProcessorParallel : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + public GlowProcessorParallel(TPixel color) + { + this.GlowColor = color; + } + + /// + /// Gets or sets the glow color to apply. + /// + public TPixel GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public float Radius { get; set; } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel glowColor = this.GlowColor; + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + + // 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) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int i = 0; i < width; i++) + { + rowColors[i] = glowColor; + } + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Default); + + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TPixel packed = default(TPixel); + packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } +} \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs index 31328d8b70..dde79a7e41 100644 --- a/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Transforms/ResizeProcessor.cs @@ -123,7 +123,7 @@ namespace ImageSharp.Processing.Processors { BufferSpan sourceRow = sourcePixels.GetRowSpan(y); - BulkPixelOperations.Instance.ToVector4( + PixelOperations.Instance.ToVector4( sourceRow, tempRowBuffer, sourceRow.Length); diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs index 2a370bc002..efec90c996 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/PackFromXyzw.cs @@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().PackFromXyzwBytes(this.source, this.destination, this.Count); + new PixelOperations().PackFromXyzwBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.PackFromXyzwBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs index 1234a99460..e2c1ac7265 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToVector4.cs @@ -47,13 +47,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToVector4(this.source, this.destination, this.Count); + new PixelOperations().ToVector4(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); + PixelOperations.Instance.ToVector4(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs index fe201549bc..88dac21cdf 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyz.cs @@ -45,13 +45,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToXyzBytes(this.source, this.destination, this.Count); + new PixelOperations().ToXyzBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToXyzBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs index f7406d0f61..11545d3d95 100644 --- a/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs +++ b/tests/ImageSharp.Benchmarks/Color/Bulk/ToXyzw.cs @@ -49,13 +49,13 @@ namespace ImageSharp.Benchmarks.Color.Bulk [Benchmark] public void CommonBulk() { - new BulkPixelOperations().ToXyzwBytes(this.source, this.destination, this.Count); + new PixelOperations().ToXyzwBytes(this.source, this.destination, this.Count); } [Benchmark] public void OptimizedBulk() { - BulkPixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); + PixelOperations.Instance.ToXyzwBytes(this.source, this.destination, this.Count); } } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs new file mode 100644 index 0000000000..9b7dbe21a2 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -0,0 +1,48 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks +{ + + using BenchmarkDotNet.Attributes; + using ImageSharp.PixelFormats; + using ImageSharp.Drawing; + using ImageSharp.Processing.Processors; + using CoreImage = ImageSharp.Image; + using CoreSize = ImageSharp.Size; + + public class Glow : BenchmarkBase + { + private GlowProcessor bulk; + private GlowProcessorParallel parallel; + + [Setup] + public void Setup() + { + this.bulk = new GlowProcessor(NamedColors.Beige) { Radius = 800 * .5f, }; + this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; + + } + [Benchmark(Description = "ImageSharp Glow - Bulk")] + public CoreSize GlowBulk() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.ApplyProcessor(bulk, image.Bounds); + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp Glow - Parallel")] + public CoreSize GLowSimple() + { + using (CoreImage image = new CoreImage(800, 800)) + { + image.ApplyProcessor(parallel, image.Bounds); + return new CoreSize(image.Width, image.Height); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs b/tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs similarity index 96% rename from tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs rename to tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs index b498c93acc..3d03927536 100644 --- a/tests/ImageSharp.Tests/Colors/BulkPixelOperationsTests.cs +++ b/tests/ImageSharp.Tests/Colors/PixelOperationsTests.cs @@ -10,7 +10,7 @@ namespace ImageSharp.Tests.Colors using Xunit; using Xunit.Abstractions; - public class BulkPixelOperationsTests + public class PixelOperationsTests { public class Color32 : BulkPixelOperationsTests { @@ -25,7 +25,7 @@ namespace ImageSharp.Tests.Colors [Fact] public void IsSpecialImplementation() { - Assert.IsType(BulkPixelOperations.Instance); + Assert.IsType(PixelOperations.Instance); } [Fact] @@ -37,7 +37,7 @@ namespace ImageSharp.Tests.Colors TestOperation( source, expected, - (s, d) => Rgba32.BulkOperations.ToVector4SimdAligned(s, d, 64) + (s, d) => Rgba32.PixelOperations.ToVector4SimdAligned(s, d, 64) ); } @@ -54,7 +54,7 @@ namespace ImageSharp.Tests.Colors times, () => { - BulkPixelOperations.Instance.ToVector4(source, dest, count); + PixelOperations.Instance.ToVector4(source, dest, count); }); } } @@ -76,7 +76,7 @@ namespace ImageSharp.Tests.Colors public void GetGlobalInstance(TestImageProvider dummy) where TPixel : struct, IPixel { - Assert.NotNull(BulkPixelOperations.Instance); + Assert.NotNull(PixelOperations.Instance); } } @@ -90,7 +90,7 @@ namespace ImageSharp.Tests.Colors public static TheoryData ArraySizesData => new TheoryData { 7, 16, 1111 }; - private static BulkPixelOperations Operations => BulkPixelOperations.Instance; + private static PixelOperations Operations => PixelOperations.Instance; internal static TPixel[] CreateExpectedPixelData(Vector4[] source) { diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index ff5eccc787..8dd2f27909 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -6,6 +6,9 @@ portable True + + + @@ -24,4 +27,7 @@ PreserveNewest + + + \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs new file mode 100644 index 0000000000..6b2bd7c6b7 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs @@ -0,0 +1,53 @@ + + +namespace ImageSharp.Tests.PixelFormats +{ + using System; + using System.Collections.Generic; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PixelOperations + { + public static TheoryData blenderMappings = new TheoryData() + { + { new TestPixel(), typeof(DefaultPremultipliedLerpPixelBlender), PixelBlenderMode.Default }, + { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultSoftLightPixelBlender), PixelBlenderMode.SoftLight }, + { new TestPixel(), typeof(DefaultDodgePixelBlender), PixelBlenderMode.Dodge }, + { new TestPixel(), typeof(DefaultBurnPixelBlender), PixelBlenderMode.Burn }, + { new TestPixel(), typeof(DefaultDifferencePixelBlender), PixelBlenderMode.Difference }, + { new TestPixel(), typeof(DefaultExclusionPixelBlender), PixelBlenderMode.Exclusion }, + + { new TestPixel(), typeof(DefaultPremultipliedLerpPixelBlender), PixelBlenderMode.Default }, + { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, + { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, + { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, + { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, + { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, + { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, + { new TestPixel(), typeof(DefaultSoftLightPixelBlender), PixelBlenderMode.SoftLight }, + { new TestPixel(), typeof(DefaultDodgePixelBlender), PixelBlenderMode.Dodge }, + { new TestPixel(), typeof(DefaultBurnPixelBlender), PixelBlenderMode.Burn }, + { new TestPixel(), typeof(DefaultDifferencePixelBlender), PixelBlenderMode.Difference }, + { new TestPixel(), typeof(DefaultExclusionPixelBlender), PixelBlenderMode.Exclusion } + }; + + [Theory] + [MemberData(nameof(blenderMappings))] + public void ReturnsCorrectBlender(TestPixel pixel, Type type, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(mode); + Assert.IsType(type, blender); + } + } +} diff --git a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs index ad10488459..ebbfdd5f01 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/GlowTest.cs @@ -71,7 +71,7 @@ namespace ImageSharp.Tests using (Image image = file.CreateImage()) using (FileStream output = File.OpenWrite($"{path}/{filename}")) { - image.Glow(new Rectangle(image.Width / 4, image.Height / 4, image.Width / 2, image.Height / 2)) + image.Glow(new Rectangle(image.Width / 8, image.Height / 8, image.Width / 2, image.Height / 2)) .Save(output); } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs new file mode 100644 index 0000000000..93ccb0b142 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -0,0 +1,62 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ImageSharp.PixelFormats; +using Xunit.Abstractions; + +namespace ImageSharp.Tests.TestUtilities +{ + public class TestPixel : IXunitSerializable + where TPixel : struct, IPixel + { + public TestPixel() + { + } + + public TestPixel(float red, float green, float blue, float alpha) + { + this.Red = red; + this.Green = green; + this.Blue = blue; + this.Alpha = alpha; + } + + public float Red { get; set; } + public float Green { get; set; } + public float Blue { get; set; } + public float Alpha { get; set; } + + public static implicit operator TPixel(TestPixel d) + { + return d?.AsPixel() ?? default(TPixel); + } + + public TPixel AsPixel() + { + TPixel pix = default(TPixel); + pix.PackFromVector4(new System.Numerics.Vector4( this.Red, this.Green, this.Blue, this.Alpha)); + return pix; + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.Red = info.GetValue("red"); + this.Green = info.GetValue("green"); + this.Blue = info.GetValue("blue"); + this.Alpha = info.GetValue("alpha"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("red", this.Red); + info.AddValue("green", this.Green); + info.AddValue("blue", this.Blue); + info.AddValue("alpha", this.Alpha); + } + + public override string ToString() + { + return $"{typeof(TPixel).Name}{this.AsPixel().ToString()}"; + } + } +} From 5940fb41446ad7fc5fcc09881dde6e3af1d77c8a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sat, 29 Apr 2017 23:11:30 +0100 Subject: [PATCH 09/28] fix sandbox --- tests/ImageSharp.Sandbox46/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/ImageSharp.Sandbox46/Program.cs b/tests/ImageSharp.Sandbox46/Program.cs index 1bd51d8f3e..7ea459aaee 100644 --- a/tests/ImageSharp.Sandbox46/Program.cs +++ b/tests/ImageSharp.Sandbox46/Program.cs @@ -53,7 +53,7 @@ namespace ImageSharp.Sandbox46 private static void RunToVector4ProfilingTest() { - BulkPixelOperationsTests.Color32 tests = new BulkPixelOperationsTests.Color32(new ConsoleOutput()); + PixelOperationsTests.Color32 tests = new PixelOperationsTests.Color32(new ConsoleOutput()); tests.Benchmark_ToVector4(); } From f435a9d0ba3483d834375e83e300ef1464c13c22 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 00:37:18 +0100 Subject: [PATCH 10/28] migrate blenders to PorterDuff --- .../Brushes/PatternBrush{TPixel}.cs | 4 +- src/ImageSharp.Drawing/DrawImage.cs | 100 +++++++++++------- src/ImageSharp.Drawing/GraphicsOptions.cs | 6 +- .../Processors/DrawImageProcessor.cs | 90 ++++++++++------ src/ImageSharp/ImageProcessor.cs | 4 + .../PixelFormats/PixelBlenderMode.cs | 51 +++------ ...}.cs => DefaultAddPixelBlender{TPixel}.cs} | 17 +-- .../DefaultDarkenPixelBlender{TPixel}.cs | 13 +-- .../DefaultDifferencePixelBlender{TPixel}.cs | 42 -------- .../DefaultDodgePixelBlender{TPixel}.cs | 42 -------- .../DefaultExclusionPixelBlender{TPixel}.cs | 42 -------- .../DefaultHardLightPixelBlender{TPixel}.cs | 13 +-- .../DefaultLightenPixelBlender{TPixel}.cs | 15 +-- .../DefaultMultiplyPixelBlender{TPixel}.cs | 13 +-- .../DefaultNormalPixelBlender{TPixel}.cs | 10 +- .../DefaultOverlayPixelBlender{TPixel}.cs | 13 +-- ...ltPremultipliedLerpPixelBlender{TPixel}.cs | 42 -------- .../DefaultScreenPixelBlender{TPixel}.cs | 13 +-- ...> DefaultSubstractPixelBlender{TPixel}.cs} | 17 +-- .../PixelOperations{TPixel}.PixelBenders.cs | 85 +++------------ .../PixelFormats/PixelTransformMode.cs | 58 ---------- .../PixelTransformModeExtensions.cs | 57 ---------- .../PixelFormats/PorterDuffFunctions.cs | 39 ++++--- .../Processors/Overlays/GlowProcessor.cs | 7 +- .../Overlays/GlowProcessorParallel.cs | 2 - .../Drawing/DrawImageEffectTest.cs | 10 +- .../ImageSharp.Tests/Drawing/DrawImageTest.cs | 2 +- .../PixelFormats/PixelOperations.cs | 18 ++-- 28 files changed, 268 insertions(+), 557 deletions(-) rename src/ImageSharp/PixelFormats/PixelBlenders/{DefaultBurnPixelBlender{TPixel}.cs => DefaultAddPixelBlender{TPixel}.cs} (67%) delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs rename src/ImageSharp/PixelFormats/PixelBlenders/{DefaultSoftLightPixelBlender{TPixel}.cs => DefaultSubstractPixelBlender{TPixel}.cs} (69%) delete mode 100644 src/ImageSharp/PixelFormats/PixelTransformMode.cs delete mode 100644 src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 4aebe00fb6..133506827c 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -155,10 +155,10 @@ namespace ImageSharp.Drawing.Brushes { for (int i = 0; i < scanline.Length; i++) { - amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; + amountBuffer[i] = (scanline[i] * this.Options.BlendPercentage).Clamp(0, 1); int patternX = (x + i) % this.pattern.Width; - overlay[i] = this.pattern[y, x]; + overlay[i] = this.pattern[patternY, patternX]; } BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 52c275595b..9ea9b549a5 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using Drawing.Processors; + using ImageSharp.Drawing; using ImageSharp.PixelFormats; /// @@ -14,31 +15,17 @@ namespace ImageSharp /// public static partial class ImageExtensions { - /// - /// Draws the given image together with the current one by blending their pixels. - /// - /// The pixel format. - /// The image this method extends. - /// The image to blend with the currently processing image. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The . - public static Image Blend(this Image source, Image image, int percent = 50) - where TPixel : struct, IPixel - { - return DrawImage(source, image, percent, default(Size), default(Point)); - } - /// /// Draws the given image together with the current one by blending their pixels. /// /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. /// The size to draw the blended image. /// The location to draw the blended image. + /// The options. /// The . - public static Image DrawImage(this Image source, Image image, int percent, Size size, Point location) + public static Image DrawImage(this Image source, Image image, Size size, Point location, GraphicsOptions options) where TPixel : struct, IPixel { if (size == default(Size)) @@ -51,27 +38,56 @@ namespace ImageSharp location = Point.Empty; } - source.ApplyProcessor(new DrawImageProcessor(image, size, location, percent), source.Bounds); + source.ApplyProcessor(new DrawImageProcessor(image, size, location, options), source.Bounds); return source; } /// /// Draws the given image together with the current one by blending their pixels. /// + /// The pixel format. /// The image this method extends. /// The image to blend with the currently processing image. - /// Pixel function effect to apply on every pixel + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The . + public static Image Blend(this Image source, Image image, float percent) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return DrawImage(source, image, default(Size), default(Point), options); + } + + /// + /// Draws the given image together with the current one by blending their pixels. + /// /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. - /// The size to draw the blended image. - /// The location to draw the blended image. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The blending mode. + /// The opacity of the image image to blend. Must be between 0 and 1. /// The . - public static Image DrawImage(this Image source, Image image, PixelTransformMode mode, int percent, Size size, Point location) + public static Image Blend(this Image source, Image image, PixelBlenderMode blender, float percent) where TPixel : struct, IPixel { - Func pixelFunc = mode.GetPixelFunction(); + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + options.BlenderMode = blender; + return DrawImage(source, image, default(Size), default(Point), options); + } - return DrawImage(source, image, pixelFunc, percent, size, location); + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The pixel format. + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The options, including the blending type and belnding amount. + /// The . + public static Image Blend(this Image source, Image image, GraphicsOptions options) + where TPixel : struct, IPixel + { + return DrawImage(source, image, default(Size), default(Point), options); } /// @@ -79,27 +95,37 @@ namespace ImageSharp /// /// The image this method extends. /// The image to blend with the currently processing image. - /// Pixel function effect to apply on every pixel /// The pixel format. /// The opacity of the image image to blend. Must be between 0 and 100. /// The size to draw the blended image. /// The location to draw the blended image. /// The . - public static Image DrawImage(this Image source, Image image, Func pixelFunc, int percent, Size size, Point location) + public static Image DrawImage(this Image source, Image image, float percent, Size size, Point location) where TPixel : struct, IPixel { - if (size == default(Size)) - { - size = new Size(image.Width, image.Height); - } - - if (location == default(Point)) - { - location = Point.Empty; - } + GraphicsOptions options = GraphicsOptions.Default; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); + } - source.ApplyProcessor(new DrawImageEffectProcessor(image, size, location, pixelFunc, percent), source.Bounds); - return source; + /// + /// Draws the given image together with the current one by blending their pixels. + /// + /// The image this method extends. + /// The image to blend with the currently processing image. + /// The pixel format. + /// The type of bending to apply. + /// The opacity of the image image to blend. Must be between 0 and 1. + /// The size to draw the blended image. + /// The location to draw the blended image. + /// The . + public static Image DrawImage(this Image source, Image image, PixelBlenderMode blender, float percent, Size size, Point location) + where TPixel : struct, IPixel + { + GraphicsOptions options = GraphicsOptions.Default; + options.BlenderMode = blender; + options.BlendPercentage = percent; + return source.DrawImage(image, size, location, options); } } } \ No newline at end of file diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp.Drawing/GraphicsOptions.cs index 2ceb654d3a..03176addf8 100644 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ b/src/ImageSharp.Drawing/GraphicsOptions.cs @@ -31,7 +31,7 @@ namespace ImageSharp.Drawing /// If set to true [enable antialiasing]. public GraphicsOptions(bool enableAntialiasing) { - this.blenderMode = PixelBlenderMode.Default; + this.blenderMode = PixelBlenderMode.Normal; this.blendPercentage = 1; this.antialiasSubpixelDepth = 16; this.antialias = enableAntialiasing; @@ -64,6 +64,10 @@ namespace ImageSharp.Drawing set => this.blendPercentage = value; } + // In the future we could expose a PixelBlender directly on here + // or some forms of PixelBlender factory for each pixel type. Will need + // some API thought post V1. + /// /// Gets or sets a value indicating the blending percentage to apply to the drawing operation /// diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index e2a9ef0248..95c5b4a2f0 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -18,19 +18,22 @@ namespace ImageSharp.Drawing.Processors internal class DrawImageProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The image to blend with the currently processing image. /// The size to draw the blended image. /// The location to draw the blended image. - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageProcessor(Image image, Size size, Point location, int alpha = 100) + /// The opacity of the image to blend. Between 0 and 100. + public DrawImageProcessor(Image image, Size size, Point location, GraphicsOptions options) { - Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); + Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage)); this.Image = image; this.Size = size; - this.Alpha = alpha; + this.Alpha = options.BlendPercentage; + this.blender = PixelOperations.Instance.GetPixelBlender(options.BlenderMode); this.Location = location; } @@ -42,7 +45,7 @@ namespace ImageSharp.Drawing.Processors /// /// Gets the alpha percentage value. /// - public int Alpha { get; } + public float Alpha { get; } /// /// Gets the size to draw the blended image. @@ -57,43 +60,60 @@ namespace ImageSharp.Drawing.Processors /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - if (this.Image.Bounds.Size != this.Size) + Image disposableImage = null; + Image targetImage = this.Image; + + try { - // should Resize be moved to core? - this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); - } + if (targetImage.Bounds.Size != this.Size) + { + // should Resize be moved to core? + targetImage = disposableImage = new Image(this.Image).Resize(this.Size.Width, this.Size.Height); + } - // 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, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + // 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, sourceRectangle.Y); + int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - float alpha = this.Alpha / 100F; + int width = maxX - minX; + using (Buffer amount = new Buffer(width)) + using (PixelAccessor toBlendPixels = this.Image.Lock()) + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int i = 0; i < width; i++) + { + amount[i] = this.Alpha; + } - using (PixelAccessor toBlendPixels = this.Image.Lock()) - using (PixelAccessor sourcePixels = source.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => { - Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4(); + BufferSpan background = sourcePixels.GetRowSpan(y).Slice(minX, width); + BufferSpan foreground = toBlendPixels.GetRowSpan(y - minY).Slice(0, width); + this.blender.Compose(background, background, foreground, amount); - // Lerping colors is dependent on the alpha of the blended color - backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha); - - TPixel packed = default(TPixel); - packed.PackFromVector4(backgroundVector); - sourcePixels[x, y] = packed; - } + // for (int x = minX; x < maxX; x++) + // { + // Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); + // Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4(); + // // Lerping colors is dependent on the alpha of the blended color + // backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha); + // TPixel packed = default(TPixel); + // packed.PackFromVector4(backgroundVector); + // sourcePixels[x, y] = packed; + // } }); + } + } + finally + { + disposableImage?.Dispose(); } } } diff --git a/src/ImageSharp/ImageProcessor.cs b/src/ImageSharp/ImageProcessor.cs index 7b9f745471..745b25fb6b 100644 --- a/src/ImageSharp/ImageProcessor.cs +++ b/src/ImageSharp/ImageProcessor.cs @@ -41,7 +41,11 @@ namespace ImageSharp.Processing } catch (Exception ex) { +#if DEBUG + throw; +#else throw new ImageProcessingException($"An error occured when processing the image using {this.GetType().Name}. See the inner exception for more detail.", ex); +#endif } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs index c189fefdcc..ebb08757b5 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -15,69 +15,48 @@ namespace ImageSharp.PixelFormats public enum PixelBlenderMode { /// - /// The default composition mode. + /// Default blending mode, also known as "Normal" or "Alpha Blending" /// - /// uses PremultipliedLerpTransform - Default = 0, + Normal = 0, /// - /// Normal transform. - /// - Normal, - - /// - /// Multiply Transform. + /// Backdrop + Source /// Multiply, /// - /// Screen Transform. + /// Backdrop + Source /// - Screen, + Add, /// - /// HardLight Transform. + /// Backdrop - Source /// - HardLight, + Substract, /// - /// Overlay Transform. + /// Screen effect /// - Overlay, + Screen, /// - /// Darken Transform. + /// Darken effect /// Darken, /// - /// Lighten Transform. + /// Lighten effect /// Lighten, /// - /// SoftLight Transform. - /// - SoftLight, - - /// - /// Dodge Transform. - /// - Dodge, - - /// - /// Burn Transform. + /// Overlay effect /// - Burn, - - /// - /// Difference Transform. - /// - Difference, + Overlay, /// - /// Exclusion Transform. + /// Hard light effect /// - Exclusion + HardLight } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs similarity index 67% rename from src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs rename to src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs index f7a71e6a16..6a77034f26 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultBurnPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,16 +13,18 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Abstract base class for calling pixel composition functions /// /// The type of the pixel - internal class DefaultBurnPixelBlender : PixelBlender + internal class DefaultAddPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultAddPixelBlender Instance { get; } = new DefaultAddPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Burn(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.AddFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Burn(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.AddFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs index 521ae7bf1b..62da4d9349 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultDarkenPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultDarkenPixelBlender Instance { get; } = new DefaultDarkenPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Darken(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.DarkenFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Darken(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.DarkenFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs deleted file mode 100644 index 08e1d36dd9..0000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDifferencePixelBlender{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - using ImageSharp.PixelFormats; - - /// - /// Abstract base class for calling pixel composition functions - /// - /// The type of the pixel - internal class DefaultDifferencePixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - public override TPixel Compose(TPixel background, TPixel source, float amount) - { - Vector4 result = Vector4BlendTransforms.Difference(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; - } - - /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - - for (int i = 0; i < destination.Length; i++) - { - Vector4 result = Vector4BlendTransforms.Difference(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs deleted file mode 100644 index cd2c4e4b81..0000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDodgePixelBlender{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - using ImageSharp.PixelFormats; - - /// - /// Abstract base class for calling pixel composition functions - /// - /// The type of the pixel - internal class DefaultDodgePixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - public override TPixel Compose(TPixel background, TPixel source, float amount) - { - Vector4 result = Vector4BlendTransforms.Dodge(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; - } - - /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - - for (int i = 0; i < destination.Length; i++) - { - Vector4 result = Vector4BlendTransforms.Dodge(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs deleted file mode 100644 index 817fe82c76..0000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultExclusionPixelBlender{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - using ImageSharp.PixelFormats; - - /// - /// Abstract base class for calling pixel composition functions - /// - /// The type of the pixel - internal class DefaultExclusionPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - public override TPixel Compose(TPixel background, TPixel source, float amount) - { - Vector4 result = Vector4BlendTransforms.Exclusion(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; - } - - /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - - for (int i = 0; i < destination.Length; i++) - { - Vector4 result = Vector4BlendTransforms.Exclusion(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs index 41c1005f6b..46e3023a72 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultHardLightPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultHardLightPixelBlender Instance { get; } = new DefaultHardLightPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.HardLight(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.HardLightFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.HardLight(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.HardLightFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs index 6de12372ba..0ee6f151da 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultLightenPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultLightenPixelBlender Instance { get; } = new DefaultLightenPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Lighten(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.LightenFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Lighten(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.LightenFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs index 8240c2b873..f9f98d7ea4 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultMultiplyPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultMultiplyPixelBlender Instance { get; } = new DefaultMultiplyPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Multiply(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.MultiplyFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Multiply(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.MultiplyFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs index cc58498b34..e55be7202d 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs @@ -6,6 +6,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders { using System; + using System.Numerics; using ImageSharp.PixelFormats; /// @@ -15,10 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultNormalPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultNormalPixelBlender Instance { get; } = new DefaultNormalPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - return source; + return PorterDuffFunctions.NormalBlendFunction(background, source, amount); } /// @@ -30,7 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - destination[i] = source[i]; + destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs index 0425436084..c6d2cfbcdc 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultOverlayPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultOverlayPixelBlender Instance { get; } = new DefaultOverlayPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Overlay(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.OverlayFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Overlay(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.OverlayFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs deleted file mode 100644 index 32ab087965..0000000000 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultPremultipliedLerpPixelBlender{TPixel}.cs +++ /dev/null @@ -1,42 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats.PixelBlenders -{ - using System; - using System.Numerics; - using ImageSharp.PixelFormats; - - /// - /// Abstract base class for calling pixel composition functions - /// - /// The type of the pixel - internal class DefaultPremultipliedLerpPixelBlender : PixelBlender - where TPixel : struct, IPixel - { - /// - public override TPixel Compose(TPixel background, TPixel source, float amount) - { - Vector4 result = Vector4BlendTransforms.PremultipliedLerp(background.ToVector4(), source.ToVector4(), amount); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; - } - - /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) - { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); - - for (int i = 0; i < destination.Length; i++) - { - Vector4 result = Vector4BlendTransforms.PremultipliedLerp(background[i].ToVector4(), source[i].ToVector4(), amount[i]); - destination[i].PackFromVector4(result); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs index be578af81b..cc8f77495b 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs @@ -16,13 +16,15 @@ namespace ImageSharp.PixelFormats.PixelBlenders internal class DefaultScreenPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultScreenPixelBlender Instance { get; } = new DefaultScreenPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.Screen(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.ScreenFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.Screen(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.ScreenFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs similarity index 69% rename from src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs rename to src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs index 357e084c9d..0d64280734 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSoftLightPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -13,16 +13,18 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Abstract base class for calling pixel composition functions /// /// The type of the pixel - internal class DefaultSoftLightPixelBlender : PixelBlender + internal class DefaultSubstractPixelBlender : PixelBlender where TPixel : struct, IPixel { + /// + /// Gets the static instance of this blender. + /// + public static DefaultSubstractPixelBlender Instance { get; } = new DefaultSubstractPixelBlender(); + /// public override TPixel Compose(TPixel background, TPixel source, float amount) { - Vector4 result = Vector4BlendTransforms.SoftLight(background.ToVector4(), source.ToVector4()); - TPixel resultPixel = default(TPixel); - resultPixel.PackFromVector4(result); - return resultPixel; + return PorterDuffFunctions.SubstractFunction(background, source, amount); } /// @@ -34,8 +36,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders for (int i = 0; i < destination.Length; i++) { - Vector4 result = Vector4BlendTransforms.SoftLight(background[i].ToVector4(), source[i].ToVector4()); - destination[i].PackFromVector4(result); + destination[i] = PorterDuffFunctions.SubstractFunction(destination[i], source[i], amount[i]); } } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index 5ab3449f2d..e2fb377ba3 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -34,56 +34,6 @@ namespace ImageSharp.PixelFormats /// private PixelBlender screenBlender = new DefaultScreenPixelBlender(); - /// - /// Gets the HardLightBlender. - /// - private PixelBlender hardLightBlender = new DefaultHardLightPixelBlender(); - - /// - /// Gets the OverlayBlender. - /// - private PixelBlender overlayBlender = new DefaultOverlayPixelBlender(); - - /// - /// Gets the DarkenBlender. - /// - private PixelBlender darkenBlender = new DefaultDarkenPixelBlender(); - - /// - /// Gets the LightenBlender. - /// - private PixelBlender lightenBlender = new DefaultLightenPixelBlender(); - - /// - /// Gets the SoftLightBlender. - /// - private PixelBlender softLightBlender = new DefaultSoftLightPixelBlender(); - - /// - /// Gets the DodgeBlender. - /// - private PixelBlender dodgeBlender = new DefaultDodgePixelBlender(); - - /// - /// Gets the BurnBlender. - /// - private PixelBlender burnBlender = new DefaultBurnPixelBlender(); - - /// - /// Gets the DifferenceBlender. - /// - private PixelBlender differenceBlender = new DefaultDifferencePixelBlender(); - - /// - /// Gets the DifferenceBlender. - /// - private PixelBlender exclusionBlender = new DefaultExclusionPixelBlender(); - - /// - /// Gets the PremultipliedLerpBlender. - /// - private PixelBlender premultipliedLerpBlender = new DefaultPremultipliedLerpPixelBlender(); - /// /// Find an instance of the pixel blender. /// @@ -93,32 +43,25 @@ namespace ImageSharp.PixelFormats { switch (mode) { - case PixelBlenderMode.Normal: - return this.normalBlender; case PixelBlenderMode.Multiply: - return this.multiplyBlender; + return DefaultMultiplyPixelBlender.Instance; + case PixelBlenderMode.Add: + return DefaultAddPixelBlender.Instance; + case PixelBlenderMode.Substract: + return DefaultSubstractPixelBlender.Instance; case PixelBlenderMode.Screen: - return this.screenBlender; - case PixelBlenderMode.HardLight: - return this.hardLightBlender; - case PixelBlenderMode.Overlay: - return this.overlayBlender; + return DefaultScreenPixelBlender.Instance; case PixelBlenderMode.Darken: - return this.darkenBlender; + return DefaultDarkenPixelBlender.Instance; case PixelBlenderMode.Lighten: - return this.lightenBlender; - case PixelBlenderMode.SoftLight: - return this.softLightBlender; - case PixelBlenderMode.Dodge: - return this.dodgeBlender; - case PixelBlenderMode.Burn: - return this.burnBlender; - case PixelBlenderMode.Difference: - return this.differenceBlender; - case PixelBlenderMode.Exclusion: - return this.exclusionBlender; + return DefaultLightenPixelBlender.Instance; + case PixelBlenderMode.Overlay: + return DefaultOverlayPixelBlender.Instance; + case PixelBlenderMode.HardLight: + return DefaultHardLightPixelBlender.Instance; + case PixelBlenderMode.Normal: default: - return this.premultipliedLerpBlender; + return DefaultNormalPixelBlender.Instance; } } } diff --git a/src/ImageSharp/PixelFormats/PixelTransformMode.cs b/src/ImageSharp/PixelFormats/PixelTransformMode.cs deleted file mode 100644 index 159b275158..0000000000 --- a/src/ImageSharp/PixelFormats/PixelTransformMode.cs +++ /dev/null @@ -1,58 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - /// - /// Porter Duff Blending composition modes - /// - public enum PixelTransformMode - { - /// - /// Default blending mode, also known as "Normal" or "Alpha Blending" - /// - Normal, - - /// - /// Backdrop + Source - /// - Multiply, - - /// - /// Backdrop + Source - /// - Add, - - /// - /// Backdrop - Source - /// - Substract, - - /// - /// Screen effect - /// - Screen, - - /// - /// Darken effect - /// - Darken, - - /// - /// Lighten effect - /// - Lighten, - - /// - /// Overlay effect - /// - Overlay, - - /// - /// Hard light effect - /// - HardLight - } -} diff --git a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs b/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs deleted file mode 100644 index 4ecddfded0..0000000000 --- a/src/ImageSharp/PixelFormats/PixelTransformModeExtensions.cs +++ /dev/null @@ -1,57 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp -{ - using System; - using System.Collections.Generic; - using System.Text; - using PixelFormats; - - /// - /// Extensions to retrieve the appropiate pixel transformation functions for - /// - public static class PixelTransformModeExtensions - { - /// - /// Gets a pixel transformation function - /// - /// The pixel format used for both Backdrop and Source - /// The Duff Porter mode - /// A function that transforms a Backdrop and Source colors into a final color - public static Func GetPixelFunction(this PixelTransformMode mode) - where TPixel : IPixel - { - return mode.GetPixelFunction(); - } - - /// - /// Gets a pixel transformation function - /// - /// The pixel format used for Backdrop and Output - /// The pixel format used for Source - /// The Duff Porter mode - /// A function that transforms a Backdrop and Source colors into a final color - public static Func GetPixelFunction(this PixelTransformMode mode) - where TBckPixel : IPixel - where TSrcPixel : IPixel - { - switch (mode) - { - case PixelTransformMode.Normal: return PorterDuffFunctions.NormalBlendFunction; - case PixelTransformMode.Multiply: return PorterDuffFunctions.MultiplyFunction; - case PixelTransformMode.Add: return PorterDuffFunctions.AddFunction; - case PixelTransformMode.Substract: return PorterDuffFunctions.SubstractFunction; - case PixelTransformMode.Screen: return PorterDuffFunctions.ScreenFunction; - case PixelTransformMode.Darken: return PorterDuffFunctions.DarkenFunction; - case PixelTransformMode.Lighten: return PorterDuffFunctions.LightenFunction; - case PixelTransformMode.Overlay: return PorterDuffFunctions.OverlayFunction; - case PixelTransformMode.HardLight: return PorterDuffFunctions.HardLightFunction; - - default: throw new NotImplementedException(nameof(mode)); - } - } - } -} diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs index 1c9f6b8425..bbf1811afd 100644 --- a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs @@ -11,8 +11,7 @@ namespace ImageSharp.PixelFormats /// /// Collection of Porter Duff alpha blending functions /// - /// Backdrop Pixel Format - /// Source Pixel Format + /// Pixel Format /// /// These functions are designed to be a general solution for all color cases, /// that is, they take in account the alpha value of both the backdrop @@ -21,9 +20,8 @@ namespace ImageSharp.PixelFormats /// Note there are faster functions for when the backdrop color is known /// to be opaque /// - internal static class PorterDuffFunctions - where TBckPixel : IPixel - where TsrcPixel : IPixel + internal static class PorterDuffFunctions + where TPixel : IPixel { /// /// Source over backdrop @@ -32,7 +30,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel NormalBlendFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -53,7 +52,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel MultiplyFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -74,7 +74,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel AddFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -95,7 +96,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel SubstractFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -116,7 +118,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel ScreenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -137,7 +140,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel DarkenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -158,7 +162,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel LightenFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -179,7 +184,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel OverlayFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -204,7 +210,8 @@ namespace ImageSharp.PixelFormats /// Source color /// Opacity applied to Source Alpha /// Output color - public static TBckPixel HardLightFunction(TBckPixel backdrop, TsrcPixel source, float opacity) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity) { Vector4 l = source.ToVector4(); l.W *= opacity; @@ -242,7 +249,7 @@ namespace ImageSharp.PixelFormats /// Desired transformed color, without taking Alpha channel in account /// The final color [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TBckPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) + private static TPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) { DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); @@ -258,7 +265,7 @@ namespace ImageSharp.PixelFormats xform = ((xform * xw) + (backdrop * bw) + (source * sw)) / a; xform.W = a; - TBckPixel packed = default(TBckPixel); + TPixel packed = default(TPixel); packed.PackFromVector4(xform); return packed; diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index fba3fbb8ed..ad7846d846 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -76,18 +76,19 @@ namespace ImageSharp.Processing.Processors } // TODO move GraphicOptions into core so all processes can use it. - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Default); + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Normal); for (int y = minY; y < maxY; y++) { int offsetY = y - startY; int offsetX = minX - startX; for (int i = 0; i < width; i++) { - float distance = Vector2.Distance(centre, new Vector2((i + offsetX), offsetY)); - amounts[i] = 1 - (.95F * (distance / maxDistance)); + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (1 - (.95F * (distance / maxDistance))).Clamp(0, 1); } BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + blender.Compose(destination, destination, rowColors, amounts); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs index 6c2b8cfc64..8f0247bc25 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs @@ -74,8 +74,6 @@ namespace ImageSharp.Processing.Processors rowColors[i] = glowColor; } - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Default); - Parallel.For( minY, maxY, diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs index 78f0a08702..f71de1466f 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs @@ -6,6 +6,7 @@ namespace ImageSharp.Tests { using System.IO; + using ImageSharp.Drawing; using ImageSharp.PixelFormats; using Xunit; @@ -16,7 +17,7 @@ namespace ImageSharp.Tests { string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect"); - PixelTransformMode[] modes = (PixelTransformMode[])System.Enum.GetValues(typeof(PixelTransformMode)); + PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode)); using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) { @@ -24,14 +25,17 @@ namespace ImageSharp.Tests { using (Image image = file.CreateImage()) { - foreach (PixelTransformMode mode in modes) + foreach (PixelBlenderMode mode in modes) { using (FileStream output = File.OpenWrite($"{path}/{mode}.{file.FileName}")) { Size size = new Size(image.Width / 2, image.Height / 2); Point loc = new Point(image.Width / 4, image.Height / 4); - image.DrawImage(blend, mode, 75, size, loc).Save(output); + image.DrawImage(blend, size, loc, new GraphicsOptions() { + BlenderMode = mode, + BlendPercentage = .75f + }).Save(output); } } } diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index 3a59de624c..f87a6170d9 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -24,7 +24,7 @@ namespace ImageSharp.Tests { using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) { - image.DrawImage(blend, 75, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + image.DrawImage(blend, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) .Save(output); } } diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs index 6b2bd7c6b7..0618114a0a 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs @@ -14,31 +14,25 @@ namespace ImageSharp.Tests.PixelFormats { public static TheoryData blenderMappings = new TheoryData() { - { new TestPixel(), typeof(DefaultPremultipliedLerpPixelBlender), PixelBlenderMode.Default }, { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultSoftLightPixelBlender), PixelBlenderMode.SoftLight }, - { new TestPixel(), typeof(DefaultDodgePixelBlender), PixelBlenderMode.Dodge }, - { new TestPixel(), typeof(DefaultBurnPixelBlender), PixelBlenderMode.Burn }, - { new TestPixel(), typeof(DefaultDifferencePixelBlender), PixelBlenderMode.Difference }, - { new TestPixel(), typeof(DefaultExclusionPixelBlender), PixelBlenderMode.Exclusion }, + { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, - { new TestPixel(), typeof(DefaultPremultipliedLerpPixelBlender), PixelBlenderMode.Default }, { new TestPixel(), typeof(DefaultNormalPixelBlender), PixelBlenderMode.Normal }, { new TestPixel(), typeof(DefaultScreenPixelBlender), PixelBlenderMode.Screen }, { new TestPixel(), typeof(DefaultHardLightPixelBlender), PixelBlenderMode.HardLight }, { new TestPixel(), typeof(DefaultOverlayPixelBlender), PixelBlenderMode.Overlay }, { new TestPixel(), typeof(DefaultDarkenPixelBlender), PixelBlenderMode.Darken }, { new TestPixel(), typeof(DefaultLightenPixelBlender), PixelBlenderMode.Lighten }, - { new TestPixel(), typeof(DefaultSoftLightPixelBlender), PixelBlenderMode.SoftLight }, - { new TestPixel(), typeof(DefaultDodgePixelBlender), PixelBlenderMode.Dodge }, - { new TestPixel(), typeof(DefaultBurnPixelBlender), PixelBlenderMode.Burn }, - { new TestPixel(), typeof(DefaultDifferencePixelBlender), PixelBlenderMode.Difference }, - { new TestPixel(), typeof(DefaultExclusionPixelBlender), PixelBlenderMode.Exclusion } + { new TestPixel(), typeof(DefaultAddPixelBlender), PixelBlenderMode.Add }, + { new TestPixel(), typeof(DefaultSubstractPixelBlender), PixelBlenderMode.Substract }, + { new TestPixel(), typeof(DefaultMultiplyPixelBlender), PixelBlenderMode.Multiply }, }; [Theory] From 105cefece85d14f59f7289ae83a593b98fb4ee53 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 00:47:57 +0100 Subject: [PATCH 11/28] remove unused processor --- .../Processors/DrawImageEffectProcessor.cs | 102 ------------------ 1 file changed, 102 deletions(-) delete mode 100644 src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs diff --git a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs deleted file mode 100644 index 4ef8800f92..0000000000 --- a/src/ImageSharp.Drawing/Processors/DrawImageEffectProcessor.cs +++ /dev/null @@ -1,102 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Drawing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - using ImageSharp.PixelFormats; - using ImageSharp.Processing; - - /// - /// Combines two images together by blending the pixels. - /// - /// The pixel format. - internal class DrawImageEffectProcessor : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The image to blend with the currently processing image. - /// The size to draw the blended image. - /// The location to draw the blended image. - /// Pixel function effect to apply on every pixel - /// The opacity of the image to blend. Between 0 and 100. - public DrawImageEffectProcessor(Image image, Size size, Point location, Func pixelFunction, int alpha = 100) - { - Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha)); - this.Image = image; - this.PixelFunction = pixelFunction; - this.Size = size; - this.Location = location; - this.Alpha = alpha; - } - - /// - /// Gets the image to blend. - /// - public Image Image { get; private set; } - - /// - /// Gets The function effect to apply on a per pixel basis - /// - public Func PixelFunction { get; private set; } - - /// - /// Gets the alpha percentage value. - /// - public int Alpha { get; } - - /// - /// Gets the size to draw the blended image. - /// - public Size Size { get; } - - /// - /// Gets the location to draw the blended image. - /// - public Point Location { get; } - - /// - protected override void OnApply(ImageBase target, Rectangle sourceRectangle) - { - if (this.Image.Bounds.Size != this.Size) - { - // should Resize be moved to core? - this.Image = this.Image.Resize(this.Size.Width, this.Size.Height); - } - - // 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, sourceRectangle.Y); - int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); - - float alpha = this.Alpha / 100F; - - using (PixelAccessor sourcePixels = this.Image.Lock()) - using (PixelAccessor targetPixels = target.Lock()) - { - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - for (int x = minX; x < maxX; x++) - { - TPixel targetColor = targetPixels[x, y]; - TPixel sourceColor = sourcePixels[x - minX, y - minY]; - - targetPixels[x, y] = this.PixelFunction(targetColor, sourceColor, alpha); - } - }); - } - } - } -} \ No newline at end of file From ff97d6b1c9ce0a5d5d2f2ecd43d4161e5ce2ac6b Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 10:27:00 +0100 Subject: [PATCH 12/28] Add Vector4 based functions. --- .../DefaultAddPixelBlender{TPixel}.cs | 22 +- .../DefaultDarkenPixelBlender{TPixel}.cs | 22 +- .../DefaultHardLightPixelBlender{TPixel}.cs | 22 +- .../DefaultLightenPixelBlender{TPixel}.cs | 22 +- .../DefaultMultiplyPixelBlender{TPixel}.cs | 22 +- .../DefaultNormalPixelBlender{TPixel}.cs | 22 +- .../DefaultOverlayPixelBlender{TPixel}.cs | 22 +- .../DefaultScreenPixelBlender{TPixel}.cs | 22 +- .../DefaultSubstractPixelBlender{TPixel}.cs | 22 +- .../PorterDuffFunctions.cs | 514 ++++++++---------- .../PorterDuffFunctions{TPixel}.cs | 151 +++++ .../PixelBlenders/PorterDuffBulkVsPixel.cs | 103 ++++ 12 files changed, 648 insertions(+), 318 deletions(-) rename src/ImageSharp/PixelFormats/{ => PixelBlenders}/PorterDuffFunctions.cs (63%) create mode 100644 src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs create mode 100644 tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs index 6a77034f26..441877f5fe 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.AddFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.AddFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs index 62da4d9349..c391aabe56 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.DarkenFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.DarkenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs index 46e3023a72..7d98a05c98 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.HardLightFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.HardLightFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs index 0ee6f151da..e97c52edd3 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.LightenFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.LightenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs index f9f98d7ea4..53748ad64b 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.MultiplyFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.MultiplyFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs index e55be7202d..823fd1c2f7 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs index c6d2cfbcdc..64393a66d7 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.OverlayFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.OverlayFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs index cc8f77495b..8538fda57f 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.ScreenFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.ScreenFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs index 0d64280734..48b7196faa 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs @@ -30,13 +30,25 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { - Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); - Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + 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)); - for (int i = 0; i < destination.Length; i++) + using (Buffer buffer = new Buffer(destination.Length * 3)) { - destination[i] = PorterDuffFunctions.SubstractFunction(destination[i], source[i], amount[i]); + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.SubstractFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); } } } diff --git a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs similarity index 63% rename from src/ImageSharp/PixelFormats/PorterDuffFunctions.cs rename to src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index bbf1811afd..6b5126f720 100644 --- a/src/ImageSharp/PixelFormats/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -1,274 +1,242 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Collection of Porter Duff alpha blending functions - /// - /// Pixel Format - /// - /// These functions are designed to be a general solution for all color cases, - /// that is, they take in account the alpha value of both the backdrop - /// and source, and there's no need to alpha-premultiply neither the backdrop - /// nor the source. - /// Note there are faster functions for when the backdrop color is known - /// to be opaque - /// - internal static class PorterDuffFunctions - where TPixel : IPixel - { - /// - /// Source over backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, l); - } - - /// - /// Source multiplied by backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, b * l); - } - - /// - /// Source added to backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, Vector4.Min(Vector4.One, b + l)); - } - - /// - /// Source substracted from backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, Vector4.Max(Vector4.Zero, b - l)); - } - - /// - /// Complement of source multiplied by the complement of backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, Vector4.One - ((Vector4.One - b) * (Vector4.One - l))); - } - - /// - /// Per element, chooses the smallest value of source and backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, Vector4.Min(b, l)); - } - - /// - /// Per element, chooses the largest value of source and backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - return Compose(b, l, Vector4.Max(b, l)); - } - - /// - /// Overlays source over backdrop - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - float cr = OverlayValueFunction(b.X, l.X); - float cg = OverlayValueFunction(b.Y, l.Y); - float cb = OverlayValueFunction(b.Z, l.Z); - - return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); - } - - /// - /// Hard light effect - /// - /// Backgrop color - /// Source color - /// Opacity applied to Source Alpha - /// Output color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity) - { - Vector4 l = source.ToVector4(); - l.W *= opacity; - if (l.W == 0) - { - return backdrop; - } - - Vector4 b = backdrop.ToVector4(); - - float cr = OverlayValueFunction(l.X, b.X); - float cg = OverlayValueFunction(l.Y, b.Y); - float cb = OverlayValueFunction(l.Z, b.Z); - - return Compose(b, l, Vector4.Min(Vector4.One, new Vector4(cr, cg, cb, 0))); - } - - /// - /// Helper function for Overlay andHardLight modes - /// - /// Backdrop color element - /// Source color element - /// Overlay value - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static float OverlayValueFunction(float backdrop, float source) - { - return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); - } - - /// - /// General composition function for all modes, with a general solution for alpha channel - /// - /// Original backgrop color - /// Original source color - /// Desired transformed color, without taking Alpha channel in account - /// The final color - [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static TPixel Compose(Vector4 backdrop, Vector4 source, Vector4 xform) - { - DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); - - // 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)) / a; - xform.W = a; - - TPixel packed = default(TPixel); - packed.PackFromVector4(xform); - - return packed; - } - } +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions + /// + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class PorterDuffFunctions + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 NormalBlendFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, source); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 MultiplyFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, backdrop * source); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 AddFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Min(Vector4.One, backdrop + source)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 SubstractFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Max(Vector4.Zero, backdrop - source)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 ScreenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.One - ((Vector4.One - backdrop) * (Vector4.One - source))); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 DarkenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Min(backdrop, source)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 LightenFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + return Compose(backdrop, source, Vector4.Max(backdrop, source)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 OverlayFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + float cr = OverlayValueFunction(backdrop.X, source.X); + 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))); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static Vector4 HardLightFunction(Vector4 backdrop, Vector4 source, float opacity) + { + source.W *= opacity; + if (source.W == 0) + { + return backdrop; + } + + 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))); + } + + /// + /// Helper function for Overlay andHardLight modes + /// + /// Backdrop color element + /// Source color element + /// Overlay value + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static float OverlayValueFunction(float backdrop, float source) + { + return backdrop <= 0.5f ? (2 * backdrop * source) : 1 - ((2 * (1 - source)) * (1 - backdrop)); + } + + /// + /// General composition function for all modes, with a general solution for alpha channel + /// + /// Original backgrop color + /// Original source color + /// Desired transformed color, without taking Alpha channel in account + /// The final color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static Vector4 Compose(Vector4 backdrop, Vector4 source, Vector4 xform) + { + DebugGuard.MustBeGreaterThan(source.W, 0, nameof(source.W)); + + // 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)) / a; + xform.W = a; + + return xform; + } + } } \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs new file mode 100644 index 0000000000..4e829212e8 --- /dev/null +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions{TPixel}.cs @@ -0,0 +1,151 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.PixelFormats.PixelBlenders +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + /// + /// Collection of Porter Duff alpha blending functions + /// + /// Pixel Format + /// + /// These functions are designed to be a general solution for all color cases, + /// that is, they take in account the alpha value of both the backdrop + /// and source, and there's no need to alpha-premultiply neither the backdrop + /// nor the source. + /// Note there are faster functions for when the backdrop color is known + /// to be opaque + /// + internal static class PorterDuffFunctions + where TPixel : IPixel + { + /// + /// Source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel NormalBlendFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.NormalBlendFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source multiplied by backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel MultiplyFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.MultiplyFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source added to backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel AddFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.AddFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Source substracted from backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel SubstractFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.SubstractFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Complement of source multiplied by the complement of backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel ScreenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.ScreenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Per element, chooses the smallest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel DarkenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.DarkenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Per element, chooses the largest value of source and backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel LightenFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.LightenFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Overlays source over backdrop + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel OverlayFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.OverlayFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + /// + /// Hard light effect + /// + /// Backgrop color + /// Source color + /// Opacity applied to Source Alpha + /// Output color + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static TPixel HardLightFunction(TPixel backdrop, TPixel source, float opacity) + { + return ToPixel(PorterDuffFunctions.HardLightFunction(backdrop.ToVector4(), source.ToVector4(), opacity)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static TPixel ToPixel(Vector4 vector) + { + TPixel p = default(TPixel); + p.PackFromVector4(vector); + return p; + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs new file mode 100644 index 0000000000..a616733b57 --- /dev/null +++ b/tests/ImageSharp.Benchmarks/PixelBlenders/PorterDuffBulkVsPixel.cs @@ -0,0 +1,103 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Benchmarks +{ + + using BenchmarkDotNet.Attributes; + using ImageSharp.PixelFormats; + using ImageSharp.Drawing; + using ImageSharp.Processing.Processors; + using CoreImage = ImageSharp.Image; + using CoreSize = ImageSharp.Size; + using System.Numerics; + using ImageSharp.PixelFormats.PixelBlenders; + + public class PorterDuffBulkVsPixel : BenchmarkBase + { + private void BulkVectorConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + where TPixel : struct, IPixel + { + 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 (Buffer buffer = new Buffer(destination.Length * 3)) + { + BufferSpan destinationSpan = buffer.Slice(0, destination.Length); + BufferSpan backgroundSpan = buffer.Slice(destination.Length, destination.Length); + BufferSpan sourceSpan = buffer.Slice(destination.Length * 2, destination.Length); + + PixelOperations.Instance.ToVector4(background, backgroundSpan, destination.Length); + PixelOperations.Instance.ToVector4(source, sourceSpan, destination.Length); + + for (int i = 0; i < destination.Length; i++) + { + destinationSpan[i] = PorterDuffFunctions.NormalBlendFunction(backgroundSpan[i], sourceSpan[i], amount[i]); + } + + PixelOperations.Instance.PackFromVector4(destinationSpan, destination, destination.Length); + } + } + private void BulkPixelConvert(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + where TPixel : struct, IPixel + { + Guard.MustBeGreaterThanOrEqualTo(destination.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(source.Length, background.Length, nameof(destination)); + Guard.MustBeGreaterThanOrEqualTo(amount.Length, background.Length, nameof(destination)); + + for (int i = 0; i < destination.Length; i++) + { + destination[i] = PorterDuffFunctions.NormalBlendFunction(destination[i], source[i], amount[i]); + } + } + + [Benchmark(Description = "ImageSharp BulkVectorConvert")] + public CoreSize BulkVectorConvert() + { + using (CoreImage image = new CoreImage(800, 800)) + { + Buffer amounts = new Buffer(image.Width); + + for (int x = 0; x < image.Width; x++) + { + amounts[x] = 1; + } + using (PixelAccessor pixels = image.Lock()) + { + for (int y = 0; y < image.Height; y++) + { + BufferSpan span = pixels.GetRowSpan(y); + BulkVectorConvert(span, span, span, amounts); + } + } + return new CoreSize(image.Width, image.Height); + } + } + + [Benchmark(Description = "ImageSharp BulkPixelConvert")] + public CoreSize BulkPixelConvert() + { + using (CoreImage image = new CoreImage(800, 800)) + { + Buffer amounts = new Buffer(image.Width); + + for (int x = 0; x < image.Width; x++) + { + amounts[x] = 1; + } + using (PixelAccessor pixels = image.Lock()) + { + for (int y = 0; y < image.Height; y++) + { + BufferSpan span = pixels.GetRowSpan(y); + BulkPixelConvert(span, span, span, amounts); + } + } + return new CoreSize(image.Width, image.Height); + } + } + } +} From f624265c998c5dc901d83461f9cd792365402163 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 11:21:42 +0100 Subject: [PATCH 13/28] PorterDuff tests --- .../ImageSharp.Tests/ImageSharp.Tests.csproj | 3 - .../PixelBlenders/PorterDuffFunctionsTests.cs | 183 +++++++++++++++++ .../PorterDuffFunctionsTests_TPixel.cs | 193 ++++++++++++++++++ .../PixelFormats/PixelOperations.cs | 5 +- .../TestUtilities/TestVector4.cs | 60 ++++++ tests/ImageSharp.Tests/VectorAssert.cs | 98 +++++++++ 6 files changed, 538 insertions(+), 4 deletions(-) create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs create mode 100644 tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs create mode 100644 tests/ImageSharp.Tests/TestUtilities/TestVector4.cs create mode 100644 tests/ImageSharp.Tests/VectorAssert.cs diff --git a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj index 8dd2f27909..55b3c80e35 100644 --- a/tests/ImageSharp.Tests/ImageSharp.Tests.csproj +++ b/tests/ImageSharp.Tests/ImageSharp.Tests.csproj @@ -27,7 +27,4 @@ PreserveNewest - - - \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs new file mode 100644 index 0000000000..45962c5893 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests.cs @@ -0,0 +1,183 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using System.Text; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PorterDuffFunctionsTests + { + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + }; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + Assert.Equal(expected, actual); + } + + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f, 0.6f, 0.6f, 1) }, + { + new TestVector4(0.9f,0.9f,0.9f,0.9f), + new TestVector4(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestVector4(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData AddFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f, .6f, .6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2075676f, .2075676f, .2075676f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(0,0,0,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(.6f,.6f,.6f, 1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.LightenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(1,1,1,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestVector4(1,1,1,1), new TestVector4(1,1,1,1), 1, new TestVector4(1,1,1,1) }, + { new TestVector4(1,1,1,1), new TestVector4(0,0,0,.8f), .5f, new TestVector4(0.6f,0.6f,0.6f,1f) }, + { + new TestVector4(0.2f,0.2f,0.2f,0.3f), + new TestVector4(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestVector4(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestVector4 back, TestVector4 source, float amount, TestVector4 expected) + { + Vector4 actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 5); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs new file mode 100644 index 0000000000..168269e514 --- /dev/null +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -0,0 +1,193 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.PixelFormats.PixelBlenders +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using System.Text; + using ImageSharp.PixelFormats; + using ImageSharp.PixelFormats.PixelBlenders; + using ImageSharp.Tests.TestUtilities; + using Xunit; + + public class PorterDuffFunctionsTests_TPixel + { + public static TheoryData NormalBlendFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + }; + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 3); + } + + public static TheoryData MultiplyFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, + { + new TestPixel(0.9f,0.9f,0.9f,0.9f), + new TestPixel(0.4f,0.4f,0.4f,0.4f), + .5f, + new TestPixel(0.7834783f, 0.7834783f, 0.7834783f, 0.92f) + }, + }; + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData AddFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f, .6f, .6f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2075676f, .2075676f, .2075676f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData SubstractFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2027027f, .2027027f, .2027027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.SubstractFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData ScreenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2383784f, .2383784f, .2383784f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.ScreenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData DarkenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2189189f, .2189189f, .2189189f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.DarkenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData LightenFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.227027f, .227027f, .227027f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.LightenFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData OverlayFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.OverlayFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + public static TheoryData HardLightFunctionData = new TheoryData() { + { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, + { + new TestPixel(0.2f,0.2f,0.2f,0.3f), + new TestPixel(0.3f,0.3f,0.3f,0.2f), + .5f, + new TestPixel(.2124324f, .2124324f, .2124324f, .37f) + }, + }; + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = PorterDuffFunctions.HardLightFunction(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + } +} diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs index 0618114a0a..a9108692ed 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelOperations.cs @@ -1,4 +1,7 @@ - +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// namespace ImageSharp.Tests.PixelFormats { diff --git a/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs new file mode 100644 index 0000000000..beb3fcd970 --- /dev/null +++ b/tests/ImageSharp.Tests/TestUtilities/TestVector4.cs @@ -0,0 +1,60 @@ +using System; +using System.Collections.Generic; +using System.Numerics; +using System.Text; +using ImageSharp.PixelFormats; +using Xunit.Abstractions; + +namespace ImageSharp.Tests.TestUtilities +{ + public class TestVector4 : IXunitSerializable + { + public TestVector4() + { + } + + public TestVector4(float x, float y, float z, float w) + { + this.X = x; + this.Y = y; + this.Z = x; + this.W = w; + } + + public float X { get; set; } + public float Y { get; set; } + public float Z { get; set; } + public float W { get; set; } + + public static implicit operator Vector4(TestVector4 d) + { + return d?.AsVector() ?? default(Vector4); + } + + public Vector4 AsVector() + { + return new Vector4(this.X, this.Y, this.Z, this.W); + } + + public void Deserialize(IXunitSerializationInfo info) + { + this.X = info.GetValue("x"); + this.Y = info.GetValue("y"); + this.Z= info.GetValue("z"); + this.W= info.GetValue("w"); + } + + public void Serialize(IXunitSerializationInfo info) + { + info.AddValue("x", this.X); + info.AddValue("y", this.Y); + info.AddValue("z", this.Z); + info.AddValue("w", this.W); + } + + public override string ToString() + { + return $"{this.AsVector().ToString()}"; + } + } +} diff --git a/tests/ImageSharp.Tests/VectorAssert.cs b/tests/ImageSharp.Tests/VectorAssert.cs new file mode 100644 index 0000000000..ad26963d43 --- /dev/null +++ b/tests/ImageSharp.Tests/VectorAssert.cs @@ -0,0 +1,98 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +// ReSharper disable MemberHidesStaticFromOuterClass +namespace ImageSharp.Tests +{ + using System; + using System.Collections.Generic; + using System.Numerics; + using ImageSharp; + using ImageSharp.PixelFormats; + using Xunit; + + /// + /// Class to perform simple image comparisons. + /// + public static class VectorAssert + { + public static void Equal(TPixel expected, TPixel actual, int precision = int.MaxValue) + where TPixel : struct, IPixel + { + Equal(expected.ToVector4(), actual.ToVector4(), precision); + } + + public static void Equal(Vector4 expected, Vector4 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector3 expected, Vector3 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + public static void Equal(Vector2 expected, Vector2 actual, int precision = int.MaxValue) + { + Assert.Equal(expected, actual, new PrecisionEqualityComparer(precision)); + } + + private struct PrecisionEqualityComparer : IEqualityComparer, IEqualityComparer, IEqualityComparer, IEqualityComparer + { + private readonly int precision; + + public PrecisionEqualityComparer(int precision) + { + this.precision = precision; + } + + public bool Equals(Vector2 x, Vector2 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y); + + } + public bool Equals(Vector3 x, Vector3 y) + { + return Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(Vector4 x, Vector4 y) + { + return Equals(x.W, y.W) && + Equals(x.X, y.X) && + Equals(x.Y, y.Y) && + Equals(x.Z, y.Z); + + } + + public bool Equals(float x, float y) + { + return Math.Round(x, this.precision) == Math.Round(y, this.precision); + } + + public int GetHashCode(Vector4 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector3 obj) + { + return obj.GetHashCode(); + } + public int GetHashCode(Vector2 obj) + { + return obj.GetHashCode(); + } + + public int GetHashCode(float obj) + { + return obj.GetHashCode(); + } + } + } +} From 14b39fd6205d7a4f6e7a1ef28dac5d832d29f26a Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 11:43:44 +0100 Subject: [PATCH 14/28] bulk blending tests --- .../PorterDuffFunctionsTests_TPixel.cs | 185 +++++++++++++++++- .../TestUtilities/TestPixel.cs | 7 +- 2 files changed, 187 insertions(+), 5 deletions(-) diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index 168269e514..1f5971424c 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -16,6 +16,12 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public class PorterDuffFunctionsTests_TPixel { + private static BufferSpan AsSpan(T value) + where T : struct + { + return new BufferSpan(new[] { value }); + } + public static TheoryData NormalBlendFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f, 0.6f, 0.6f, 1) }, @@ -27,7 +33,26 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { TPixel actual = PorterDuffFunctions.NormalBlendFunction(back, source, amount); - VectorAssert.Equal(expected, actual, 3); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultNormalPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(NormalBlendFunctionData))] + public void NormalBlendFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultNormalPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); } public static TheoryData MultiplyFunctionData = new TheoryData() { @@ -50,14 +75,33 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultMultiplyPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(MultiplyFunctionData))] + public void MultiplyFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultMultiplyPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData AddFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, - { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f, .6f, .6f, 1f) }, + { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1f, 1f, 1f, 1f) }, { new TestPixel(0.2f,0.2f,0.2f,0.3f), new TestPixel(0.3f,0.3f,0.3f,0.2f), .5f, - new TestPixel(.2075676f, .2075676f, .2075676f, .37f) + new TestPixel(.2431373f, .2431373f, .2431373f, .372549f) }, }; @@ -66,10 +110,29 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void AddFunction(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = PorterDuffFunctions.MultiplyFunction(back, source, amount); + TPixel actual = PorterDuffFunctions.AddFunction(back, source, amount); VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultAddPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(AddFunctionData))] + public void AddFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultAddPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData SubstractFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(0,0,0,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, @@ -90,6 +153,25 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultSubstractPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(SubstractFunctionData))] + public void SubstractFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultSubstractPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData ScreenFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1, 1f) }, @@ -110,6 +192,25 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultScreenPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(ScreenFunctionData))] + public void ScreenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultScreenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData DarkenFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(.6f,.6f,.6f, 1f) }, @@ -130,6 +231,25 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultDarkenPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(DarkenFunctionData))] + public void DarkenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultDarkenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData LightenFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, @@ -150,6 +270,25 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultLightenPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(LightenFunctionData))] + public void LightenFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultLightenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData OverlayFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(1,1,1,1f) }, @@ -170,6 +309,25 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders VectorAssert.Equal(expected, actual, 2); } + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultOverlayPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(OverlayFunctionData))] + public void OverlayFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultOverlayPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } + public static TheoryData HardLightFunctionData = new TheoryData() { { new TestPixel(1,1,1,1), new TestPixel(1,1,1,1), 1, new TestPixel(1,1,1,1) }, { new TestPixel(1,1,1,1), new TestPixel(0,0,0,.8f), .5f, new TestPixel(0.6f,0.6f,0.6f,1f) }, @@ -189,5 +347,24 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders TPixel actual = PorterDuffFunctions.HardLightFunction(back, source, amount); VectorAssert.Equal(expected, actual, 2); } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + TPixel actual = new DefaultHardLightPixelBlender().Compose(back, source, amount); + VectorAssert.Equal(expected, actual, 2); + } + + [Theory] + [MemberData(nameof(HardLightFunctionData))] + public void HardLightFunction_Blender_Bulk(TestPixel back, TestPixel source, float amount, TestPixel expected) + where TPixel : struct, IPixel + { + BufferSpan dest = new BufferSpan(new TPixel[1]); + new DefaultHardLightPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + VectorAssert.Equal(expected, dest[0], 2); + } } } diff --git a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs index 93ccb0b142..7e3d318c0e 100644 --- a/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs +++ b/tests/ImageSharp.Tests/TestUtilities/TestPixel.cs @@ -34,10 +34,15 @@ namespace ImageSharp.Tests.TestUtilities public TPixel AsPixel() { TPixel pix = default(TPixel); - pix.PackFromVector4(new System.Numerics.Vector4( this.Red, this.Green, this.Blue, this.Alpha)); + pix.PackFromVector4(new System.Numerics.Vector4(this.Red, this.Green, this.Blue, this.Alpha)); return pix; } + internal BufferSpan AsSpan() + { + return new BufferSpan(new[] { AsPixel() }); + } + public void Deserialize(IXunitSerializationInfo info) { this.Red = info.GetValue("red"); From 1c9ab4b61448aa0b2391a2f5e1f31017a1b7b486 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 11:58:55 +0100 Subject: [PATCH 15/28] revent solid color brush --- .../Brushes/SolidBrush{TPixel}.cs | 26 +------------------ 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index f97266c77d..a18f8de1d7 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -41,31 +41,7 @@ namespace ImageSharp.Drawing.Brushes /// public BrushApplicator CreateApplicator(PixelAccessor sourcePixels, RectangleF region, GraphicsOptions options) { - if (options.BlendPercentage < 0) - { - return new SolidBrushApplicator(sourcePixels, this.color, options); - } - else - { - return new SolidNoBlendBrushApplicator(sourcePixels, this.color, options); - } - } - - /// - /// The solid brush applicator. - /// - private class SolidNoBlendBrushApplicator : SolidBrushApplicator - { - public SolidNoBlendBrushApplicator(PixelAccessor sourcePixels, TPixel color, GraphicsOptions options) - : base(sourcePixels, color, options) - { - } - - internal override void Apply(BufferSpan scanline, int x, int y) - { - BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - this.Blender.Compose(destinationRow, destinationRow, this.Colors, scanline); - } + return new SolidBrushApplicator(sourcePixels, this.color, options); } /// From 3f0cc9abf6169b04b3b3f108e99553904b2f9199 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 15:35:37 +0100 Subject: [PATCH 16/28] updates to comments and moved code --- .../Brushes/ImageBrush{TPixel}.cs | 2 +- .../Brushes/PatternBrush{TPixel}.cs | 2 +- .../Brushes/Processors/BrushApplicator.cs | 2 +- .../Brushes/RecolorBrush{TPixel}.cs | 2 +- .../Brushes/SolidBrush{TPixel}.cs | 2 +- .../Processors/DrawImageProcessor.cs | 18 +--- .../GraphicsOptions.cs | 2 +- .../PixelFormats/PixelBlenderMode.cs | 16 +-- .../DefaultAddPixelBlender{TPixel}.cs | 6 +- .../DefaultDarkenPixelBlender{TPixel}.cs | 6 +- .../DefaultHardLightPixelBlender{TPixel}.cs | 6 +- .../DefaultLightenPixelBlender{TPixel}.cs | 6 +- .../DefaultMultiplyPixelBlender{TPixel}.cs | 6 +- .../DefaultNormalPixelBlender{TPixel}.cs | 6 +- .../DefaultOverlayPixelBlender{TPixel}.cs | 6 +- .../DefaultScreenPixelBlender{TPixel}.cs | 6 +- .../DefaultSubstractPixelBlender{TPixel}.cs | 6 +- .../PixelFormats/PixelBlender{TPixel}.cs | 8 +- .../PixelOperations{TPixel}.PixelBenders.cs | 27 +---- .../PixelFormats/PixelOperations{TPixel}.cs | 12 +-- src/ImageSharp/Processing/Overlays/Glow.cs | 85 +++++++++++++++- .../ColorMatrix/PolaroidProcessor.cs | 2 +- .../Processors/Overlays/GlowProcessor.cs | 42 +++++--- .../Overlays/GlowProcessorParallel.cs | 98 ------------------- tests/ImageSharp.Benchmarks/Samplers/Glow.cs | 87 +++++++++++++++- .../ImageSharp.Tests/Drawing/BlendedShapes.cs | 56 +++++++++++ .../Drawing/DrawImageEffectTest.cs | 56 +++++------ .../PorterDuffFunctionsTests_TPixel.cs | 36 +++---- 28 files changed, 351 insertions(+), 258 deletions(-) rename src/{ImageSharp.Drawing => ImageSharp}/GraphicsOptions.cs (98%) delete mode 100644 src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs create mode 100644 tests/ImageSharp.Tests/Drawing/BlendedShapes.cs diff --git a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs index 5b1409fe2d..5038ea01b5 100644 --- a/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/ImageBrush{TPixel}.cs @@ -134,7 +134,7 @@ namespace ImageSharp.Drawing.Brushes } BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs index 133506827c..dc8a4bc902 100644 --- a/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/PatternBrush{TPixel}.cs @@ -162,7 +162,7 @@ namespace ImageSharp.Drawing.Brushes } BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs index 59cb0820a4..d7c70220c5 100644 --- a/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs +++ b/src/ImageSharp.Drawing/Brushes/Processors/BrushApplicator.cs @@ -80,7 +80,7 @@ namespace ImageSharp.Drawing.Processors } BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index b8b2499ff7..19ce469141 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -154,7 +154,7 @@ namespace ImageSharp.Drawing.Brushes } BufferSpan destinationRow = this.Target.GetRowSpan(x, y).Slice(0, scanline.Length); - this.Blender.Compose(destinationRow, destinationRow, overlay, amountBuffer); + this.Blender.Blend(destinationRow, destinationRow, overlay, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs index a18f8de1d7..71b802136d 100644 --- a/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/SolidBrush{TPixel}.cs @@ -98,7 +98,7 @@ namespace ImageSharp.Drawing.Brushes amountBuffer[i] = scanline[i] * this.Options.BlendPercentage; } - this.Blender.Compose(destinationRow, destinationRow, this.Colors, amountBuffer); + this.Blender.Blend(destinationRow, destinationRow, this.Colors, amountBuffer); } } } diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index 95c5b4a2f0..e58db46895 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -67,7 +67,6 @@ namespace ImageSharp.Drawing.Processors { if (targetImage.Bounds.Size != this.Size) { - // should Resize be moved to core? targetImage = disposableImage = new Image(this.Image).Resize(this.Size.Width, this.Size.Height); } @@ -80,7 +79,7 @@ namespace ImageSharp.Drawing.Processors int width = maxX - minX; using (Buffer amount = new Buffer(width)) - using (PixelAccessor toBlendPixels = this.Image.Lock()) + using (PixelAccessor toBlendPixels = targetImage.Lock()) using (PixelAccessor sourcePixels = source.Lock()) { for (int i = 0; i < width; i++) @@ -96,19 +95,8 @@ namespace ImageSharp.Drawing.Processors { BufferSpan background = sourcePixels.GetRowSpan(y).Slice(minX, width); BufferSpan foreground = toBlendPixels.GetRowSpan(y - minY).Slice(0, width); - this.blender.Compose(background, background, foreground, amount); - - // for (int x = minX; x < maxX; x++) - // { - // Vector4 backgroundVector = sourcePixels[x, y].ToVector4(); - // Vector4 sourceVector = toBlendPixels[x - minX, y - minY].ToVector4(); - // // Lerping colors is dependent on the alpha of the blended color - // backgroundVector = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, alpha); - // TPixel packed = default(TPixel); - // packed.PackFromVector4(backgroundVector); - // sourcePixels[x, y] = packed; - // } - }); + this.blender.Blend(background, background, foreground, amount); + }); } } finally diff --git a/src/ImageSharp.Drawing/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs similarity index 98% rename from src/ImageSharp.Drawing/GraphicsOptions.cs rename to src/ImageSharp/GraphicsOptions.cs index 03176addf8..c32103652b 100644 --- a/src/ImageSharp.Drawing/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -3,7 +3,7 @@ // Licensed under the Apache License, Version 2.0. // -namespace ImageSharp.Drawing +namespace ImageSharp { using ImageSharp.PixelFormats; diff --git a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs index ebb08757b5..d8031fe6e5 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenderMode.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenderMode.cs @@ -20,42 +20,42 @@ namespace ImageSharp.PixelFormats Normal = 0, /// - /// Backdrop + Source + /// Blends the 2 values by multiplication. /// Multiply, /// - /// Backdrop + Source + /// Blends the 2 values by addition. /// Add, /// - /// Backdrop - Source + /// Blends the 2 values by subtraction. /// Substract, /// - /// Screen effect + /// Multiplies the complements of the backdrop and source values, then complements the result. /// Screen, /// - /// Darken effect + /// Selects the minimum of the backdrop and source values. /// Darken, /// - /// Lighten effect + /// Selects the max of the backdrop and source values. /// Lighten, /// - /// Overlay effect + /// Multiplies or screens the values, depending on the backdrop vector values. /// Overlay, /// - /// Hard light effect + /// Multiplies or screens the colors, depending on the source value. /// HardLight } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs index 441877f5fe..ab3aee0411 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultAddPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Add" blending to pixels. /// /// The type of the pixel internal class DefaultAddPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultAddPixelBlender Instance { get; } = new DefaultAddPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.AddFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs index c391aabe56..e0ff80b66e 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultDarkenPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Darken" blending to pixels. /// /// The type of the pixel internal class DefaultDarkenPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultDarkenPixelBlender Instance { get; } = new DefaultDarkenPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.DarkenFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs index 7d98a05c98..cec0dc0db1 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultHardLightPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Hard Light" blending to pixels. /// /// The type of the pixel internal class DefaultHardLightPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultHardLightPixelBlender Instance { get; } = new DefaultHardLightPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.HardLightFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs index e97c52edd3..32cd20650c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultLightenPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Lighten" blending to pixels. /// /// The type of the pixel internal class DefaultLightenPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultLightenPixelBlender Instance { get; } = new DefaultLightenPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.LightenFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs index 53748ad64b..7e01370185 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultMultiplyPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Multiply" blending to pixels. /// /// The type of the pixel internal class DefaultMultiplyPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultMultiplyPixelBlender Instance { get; } = new DefaultMultiplyPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.MultiplyFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs index 823fd1c2f7..47bb084ca0 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultNormalPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies a "Normal" otherwise nown as "Alpha Blending" blending to pixels. /// /// The type of the pixel internal class DefaultNormalPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultNormalPixelBlender Instance { get; } = new DefaultNormalPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.NormalBlendFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs index 64393a66d7..fcb56e3dcc 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultOverlayPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Overlay" blending to pixels. /// /// The type of the pixel internal class DefaultOverlayPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultOverlayPixelBlender Instance { get; } = new DefaultOverlayPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.OverlayFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs index 8538fda57f..df0de293c8 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultScreenPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Screen" blending to pixels. /// /// The type of the pixel internal class DefaultScreenPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultScreenPixelBlender Instance { get; } = new DefaultScreenPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.ScreenFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs index 48b7196faa..415ac04b2c 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/DefaultSubstractPixelBlender{TPixel}.cs @@ -10,7 +10,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using ImageSharp.PixelFormats; /// - /// Abstract base class for calling pixel composition functions + /// Applies an "Subtract" blending to pixels. /// /// The type of the pixel internal class DefaultSubstractPixelBlender : PixelBlender @@ -22,13 +22,13 @@ namespace ImageSharp.PixelFormats.PixelBlenders public static DefaultSubstractPixelBlender Instance { get; } = new DefaultSubstractPixelBlender(); /// - public override TPixel Compose(TPixel background, TPixel source, float amount) + public override TPixel Blend(TPixel background, TPixel source, float amount) { return PorterDuffFunctions.SubstractFunction(background, source, amount); } /// - public override void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) + public override void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount) { Guard.MustBeGreaterThanOrEqualTo(background.Length, destination.Length, nameof(background.Length)); Guard.MustBeGreaterThanOrEqualTo(source.Length, destination.Length, nameof(source.Length)); diff --git a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs index ee0c67396d..23340a60a6 100644 --- a/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelBlender{TPixel}.cs @@ -13,7 +13,7 @@ namespace ImageSharp.PixelFormats where TPixel : struct, IPixel { /// - /// Composes 2 pixels together. + /// Blend 2 pixels together. /// /// The background color. /// The source color. @@ -22,10 +22,10 @@ namespace ImageSharp.PixelFormats /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. /// /// The final pixel value after composition - public abstract TPixel Compose(TPixel background, TPixel source, float amount); + public abstract TPixel Blend(TPixel background, TPixel source, float amount); /// - /// Composes 2 pixels together. + /// Blend 2 pixels together. /// /// The destination span. /// The background span. @@ -34,6 +34,6 @@ namespace ImageSharp.PixelFormats /// 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. /// - public abstract void Compose(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount); + public abstract void Blend(BufferSpan destination, BufferSpan background, BufferSpan source, BufferSpan amount); } } diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs index e2fb377ba3..cab357c412 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.PixelBenders.cs @@ -5,35 +5,14 @@ namespace ImageSharp.PixelFormats { - using System.Numerics; - using System.Runtime.CompilerServices; using ImageSharp.PixelFormats.PixelBlenders; -#pragma warning disable CS1710 // XML comment has a duplicate typeparam tag - /// - /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations - /// for pixel buffers of type . - /// - /// The pixel format. + /// + /// Provides access to pixel blenders + /// public partial class PixelOperations -#pragma warning restore CS1710 // XML comment has a duplicate typeparam tag where TPixel : struct, IPixel { - /// - /// Gets the NormalBlender. - /// - private PixelBlender normalBlender = new DefaultNormalPixelBlender(); - - /// - /// Gets the MultiplyBlender. - /// - private PixelBlender multiplyBlender = new DefaultMultiplyPixelBlender(); - - /// - /// Gets the ScreenBlender. - /// - private PixelBlender screenBlender = new DefaultScreenPixelBlender(); - /// /// Find an instance of the pixel blender. /// diff --git a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs index 11ff422221..2070405211 100644 --- a/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs +++ b/src/ImageSharp/PixelFormats/PixelOperations{TPixel}.cs @@ -8,14 +8,12 @@ namespace ImageSharp.PixelFormats using System.Numerics; using System.Runtime.CompilerServices; -#pragma warning disable CS1710 // XML comment has a duplicate typeparam tag - /// - /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations - /// for pixel buffers of type . - /// - /// The pixel format. + /// + /// A stateless class implementing Strategy Pattern for batched pixel-data conversion operations + /// for pixel buffers of type . + /// + /// The pixel format. public partial class PixelOperations -#pragma warning restore CS1710 // XML comment has a duplicate typeparam tag where TPixel : struct, IPixel { /// diff --git a/src/ImageSharp/Processing/Overlays/Glow.cs b/src/ImageSharp/Processing/Overlays/Glow.cs index 1be15ad650..587bbe6104 100644 --- a/src/ImageSharp/Processing/Overlays/Glow.cs +++ b/src/ImageSharp/Processing/Overlays/Glow.cs @@ -25,7 +25,7 @@ namespace ImageSharp public static Image Glow(this Image source) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds); + return Glow(source, GraphicsOptions.Default); } /// @@ -38,7 +38,7 @@ namespace ImageSharp public static Image Glow(this Image source, TPixel color) where TPixel : struct, IPixel { - return Glow(source, color, source.Bounds.Width * .5F, source.Bounds); + return Glow(source, color, GraphicsOptions.Default); } /// @@ -51,7 +51,7 @@ namespace ImageSharp public static Image Glow(this Image source, float radius) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, radius, source.Bounds); + return Glow(source, radius, GraphicsOptions.Default); } /// @@ -66,7 +66,7 @@ namespace ImageSharp public static Image Glow(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - return Glow(source, NamedColors.Black, 0, rectangle); + return Glow(source, rectangle, GraphicsOptions.Default); } /// @@ -83,7 +83,82 @@ namespace ImageSharp public static Image Glow(this Image source, TPixel color, float radius, Rectangle rectangle) where TPixel : struct, IPixel { - GlowProcessor processor = new GlowProcessor(color) { Radius = radius, }; + return Glow(source, color, radius, rectangle, GraphicsOptions.Default); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, color, source.Bounds.Width * .5F, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the radius. + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, float radius, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, radius, source.Bounds, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Glow(source, NamedColors.Black, 0, rectangle, options); + } + + /// + /// Applies a radial glow effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the glow. + /// The the radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting things like blending. + /// The . + public static Image Glow(this Image source, TPixel color, float radius, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + GlowProcessor processor = new GlowProcessor(color, options) { Radius = radius, }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index 74185f11f4..c06275314b 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -42,7 +42,7 @@ namespace ImageSharp.Processing.Processors protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); - new GlowProcessorParallel(lightOrange) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new GlowProcessor(lightOrange, GraphicsOptions.Default) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs index ad7846d846..5b5d64a9c0 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessor.cs @@ -18,13 +18,19 @@ namespace ImageSharp.Processing.Processors internal class GlowProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The color or the glow. - public GlowProcessor(TPixel color) + /// The options effecting blending and composition. + public GlowProcessor(TPixel color, GraphicsOptions options) { + this.options = options; this.GlowColor = color; + this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } /// @@ -67,7 +73,6 @@ namespace ImageSharp.Processing.Processors int width = maxX - minX; using (Buffer rowColors = new Buffer(width)) - using (Buffer amounts = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { for (int i = 0; i < width; i++) @@ -75,22 +80,27 @@ namespace ImageSharp.Processing.Processors rowColors[i] = glowColor; } - // TODO move GraphicOptions into core so all processes can use it. - PixelBlender blender = PixelOperations.Instance.GetPixelBlender(PixelBlenderMode.Normal); - for (int y = minY; y < maxY; y++) - { - int offsetY = y - startY; - int offsetX = minX - startX; - for (int i = 0; i < width; i++) - { - float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); - amounts[i] = (1 - (.95F * (distance / maxDistance))).Clamp(0, 1); - } + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + using (Buffer amounts = new Buffer(width)) + { + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (1 - (.95F * (distance / maxDistance)))).Clamp(0, 1); + } - BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); - blender.Compose(destination, destination, rowColors, amounts); - } + this.blender.Blend(destination, destination, rowColors, amounts); + } + }); } } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs b/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs deleted file mode 100644 index 8f0247bc25..0000000000 --- a/src/ImageSharp/Processing/Processors/Overlays/GlowProcessorParallel.cs +++ /dev/null @@ -1,98 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Processing.Processors -{ - using System; - using System.Numerics; - using System.Threading.Tasks; - - using ImageSharp.PixelFormats; - - /// - /// An that applies a radial glow effect an . - /// - /// The pixel format. - internal class GlowProcessorParallel : ImageProcessor - where TPixel : struct, IPixel - { - /// - /// Initializes a new instance of the class. - /// - /// The color or the glow. - public GlowProcessorParallel(TPixel color) - { - this.GlowColor = color; - } - - /// - /// Gets or sets the glow color to apply. - /// - public TPixel GlowColor { get; set; } - - /// - /// Gets or sets the the radius. - /// - public float Radius { get; set; } - - /// - protected override void OnApply(ImageBase source, Rectangle sourceRectangle) - { - int startY = sourceRectangle.Y; - int endY = sourceRectangle.Bottom; - int startX = sourceRectangle.X; - int endX = sourceRectangle.Right; - TPixel glowColor = this.GlowColor; - Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); - float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; - - // 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) - { - startX = 0; - } - - if (minY > 0) - { - startY = 0; - } - - int width = maxX - minX; - using (Buffer rowColors = new Buffer(width)) - using (PixelAccessor sourcePixels = source.Lock()) - { - for (int i = 0; i < width; i++) - { - rowColors[i] = glowColor; - } - - Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - startY; - - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); - sourcePixels[offsetX, offsetY] = packed; - } - }); - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 9b7dbe21a2..748bbf4fad 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -12,6 +12,10 @@ namespace ImageSharp.Benchmarks using ImageSharp.Processing.Processors; using CoreImage = ImageSharp.Image; using CoreSize = ImageSharp.Size; + using ImageSharp.Processing; + using System.Numerics; + using System; + using System.Threading.Tasks; public class Glow : BenchmarkBase { @@ -21,7 +25,7 @@ namespace ImageSharp.Benchmarks [Setup] public void Setup() { - this.bulk = new GlowProcessor(NamedColors.Beige) { Radius = 800 * .5f, }; + this.bulk = new GlowProcessor(NamedColors.Beige, GraphicsOptions.Default) { Radius = 800 * .5f, }; this.parallel = new GlowProcessorParallel(NamedColors.Beige) { Radius = 800 * .5f, }; } @@ -44,5 +48,86 @@ namespace ImageSharp.Benchmarks return new CoreSize(image.Width, image.Height); } } + + internal class GlowProcessorParallel : ImageProcessor + where TPixel : struct, IPixel + { + /// + /// Initializes a new instance of the class. + /// + /// The color or the glow. + public GlowProcessorParallel(TPixel color) + { + this.GlowColor = color; + } + + /// + /// Gets or sets the glow color to apply. + /// + public TPixel GlowColor { get; set; } + + /// + /// Gets or sets the the radius. + /// + public float Radius { get; set; } + + /// + protected override void OnApply(ImageBase source, Rectangle sourceRectangle) + { + int startY = sourceRectangle.Y; + int endY = sourceRectangle.Bottom; + int startX = sourceRectangle.X; + int endX = sourceRectangle.Right; + TPixel glowColor = this.GlowColor; + Vector2 centre = Rectangle.Center(sourceRectangle).ToVector2(); + float maxDistance = this.Radius > 0 ? MathF.Min(this.Radius, sourceRectangle.Width * .5F) : sourceRectangle.Width * .5F; + + // 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) + { + startX = 0; + } + + if (minY > 0) + { + startY = 0; + } + + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) + using (PixelAccessor sourcePixels = source.Lock()) + { + for (int i = 0; i < width; i++) + { + rowColors[i] = glowColor; + } + + Parallel.For( + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - startY; + + for (int x = minX; x < maxX; x++) + { + int offsetX = x - startX; + float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); + Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); + TPixel packed = default(TPixel); + packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + sourcePixels[offsetX, offsetY] = packed; + } + }); + } + } + } } } diff --git a/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs new file mode 100644 index 0000000000..6c742c2e0f --- /dev/null +++ b/tests/ImageSharp.Tests/Drawing/BlendedShapes.cs @@ -0,0 +1,56 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Drawing +{ + using System; + using System.Linq; + using System.Collections.Generic; + using System.Text; + using ImageSharp.PixelFormats; + using Xunit; + + public class BlendedShapes + { + public static IEnumerable modes = ((PixelBlenderMode[])Enum.GetValues(typeof(PixelBlenderMode))) + .Select(x=> new object[] { x }); + + [Theory] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + public void DrawBlendedValues(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); + img.Fill(NamedColors.HotPink, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } + + [Theory] + [WithBlankImages(nameof(modes), 100, 100, PixelTypes.StandardImageClass)] + public void DrawBlendedValues_transparent(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (var img = provider.GetImage()) + { + img.Fill(NamedColors.DarkBlue, new Rectangle(0, 40, 100, 20)); + img.Fill(NamedColors.HotPink, new Rectangle(20, 0, 40, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.Fill(NamedColors.Transparent, new Rectangle(40, 0, 20, 100), new ImageSharp.GraphicsOptions(true) + { + BlenderMode = mode + }); + img.DebugSave(provider, new { mode }); + } + } + } +} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs index f71de1466f..885029fdff 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs @@ -1,29 +1,29 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ using System.IO; using ImageSharp.Drawing; - using ImageSharp.PixelFormats; - using Xunit; - - public class DrawImageEffectTest : FileTestBase - { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { + using ImageSharp.PixelFormats; + using Xunit; + + public class DrawImageEffectTest : FileTestBase + { + [Fact] + public void ImageShouldApplyDrawImageFilter() + { string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect"); - PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode)); - - using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) - { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) + PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode)); + + using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) + { + foreach (TestFile file in Files) + { + using (Image image = file.CreateImage()) { foreach (PixelBlenderMode mode in modes) { @@ -37,10 +37,10 @@ namespace ImageSharp.Tests BlendPercentage = .75f }).Save(output); } - } - } - } - } - } - } + } + } + } + } + } + } } \ No newline at end of file diff --git a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs index 1f5971424c..5fa1fccbc0 100644 --- a/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs +++ b/tests/ImageSharp.Tests/PixelFormats/PixelBlenders/PorterDuffFunctionsTests_TPixel.cs @@ -41,7 +41,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void NormalBlendFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultNormalPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultNormalPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -51,7 +51,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultNormalPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultNormalPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -80,7 +80,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void MultiplyFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultMultiplyPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultMultiplyPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -90,7 +90,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultMultiplyPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultMultiplyPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -119,7 +119,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void AddFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultAddPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultAddPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -129,7 +129,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultAddPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultAddPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -158,7 +158,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void SubstractFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultSubstractPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultSubstractPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -168,7 +168,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultSubstractPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultSubstractPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -197,7 +197,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void ScreenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultScreenPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultScreenPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -207,7 +207,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultScreenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultScreenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -236,7 +236,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void DarkenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultDarkenPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultDarkenPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -246,7 +246,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultDarkenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultDarkenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -275,7 +275,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void LightenFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultLightenPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultLightenPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -285,7 +285,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultLightenPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultLightenPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -314,7 +314,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void OverlayFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultOverlayPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultOverlayPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -324,7 +324,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultOverlayPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultOverlayPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } @@ -353,7 +353,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders public void HardLightFunction_Blender(TestPixel back, TestPixel source, float amount, TestPixel expected) where TPixel : struct, IPixel { - TPixel actual = new DefaultHardLightPixelBlender().Compose(back, source, amount); + TPixel actual = new DefaultHardLightPixelBlender().Blend(back, source, amount); VectorAssert.Equal(expected, actual, 2); } @@ -363,7 +363,7 @@ namespace ImageSharp.Tests.PixelFormats.PixelBlenders where TPixel : struct, IPixel { BufferSpan dest = new BufferSpan(new TPixel[1]); - new DefaultHardLightPixelBlender().Compose(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); + new DefaultHardLightPixelBlender().Blend(dest, back.AsSpan(), source.AsSpan(), AsSpan(amount)); VectorAssert.Equal(expected, dest[0], 2); } } From 0e216e05b80f79120554a19d9bf72055dce51f35 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Sun, 30 Apr 2017 15:36:26 +0100 Subject: [PATCH 17/28] bump version number due to moving GraphicOptions --- src/ImageSharp.Drawing/ImageSharp.Drawing.csproj | 2 +- src/ImageSharp/ImageSharp.csproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj index 7c483712d2..9fb0e1e8da 100644 --- a/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj +++ b/src/ImageSharp.Drawing/ImageSharp.Drawing.csproj @@ -2,7 +2,7 @@ An extension to ImageSharp that allows the drawing of images, paths, and text. ImageSharp.Drawing - 1.0.0-alpha7 + 1.0.0-alpha8 James Jackson-South and contributors netstandard1.1 true diff --git a/src/ImageSharp/ImageSharp.csproj b/src/ImageSharp/ImageSharp.csproj index 64183d05cc..16fff32120 100644 --- a/src/ImageSharp/ImageSharp.csproj +++ b/src/ImageSharp/ImageSharp.csproj @@ -2,7 +2,7 @@ A cross-platform library for the processing of image files; written in C# ImageSharp - 1.0.0-alpha7 + 1.0.0-alpha8 James Jackson-South and contributors netstandard1.3;netstandard1.1 true From 116aaa1158fb6799d15f9490e074fa1ac8bfb29d Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 May 2017 11:34:31 +1000 Subject: [PATCH 18/28] Fix repeat count --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 82f32323e8..9ba385c0b2 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -224,8 +224,7 @@ namespace ImageSharp.Formats writer.Write((byte)1); // Data sub-block index (always 1) // 0 means loop indefinitely. Count is set as play n + 1 times. - repeatCount = (ushort)(Math.Max((ushort)0, repeatCount) - 1); - + repeatCount = (ushort)Math.Max(0, repeatCount - 1); writer.Write(repeatCount); // Repeat count for images. writer.Write(GifConstants.Terminator); // Terminator From 1344b18a1e7deb6b22e8d2a7100d5b3790d36a31 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 May 2017 12:55:53 +1000 Subject: [PATCH 19/28] Fix pixel tearing with animated gifs. --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 17 +- src/ImageSharp/Formats/Gif/spec-gif89a.txt | 2476 +++++++++++++++++ .../Quantizers/IQuantizer{TPixel}.cs | 17 +- .../Quantizers/OctreeQuantizer{TPixel}.cs | 3 +- .../Quantizers/PaletteQuantizer{TPixel}.cs | 2 +- .../Quantizers/Quantizer{TPixel}.cs | 4 +- .../Quantizers/WuQuantizer{TPixel}.cs | 3 +- 7 files changed, 2501 insertions(+), 21 deletions(-) create mode 100644 src/ImageSharp/Formats/Gif/spec-gif89a.txt diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 9ba385c0b2..3546b56628 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -35,6 +35,11 @@ namespace ImageSharp.Formats /// private int bitDepth; + /// + /// Whether the current image has multiple frames. + /// + private bool hasMultipleFrames; + /// /// Initializes a new instance of the class. /// @@ -74,7 +79,13 @@ namespace ImageSharp.Formats this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); // Quantize the image returning a palette. - QuantizedImage quantized = ((IQuantizer)this.Quantizer).Quantize(image, quality); + this.hasMultipleFrames = image.Frames.Any(); + + // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. + IQuantizer ditheredQuantizer = (IQuantizer)this.Quantizer; + ditheredQuantizer.Dither = !this.hasMultipleFrames; + + QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); int index = this.GetTransparentIndex(quantized); @@ -92,7 +103,7 @@ namespace ImageSharp.Formats this.WriteImageData(quantized, writer); // Write additional frames. - if (image.Frames.Any()) + if (this.hasMultipleFrames) { this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); @@ -100,7 +111,7 @@ namespace ImageSharp.Formats for (int i = 0; i < image.Frames.Count; i++) { ImageFrame frame = image.Frames[i]; - QuantizedImage quantizedFrame = ((IQuantizer)this.Quantizer).Quantize(frame, quality); + QuantizedImage quantizedFrame = ditheredQuantizer.Quantize(frame, quality); this.WriteGraphicalControlExtension(frame, writer, this.GetTransparentIndex(quantizedFrame)); this.WriteImageDescriptor(frame, writer); diff --git a/src/ImageSharp/Formats/Gif/spec-gif89a.txt b/src/ImageSharp/Formats/Gif/spec-gif89a.txt new file mode 100644 index 0000000000..64a07299b1 --- /dev/null +++ b/src/ImageSharp/Formats/Gif/spec-gif89a.txt @@ -0,0 +1,2476 @@ + + + + + Cover Sheet for the GIF89a Specification + + + DEFERRED CLEAR CODE IN LZW COMPRESSION + + There has been confusion about where clear codes can be found in the + data stream. As the specification says, they may appear at anytime. There + is not a requirement to send a clear code when the string table is full. + + It is the encoder's decision as to when the table should be cleared. When + the table is full, the encoder can chose to use the table as is, making no + changes to it until the encoder chooses to clear it. The encoder during + this time sends out codes that are of the maximum Code Size. + + As we can see from the above, when the decoder's table is full, it must + not change the table until a clear code is received. The Code Size is that + of the maximum Code Size. Processing other than this is done normally. + + Because of a large base of decoders that do not handle the decompression in + this manner, we ask developers of GIF encoding software to NOT implement + this feature until at least January 1991 and later if they see that their + particular market is not ready for it. This will give developers of GIF + decoding software time to implement this feature and to get it into the + hands of their clients before the decoders start "breaking" on the new + GIF's. It is not required that encoders change their software to take + advantage of the deferred clear code, but it is for decoders. + + APPLICATION EXTENSION BLOCK - APPLICATION IDENTIFIER + + There will be a Courtesy Directory file located on CompuServe in the PICS + forum. This directory will contain Application Identifiers for Application + Extension Blocks that have been used by developers of GIF applications. + This file is intended to help keep developers that wish to create + Application Extension Blocks from using the same Application Identifiers. + This is not an official directory; it is for voluntary participation only + and does not guarantee that someone will not use the same identifier. + + E-Mail can be sent to Larry Wood (forum manager of PICS) indicating the + request for inclusion in this file with an identifier. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GRAPHICS INTERCHANGE FORMAT(sm) + + Version 89a + + (c)1987,1988,1989,1990 + + Copyright + CompuServe Incorporated + Columbus, Ohio + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +CompuServe Incorporated Graphics Interchange Format +Document Date : 31 July 1990 Programming Reference + + + + + + + + + + + Table of Contents + +Disclaimer................................................................. 1 + +Foreword................................................................... 1 + +Licensing.................................................................. 1 + +About the Document......................................................... 2 + +General Description........................................................ 2 + +Version Numbers............................................................ 2 + +The Encoder................................................................ 3 + +The Decoder................................................................ 3 + +Compliance................................................................. 3 + +About Recommendations...................................................... 4 + +About Color Tables......................................................... 4 + +Blocks, Extensions and Scope............................................... 4 + +Block Sizes................................................................ 5 + +Using GIF as an embedded protocol.......................................... 5 + +Data Sub-blocks............................................................ 5 + +Block Terminator........................................................... 6 + +Header..................................................................... 7 + +Logical Screen Descriptor.................................................. 8 + +Global Color Table......................................................... 10 + +Image Descriptor........................................................... 11 + +Local Color Table.......................................................... 13 + +Table Based Image Data..................................................... 14 + +Graphic Control Extension.................................................. 15 + +Comment Extension.......................................................... 17 + +Plain Text Extension....................................................... 18 + +Application Extension...................................................... 21 + +Trailer.................................................................... 23 + + + + + + + + + + + +Quick Reference Table...................................................... 24 + +GIF Grammar................................................................ 25 + +Glossary................................................................... 27 + +Conventions................................................................ 28 + +Interlaced Images.......................................................... 29 + +Variable-Length-Code LZW Compression....................................... 30 + +On-line Capabilities Dialogue.............................................. 33 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + +1. Disclaimer. + +The information provided herein is subject to change without notice. In no +event will CompuServe Incorporated be liable for damages, including any loss of +revenue, loss of profits or other incidental or consequential damages arising +out of the use or inability to use the information; CompuServe Incorporated +makes no claim as to the suitability of the information. + + +2. Foreword. + +This document defines the Graphics Interchange Format(sm). The specification +given here defines version 89a, which is an extension of version 87a. + +The Graphics Interchange Format(sm) as specified here should be considered +complete; any deviation from it should be considered invalid, including but not +limited to, the use of reserved or undefined fields within control or data +blocks, the inclusion of extraneous data within or between blocks, the use of +methods or algorithms not specifically listed as part of the format, etc. In +general, any and all deviations, extensions or modifications not specified in +this document should be considered to be in violation of the format and should +be avoided. + + +3. Licensing. + +The Graphics Interchange Format(c) is the copyright property of CompuServe +Incorporated. Only CompuServe Incorporated is authorized to define, redefine, +enhance, alter, modify or change in any way the definition of the format. + +CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free +license for the use of the Graphics Interchange Format(sm) in computer +software; computer software utilizing GIF(sm) must acknowledge ownership of the +Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in +User and Technical Documentation. Computer software utilizing GIF, which is +distributed or may be distributed without User or Technical Documentation must +display to the screen or printer a message acknowledging ownership of the +Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in +this case, the acknowledgement may be displayed in an opening screen or leading +banner, or a closing screen or trailing banner. A message such as the following +may be used: + + "The Graphics Interchange Format(c) is the Copyright property of + CompuServe Incorporated. GIF(sm) is a Service Mark property of + CompuServe Incorporated." + +For further information, please contact : + + CompuServe Incorporated + Graphics Technology Department + 5000 Arlington Center Boulevard + Columbus, Ohio 43220 + U. S. A. + +CompuServe Incorporated maintains a mailing list with all those individuals and +organizations who wish to receive copies of this document when it is corrected + + + + + + + + 2 + + +or revised. This service is offered free of charge; please provide us with your +mailing address. + + +4. About the Document. + +This document describes in detail the definition of the Graphics Interchange +Format. This document is intended as a programming reference; it is +recommended that the entire document be read carefully before programming, +because of the interdependence of the various parts. There is an individual +section for each of the Format blocks. Within each section, the sub-section +labeled Required Version refers to the version number that an encoder will have +to use if the corresponding block is used in the Data Stream. Within each +section, a diagram describes the individual fields in the block; the diagrams +are drawn vertically; top bytes in the diagram appear first in the Data Stream. +Bits within a byte are drawn most significant on the left end. Multi-byte +numeric fields are ordered Least Significant Byte first. Numeric constants are +represented as Hexadecimal numbers, preceded by "0x". Bit fields within a byte +are described in order from most significant bits to least significant bits. + + +5. General Description. + +The Graphics Interchange Format(sm) defines a protocol intended for the on-line +transmission and interchange of raster graphic data in a way that is +independent of the hardware used in their creation or display. + +The Graphics Interchange Format is defined in terms of blocks and sub-blocks +which contain relevant parameters and data used in the reproduction of a +graphic. A GIF Data Stream is a sequence of protocol blocks and sub-blocks +representing a collection of graphics. In general, the graphics in a Data +Stream are assumed to be related to some degree, and to share some control +information; it is recommended that encoders attempt to group together related +graphics in order to minimize hardware changes during processing and to +minimize control information overhead. For the same reason, unrelated graphics +or graphics which require resetting hardware parameters should be encoded +separately to the extent possible. + +A Data Stream may originate locally, as when read from a file, or it may +originate remotely, as when transmitted over a data communications line. The +Format is defined with the assumption that an error-free Transport Level +Protocol is used for communications; the Format makes no provisions for +error-detection and error-correction. + +The GIF Data Stream must be interpreted in context, that is, the application +program must rely on information external to the Data Stream to invoke the +decoder process. + + +6. Version Numbers. + +The version number in the Header of a Data Stream is intended to identify the +minimum set of capabilities required of a decoder in order to fully process the +Data Stream. An encoder should use the earliest possible version number that +includes all the blocks used in the Data Stream. Within each block section in +this document, there is an entry labeled Required Version which specifies the + + + + + + + + 3 + + +earliest version number that includes the corresponding block. The encoder +should make every attempt to use the earliest version number covering all the +blocks in the Data Stream; the unnecessary use of later version numbers will +hinder processing by some decoders. + + +7. The Encoder. + +The Encoder is the program used to create a GIF Data Stream. From raster data +and other information, the encoder produces the necessary control and data +blocks needed for reproducing the original graphics. + +The encoder has the following primary responsibilities. + + - Include in the Data Stream all the necessary information to + reproduce the graphics. + + - Insure that a Data Stream is labeled with the earliest possible + Version Number that will cover the definition of all the blocks in + it; this is to ensure that the largest number of decoders can + process the Data Stream. + + - Ensure encoding of the graphics in such a way that the decoding + process is optimized. Avoid redundant information as much as + possible. + + - To the extent possible, avoid grouping graphics which might + require resetting hardware parameters during the decoding process. + + - Set to zero (off) each of the bits of each and every field + designated as reserved. Note that some fields in the Logical Screen + Descriptor and the Image Descriptor were reserved under Version + 87a, but are used under version 89a. + + +8. The Decoder. + +The Decoder is the program used to process a GIF Data Stream. It processes the +Data Stream sequentially, parsing the various blocks and sub-blocks, using the +control information to set hardware and process parameters and interpreting the +data to render the graphics. + +The decoder has the following primary responsibilities. + + - Process each graphic in the Data Stream in sequence, without + delays other than those specified in the control information. + + - Set its hardware parameters to fit, as closely as possible, the + control information contained in the Data Stream. + + +9. Compliance. + +An encoder or a decoder is said to comply with a given version of the Graphics +Interchange Format if and only if it fully conforms with and correctly +implements the definition of the standard associated with that version. An + + + + + + + + 4 + + +encoder or a decoder may be compliant with a given version number and not +compliant with some subsequent version. + + +10. About Recommendations. + +Each block section in this document contains an entry labeled Recommendation; +this section lists a set of recommendations intended to guide and organize the +use of the particular blocks. Such recommendations are geared towards making +the functions of encoders and decoders more efficient, as well as making +optimal use of the communications bandwidth. It is advised that these +recommendations be followed. + + +11. About Color Tables. + +The GIF format utilizes color tables to render raster-based graphics. A color +table can have one of two different scopes: global or local. A Global Color +Table is used by all those graphics in the Data Stream which do not have a +Local Color Table associated with them. The scope of the Global Color Table is +the entire Data Stream. A Local Color Table is always associated with the +graphic that immediately follows it; the scope of a Local Color Table is +limited to that single graphic. A Local Color Table supersedes a Global Color +Table, that is, if a Data Stream contains a Global Color Table, and an image +has a Local Color Table associated with it, the decoder must save the Global +Color Table, use the Local Color Table to render the image, and then restore +the Global Color Table. Both types of color tables are optional, making it +possible for a Data Stream to contain numerous graphics without a color table +at all. For this reason, it is recommended that the decoder save the last +Global Color Table used until another Global Color Table is encountered. In +this way, a Data Stream which does not contain either a Global Color Table or +a Local Color Table may be processed using the last Global Color Table saved. +If a Global Color Table from a previous Stream is used, that table becomes the +Global Color Table of the present Stream. This is intended to reduce the +overhead incurred by color tables. In particular, it is recommended that an +encoder use only one Global Color Table if all the images in related Data +Streams can be rendered with the same table. If no color table is available at +all, the decoder is free to use a system color table or a table of its own. In +that case, the decoder may use a color table with as many colors as its +hardware is able to support; it is recommended that such a table have black and +white as its first two entries, so that monochrome images can be rendered +adequately. + +The Definition of the GIF Format allows for a Data Stream to contain only the +Header, the Logical Screen Descriptor, a Global Color Table and the GIF +Trailer. Such a Data Stream would be used to load a decoder with a Global Color +Table, in preparation for subsequent Data Streams without a color table at all. + + +12. Blocks, Extensions and Scope. + +Blocks can be classified into three groups : Control, Graphic-Rendering and +Special Purpose. Control blocks, such as the Header, the Logical Screen +Descriptor, the Graphic Control Extension and the Trailer, contain information +used to control the process of the Data Stream or information used in setting +hardware parameters. Graphic-Rendering blocks such as the Image Descriptor and + + + + + + + + 5 + + +the Plain Text Extension contain information and data used to render a graphic +on the display device. Special Purpose blocks such as the Comment Extension and +the Application Extension are neither used to control the process of the Data +Stream nor do they contain information or data used to render a graphic on the +display device. With the exception of the Logical Screen Descriptor and the +Global Color Table, whose scope is the entire Data Stream, all other Control +blocks have a limited scope, restricted to the Graphic-Rendering block that +follows them. Special Purpose blocks do not delimit the scope of any Control +blocks; Special Purpose blocks are transparent to the decoding process. +Graphic-Rendering blocks and extensions are used as scope delimiters for +Control blocks and extensions. The labels used to identify labeled blocks fall +into three ranges : 0x00-0x7F (0-127) are the Graphic Rendering blocks, +excluding the Trailer (0x3B); 0x80-0xF9 (128-249) are the Control blocks; +0xFA-0xFF (250-255) are the Special Purpose blocks. These ranges are defined so +that decoders can handle block scope by appropriately identifying block labels, +even when the block itself cannot be processed. + + +13. Block Sizes. + +The Block Size field in a block, counts the number of bytes remaining in the +block, not counting the Block Size field itself, and not counting the Block +Terminator, if one is to follow. Blocks other than Data Blocks are intended to +be of fixed length; the Block Size field is provided in order to facilitate +skipping them, not to allow their size to change in the future. Data blocks +and sub-blocks are of variable length to accommodate the amount of data. + + +14. Using GIF as an embedded protocol. + +As an embedded protocol, GIF may be part of larger application protocols, +within which GIF is used to render graphics. In such a case, the application +protocol could define a block within which the GIF Data Stream would be +contained. The application program would then invoke a GIF decoder upon +encountering a block of type GIF. This approach is recommended in favor of +using Application Extensions, which become overhead for all other applications +that do not process them. Because a GIF Data Stream must be processed in +context, the application must rely on some means of identifying the GIF Data +Stream outside of the Stream itself. + + +15. Data Sub-blocks. + + a. Description. Data Sub-blocks are units containing data. They do not + have a label, these blocks are processed in the context of control + blocks, wherever data blocks are specified in the format. The first byte + of the Data sub-block indicates the number of data bytes to follow. A + data sub-block may contain from 0 to 255 data bytes. The size of the + block does not account for the size byte itself, therefore, the empty + sub-block is one whose size field contains 0x00. + + b. Required Version. 87a. + + + + + + + + + + + + 6 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Block Size Byte + +---------------+ + 1 | | + +- -+ + 2 | | + +- -+ + 3 | | + +- -+ + | | Data Values Byte + +- -+ + up | | + +- . . . . -+ + to | | + +- -+ + | | + +- -+ +255 | | + +---------------+ + + i) Block Size - Number of bytes in the Data Sub-block; the size + must be within 0 and 255 bytes, inclusive. + + ii) Data Values - Any 8-bit value. There must be exactly as many + Data Values as specified by the Block Size field. + + d. Extensions and Scope. This type of block always occurs as part of a + larger unit. It does not have a scope of itself. + + e. Recommendation. None. + + +16. Block Terminator. + + a. Description. This zero-length Data Sub-block is used to terminate a + sequence of Data Sub-blocks. It contains a single byte in the position of + the Block Size field and does not contain data. + + b. Required Version. 87a. + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Block Size Byte + +---------------+ + + i) Block Size - Number of bytes in the Data Sub-block; this field + contains the fixed value 0x00. + + ii) Data Values - This block does not contain any data. + + + + + + + + + + 7 + + + d. Extensions and Scope. This block terminates the immediately preceding + sequence of Data Sub-blocks. This block cannot be modified by any + extension. + + e. Recommendation. None. + + +17. Header. + + a. Description. The Header identifies the GIF Data Stream in context. The + Signature field marks the beginning of the Data Stream, and the Version + field identifies the set of capabilities required of a decoder to fully + process the Data Stream. This block is REQUIRED; exactly one Header must + be present per Data Stream. + + b. Required Version. Not applicable. This block is not subject to a + version number. This block must appear at the beginning of every Data + Stream. + + c. Syntax. + + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Signature 3 Bytes + +- -+ + 1 | | + +- -+ + 2 | | + +---------------+ + 3 | | Version 3 Bytes + +- -+ + 4 | | + +- -+ + 5 | | + +---------------+ + + i) Signature - Identifies the GIF Data Stream. This field contains + the fixed value 'GIF'. + + ii) Version - Version number used to format the data stream. + Identifies the minimum set of capabilities necessary to a decoder + to fully process the contents of the Data Stream. + + Version Numbers as of 10 July 1990 : "87a" - May 1987 + "89a" - July 1989 + + Version numbers are ordered numerically increasing on the first two + digits starting with 87 (87,88,...,99,00,...,85,86) and + alphabetically increasing on the third character (a,...,z). + + iii) Extensions and Scope. The scope of this block is the entire + Data Stream. This block cannot be modified by any extension. + + + + + + + + + + + 8 + + + d. Recommendations. + + i) Signature - This field identifies the beginning of the GIF Data + Stream; it is not intended to provide a unique signature for the + identification of the data. It is recommended that the GIF Data + Stream be identified externally by the application. (Refer to + Appendix G for on-line identification of the GIF Data Stream.) + + ii) Version - ENCODER : An encoder should use the earliest possible + version number that defines all the blocks used in the Data Stream. + When two or more Data Streams are combined, the latest of the + individual version numbers should be used for the resulting Data + Stream. DECODER : A decoder should attempt to process the data + stream to the best of its ability; if it encounters a version + number which it is not capable of processing fully, it should + nevertheless, attempt to process the data stream to the best of its + ability, perhaps after warning the user that the data may be + incomplete. + + +18. Logical Screen Descriptor. + + a. Description. The Logical Screen Descriptor contains the parameters + necessary to define the area of the display device within which the + images will be rendered. The coordinates in this block are given with + respect to the top-left corner of the virtual screen; they do not + necessarily refer to absolute coordinates on the display device. This + implies that they could refer to window coordinates in a window-based + environment or printer coordinates when a printer is used. + + This block is REQUIRED; exactly one Logical Screen Descriptor must be + present per Data Stream. + + b. Required Version. Not applicable. This block is not subject to a + version number. This block must appear immediately after the Header. + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Logical Screen Width Unsigned + +- -+ + 1 | | + +---------------+ + 2 | | Logical Screen Height Unsigned + +- -+ + 3 | | + +---------------+ + 4 | | | | | See below + +---------------+ + 5 | | Background Color Index Byte + +---------------+ + 6 | | Pixel Aspect Ratio Byte + +---------------+ + + + + + + + + + + 9 + + + = Global Color Table Flag 1 Bit + Color Resolution 3 Bits + Sort Flag 1 Bit + Size of Global Color Table 3 Bits + + i) Logical Screen Width - Width, in pixels, of the Logical Screen + where the images will be rendered in the displaying device. + + ii) Logical Screen Height - Height, in pixels, of the Logical + Screen where the images will be rendered in the displaying device. + + iii) Global Color Table Flag - Flag indicating the presence of a + Global Color Table; if the flag is set, the Global Color Table will + immediately follow the Logical Screen Descriptor. This flag also + selects the interpretation of the Background Color Index; if the + flag is set, the value of the Background Color Index field should + be used as the table index of the background color. (This field is + the most significant bit of the byte.) + + Values : 0 - No Global Color Table follows, the Background + Color Index field is meaningless. + 1 - A Global Color Table will immediately follow, the + Background Color Index field is meaningful. + + iv) Color Resolution - Number of bits per primary color available + to the original image, minus 1. This value represents the size of + the entire palette from which the colors in the graphic were + selected, not the number of colors actually used in the graphic. + For example, if the value in this field is 3, then the palette of + the original image had 4 bits per primary color available to create + the image. This value should be set to indicate the richness of + the original palette, even if not every color from the whole + palette is available on the source machine. + + v) Sort Flag - Indicates whether the Global Color Table is sorted. + If the flag is set, the Global Color Table is sorted, in order of + decreasing importance. Typically, the order would be decreasing + frequency, with most frequent color first. This assists a decoder, + with fewer available colors, in choosing the best subset of colors; + the decoder may use an initial segment of the table to render the + graphic. + + Values : 0 - Not ordered. + 1 - Ordered by decreasing importance, most + important color first. + + vi) Size of Global Color Table - If the Global Color Table Flag is + set to 1, the value in this field is used to calculate the number + of bytes contained in the Global Color Table. To determine that + actual size of the color table, raise 2 to [the value of the field + + 1]. Even if there is no Global Color Table specified, set this + field according to the above formula so that decoders can choose + the best graphics mode to display the stream in. (This field is + made up of the 3 least significant bits of the byte.) + + vii) Background Color Index - Index into the Global Color Table for + + + + + + + + 10 + + + the Background Color. The Background Color is the color used for + those pixels on the screen that are not covered by an image. If the + Global Color Table Flag is set to (zero), this field should be zero + and should be ignored. + + viii) Pixel Aspect Ratio - Factor used to compute an approximation + of the aspect ratio of the pixel in the original image. If the + value of the field is not 0, this approximation of the aspect ratio + is computed based on the formula: + + Aspect Ratio = (Pixel Aspect Ratio + 15) / 64 + + The Pixel Aspect Ratio is defined to be the quotient of the pixel's + width over its height. The value range in this field allows + specification of the widest pixel of 4:1 to the tallest pixel of + 1:4 in increments of 1/64th. + + Values : 0 - No aspect ratio information is given. + 1..255 - Value used in the computation. + + d. Extensions and Scope. The scope of this block is the entire Data + Stream. This block cannot be modified by any extension. + + e. Recommendations. None. + + +19. Global Color Table. + + a. Description. This block contains a color table, which is a sequence of + bytes representing red-green-blue color triplets. The Global Color Table + is used by images without a Local Color Table and by Plain Text + Extensions. Its presence is marked by the Global Color Table Flag being + set to 1 in the Logical Screen Descriptor; if present, it immediately + follows the Logical Screen Descriptor and contains a number of bytes + equal to + 3 x 2^(Size of Global Color Table+1). + + This block is OPTIONAL; at most one Global Color Table may be present + per Data Stream. + + b. Required Version. 87a + + + + + + + + + + + + + + + + + + + + + + + 11 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +===============+ + 0 | | Red 0 Byte + +- -+ + 1 | | Green 0 Byte + +- -+ + 2 | | Blue 0 Byte + +- -+ + 3 | | Red 1 Byte + +- -+ + | | Green 1 Byte + +- -+ + up | | + +- . . . . -+ ... + to | | + +- -+ + | | Green 255 Byte + +- -+ +767 | | Blue 255 Byte + +===============+ + + + d. Extensions and Scope. The scope of this block is the entire Data + Stream. This block cannot be modified by any extension. + + e. Recommendation. None. + + +20. Image Descriptor. + + a. Description. Each image in the Data Stream is composed of an Image + Descriptor, an optional Local Color Table, and the image data. Each + image must fit within the boundaries of the Logical Screen, as defined + in the Logical Screen Descriptor. + + The Image Descriptor contains the parameters necessary to process a table + based image. The coordinates given in this block refer to coordinates + within the Logical Screen, and are given in pixels. This block is a + Graphic-Rendering Block, optionally preceded by one or more Control + blocks such as the Graphic Control Extension, and may be optionally + followed by a Local Color Table; the Image Descriptor is always followed + by the image data. + + This block is REQUIRED for an image. Exactly one Image Descriptor must + be present per image in the Data Stream. An unlimited number of images + may be present per Data Stream. + + b. Required Version. 87a. + + + + + + + + + + + + + + 12 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Image Separator Byte + +---------------+ + 1 | | Image Left Position Unsigned + +- -+ + 2 | | + +---------------+ + 3 | | Image Top Position Unsigned + +- -+ + 4 | | + +---------------+ + 5 | | Image Width Unsigned + +- -+ + 6 | | + +---------------+ + 7 | | Image Height Unsigned + +- -+ + 8 | | + +---------------+ + 9 | | | | | | See below + +---------------+ + + = Local Color Table Flag 1 Bit + Interlace Flag 1 Bit + Sort Flag 1 Bit + Reserved 2 Bits + Size of Local Color Table 3 Bits + + i) Image Separator - Identifies the beginning of an Image + Descriptor. This field contains the fixed value 0x2C. + + ii) Image Left Position - Column number, in pixels, of the left edge + of the image, with respect to the left edge of the Logical Screen. + Leftmost column of the Logical Screen is 0. + + iii) Image Top Position - Row number, in pixels, of the top edge of + the image with respect to the top edge of the Logical Screen. Top + row of the Logical Screen is 0. + + iv) Image Width - Width of the image in pixels. + + v) Image Height - Height of the image in pixels. + + vi) Local Color Table Flag - Indicates the presence of a Local Color + Table immediately following this Image Descriptor. (This field is + the most significant bit of the byte.) + + + Values : 0 - Local Color Table is not present. Use + Global Color Table if available. + 1 - Local Color Table present, and to follow + immediately after this Image Descriptor. + + + + + + + + + 13 + + + vii) Interlace Flag - Indicates if the image is interlaced. An image + is interlaced in a four-pass interlace pattern; see Appendix E for + details. + + Values : 0 - Image is not interlaced. + 1 - Image is interlaced. + + viii) Sort Flag - Indicates whether the Local Color Table is + sorted. If the flag is set, the Local Color Table is sorted, in + order of decreasing importance. Typically, the order would be + decreasing frequency, with most frequent color first. This assists + a decoder, with fewer available colors, in choosing the best subset + of colors; the decoder may use an initial segment of the table to + render the graphic. + + Values : 0 - Not ordered. + 1 - Ordered by decreasing importance, most + important color first. + + ix) Size of Local Color Table - If the Local Color Table Flag is + set to 1, the value in this field is used to calculate the number + of bytes contained in the Local Color Table. To determine that + actual size of the color table, raise 2 to the value of the field + + 1. This value should be 0 if there is no Local Color Table + specified. (This field is made up of the 3 least significant bits + of the byte.) + + d. Extensions and Scope. The scope of this block is the Table-based Image + Data Block that follows it. This block may be modified by the Graphic + Control Extension. + + e. Recommendation. None. + + +21. Local Color Table. + + a. Description. This block contains a color table, which is a sequence of + bytes representing red-green-blue color triplets. The Local Color Table + is used by the image that immediately follows. Its presence is marked by + the Local Color Table Flag being set to 1 in the Image Descriptor; if + present, the Local Color Table immediately follows the Image Descriptor + and contains a number of bytes equal to + 3x2^(Size of Local Color Table+1). + If present, this color table temporarily becomes the active color table + and the following image should be processed using it. This block is + OPTIONAL; at most one Local Color Table may be present per Image + Descriptor and its scope is the single image associated with the Image + Descriptor that precedes it. + + b. Required Version. 87a. + + + + + + + + + + + + + + 14 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +===============+ + 0 | | Red 0 Byte + +- -+ + 1 | | Green 0 Byte + +- -+ + 2 | | Blue 0 Byte + +- -+ + 3 | | Red 1 Byte + +- -+ + | | Green 1 Byte + +- -+ + up | | + +- . . . . -+ ... + to | | + +- -+ + | | Green 255 Byte + +- -+ +767 | | Blue 255 Byte + +===============+ + + + d. Extensions and Scope. The scope of this block is the Table-based Image + Data Block that immediately follows it. This block cannot be modified by + any extension. + + e. Recommendations. None. + + +22. Table Based Image Data. + + a. Description. The image data for a table based image consists of a + sequence of sub-blocks, of size at most 255 bytes each, containing an + index into the active color table, for each pixel in the image. Pixel + indices are in order of left to right and from top to bottom. Each index + must be within the range of the size of the active color table, starting + at 0. The sequence of indices is encoded using the LZW Algorithm with + variable-length code, as described in Appendix F + + b. Required Version. 87a. + + c. Syntax. The image data format is as follows: + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + | | LZW Minimum Code Size Byte + +---------------+ + + +===============+ + | | + / / Image Data Data Sub-blocks + | | + +===============+ + + + + + + + + + 15 + + + i) LZW Minimum Code Size. This byte determines the initial number + of bits used for LZW codes in the image data, as described in + Appendix F. + + d. Extensions and Scope. This block has no scope, it contains raster + data. Extensions intended to modify a Table-based image must appear + before the corresponding Image Descriptor. + + e. Recommendations. None. + + +23. Graphic Control Extension. + + a. Description. The Graphic Control Extension contains parameters used + when processing a graphic rendering block. The scope of this extension is + the first graphic rendering block to follow. The extension contains only + one data sub-block. + + This block is OPTIONAL; at most one Graphic Control Extension may precede + a graphic rendering block. This is the only limit to the number of + Graphic Control Extensions that may be contained in a Data Stream. + + b. Required Version. 89a. + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Extension Introducer Byte + +---------------+ + 1 | | Graphic Control Label Byte + +---------------+ + + +---------------+ + 0 | | Block Size Byte + +---------------+ + 1 | | | | | See below + +---------------+ + 2 | | Delay Time Unsigned + +- -+ + 3 | | + +---------------+ + 4 | | Transparent Color Index Byte + +---------------+ + + +---------------+ + 0 | | Block Terminator Byte + +---------------+ + + + = Reserved 3 Bits + Disposal Method 3 Bits + User Input Flag 1 Bit + Transparent Color Flag 1 Bit + + i) Extension Introducer - Identifies the beginning of an extension + + + + + + + + 16 + + + block. This field contains the fixed value 0x21. + + ii) Graphic Control Label - Identifies the current block as a + Graphic Control Extension. This field contains the fixed value + 0xF9. + + iii) Block Size - Number of bytes in the block, after the Block + Size field and up to but not including the Block Terminator. This + field contains the fixed value 4. + + iv) Disposal Method - Indicates the way in which the graphic is to + be treated after being displayed. + + Values : 0 - No disposal specified. The decoder is + not required to take any action. + 1 - Do not dispose. The graphic is to be left + in place. + 2 - Restore to background color. The area used by the + graphic must be restored to the background color. + 3 - Restore to previous. The decoder is required to + restore the area overwritten by the graphic with + what was there prior to rendering the graphic. + 4-7 - To be defined. + + v) User Input Flag - Indicates whether or not user input is + expected before continuing. If the flag is set, processing will + continue when user input is entered. The nature of the User input + is determined by the application (Carriage Return, Mouse Button + Click, etc.). + + Values : 0 - User input is not expected. + 1 - User input is expected. + + When a Delay Time is used and the User Input Flag is set, + processing will continue when user input is received or when the + delay time expires, whichever occurs first. + + vi) Transparency Flag - Indicates whether a transparency index is + given in the Transparent Index field. (This field is the least + significant bit of the byte.) + + Values : 0 - Transparent Index is not given. + 1 - Transparent Index is given. + + vii) Delay Time - If not 0, this field specifies the number of + hundredths (1/100) of a second to wait before continuing with the + processing of the Data Stream. The clock starts ticking immediately + after the graphic is rendered. This field may be used in + conjunction with the User Input Flag field. + + viii) Transparency Index - The Transparency Index is such that when + encountered, the corresponding pixel of the display device is not + modified and processing goes on to the next pixel. The index is + present if and only if the Transparency Flag is set to 1. + + ix) Block Terminator - This zero-length data block marks the end of + + + + + + + + 17 + + the Graphic Control Extension. + + d. Extensions and Scope. The scope of this Extension is the graphic + rendering block that follows it; it is possible for other extensions to + be present between this block and its target. This block can modify the + Image Descriptor Block and the Plain Text Extension. + + e. Recommendations. + + i) Disposal Method - The mode Restore To Previous is intended to be + used in small sections of the graphic; the use of this mode imposes + severe demands on the decoder to store the section of the graphic + that needs to be saved. For this reason, this mode should be used + sparingly. This mode is not intended to save an entire graphic or + large areas of a graphic; when this is the case, the encoder should + make every attempt to make the sections of the graphic to be + restored be separate graphics in the data stream. In the case where + a decoder is not capable of saving an area of a graphic marked as + Restore To Previous, it is recommended that a decoder restore to + the background color. + + ii) User Input Flag - When the flag is set, indicating that user + input is expected, the decoder may sound the bell (0x07) to alert + the user that input is being expected. In the absence of a + specified Delay Time, the decoder should wait for user input + indefinitely. It is recommended that the encoder not set the User + Input Flag without a Delay Time specified. + + +24. Comment Extension. + + a. Description. The Comment Extension contains textual information which + is not part of the actual graphics in the GIF Data Stream. It is suitable + for including comments about the graphics, credits, descriptions or any + other type of non-control and non-graphic data. The Comment Extension + may be ignored by the decoder, or it may be saved for later processing; + under no circumstances should a Comment Extension disrupt or interfere + with the processing of the Data Stream. + + This block is OPTIONAL; any number of them may appear in the Data Stream. + + b. Required Version. 89a. + + + + + + + + + + + + + + + + + + + + + + + 18 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Extension Introducer Byte + +---------------+ + 1 | | Comment Label Byte + +---------------+ + + +===============+ + | | + N | | Comment Data Data Sub-blocks + | | + +===============+ + + +---------------+ + 0 | | Block Terminator Byte + +---------------+ + + i) Extension Introducer - Identifies the beginning of an extension + block. This field contains the fixed value 0x21. + + ii) Comment Label - Identifies the block as a Comment Extension. + This field contains the fixed value 0xFE. + + iii) Comment Data - Sequence of sub-blocks, each of size at most + 255 bytes and at least 1 byte, with the size in a byte preceding + the data. The end of the sequence is marked by the Block + Terminator. + + iv) Block Terminator - This zero-length data block marks the end of + the Comment Extension. + + d. Extensions and Scope. This block does not have scope. This block + cannot be modified by any extension. + + e. Recommendations. + + i) Data - This block is intended for humans. It should contain + text using the 7-bit ASCII character set. This block should + not be used to store control information for custom processing. + + ii) Position - This block may appear at any point in the Data + Stream at which a block can begin; however, it is recommended that + Comment Extensions do not interfere with Control or Data blocks; + they should be located at the beginning or at the end of the Data + Stream to the extent possible. + + +25. Plain Text Extension. + + a. Description. The Plain Text Extension contains textual data and the + parameters necessary to render that data as a graphic, in a simple form. + The textual data will be encoded with the 7-bit printable ASCII + characters. Text data are rendered using a grid of character cells + + + + + + + + + 19 + + + defined by the parameters in the block fields. Each character is rendered + in an individual cell. The textual data in this block is to be rendered + as mono-spaced characters, one character per cell, with a best fitting + font and size. For further information, see the section on + Recommendations below. The data characters are taken sequentially from + the data portion of the block and rendered within a cell, starting with + the upper left cell in the grid and proceeding from left to right and + from top to bottom. Text data is rendered until the end of data is + reached or the character grid is filled. The Character Grid contains an + integral number of cells; in the case that the cell dimensions do not + allow for an integral number, fractional cells must be discarded; an + encoder must be careful to specify the grid dimensions accurately so that + this does not happen. This block requires a Global Color Table to be + available; the colors used by this block reference the Global Color Table + in the Stream if there is one, or the Global Color Table from a previous + Stream, if one was saved. This block is a graphic rendering block, + therefore it may be modified by a Graphic Control Extension. This block + is OPTIONAL; any number of them may appear in the Data Stream. + + b. Required Version. 89a. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 20 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Extension Introducer Byte + +---------------+ + 1 | | Plain Text Label Byte + +---------------+ + + +---------------+ + 0 | | Block Size Byte + +---------------+ + 1 | | Text Grid Left Position Unsigned + +- -+ + 2 | | + +---------------+ + 3 | | Text Grid Top Position Unsigned + +- -+ + 4 | | + +---------------+ + 5 | | Text Grid Width Unsigned + +- -+ + 6 | | + +---------------+ + 7 | | Text Grid Height Unsigned + +- -+ + 8 | | + +---------------+ + 9 | | Character Cell Width Byte + +---------------+ + 10 | | Character Cell Height Byte + +---------------+ + 11 | | Text Foreground Color Index Byte + +---------------+ + 12 | | Text Background Color Index Byte + +---------------+ + + +===============+ + | | + N | | Plain Text Data Data Sub-blocks + | | + +===============+ + + +---------------+ + 0 | | Block Terminator Byte + +---------------+ + + i) Extension Introducer - Identifies the beginning of an extension + block. This field contains the fixed value 0x21. + + ii) Plain Text Label - Identifies the current block as a Plain Text + Extension. This field contains the fixed value 0x01. + + iii) Block Size - Number of bytes in the extension, after the Block + Size field and up to but not including the beginning of the data + portion. This field contains the fixed value 12. + + + + + + + + 21 + + + iv) Text Grid Left Position - Column number, in pixels, of the left + edge of the text grid, with respect to the left edge of the Logical + Screen. + + v) Text Grid Top Position - Row number, in pixels, of the top edge + of the text grid, with respect to the top edge of the Logical + Screen. + + vi) Image Grid Width - Width of the text grid in pixels. + + vii) Image Grid Height - Height of the text grid in pixels. + + viii) Character Cell Width - Width, in pixels, of each cell in the + grid. + + ix) Character Cell Height - Height, in pixels, of each cell in the + grid. + + x) Text Foreground Color Index - Index into the Global Color Table + to be used to render the text foreground. + + xi) Text Background Color Index - Index into the Global Color Table + to be used to render the text background. + + xii) Plain Text Data - Sequence of sub-blocks, each of size at most + 255 bytes and at least 1 byte, with the size in a byte preceding + the data. The end of the sequence is marked by the Block + Terminator. + + xiii) Block Terminator - This zero-length data block marks the end + of the Plain Text Data Blocks. + + d. Extensions and Scope. The scope of this block is the Plain Text Data + Block contained in it. This block may be modified by the Graphic Control + Extension. + + e. Recommendations. The data in the Plain Text Extension is assumed to be + preformatted. The selection of font and size is left to the discretion of + the decoder. If characters less than 0x20 or greater than 0xf7 are + encountered, it is recommended that the decoder display a Space character + (0x20). The encoder should use grid and cell dimensions such that an + integral number of cells fit in the grid both horizontally as well as + vertically. For broadest compatibility, character cell dimensions should + be around 8x8 or 8x16 (width x height); consider an image for unusual + sized text. + + +26. Application Extension. + + a. Description. The Application Extension contains application-specific + information; it conforms with the extension block syntax, as described + below, and its block label is 0xFF. + + b. Required Version. 89a. + + + + + + + + + + 22 + + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | Extension Introducer Byte + +---------------+ + 1 | | Extension Label Byte + +---------------+ + + +---------------+ + 0 | | Block Size Byte + +---------------+ + 1 | | + +- -+ + 2 | | + +- -+ + 3 | | Application Identifier 8 Bytes + +- -+ + 4 | | + +- -+ + 5 | | + +- -+ + 6 | | + +- -+ + 7 | | + +- -+ + 8 | | + +---------------+ + 9 | | + +- -+ + 10 | | Appl. Authentication Code 3 Bytes + +- -+ + 11 | | + +---------------+ + + +===============+ + | | + | | Application Data Data Sub-blocks + | | + | | + +===============+ + + +---------------+ + 0 | | Block Terminator Byte + +---------------+ + + i) Extension Introducer - Defines this block as an extension. This + field contains the fixed value 0x21. + + ii) Application Extension Label - Identifies the block as an + Application Extension. This field contains the fixed value 0xFF. + + iii) Block Size - Number of bytes in this extension block, + following the Block Size field, up to but not including the + beginning of the Application Data. This field contains the fixed + value 11. + + + + + + + + 23 + + + iv) Application Identifier - Sequence of eight printable ASCII + characters used to identify the application owning the Application + Extension. + + v) Application Authentication Code - Sequence of three bytes used + to authenticate the Application Identifier. An Application program + may use an algorithm to compute a binary code that uniquely + identifies it as the application owning the Application Extension. + + + d. Extensions and Scope. This block does not have scope. This block + cannot be modified by any extension. + + e. Recommendation. None. + + +27. Trailer. + + a. Description. This block is a single-field block indicating the end of + the GIF Data Stream. It contains the fixed value 0x3B. + + b. Required Version. 87a. + + c. Syntax. + + 7 6 5 4 3 2 1 0 Field Name Type + +---------------+ + 0 | | GIF Trailer Byte + +---------------+ + + d. Extensions and Scope. This block does not have scope, it terminates + the GIF Data Stream. This block may not be modified by any extension. + + e. Recommendations. None. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 24 + + +Appendix +A. Quick Reference Table. + +Block Name Required Label Ext. Vers. +Application Extension Opt. (*) 0xFF (255) yes 89a +Comment Extension Opt. (*) 0xFE (254) yes 89a +Global Color Table Opt. (1) none no 87a +Graphic Control Extension Opt. (*) 0xF9 (249) yes 89a +Header Req. (1) none no N/A +Image Descriptor Opt. (*) 0x2C (044) no 87a (89a) +Local Color Table Opt. (*) none no 87a +Logical Screen Descriptor Req. (1) none no 87a (89a) +Plain Text Extension Opt. (*) 0x01 (001) yes 89a +Trailer Req. (1) 0x3B (059) no 87a + +Unlabeled Blocks +Header Req. (1) none no N/A +Logical Screen Descriptor Req. (1) none no 87a (89a) +Global Color Table Opt. (1) none no 87a +Local Color Table Opt. (*) none no 87a + +Graphic-Rendering Blocks +Plain Text Extension Opt. (*) 0x01 (001) yes 89a +Image Descriptor Opt. (*) 0x2C (044) no 87a (89a) + +Control Blocks +Graphic Control Extension Opt. (*) 0xF9 (249) yes 89a + +Special Purpose Blocks +Trailer Req. (1) 0x3B (059) no 87a +Comment Extension Opt. (*) 0xFE (254) yes 89a +Application Extension Opt. (*) 0xFF (255) yes 89a + +legend: (1) if present, at most one occurrence + (*) zero or more occurrences + (+) one or more occurrences + +Notes : The Header is not subject to Version Numbers. +(89a) The Logical Screen Descriptor and the Image Descriptor retained their +syntax from version 87a to version 89a, but some fields reserved under version +87a are used under version 89a. + + + + + + + + + + + + + + + + + + + + + + + 25 + + +Appendix +B. GIF Grammar. + +A Grammar is a form of notation to represent the sequence in which certain +objects form larger objects. A grammar is also used to represent the number of +objects that can occur at a given position. The grammar given here represents +the sequence of blocks that form the GIF Data Stream. A grammar is given by +listing its rules. Each rule consists of the left-hand side, followed by some +form of equals sign, followed by the right-hand side. In a rule, the +right-hand side describes how the left-hand side is defined. The right-hand +side consists of a sequence of entities, with the possible presence of special +symbols. The following legend defines the symbols used in this grammar for GIF. + +Legend: <> grammar word + ::= defines symbol + * zero or more occurrences + + one or more occurrences + | alternate element + [] optional element + +Example: + + ::= Header * Trailer + +This rule defines the entity as follows. It must begin with a +Header. The Header is followed by an entity called Logical Screen, which is +defined below by another rule. The Logical Screen is followed by the entity +Data, which is also defined below by another rule. Finally, the entity Data is +followed by the Trailer. Since there is no rule defining the Header or the +Trailer, this means that these blocks are defined in the document. The entity +Data has a special symbol (*) following it which means that, at this position, +the entity Data may be repeated any number of times, including 0 times. For +further reading on this subject, refer to a standard text on Programming +Languages. + + +The Grammar. + + ::= Header * Trailer + + ::= Logical Screen Descriptor [Global Color Table] + + ::= | + + + ::= [Graphic Control Extension] + + ::= | + Plain Text Extension + + ::= Image Descriptor [Local Color Table] Image Data + + ::= Application Extension | + Comment Extension + + + + + + + + + + 26 + + +NOTE : The grammar indicates that it is possible for a GIF Data Stream to +contain the Header, the Logical Screen Descriptor, a Global Color Table and the +GIF Trailer. This special case is used to load a GIF decoder with a Global +Color Table, in preparation for subsequent Data Streams without color tables at +all. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 27 + + +Appendix +C. Glossary. + +Active Color Table - Color table used to render the next graphic. If the next +graphic is an image which has a Local Color Table associated with it, the +active color table becomes the Local Color Table associated with that image. +If the next graphic is an image without a Local Color Table, or a Plain Text +Extension, the active color table is the Global Color Table associated with the +Data Stream, if there is one; if there is no Global Color Table in the Data +Stream, the active color table is a color table saved from a previous Data +Stream, or one supplied by the decoder. + +Block - Collection of bytes forming a protocol unit. In general, the term +includes labeled and unlabeled blocks, as well as Extensions. + +Data Stream - The GIF Data Stream is composed of blocks and sub-blocks +representing images and graphics, together with control information to render +them on a display device. All control and data blocks in the Data Stream must +follow the Header and must precede the Trailer. + +Decoder - A program capable of processing a GIF Data Stream to render the +images and graphics contained in it. + +Encoder - A program capable of capturing and formatting image and graphic +raster data, following the definitions of the Graphics Interchange Format. + +Extension - A protocol block labeled by the Extension Introducer 0x21. + +Extension Introducer - Label (0x21) defining an Extension. + +Graphic - Data which can be rendered on the screen by virtue of some algorithm. +The term graphic is more general than the term image; in addition to images, +the term graphic also includes data such as text, which is rendered using +character bit-maps. + +Image - Data representing a picture or a drawing; an image is represented by an +array of pixels called the raster of the image. + +Raster - Array of pixel values representing an image. + + + + + + + + + + + + + + + + + + + + + + + + + 28 + + +Appendix +D. Conventions. + +Animation - The Graphics Interchange Format is not intended as a platform for +animation, even though it can be done in a limited way. + +Byte Ordering - Unless otherwise stated, multi-byte numeric fields are ordered +with the Least Significant Byte first. + +Color Indices - Color indices always refer to the active color table, either +the Global Color Table or the Local Color Table. + +Color Order - Unless otherwise stated, all triple-component RGB color values +are specified in Red-Green-Blue order. + +Color Tables - Both color tables, the Global and the Local, are optional; if +present, the Global Color Table is to be used with every image in the Data +Stream for which a Local Color Table is not given; if present, a Local Color +Table overrides the Global Color Table. However, if neither color table is +present, the application program is free to use an arbitrary color table. If +the graphics in several Data Streams are related and all use the same color +table, an encoder could place the color table as the Global Color Table in the +first Data Stream and leave subsequent Data Streams without a Global Color +Table or any Local Color Tables; in this way, the overhead for the table is +eliminated. It is recommended that the decoder save the previous Global Color +Table to be used with the Data Stream that follows, in case it does not contain +either a Global Color Table or any Local Color Tables. In general, this allows +the application program to use past color tables, significantly reducing +transmission overhead. + +Extension Blocks - Extensions are defined using the Extension Introducer code +to mark the beginning of the block, followed by a block label, identifying the +type of extension. Extension Codes are numbers in the range from 0x00 to 0xFF, +inclusive. Special purpose extensions are transparent to the decoder and may be +omitted when transmitting the Data Stream on-line. The GIF capabilities +dialogue makes the provision for the receiver to request the transmission of +all blocks; the default state in this regard is no transmission of Special +purpose blocks. + +Reserved Fields - All Reserved Fields are expected to have each bit set to zero +(off). + + + + + + + + + + + + + + + + + + + + + + + 29 + + +Appendix +E. Interlaced Images. + +The rows of an Interlaced images are arranged in the following order: + + Group 1 : Every 8th. row, starting with row 0. (Pass 1) + Group 2 : Every 8th. row, starting with row 4. (Pass 2) + Group 3 : Every 4th. row, starting with row 2. (Pass 3) + Group 4 : Every 2nd. row, starting with row 1. (Pass 4) + +The Following example illustrates how the rows of an interlaced image are +ordered. + + Row Number Interlace Pass + + 0 ----------------------------------------- 1 + 1 ----------------------------------------- 4 + 2 ----------------------------------------- 3 + 3 ----------------------------------------- 4 + 4 ----------------------------------------- 2 + 5 ----------------------------------------- 4 + 6 ----------------------------------------- 3 + 7 ----------------------------------------- 4 + 8 ----------------------------------------- 1 + 9 ----------------------------------------- 4 + 10 ----------------------------------------- 3 + 11 ----------------------------------------- 4 + 12 ----------------------------------------- 2 + 13 ----------------------------------------- 4 + 14 ----------------------------------------- 3 + 15 ----------------------------------------- 4 + 16 ----------------------------------------- 1 + 17 ----------------------------------------- 4 + 18 ----------------------------------------- 3 + 19 ----------------------------------------- 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 30 + + +Appendix +F. Variable-Length-Code LZW Compression. + +The Variable-Length-Code LZW Compression is a variation of the Lempel-Ziv +Compression algorithm in which variable-length codes are used to replace +patterns detected in the original data. The algorithm uses a code or +translation table constructed from the patterns encountered in the original +data; each new pattern is entered into the table and its index is used to +replace it in the compressed stream. + +The compressor takes the data from the input stream and builds a code or +translation table with the patterns as it encounters them; each new pattern is +entered into the code table and its index is added to the output stream; when a +pattern is encountered which had been detected since the last code table +refresh, its index from the code table is put on the output stream, thus +achieving the data compression. The expander takes input from the compressed +data stream and builds the code or translation table from it; as the compressed +data stream is processed, codes are used to index into the code table and the +corresponding data is put on the decompressed output stream, thus achieving +data decompression. The details of the algorithm are explained below. The +Variable-Length-Code aspect of the algorithm is based on an initial code size +(LZW-initial code size), which specifies the initial number of bits used for +the compression codes. When the number of patterns detected by the compressor +in the input stream exceeds the number of patterns encodable with the current +number of bits, the number of bits per LZW code is increased by one. + +The Raster Data stream that represents the actual output image can be +represented as: + + 7 6 5 4 3 2 1 0 + +---------------+ + | LZW code size | + +---------------+ + + +---------------+ ----+ + | block size | | + +---------------+ | + | | +-- Repeated as many + | data bytes | | times as necessary. + | | | + +---------------+ ----+ + + . . . . . . ------- The code that terminates the LZW + compressed data must appear before + Block Terminator. + +---------------+ + |0 0 0 0 0 0 0 0| Block Terminator + +---------------+ + +The conversion of the image from a series of pixel values to a transmitted or +stored character stream involves several steps. In brief these steps are: + +1. Establish the Code Size - Define the number of bits needed to represent the +actual data. + +2. Compress the Data - Compress the series of image pixels to a series of + + + + + + + + 31 + + +compression codes. + +3. Build a Series of Bytes - Take the set of compression codes and convert to a +string of 8-bit bytes. + +4. Package the Bytes - Package sets of bytes into blocks preceded by character +counts and output. + +ESTABLISH CODE SIZE + +The first byte of the Compressed Data stream is a value indicating the minimum +number of bits required to represent the set of actual pixel values. Normally +this will be the same as the number of color bits. Because of some algorithmic +constraints however, black & white images which have one color bit must be +indicated as having a code size of 2. +This code size value also implies that the compression codes must start out one +bit longer. + +COMPRESSION + +The LZW algorithm converts a series of data values into a series of codes which +may be raw values or a code designating a series of values. Using text +characters as an analogy, the output code consists of a character or a code +representing a string of characters. + +The LZW algorithm used in GIF matches algorithmically with the standard LZW +algorithm with the following differences: + +1. A special Clear code is defined which resets all compression/decompression +parameters and tables to a start-up state. The value of this code is 2**. For example if the code size indicated was 4 (image was 4 bits/pixel) +the Clear code value would be 16 (10000 binary). The Clear code can appear at +any point in the image data stream and therefore requires the LZW algorithm to +process succeeding codes as if a new data stream was starting. Encoders should +output a Clear code as the first code of each image data stream. + +2. An End of Information code is defined that explicitly indicates the end of +the image data stream. LZW processing terminates when this code is encountered. +It must be the last code output by the encoder for an image. The value of this +code is +1. + +3. The first available compression code value is +2. + +4. The output codes are of variable length, starting at +1 bits per +code, up to 12 bits per code. This defines a maximum code value of 4095 +(0xFFF). Whenever the LZW code value would exceed the current code length, the +code length is increased by one. The packing/unpacking of these codes must then +be altered to reflect the new code length. + +BUILD 8-BIT BYTES + +Because the LZW compression used for GIF creates a series of variable length +codes, of between 3 and 12 bits each, these codes must be reformed into a +series of 8-bit bytes that will be the characters actually stored or +transmitted. This provides additional compression of the image. The codes are +formed into a stream of bits as if they were packed right to left and then + + + + + + + + 32 + + +picked off 8 bits at a time to be output. + +Assuming a character array of 8 bits per character and using 5 bit codes to be +packed, an example layout would be similar to: + + + +---------------+ + 0 | | bbbaaaaa + +---------------+ + 1 | | dcccccbb + +---------------+ + 2 | | eeeedddd + +---------------+ + 3 | | ggfffffe + +---------------+ + 4 | | hhhhhggg + +---------------+ + . . . + +---------------+ + N | | + +---------------+ + + +Note that the physical packing arrangement will change as the number of bits +per compression code change but the concept remains the same. + +PACKAGE THE BYTES + +Once the bytes have been created, they are grouped into blocks for output by +preceding each block of 0 to 255 bytes with a character count byte. A block +with a zero byte count terminates the Raster Data stream for a given image. +These blocks are what are actually output for the GIF image. This block format +has the side effect of allowing a decoding program the ability to read past the +actual image data if necessary by reading block counts and then skipping over +the data. + + + +FURTHER READING + +[1] Ziv, J. and Lempel, A. : "A Universal Algorithm for Sequential Data +Compression", IEEE Transactions on Information Theory, May 1977. +[2] Welch, T. : "A Technique for High-Performance Data Compression", Computer, +June 1984. +[3] Nelson, M.R. : "LZW Data Compression", Dr. Dobb's Journal, October 1989. + + + + + + + + + + + + + + + + + + + 33 + + +Appendix +G. On-line Capabilities Dialogue. + +NOTE : This section is currently (10 July 1990) under revision; the information +provided here should be used as general guidelines. Code written based on this +information should be designed in a flexible way to accommodate any changes +resulting from the revisions. + +The following sequences are defined for use in mediating control between a GIF +sender and GIF receiver over an interactive communications line. These +sequences do not apply to applications that involve downloading of static GIF +files and are not considered part of a GIF file. + +GIF CAPABILITIES ENQUIRY + +The GIF Capabilities Enquiry sequence is issued from a host and requests an +interactive GIF decoder to return a response message that defines the graphics +parameters for the decoder. This involves returning information about available +screen sizes, number of bits/color supported and the amount of color detail +supported. The escape sequence for the GIF Capabilities Enquiry is defined as: + +ESC[>0g 0x1B 0x5B 0x3E 0x30 0x67 + +GIF CAPABILITIES RESPONSE + +The GIF Capabilities Response message is returned by an interactive GIF decoder +and defines the decoder's display capabilities for all graphics modes that are +supported by the software. Note that this can also include graphics printers as +well as a monitor screen. The general format of this message is: + +#version;protocol{;dev, width, height, color-bits, color-res}... + + +'#' GIF Capabilities Response identifier character. +version GIF format version number; initially '87a'. +protocol='0' No end-to-end protocol supported by decoder Transfer as direct + 8-bit data stream. +protocol='1' Can use CIS B+ error correction protocol to transfer GIF data + interactively from the host directly to the display. +dev = '0' Screen parameter set follows. +dev = '1' Printer parameter set follows. +width Maximum supported display width in pixels. +height Maximum supported display height in pixels. +color-bits Number of bits per pixel supported. The number of supported + colors is therefore 2**color-bits. +color-res Number of bits per color component supported in the hardware + color palette. If color-res is '0' then no hardware palette + table is available. + +Note that all values in the GIF Capabilities Response are returned as ASCII +decimal numbers and the message is terminated by a Carriage Return character. + +The following GIF Capabilities Response message describes three standard IBM PC +Enhanced Graphics Adapter configurations with no printer; the GIF data stream + + + + + + + + + + 34 + + +can be processed within an error correcting protocol: + +#87a;1;0,320,200,4,0;0,640,200,2,2;0,640,350,4,2 + +ENTER GIF GRAPHICS MODE + +Two sequences are currently defined to invoke an interactive GIF decoder into +action. The only difference between them is that different output media are +selected. These sequences are: + +ESC[>1g Display GIF image on screen + + 0x1B 0x5B 0x3E 0x31 0x67 + +ESC[>2g Display image directly to an attached graphics printer. The image may +optionally be displayed on the screen as well. + + 0x1B 0x5B 0x3E 0x32 0x67 + +Note that the 'g' character terminating each sequence is in lowercase. + +INTERACTIVE ENVIRONMENT + +The assumed environment for the transmission of GIF image data from an +interactive application is a full 8-bit data stream from host to micro. All +256 character codes must be transferrable. The establishing of an 8-bit data +path for communications will normally be taken care of by the host application +programs. It is however up to the receiving communications programs supporting +GIF to be able to receive and pass on all 256 8-bit codes to the GIF decoder +software. +. diff --git a/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs index f3bf9b56d6..a02bb5cd5d 100644 --- a/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // @@ -9,7 +9,7 @@ namespace ImageSharp.Quantizers using ImageSharp.PixelFormats; /// - /// Provides methods for allowing quantization of images pixels. + /// Provides methods for for allowing quantization of images pixels with configurable dithering. /// /// The pixel format. public interface IQuantizer : IQuantizer @@ -27,11 +27,9 @@ namespace ImageSharp.Quantizers } /// - /// Provides methods for allowing dithering of quantized image pixels. + /// Provides methods for allowing quantization of images pixels with configurable dithering. /// - /// The pixel format. - public interface IDitheredQuantizer : IQuantizer - where TPixel : struct, IPixel + public interface IQuantizer { /// /// Gets or sets a value indicating whether to apply dithering to the output image. @@ -43,11 +41,4 @@ namespace ImageSharp.Quantizers /// IErrorDiffuser DitherType { get; set; } } - - /// - /// Provides methods for allowing quantization of images pixels. - /// - public interface IQuantizer - { - } } diff --git a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs index d57d297fc8..40bce74c3f 100644 --- a/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/OctreeQuantizer{TPixel}.cs @@ -60,6 +60,7 @@ namespace ImageSharp.Quantizers { this.colors = maxColors.Clamp(1, 255); this.octree = new Octree(this.GetBitsNeededForColorDepth(this.colors)); + this.palette = null; return base.Quantize(image, this.colors); } @@ -137,7 +138,7 @@ namespace ImageSharp.Quantizers { // The colors have changed so we need to use Euclidean distance caclulation to find the closest value. // This palette can never be null here. - return this.GetClosesTPixel(pixel, this.palette, this.colorMap); + return this.GetClosestPixel(pixel, this.palette, this.colorMap); } return (byte)this.octree.GetPaletteIndex(pixel, this.pixelBuffer); diff --git a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs index b5e5ccb25e..7e07da6c3d 100644 --- a/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/PaletteQuantizer{TPixel}.cs @@ -133,7 +133,7 @@ namespace ImageSharp.Quantizers [MethodImpl(MethodImplOptions.AggressiveInlining)] private byte QuantizePixel(TPixel pixel) { - return this.GetClosesTPixel(pixel, this.GetPalette(), this.colorMap); + return this.GetClosestPixel(pixel, this.GetPalette(), this.colorMap); } } } \ No newline at end of file diff --git a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs index 02447062ef..48f33f98b9 100644 --- a/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/Quantizer{TPixel}.cs @@ -15,7 +15,7 @@ namespace ImageSharp.Quantizers /// Encapsulates methods to calculate the color palette of an image. /// /// The pixel format. - public abstract class Quantizer : IDitheredQuantizer + public abstract class Quantizer : IQuantizer where TPixel : struct, IPixel { /// @@ -144,7 +144,7 @@ namespace ImageSharp.Quantizers /// The cache to store the result in. /// The [MethodImpl(MethodImplOptions.AggressiveInlining)] - protected byte GetClosesTPixel(TPixel pixel, TPixel[] colorPalette, Dictionary cache) + protected byte GetClosestPixel(TPixel pixel, TPixel[] colorPalette, Dictionary cache) { // Check if the color is in the lookup table if (cache.ContainsKey(pixel)) diff --git a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs index 482481c710..fb63c9dcd9 100644 --- a/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/WuQuantizer{TPixel}.cs @@ -138,6 +138,7 @@ namespace ImageSharp.Quantizers Guard.NotNull(image, nameof(image)); this.colors = maxColors.Clamp(1, 255); + this.palette = null; try { @@ -832,7 +833,7 @@ namespace ImageSharp.Quantizers { // The colors have changed so we need to use Euclidean distance caclulation to find the closest value. // This palette can never be null here. - return this.GetClosesTPixel(pixel, this.palette, this.colorMap); + return this.GetClosestPixel(pixel, this.palette, this.colorMap); } // Expected order r->g->b->a From df4676022e9cf29f41b6472d6322cdf6bdb8d3a1 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 May 2017 14:54:33 +1000 Subject: [PATCH 20/28] Fix frame delay getting copied over --- src/ImageSharp/Image/ImageFrame{TPixel}.cs | 10 ++++++++++ src/ImageSharp/Image/Image{TPixel}.cs | 3 +-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/ImageSharp/Image/ImageFrame{TPixel}.cs b/src/ImageSharp/Image/ImageFrame{TPixel}.cs index e502950d06..0731b14435 100644 --- a/src/ImageSharp/Image/ImageFrame{TPixel}.cs +++ b/src/ImageSharp/Image/ImageFrame{TPixel}.cs @@ -30,6 +30,16 @@ namespace ImageSharp { } + /// + /// Initializes a new instance of the class. + /// + /// The image to create the frame from. + public ImageFrame(ImageFrame image) + : base(image) + { + this.CopyProperties(image); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/ImageSharp/Image/Image{TPixel}.cs b/src/ImageSharp/Image/Image{TPixel}.cs index f49943b539..9e103c700c 100644 --- a/src/ImageSharp/Image/Image{TPixel}.cs +++ b/src/ImageSharp/Image/Image{TPixel}.cs @@ -79,7 +79,6 @@ namespace ImageSharp public Image(ImageBase other) : base(other) { - this.MetaData = new ImageMetaData(); } /// @@ -107,7 +106,7 @@ namespace ImageSharp /// /// Gets the meta data of the image. /// - public ImageMetaData MetaData { get; private set; } + public ImageMetaData MetaData { get; private set; } = new ImageMetaData(); /// /// Gets the width of the image in inches. It is calculated as the width of the image From de2e51e2244bad482c5ec81543b4fbd6fcea1dc9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 May 2017 15:16:49 +1000 Subject: [PATCH 21/28] Ensure correct disposal method is carried across --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 18 ++++++++++++----- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 12 +++-------- src/ImageSharp/MetaData/IMetaData.cs | 11 +++++++++- src/ImageSharp/MetaData/ImageFrameMetaData.cs | 13 ++++++------ src/ImageSharp/MetaData/ImageMetaData.cs | 20 +++++++++---------- 5 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 93d0bcaf18..5bec460cc7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -8,6 +8,7 @@ namespace ImageSharp.Formats using System; using System.Buffers; using System.IO; + using System.Runtime.CompilerServices; using System.Text; using ImageSharp.PixelFormats; @@ -332,6 +333,7 @@ namespace ImageSharp.Formats /// /// The . /// The pixel array to write to. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void ReadFrameIndices(GifImageDescriptor imageDescriptor, byte[] indices) { int dataSize = this.currentStream.ReadByte(); @@ -366,7 +368,7 @@ namespace ImageSharp.Formats // This initializes the image to become fully transparent because the alpha channel is zero. this.image = Image.Create(imageWidth, imageHeight, this.metaData, this.configuration); - this.SetFrameDelay(this.metaData); + this.SetFrameMetaData(this.metaData); image = this.image; } @@ -380,7 +382,7 @@ namespace ImageSharp.Formats currentFrame = this.previousFrame.Clone(); - this.SetFrameDelay(currentFrame.MetaData); + this.SetFrameMetaData(currentFrame.MetaData); image = currentFrame; @@ -506,14 +508,20 @@ namespace ImageSharp.Formats } /// - /// Sets the frame delay in the metadata. + /// Sets the frames metadata. /// /// The meta data. - private void SetFrameDelay(IMetaData metaData) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetFrameMetaData(IMetaData metaData) { if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) { - metaData.FrameDelay = this.graphicsControlExtension.DelayTime; + if (this.graphicsControlExtension.DelayTime > 0) + { + metaData.FrameDelay = this.graphicsControlExtension.DelayTime; + } + + metaData.DisposalMethod = this.graphicsControlExtension.DisposalMethod; } } } diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index 3546b56628..becb56eabf 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -191,7 +191,7 @@ namespace ImageSharp.Formats { Width = (short)image.Width, Height = (short)image.Height, - GlobalColorTableFlag = false, // Always false for now. + GlobalColorTableFlag = false, // TODO: Always false for now. GlobalColorTableSize = this.bitDepth - 1, BackgroundColorIndex = (byte)tranparencyIndex }; @@ -312,16 +312,10 @@ namespace ImageSharp.Formats private void WriteGraphicalControlExtension(ImageBase image, IMetaData metaData, EndianBinaryWriter writer, int transparencyIndex) where TPixel : struct, IPixel { - // TODO: Check transparency logic. - bool hasTransparent = transparencyIndex < 255; - DisposalMethod disposalMethod = hasTransparent - ? DisposalMethod.RestoreToBackground - : DisposalMethod.Unspecified; - GifGraphicsControlExtension extension = new GifGraphicsControlExtension() { - DisposalMethod = disposalMethod, - TransparencyFlag = hasTransparent, + DisposalMethod = metaData.DisposalMethod, + TransparencyFlag = transparencyIndex < 255, TransparencyIndex = transparencyIndex, DelayTime = metaData.FrameDelay }; diff --git a/src/ImageSharp/MetaData/IMetaData.cs b/src/ImageSharp/MetaData/IMetaData.cs index 38fd313493..6daa04dd63 100644 --- a/src/ImageSharp/MetaData/IMetaData.cs +++ b/src/ImageSharp/MetaData/IMetaData.cs @@ -5,6 +5,8 @@ namespace ImageSharp { + using ImageSharp.Formats; + /// /// Encapsulates the metadata of an image frame. /// @@ -12,10 +14,17 @@ namespace ImageSharp { /// /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to + /// If not 0, when utilized in Gif animation, this field specifies the number of hundredths (1/100) of a second to /// wait before continuing with the processing of the Data Stream. /// The clock starts ticking immediately after the graphic is rendered. /// int FrameDelay { get; set; } + + /// + /// Gets or sets the disposal method for animated images. + /// Primarily used in Gif animation, this field indicates the way in which the graphic is to + /// be treated after being displayed. + /// + DisposalMethod DisposalMethod { get; set; } } } diff --git a/src/ImageSharp/MetaData/ImageFrameMetaData.cs b/src/ImageSharp/MetaData/ImageFrameMetaData.cs index 50119096e9..b55bfd1ad1 100644 --- a/src/ImageSharp/MetaData/ImageFrameMetaData.cs +++ b/src/ImageSharp/MetaData/ImageFrameMetaData.cs @@ -5,6 +5,8 @@ namespace ImageSharp { + using ImageSharp.Formats; + /// /// Encapsulates the metadata of an image frame. /// @@ -29,14 +31,13 @@ namespace ImageSharp DebugGuard.NotNull(other, nameof(other)); this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; } - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// + /// public int FrameDelay { get; set; } + + /// + public DisposalMethod DisposalMethod { get; set; } } } diff --git a/src/ImageSharp/MetaData/ImageMetaData.cs b/src/ImageSharp/MetaData/ImageMetaData.cs index aed6efa2cb..127ae720f2 100644 --- a/src/ImageSharp/MetaData/ImageMetaData.cs +++ b/src/ImageSharp/MetaData/ImageMetaData.cs @@ -7,6 +7,7 @@ namespace ImageSharp { using System; using System.Collections.Generic; + using ImageSharp.Formats; /// /// Encapsulates the metadata of an image. @@ -52,6 +53,7 @@ namespace ImageSharp this.VerticalResolution = other.VerticalResolution; this.Quality = other.Quality; this.FrameDelay = other.FrameDelay; + this.DisposalMethod = other.DisposalMethod; this.RepeatCount = other.RepeatCount; foreach (ImageProperty property in other.Properties) @@ -83,10 +85,10 @@ namespace ImageSharp set { - if (value > 0) - { - this.horizontalResolution = value; - } + if (value > 0) + { + this.horizontalResolution = value; + } } } @@ -116,14 +118,12 @@ namespace ImageSharp /// public ExifProfile ExifProfile { get; set; } - /// - /// Gets or sets the frame delay for animated images. - /// If not 0, this field specifies the number of hundredths (1/100) of a second to - /// wait before continuing with the processing of the Data Stream. - /// The clock starts ticking immediately after the graphic is rendered. - /// + /// public int FrameDelay { get; set; } + /// + public DisposalMethod DisposalMethod { get; set; } + /// /// Gets the list of properties for storing meta information about this image. /// From 6d0e5484209fd809c50b21c31b055b21d6600438 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Mon, 1 May 2017 15:23:47 +1000 Subject: [PATCH 22/28] Fix summary classname --- src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs b/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs index a02bb5cd5d..b7cf2d469a 100644 --- a/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs +++ b/src/ImageSharp/Quantizers/IQuantizer{TPixel}.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // From acc0fa9711bc68126cad29b8311e6c0c73eab6c4 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 1 May 2017 14:10:32 +0100 Subject: [PATCH 23/28] blend functions everywhere. --- .../Brushes/RecolorBrush{TPixel}.cs | 18 +- .../Processors/DrawImageProcessor.cs | 6 +- .../Processors/DrawPathProcessor.cs | 45 ++- .../Processors/FillProcessor.cs | 24 +- src/ImageSharp/GraphicsOptions.cs | 2 +- .../PixelBlenders/PorterDuffFunctions.cs | 4 +- .../PixelFormats/Rgba32.Transforms.cs | 254 --------------- .../PixelFormats/RgbaVector.Transforms.cs | 251 --------------- .../PixelFormats/Vector4BlendTransforms.cs | 292 ------------------ .../Processing/ColorMatrix/Lomograph.cs | 33 +- .../Processing/ColorMatrix/Polaroid.cs | 33 +- .../Processing/Effects/BackgroundColor.cs | 37 ++- .../Processing/Overlays/Vignette.cs | 87 +++++- .../ColorMatrix/LomographProcessor.cs | 12 +- .../ColorMatrix/PolaroidProcessor.cs | 14 +- .../Effects/BackgroundColorProcessor.cs | 38 ++- .../Processors/Overlays/VignetteProcessor.cs | 36 ++- tests/ImageSharp.Benchmarks/Samplers/Glow.cs | 33 +- .../Colors/Rgba32TransformTests.cs | 118 ------- .../Colors/RgbaVectorTransformTests.cs | 120 ------- .../Drawing/DrawImageEffectTest.cs | 46 --- .../ImageSharp.Tests/Drawing/DrawImageTest.cs | 48 +-- 22 files changed, 358 insertions(+), 1193 deletions(-) delete mode 100644 src/ImageSharp/PixelFormats/Rgba32.Transforms.cs delete mode 100644 src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs delete mode 100644 src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs delete mode 100644 tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs delete mode 100644 tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs delete mode 100644 tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs diff --git a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs index 19ce469141..a96202b7b4 100644 --- a/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs +++ b/src/ImageSharp.Drawing/Brushes/RecolorBrush{TPixel}.cs @@ -72,26 +72,29 @@ namespace ImageSharp.Drawing.Brushes /// /// The target color. /// - private readonly Vector4 targeTPixel; + private readonly Vector4 targetColor; /// /// The threshold. /// private readonly float threshold; + private readonly TPixel targetColorPixel; + /// /// Initializes a new instance of the class. /// /// The source pixels. /// Color of the source. - /// Color of the target. + /// Color of the target. /// The threshold . /// The options - public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targeTPixel, float threshold, GraphicsOptions options) + public RecolorBrushApplicator(PixelAccessor sourcePixels, TPixel sourceColor, TPixel targetColor, float threshold, GraphicsOptions options) : base(sourcePixels, options) { this.sourceColor = sourceColor.ToVector4(); - this.targeTPixel = targeTPixel.ToVector4(); + this.targetColor = targetColor.ToVector4(); + this.targetColorPixel = targetColor; // Lets hack a min max extreams for a color space by letteing the IPackedPixel clamp our values to something in the correct spaces :) TPixel maxColor = default(TPixel); @@ -120,11 +123,10 @@ namespace ImageSharp.Drawing.Brushes if (distance <= this.threshold) { float lerpAmount = (this.threshold - distance) / this.threshold; - Vector4 blended = Vector4BlendTransforms.PremultipliedLerp( - background, - this.targeTPixel, + return this.Blender.Blend( + result, + this.targetColorPixel, lerpAmount); - result.PackFromVector4(blended); } return result; diff --git a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs index e58db46895..c49631de85 100644 --- a/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs @@ -74,9 +74,13 @@ namespace ImageSharp.Drawing.Processors 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); + maxX = Math.Min(this.Location.X + this.Size.Width, maxX); + int minY = Math.Max(this.Location.Y, sourceRectangle.Y); int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); + maxY = Math.Min(this.Location.Y + this.Size.Height, maxY); + int width = maxX - minX; using (Buffer amount = new Buffer(width)) using (PixelAccessor toBlendPixels = targetImage.Lock()) @@ -94,7 +98,7 @@ namespace ImageSharp.Drawing.Processors y => { BufferSpan background = sourcePixels.GetRowSpan(y).Slice(minX, width); - BufferSpan foreground = toBlendPixels.GetRowSpan(y - minY).Slice(0, width); + BufferSpan foreground = toBlendPixels.GetRowSpan(y - this.Location.Y).Slice(0, width); this.blender.Blend(background, background, foreground, amount); }); } diff --git a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs index 124722cc09..3fd829549d 100644 --- a/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/DrawPathProcessor.cs @@ -86,37 +86,34 @@ namespace ImageSharp.Drawing.Processors polyStartY = 0; } + int width = maxX - minX; + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.Options.BlenderMode); + Parallel.For( - minY, - maxY, - this.ParallelOptions, - y => - { - int offsetY = y - polyStartY; + minY, + maxY, + this.ParallelOptions, + y => + { + int offsetY = y - polyStartY; - for (int x = minX; x < maxX; x++) + using (Buffer amount = new Buffer(width)) + using (Buffer colors = new Buffer(width)) + { + for (int i = 0; i < width; i++) { - // TODO add find intersections code to skip and scan large regions of this. + int x = i + minX; int offsetX = x - startX; PointInfo info = this.Path.GetPointInfo(offsetX, offsetY); - ColoredPointInfo color = applicator.GetColor(offsetX, offsetY, info); - - float opacity = this.Opacity(color.DistanceFromElement); - - if (opacity > Constants.Epsilon) - { - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = color.Color.ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, opacity); - - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + amount[i] = (this.Opacity(color.DistanceFromElement) * this.Options.BlendPercentage).Clamp(0, 1); + colors[i] = color.Color; } - }); + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); + blender.Blend(destination, destination, colors, amount); + } + }); } } diff --git a/src/ImageSharp.Drawing/Processors/FillProcessor.cs b/src/ImageSharp.Drawing/Processors/FillProcessor.cs index 0634a06a3e..25eccd245c 100644 --- a/src/ImageSharp.Drawing/Processors/FillProcessor.cs +++ b/src/ImageSharp.Drawing/Processors/FillProcessor.cs @@ -62,32 +62,30 @@ namespace ImageSharp.Drawing.Processors startY = 0; } + int width = maxX - minX; + // we could possibly do some optermising by having knowledge about the individual brushes operate // for example If brush is SolidBrush then we could just get the color upfront // and skip using the IBrushApplicator?. using (PixelAccessor sourcePixels = source.Lock()) + using (Buffer amount = new Buffer(width)) using (BrushApplicator applicator = this.brush.CreateApplicator(sourcePixels, sourceRectangle, this.options)) { - Parallel.For( + for (int i = 0; i < width; i++) + { + amount[i] = this.options.BlendPercentage; + } + + Parallel.For( minY, maxY, this.ParallelOptions, y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - - Vector4 backgroundVector = sourcePixels[offsetX, offsetY].ToVector4(); - Vector4 sourceVector = applicator[offsetX, offsetY].ToVector4(); - - Vector4 finalColor = Vector4BlendTransforms.PremultipliedLerp(backgroundVector, sourceVector, 1); + int offsetX = minX - startX; - TPixel packed = default(TPixel); - packed.PackFromVector4(finalColor); - sourcePixels[offsetX, offsetY] = packed; - } + applicator.Apply(amount, offsetX, offsetY); }); } } diff --git a/src/ImageSharp/GraphicsOptions.cs b/src/ImageSharp/GraphicsOptions.cs index c32103652b..f45582e1e6 100644 --- a/src/ImageSharp/GraphicsOptions.cs +++ b/src/ImageSharp/GraphicsOptions.cs @@ -60,7 +60,7 @@ namespace ImageSharp /// public float BlendPercentage { - get => this.blendPercentage ?? 1; + get => (this.blendPercentage ?? 1).Clamp(0, 1); set => this.blendPercentage = value; } diff --git a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs index 6b5126f720..25eb6a5c87 100644 --- a/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs +++ b/src/ImageSharp/PixelFormats/PixelBlenders/PorterDuffFunctions.cs @@ -9,7 +9,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders using System.Runtime.CompilerServices; /// - /// Collection of Porter Duff alpha blending functions + /// Collection of Porter Duff alpha blending functions applying an the 'Over' composition model. /// /// /// These functions are designed to be a general solution for all color cases, @@ -19,7 +19,7 @@ namespace ImageSharp.PixelFormats.PixelBlenders /// Note there are faster functions for when the backdrop color is known /// to be opaque /// - internal static class PorterDuffFunctions + internal static partial class PorterDuffFunctions { /// /// Source over backdrop diff --git a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs b/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs deleted file mode 100644 index bd13a2de1a..0000000000 --- a/src/ImageSharp/PixelFormats/Rgba32.Transforms.cs +++ /dev/null @@ -1,254 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct Rgba32 - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator +(Rgba32 left, Rgba32 right) - { - Vector4 add = left.ToVector4() + right.ToVector4(); - return PackNew(ref add); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static Rgba32 operator -(Rgba32 left, Rgba32 right) - { - Vector4 sub = left.ToVector4() - right.ToVector4(); - return PackNew(ref sub); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Normal(Rgba32 backdrop, Rgba32 source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Multiply(Rgba32 backdrop, Rgba32 source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// 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. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Screen(Rgba32 backdrop, Rgba32 source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 HardLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// 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. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Overlay(Rgba32 backdrop, Rgba32 source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref overlay); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Darken(Rgba32 backdrop, Rgba32 source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref darken); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Lighten(Rgba32 backdrop, Rgba32 source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 SoftLight(Rgba32 backdrop, Rgba32 source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorDodge(Rgba32 backdrop, Rgba32 source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 ColorBurn(Rgba32 backdrop, Rgba32 source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref burn); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Difference(Rgba32 backdrop, Rgba32 source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static Rgba32 Exclusion(Rgba32 backdrop, Rgba32 source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); - return PackNew(ref exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// 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. - /// - /// - /// The - /// - public static Rgba32 Lerp(Rgba32 from, Rgba32 to, float amount) - { - Vector4 lerp = Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount); - return PackNew(ref lerp); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs b/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs deleted file mode 100644 index fec1aa346e..0000000000 --- a/src/ImageSharp/PixelFormats/RgbaVector.Transforms.cs +++ /dev/null @@ -1,251 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - using System.Runtime.CompilerServices; - - /// - /// Provides operators and composition algorithms. - /// - public partial struct RgbaVector - { - /// - /// Adds the second color to the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator +(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector + right.backingVector); - } - - /// - /// Subtracts the second color from the first. - /// - /// The first source color. - /// The second source color. - /// - /// The . - /// - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static RgbaVector operator -(RgbaVector left, RgbaVector right) - { - return new RgbaVector(left.backingVector - right.backingVector); - } - - /// - /// The blending formula simply selects the source color. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Normal(RgbaVector backdrop, RgbaVector source) - { - Vector4 normal = Vector4BlendTransforms.Normal(backdrop.backingVector, source.backingVector); - return new RgbaVector(normal); - } - - /// - /// Blends two colors by multiplication. - /// - /// The source color is multiplied by the destination color and replaces the destination. - /// The resultant color is always at least as dark as either the source or destination color. - /// Multiplying any color with black results in black. Multiplying any color with white preserves the - /// original color. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Multiply(RgbaVector backdrop, RgbaVector source) - { - Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.backingVector, source.backingVector); - return new RgbaVector(multiply); - } - - /// - /// Multiplies the complements of the backdrop and source color values, then complements the result. - /// - /// 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. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Screen(RgbaVector backdrop, RgbaVector source) - { - Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.backingVector, source.backingVector); - return new RgbaVector(subtract); - } - - /// - /// Multiplies or screens the colors, depending on the source color value. The effect is similar to - /// shining a harsh spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector HardLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(hardlight); - } - - /// - /// Multiplies or screens the colors, depending on the backdrop color value. - /// - /// 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. - /// - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Overlay(RgbaVector backdrop, RgbaVector source) - { - Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.backingVector, source.backingVector); - return new RgbaVector(overlay); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Darken(RgbaVector backdrop, RgbaVector source) - { - Vector4 darken = Vector4BlendTransforms.Darken(backdrop.backingVector, source.backingVector); - return new RgbaVector(darken); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Lighten(RgbaVector backdrop, RgbaVector source) - { - Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.backingVector, source.backingVector); - return new RgbaVector(lighten); - } - - /// - /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining - /// a diffused spotlight on the backdrop. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector SoftLight(RgbaVector backdrop, RgbaVector source) - { - Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.backingVector, source.backingVector); - return new RgbaVector(softlight); - } - - /// - /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorDodge(RgbaVector backdrop, RgbaVector source) - { - Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.backingVector, source.backingVector); - return new RgbaVector(dodge); - } - - /// - /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector ColorBurn(RgbaVector backdrop, RgbaVector source) - { - Vector4 burn = Vector4BlendTransforms.Burn(backdrop.backingVector, source.backingVector); - return new RgbaVector(burn); - } - - /// - /// 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. - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Difference(RgbaVector backdrop, RgbaVector source) - { - Vector4 difference = Vector4BlendTransforms.Difference(backdrop.backingVector, source.backingVector); - return new RgbaVector(difference); - } - - /// - /// Produces an effect similar to that of the mode but lower in contrast. Painting with white - /// inverts the backdrop color; painting with black produces no change - /// - /// The backdrop color. - /// The source color. - /// - /// The . - /// - public static RgbaVector Exclusion(RgbaVector backdrop, RgbaVector source) - { - Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.backingVector, source.backingVector); - return new RgbaVector(exclusion); - } - - /// - /// Linearly interpolates from one color to another based on the given weighting. - /// - /// The first color value. - /// The second color value. - /// - /// 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. - /// - /// - /// The - /// - public static RgbaVector Lerp(RgbaVector from, RgbaVector to, float amount) - { - return new RgbaVector(Vector4.Lerp(from.backingVector, to.backingVector, amount)); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs b/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs deleted file mode 100644 index f066e9a816..0000000000 --- a/src/ImageSharp/PixelFormats/Vector4BlendTransforms.cs +++ /dev/null @@ -1,292 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.PixelFormats -{ - using System.Numerics; - - /// - /// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification. - /// - /// - internal class Vector4BlendTransforms - { - /// - /// The blending formula simply selects the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Normal(Vector4 backdrop, Vector4 source) - { - return new Vector4(source.X, source.Y, source.Z, source.W); - } - - /// - /// Blends two vectors by multiplication. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Multiply(Vector4 backdrop, Vector4 source) - { - Vector4 multiply = backdrop * source; - multiply.W = backdrop.W; - return multiply; - } - - /// - /// Multiplies the complements of the backdrop and source vector values, then complements the result. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Screen(Vector4 backdrop, Vector4 source) - { - Vector4 subtract = backdrop + source - (backdrop * source); - subtract.W = backdrop.W; - return subtract; - } - - /// - /// Multiplies or screens the colors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Multiplies or screens the vectors, depending on the backdrop vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Selects the minimum of the backdrop and source vectors. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Darken(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Min(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the max of the backdrop and source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Lighten(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Max(backdrop, source); - result.W = backdrop.W; - return result; - } - - /// - /// Selects the maximum or minimum of the vectors, depending on the source vector value. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Increases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Decreases the backdrop vector to reflect the source vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Subtracts the minimum of the two constituent vectors from the maximum vector. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - public static Vector4 Difference(Vector4 backdrop, Vector4 source) - { - Vector4 result = Vector4.Abs(backdrop - source); - result.W = backdrop.W; - return result; - } - - /// - /// Produces an effect similar to that of the mode but lower in magnitude. - /// - /// The backdrop vector. - /// The source vector. - /// - /// The . - /// - 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); - } - - /// - /// Linearly interpolates from one vector to another based on the given weighting. - /// The two vectors are premultiplied before operating. - /// - /// The backdrop vector. - /// The source vector. - /// - /// 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. - /// - /// - /// The - /// - public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) - { - amount = amount.Clamp(0, 1); - - // Santize on zero alpha - if (MathF.Abs(backdrop.W) < Constants.Epsilon) - { - source.W *= amount; - return source; - } - - if (MathF.Abs(source.W) < Constants.Epsilon) - { - return backdrop; - } - - // Premultiply the source vector. - // Oddly premultiplying the background vector creates dark outlines when pixels - // Have low alpha values. - source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); - - // This should be implementing the following formula - // https://en.wikipedia.org/wiki/Alpha_compositing - // Vout = Vs + Vb (1 - Vsa) - // Aout = Vsa + Vsb (1 - Vsa) - Vector3 inverseW = new Vector3(1 - source.W); - Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); - Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); - - return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); - } - - /// - /// Multiplies or screens the backdrop component, depending on the component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendOverlay(float b, float s) - { - return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); - } - - /// - /// Darkens or lightens the backdrop component, depending on the source component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendSoftLight(float b, float s) - { - return s <= .5F ? ((2F * b * s) + (b * b * (1F - (2F * s)))) : (MathF.Sqrt(b) * ((2F * s) - 1F)) + (2F * b * (1F - s)); - } - - /// - /// Brightens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendDodge(float b, float s) - { - return MathF.Abs(s - 1F) < Constants.Epsilon ? s : MathF.Min(b / (1F - s), 1F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendBurn(float b, float s) - { - return MathF.Abs(s) < Constants.Epsilon ? s : MathF.Max(1F - ((1F - b) / s), 0F); - } - - /// - /// Darkens the backdrop component to reflect the source component. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendExclusion(float b, float s) - { - return b + s - (2F * b * s); - } - } -} \ No newline at end of file diff --git a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs index 3299add7f7..937dca9ba8 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Lomograph.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Lomograph(this Image source) where TPixel : struct, IPixel { - return Lomograph(source, source.Bounds); + return Lomograph(source, source.Bounds, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Lomograph(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new LomographProcessor(), rectangle); + return Lomograph(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Lomograph(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Lomograph camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Lomograph(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new LomographProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs index 194800ec72..f1a573c05d 100644 --- a/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs +++ b/src/ImageSharp/Processing/ColorMatrix/Polaroid.cs @@ -26,7 +26,7 @@ namespace ImageSharp public static Image Polaroid(this Image source) where TPixel : struct, IPixel { - return Polaroid(source, source.Bounds); + return Polaroid(source, GraphicsOptions.Default); } /// @@ -41,7 +41,36 @@ namespace ImageSharp public static Image Polaroid(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new PolaroidProcessor(), rectangle); + return Polaroid(source, rectangle, GraphicsOptions.Default); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Polaroid(source, source.Bounds, options); + } + + /// + /// Alters the colors of the image recreating an old Polaroid camera effect. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Polaroid(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new PolaroidProcessor(options), rectangle); return source; } } diff --git a/src/ImageSharp/Processing/Effects/BackgroundColor.cs b/src/ImageSharp/Processing/Effects/BackgroundColor.cs index a1914fee32..f52cf1cb2a 100644 --- a/src/ImageSharp/Processing/Effects/BackgroundColor.cs +++ b/src/ImageSharp/Processing/Effects/BackgroundColor.cs @@ -16,6 +16,38 @@ namespace ImageSharp /// public static partial class ImageExtensions { + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return BackgroundColor(source, color, source.Bounds, options); + } + + /// + /// Replaces the background color of image with the given one. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the background. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + source.ApplyProcessor(new BackgroundColorProcessor(color, options), rectangle); + return source; + } + /// /// Replaces the background color of image with the given one. /// @@ -26,7 +58,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color) where TPixel : struct, IPixel { - return BackgroundColor(source, color, source.Bounds); + return BackgroundColor(source, color, GraphicsOptions.Default); } /// @@ -42,8 +74,7 @@ namespace ImageSharp public static Image BackgroundColor(this Image source, TPixel color, Rectangle rectangle) where TPixel : struct, IPixel { - source.ApplyProcessor(new BackgroundColorProcessor(color), rectangle); - return source; + return BackgroundColor(source, color, rectangle, GraphicsOptions.Default); } } } diff --git a/src/ImageSharp/Processing/Overlays/Vignette.cs b/src/ImageSharp/Processing/Overlays/Vignette.cs index f805dd07a0..2eaf2e1efa 100644 --- a/src/ImageSharp/Processing/Overlays/Vignette.cs +++ b/src/ImageSharp/Processing/Overlays/Vignette.cs @@ -25,7 +25,7 @@ namespace ImageSharp public static Image Vignette(this Image source) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, GraphicsOptions.Default); } /// @@ -38,7 +38,7 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color) where TPixel : struct, IPixel { - return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds); + return Vignette(source, color, GraphicsOptions.Default); } /// @@ -52,7 +52,7 @@ namespace ImageSharp public static Image Vignette(this Image source, float radiusX, float radiusY) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds); + return Vignette(source, radiusX, radiusY, GraphicsOptions.Default); } /// @@ -67,7 +67,7 @@ namespace ImageSharp public static Image Vignette(this Image source, Rectangle rectangle) where TPixel : struct, IPixel { - return Vignette(source, NamedColors.Black, 0, 0, rectangle); + return Vignette(source, rectangle, GraphicsOptions.Default); } /// @@ -85,7 +85,84 @@ namespace ImageSharp public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle) where TPixel : struct, IPixel { - VignetteProcessor processor = new VignetteProcessor(color) { RadiusX = radiusX, RadiusY = radiusY }; + return Vignette(source, color, radiusX, radiusY, rectangle, GraphicsOptions.Default); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, color, source.Bounds.Width * .5F, source.Bounds.Height * .5F, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The the x-radius. + /// The the y-radius. + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, float radiusX, float radiusY, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, radiusX, radiusY, source.Bounds, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + return Vignette(source, NamedColors.Black, 0, 0, rectangle, options); + } + + /// + /// Applies a radial vignette effect to an image. + /// + /// The pixel format. + /// The image this method extends. + /// The color to set as the vignette. + /// The the x-radius. + /// The the y-radius. + /// + /// The structure that specifies the portion of the image object to alter. + /// + /// The options effecting pixel blending. + /// The . + public static Image Vignette(this Image source, TPixel color, float radiusX, float radiusY, Rectangle rectangle, GraphicsOptions options) + where TPixel : struct, IPixel + { + VignetteProcessor processor = new VignetteProcessor(color, options) { RadiusX = radiusX, RadiusY = radiusY }; source.ApplyProcessor(processor, rectangle); return source; } diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs index 70b9979972..f6480c1837 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/LomographProcessor.cs @@ -18,6 +18,16 @@ namespace ImageSharp.Processing.Processors where TPixel : struct, IPixel { private static readonly TPixel VeryDarkGreen = ColorBuilder.FromRGBA(0, 10, 0, 255); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public LomographProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -34,7 +44,7 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(VeryDarkGreen).Apply(source, sourceRectangle); + new VignetteProcessor(VeryDarkGreen, this.options).Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs index c06275314b..5df034add2 100644 --- a/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs +++ b/src/ImageSharp/Processing/Processors/ColorMatrix/PolaroidProcessor.cs @@ -19,6 +19,16 @@ namespace ImageSharp.Processing.Processors { private static TPixel veryDarkOrange = ColorBuilder.FromRGB(102, 34, 0); private static TPixel lightOrange = ColorBuilder.FromRGBA(255, 153, 102, 178); + private GraphicsOptions options; + + /// + /// Initializes a new instance of the class. + /// + /// The options effecting blending and composition. + public PolaroidProcessor(GraphicsOptions options) + { + this.options = options; + } /// public override Matrix4x4 Matrix => new Matrix4x4() @@ -41,8 +51,8 @@ namespace ImageSharp.Processing.Processors /// protected override void AfterApply(ImageBase source, Rectangle sourceRectangle) { - new VignetteProcessor(veryDarkOrange).Apply(source, sourceRectangle); - new GlowProcessor(lightOrange, GraphicsOptions.Default) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); + new VignetteProcessor(veryDarkOrange, this.options).Apply(source, sourceRectangle); + new GlowProcessor(lightOrange, this.options) { Radius = source.Width / 4F }.Apply(source, sourceRectangle); } } } \ No newline at end of file diff --git a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs index 21973de3e4..511a810b27 100644 --- a/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/BackgroundColorProcessor.cs @@ -18,13 +18,17 @@ namespace ImageSharp.Processing.Processors internal class BackgroundColorProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + /// /// Initializes a new instance of the class. /// /// The to set the background color to. - public BackgroundColorProcessor(TPixel color) + /// The options defining blending algorithum and amount. + public BackgroundColorProcessor(TPixel color, GraphicsOptions options) { this.Value = color; + this.options = options; } /// @@ -57,10 +61,19 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 backgroundColor = this.Value.ToVector4(); + int width = maxX - minX; + using (Buffer colors = new Buffer(width)) + using (Buffer amount = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + colors[i] = this.Value; + amount[i] = this.options.BlendPercentage; + } + + PixelBlender blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); Parallel.For( minY, maxY, @@ -68,26 +81,11 @@ namespace ImageSharp.Processing.Processors y => { int offsetY = y - startY; - for (int x = minX; x < maxX; x++) - { - int offsetX = x - startX; - Vector4 color = sourcePixels[offsetX, offsetY].ToVector4(); - float a = color.W; - - if (a < 1 && a > 0) - { - color = Vector4BlendTransforms.PremultipliedLerp(backgroundColor, color, .5F); - } - if (MathF.Abs(a) < Constants.Epsilon) - { - color = backgroundColor; - } + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(minX - startX, width); - TPixel packed = default(TPixel); - packed.PackFromVector4(color); - sourcePixels[offsetX, offsetY] = packed; - } + // this switched color & destination in the 2nd and 3rd places because we are applying the target colour under the current one + blender.Blend(destination, colors, destination, amount); }); } } diff --git a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs index 31e813564b..d698b543c4 100644 --- a/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Overlays/VignetteProcessor.cs @@ -18,13 +18,20 @@ namespace ImageSharp.Processing.Processors internal class VignetteProcessor : ImageProcessor where TPixel : struct, IPixel { + private readonly GraphicsOptions options; + private readonly PixelBlender blender; + /// /// Initializes a new instance of the class. /// /// The color of the vignette. - public VignetteProcessor(TPixel color) + /// The options effecting blending and composition. + public VignetteProcessor(TPixel color, GraphicsOptions options) { this.VignetteColor = color; + + this.options = options; + this.blender = PixelOperations.Instance.GetPixelBlender(this.options.BlenderMode); } /// @@ -72,23 +79,34 @@ namespace ImageSharp.Processing.Processors startY = 0; } + int width = maxX - minX; + using (Buffer rowColors = new Buffer(width)) using (PixelAccessor sourcePixels = source.Lock()) { + for (int i = 0; i < width; i++) + { + rowColors[i] = vignetteColor; + } + Parallel.For( minY, maxY, this.ParallelOptions, y => { - int offsetY = y - startY; - for (int x = minX; x < maxX; x++) + using (Buffer amounts = new Buffer(width)) { - int offsetX = x - startX; - float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); - Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); - TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, vignetteColor.ToVector4(), .9F * (distance / maxDistance))); - sourcePixels[offsetX, offsetY] = packed; + int offsetY = y - startY; + int offsetX = minX - startX; + for (int i = 0; i < width; i++) + { + float distance = Vector2.Distance(centre, new Vector2(i + offsetX, offsetY)); + amounts[i] = (this.options.BlendPercentage * (.9F * (distance / maxDistance))).Clamp(0, 1); + } + + BufferSpan destination = sourcePixels.GetRowSpan(offsetY).Slice(offsetX, width); + + this.blender.Blend(destination, destination, rowColors, amounts); } }); } diff --git a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs index 748bbf4fad..6daf120fac 100644 --- a/tests/ImageSharp.Benchmarks/Samplers/Glow.cs +++ b/tests/ImageSharp.Benchmarks/Samplers/Glow.cs @@ -122,12 +122,43 @@ namespace ImageSharp.Benchmarks float distance = Vector2.Distance(centre, new Vector2(offsetX, offsetY)); Vector4 sourceColor = sourcePixels[offsetX, offsetY].ToVector4(); TPixel packed = default(TPixel); - packed.PackFromVector4(Vector4BlendTransforms.PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); + packed.PackFromVector4(PremultipliedLerp(sourceColor, glowColor.ToVector4(), 1 - (.95F * (distance / maxDistance)))); sourcePixels[offsetX, offsetY] = packed; } }); } } + public static Vector4 PremultipliedLerp(Vector4 backdrop, Vector4 source, float amount) + { + amount = amount.Clamp(0, 1); + + // Santize on zero alpha + if (MathF.Abs(backdrop.W) < Constants.Epsilon) + { + source.W *= amount; + return source; + } + + if (MathF.Abs(source.W) < Constants.Epsilon) + { + return backdrop; + } + + // Premultiply the source vector. + // Oddly premultiplying the background vector creates dark outlines when pixels + // Have low alpha values. + source = new Vector4(source.X, source.Y, source.Z, 1) * (source.W * amount); + + // This should be implementing the following formula + // https://en.wikipedia.org/wiki/Alpha_compositing + // Vout = Vs + Vb (1 - Vsa) + // Aout = Vsa + Vsb (1 - Vsa) + Vector3 inverseW = new Vector3(1 - source.W); + Vector3 xyzB = new Vector3(backdrop.X, backdrop.Y, backdrop.Z); + Vector3 xyzS = new Vector3(source.X, source.Y, source.Z); + + return new Vector4(xyzS + (xyzB * inverseW), source.W + (backdrop.W * (1 - source.W))); + } } } } diff --git a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs b/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs deleted file mode 100644 index 8d5e973b13..0000000000 --- a/tests/ImageSharp.Tests/Colors/Rgba32TransformTests.cs +++ /dev/null @@ -1,118 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class ColorTransformTests - { - /// - /// Orange backdrop - /// - private static readonly Rgba32 Backdrop = new Rgba32(204, 102, 0); - - /// - /// Blue source - /// - private static readonly Rgba32 Source = new Rgba32(0, 102, 153); - - [Fact] - public void Normal() - { - Rgba32 normal = Rgba32.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - [Fact] - public void Multiply() - { - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.Black) == Rgba32.Black); - Assert.True(Rgba32.Multiply(Backdrop, Rgba32.White) == Backdrop); - - Rgba32 multiply = Rgba32.Multiply(Backdrop, Source); - Assert.True(multiply == new Rgba32(0, 41, 0)); - } - - [Fact] - public void Screen() - { - Assert.True(Rgba32.Screen(Backdrop, Rgba32.Black) == Backdrop); - Assert.True(Rgba32.Screen(Backdrop, Rgba32.White) == Rgba32.White); - - Rgba32 screen = Rgba32.Screen(Backdrop, Source); - Assert.True(screen == new Rgba32(204, 163, 153)); - } - - [Fact] - public void HardLight() - { - Rgba32 hardLight = Rgba32.HardLight(Backdrop, Source); - Assert.True(hardLight == new Rgba32(0, 82, 51)); - } - - [Fact] - public void Overlay() - { - Rgba32 overlay = Rgba32.Overlay(Backdrop, Source); - Assert.True(overlay == new Rgba32(153, 82, 0)); - } - - [Fact] - public void Darken() - { - Rgba32 darken = Rgba32.Darken(Backdrop, Source); - Assert.True(darken == new Rgba32(0, 102, 0)); - } - - [Fact] - public void Lighten() - { - Rgba32 lighten = Rgba32.Lighten(Backdrop, Source); - Assert.True(lighten == new Rgba32(204, 102, 153)); - } - - [Fact] - public void SoftLight() - { - Rgba32 softLight = Rgba32.SoftLight(Backdrop, Source); - Assert.True(softLight == new Rgba32(163, 90, 0)); - } - - [Fact] - public void ColorDodge() - { - Rgba32 colorDodge = Rgba32.ColorDodge(Backdrop, Source); - Assert.True(colorDodge == new Rgba32(204, 170, 0)); - } - - [Fact] - public void ColorBurn() - { - Rgba32 colorBurn = Rgba32.ColorBurn(Backdrop, Source); - Assert.True(colorBurn == new Rgba32(0, 0, 0)); - } - - [Fact] - public void Difference() - { - Rgba32 difference = Rgba32.Difference(Backdrop, Source); - Assert.True(difference == new Rgba32(204, 0, 153)); - } - - [Fact] - public void Exclusion() - { - Rgba32 exclusion = Rgba32.Exclusion(Backdrop, Source); - Assert.True(exclusion == new Rgba32(204, 122, 153)); - } - } -} diff --git a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs b/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs deleted file mode 100644 index 81cbb63c41..0000000000 --- a/tests/ImageSharp.Tests/Colors/RgbaVectorTransformTests.cs +++ /dev/null @@ -1,120 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests.Colors -{ - using ImageSharp.PixelFormats; - using Xunit; - - /// - /// Tests the color transform algorithms. Test results match the output of CSS equivalents. - /// - /// - public class RgbaVectorTransformTests - { - private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(0.01F); - - /// - /// Orange backdrop - /// - private static readonly RgbaVector Backdrop = new RgbaVector(204, 102, 0); - - /// - /// Blue source - /// - private static readonly RgbaVector Source = new RgbaVector(0, 102, 153); - - [Fact] - public void Normal() - { - RgbaVector normal = RgbaVector.Normal(Backdrop, Source); - Assert.True(normal == Source); - } - - // TODO: These tests keep sporadically breaking our builds. Fins out why they work locally but not on the CI. - // [Fact] - // public void Multiply() - // { - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.Black).ToVector4(), RgbaVector.Black.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Multiply(Backdrop, RgbaVector.White).ToVector4(), Backdrop.ToVector4(), FloatComparer); - - // RgbaVector multiply = RgbaVector.Multiply(Backdrop, Source); - // Assert.Equal(multiply.ToVector4(), new RgbaVector(0, 41, 0).ToVector4(), FloatComparer); - // } - - // [Fact] - // public void Screen() - // { - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer); - // Assert.Equal(RgbaVector.Screen(Backdrop, RgbaVector.White).ToVector4(), RgbaVector.White.ToVector4(), FloatComparer); - - // RgbaVector screen = RgbaVector.Screen(Backdrop, Source); - // Assert.Equal(screen.ToVector4(), new RgbaVector(204, 163, 153).ToVector4(), FloatComparer); - // } - - [Fact] - public void HardLight() - { - RgbaVector hardLight = RgbaVector.HardLight(Backdrop, Source); - Assert.Equal(hardLight.ToVector4(), new RgbaVector(0, 82, 51).ToVector4(), FloatComparer); - } - - [Fact] - public void Overlay() - { - RgbaVector overlay = RgbaVector.Overlay(Backdrop, Source); - Assert.Equal(overlay.ToVector4(), new RgbaVector(153, 82, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Darken() - { - RgbaVector darken = RgbaVector.Darken(Backdrop, Source); - Assert.Equal(darken.ToVector4(), new RgbaVector(0, 102, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Lighten() - { - RgbaVector lighten = RgbaVector.Lighten(Backdrop, Source); - Assert.Equal(lighten.ToVector4(), new RgbaVector(204, 102, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void SoftLight() - { - RgbaVector softLight = RgbaVector.SoftLight(Backdrop, Source); - Assert.Equal(softLight.ToVector4(), new RgbaVector(163, 90, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorDodge() - { - RgbaVector colorDodge = RgbaVector.ColorDodge(Backdrop, Source); - Assert.Equal(colorDodge.ToVector4(), new RgbaVector(204, 170, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void ColorBurn() - { - RgbaVector colorBurn = RgbaVector.ColorBurn(Backdrop, Source); - Assert.Equal(colorBurn.ToVector4(), new RgbaVector(0, 0, 0).ToVector4(), FloatComparer); - } - - [Fact] - public void Difference() - { - RgbaVector difference = RgbaVector.Difference(Backdrop, Source); - Assert.Equal(difference.ToVector4(), new RgbaVector(204, 0, 153).ToVector4(), FloatComparer); - } - - [Fact] - public void Exclusion() - { - RgbaVector exclusion = RgbaVector.Exclusion(Backdrop, Source); - Assert.Equal(exclusion.ToVector4(), new RgbaVector(204, 122, 153).ToVector4(), FloatComparer); - } - } -} diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs deleted file mode 100644 index 885029fdff..0000000000 --- a/tests/ImageSharp.Tests/Drawing/DrawImageEffectTest.cs +++ /dev/null @@ -1,46 +0,0 @@ -// -// Copyright (c) James Jackson-South and contributors. -// Licensed under the Apache License, Version 2.0. -// - -namespace ImageSharp.Tests -{ - using System.IO; - using ImageSharp.Drawing; - using ImageSharp.PixelFormats; - using Xunit; - - public class DrawImageEffectTest : FileTestBase - { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { - string path = this.CreateOutputDirectory("Drawing", "DrawImageEffect"); - - PixelBlenderMode[] modes = (PixelBlenderMode[])System.Enum.GetValues(typeof(PixelBlenderMode)); - - using (Image blend = TestFile.Create(TestImages.Png.Blur).CreateImage()) - { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - foreach (PixelBlenderMode mode in modes) - { - using (FileStream output = File.OpenWrite($"{path}/{mode}.{file.FileName}")) - { - Size size = new Size(image.Width / 2, image.Height / 2); - Point loc = new Point(image.Width / 4, image.Height / 4); - - image.DrawImage(blend, size, loc, new GraphicsOptions() { - BlenderMode = mode, - BlendPercentage = .75f - }).Save(output); - } - } - } - } - } - } - } -} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs index f87a6170d9..030034a8f2 100644 --- a/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs +++ b/tests/ImageSharp.Tests/Drawing/DrawImageTest.cs @@ -6,30 +6,42 @@ namespace ImageSharp.Tests { using System.IO; - + using System.Linq; + using ImageSharp.PixelFormats; using Xunit; public class DrawImageTest : FileTestBase { - [Fact] - public void ImageShouldApplyDrawImageFilter() - { - string path = this.CreateOutputDirectory("Drawing", "DrawImage"); + private const PixelTypes PixelTypes = Tests.PixelTypes.StandardImageClass; + + public static readonly string[] TestFiles = { + TestImages.Jpeg.Baseline.Calliphora, + TestImages.Bmp.Car, + TestImages.Png.Splash, + TestImages.Gif.Rings + }; - using (Image blend = TestFile.Create(TestImages.Bmp.Car).CreateImage()) + object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast().Select(x => new object[] { x }).ToArray(); + + [Theory] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Add)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Substract)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Screen)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Darken)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Lighten)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Overlay)] + [WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.HardLight)] + public void ImageShouldApplyDrawImage(TestImageProvider provider, PixelBlenderMode mode) + where TPixel : struct, IPixel + { + using (Image image = provider.GetImage()) + using (Image blend = Image.Load(TestFile.Create(TestImages.Bmp.Car).Bytes)) { - foreach (TestFile file in Files) - { - using (Image image = file.CreateImage()) - { - using (FileStream output = File.OpenWrite($"{path}/{file.FileName}")) - { - image.DrawImage(blend, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) - .Save(output); - } - } - } + image.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)) + .DebugSave(provider, new { mode }); } } } -} \ No newline at end of file +} From cfe1f8ca4a2743750807c486cfd13a8dae0aa3c7 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 1 May 2017 14:19:31 +0100 Subject: [PATCH 24/28] make alppha process pecentage consistent with blenders --- src/ImageSharp/Processing/Effects/Alpha.cs | 8 ++++---- .../Processors/Effects/AlphaProcessor.cs | 14 ++++++-------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/src/ImageSharp/Processing/Effects/Alpha.cs b/src/ImageSharp/Processing/Effects/Alpha.cs index a54bde675b..a2f721cfac 100644 --- a/src/ImageSharp/Processing/Effects/Alpha.cs +++ b/src/ImageSharp/Processing/Effects/Alpha.cs @@ -21,9 +21,9 @@ namespace ImageSharp /// /// The pixel format. /// The image this method extends. - /// The new opacity of the image. Must be between 0 and 100. + /// The new opacity of the image. Must be between 0 and 1. /// The . - public static Image Alpha(this Image source, int percent) + public static Image Alpha(this Image source, float percent) where TPixel : struct, IPixel { return Alpha(source, percent, source.Bounds); @@ -34,12 +34,12 @@ namespace ImageSharp /// /// The pixel format. /// The image this method extends. - /// The new opacity of the image. Must be between 0 and 100. + /// The new opacity of the image. Must be between 0 and 1. /// /// The structure that specifies the portion of the image object to alter. /// /// The . - public static Image Alpha(this Image source, int percent, Rectangle rectangle) + public static Image Alpha(this Image source, float percent, Rectangle rectangle) where TPixel : struct, IPixel { source.ApplyProcessor(new AlphaProcessor(percent), rectangle); diff --git a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs index a601065461..5e7310e32b 100644 --- a/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs +++ b/src/ImageSharp/Processing/Processors/Effects/AlphaProcessor.cs @@ -21,26 +21,24 @@ namespace ImageSharp.Processing.Processors /// /// Initializes a new instance of the class. /// - /// The percentage to adjust the opacity of the image. Must be between 0 and 100. + /// The percentage to adjust the opacity of the image. Must be between 0 and 1. /// - /// is less than 0 or is greater than 100. + /// is less than 0 or is greater than 1. /// - public AlphaProcessor(int percent) + public AlphaProcessor(float percent) { - Guard.MustBeBetweenOrEqualTo(percent, 0, 100, nameof(percent)); + Guard.MustBeBetweenOrEqualTo(percent, 0, 1, nameof(percent)); this.Value = percent; } /// /// Gets the alpha value. /// - public int Value { get; } + public float Value { get; } /// protected override void OnApply(ImageBase source, Rectangle sourceRectangle) { - float alpha = this.Value / 100F; - int startY = sourceRectangle.Y; int endY = sourceRectangle.Bottom; int startX = sourceRectangle.X; @@ -63,7 +61,7 @@ namespace ImageSharp.Processing.Processors startY = 0; } - Vector4 alphaVector = new Vector4(1, 1, 1, alpha); + Vector4 alphaVector = new Vector4(1, 1, 1, this.Value); using (PixelAccessor sourcePixels = source.Lock()) { From 4b5efa0b77136870677339aa5759f0331c24efa9 Mon Sep 17 00:00:00 2001 From: Scott Williams Date: Mon, 1 May 2017 14:41:52 +0100 Subject: [PATCH 25/28] fix alpha tests --- src/ImageSharp.Drawing/DrawImage.cs | 2 +- tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/ImageSharp.Drawing/DrawImage.cs b/src/ImageSharp.Drawing/DrawImage.cs index 9ea9b549a5..acc8212921 100644 --- a/src/ImageSharp.Drawing/DrawImage.cs +++ b/src/ImageSharp.Drawing/DrawImage.cs @@ -96,7 +96,7 @@ namespace ImageSharp /// The image this method extends. /// The image to blend with the currently processing image. /// The pixel format. - /// The opacity of the image image to blend. Must be between 0 and 100. + /// The opacity of the image image to blend. Must be between 0 and 1. /// The size to draw the blended image. /// The location to draw the blended image. /// The . diff --git a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs index 2b39086e34..e1557abcab 100644 --- a/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs +++ b/tests/ImageSharp.Tests/Processors/Filters/AlphaTest.cs @@ -11,11 +11,11 @@ namespace ImageSharp.Tests public class AlphaTest : FileTestBase { - public static readonly TheoryData AlphaValues - = new TheoryData + public static readonly TheoryData AlphaValues + = new TheoryData { - 20 , - 80 + 20/100f , + 80/100f }; [Theory] From ed69858f24c720598af4b39acc0675d0e570edfc Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 2 May 2017 11:13:16 +1000 Subject: [PATCH 26/28] Rename to hasFrames --- src/ImageSharp/Formats/Gif/GifEncoderCore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs index becb56eabf..5dc1a150d0 100644 --- a/src/ImageSharp/Formats/Gif/GifEncoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifEncoderCore.cs @@ -38,7 +38,7 @@ namespace ImageSharp.Formats /// /// Whether the current image has multiple frames. /// - private bool hasMultipleFrames; + private bool hasFrames; /// /// Initializes a new instance of the class. @@ -79,11 +79,11 @@ namespace ImageSharp.Formats this.bitDepth = ImageMaths.GetBitsNeededForColorDepth(quality); // Quantize the image returning a palette. - this.hasMultipleFrames = image.Frames.Any(); + this.hasFrames = image.Frames.Any(); // Dithering when animating gifs is a bad idea as we introduce pixel tearing across frames. IQuantizer ditheredQuantizer = (IQuantizer)this.Quantizer; - ditheredQuantizer.Dither = !this.hasMultipleFrames; + ditheredQuantizer.Dither = !this.hasFrames; QuantizedImage quantized = ditheredQuantizer.Quantize(image, quality); @@ -103,7 +103,7 @@ namespace ImageSharp.Formats this.WriteImageData(quantized, writer); // Write additional frames. - if (this.hasMultipleFrames) + if (this.hasFrames) { this.WriteApplicationExtension(writer, image.MetaData.RepeatCount, image.Frames.Count); From e2533d3635b30fd029187a63a7e4849b86467981 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 2 May 2017 11:16:09 +1000 Subject: [PATCH 27/28] Remove unneeded check --- src/ImageSharp/Formats/Gif/GifDecoderCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs index 5bec460cc7..589b7037a7 100644 --- a/src/ImageSharp/Formats/Gif/GifDecoderCore.cs +++ b/src/ImageSharp/Formats/Gif/GifDecoderCore.cs @@ -514,7 +514,7 @@ namespace ImageSharp.Formats [MethodImpl(MethodImplOptions.AggressiveInlining)] private void SetFrameMetaData(IMetaData metaData) { - if (this.graphicsControlExtension != null && this.graphicsControlExtension.DelayTime > 0) + if (this.graphicsControlExtension != null) { if (this.graphicsControlExtension.DelayTime > 0) { From c18c26103307b710ea41ccd632a0d3763c81ea6c Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 2 May 2017 11:16:21 +1000 Subject: [PATCH 28/28] Update tests --- tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs | 3 +++ tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs index 24dd2eac56..da2bb41561 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageFrameMetaDataTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests { + using ImageSharp.Formats; using Xunit; /// @@ -17,10 +18,12 @@ namespace ImageSharp.Tests { ImageFrameMetaData metaData = new ImageFrameMetaData(); metaData.FrameDelay = 42; + metaData.DisposalMethod = DisposalMethod.RestoreToBackground; ImageFrameMetaData clone = new ImageFrameMetaData(metaData); Assert.Equal(42, clone.FrameDelay); + Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); } } } diff --git a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs index 3c0057b627..4ef9c57aa3 100644 --- a/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs +++ b/tests/ImageSharp.Tests/MetaData/ImageMetaDataTests.cs @@ -5,6 +5,7 @@ namespace ImageSharp.Tests { + using ImageSharp.Formats; using Xunit; /// @@ -27,6 +28,7 @@ namespace ImageSharp.Tests metaData.Properties.Add(imageProperty); metaData.Quality = 24; metaData.RepeatCount = 1; + metaData.DisposalMethod = DisposalMethod.RestoreToBackground; ImageMetaData clone = new ImageMetaData(metaData); @@ -37,6 +39,7 @@ namespace ImageSharp.Tests Assert.Equal(imageProperty, clone.Properties[0]); Assert.Equal(24, clone.Quality); Assert.Equal(1, clone.RepeatCount); + Assert.Equal(DisposalMethod.RestoreToBackground, clone.DisposalMethod); } [Fact]