Browse Source

Merge branch 'master' of https://github.com/SixLabors/ImageSharp into feature/memory-manager

# Conflicts:
#	src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
#	src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
#	tests/ImageSharp.Tests/Image/PixelAccessorTests.cs
pull/475/head
Anton Firszov 8 years ago
parent
commit
9368d3ef4e
  1. 53
      src/ImageSharp.Drawing/DrawImage.cs
  2. 76
      src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs
  3. 2
      src/ImageSharp/GraphicsOptions.cs
  4. 5
      src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs
  5. 13
      src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs
  6. 11
      src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs
  7. 37
      src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs
  8. 10
      src/ImageSharp/Processing/Transforms/Transform.cs
  9. 48
      src/ImageSharp/Processing/Transforms/TransformHelpers.cs
  10. 38
      tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

53
src/ImageSharp.Drawing/DrawImage.cs

@ -18,24 +18,13 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The options.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Size size, Point location, GraphicsOptions options)
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, Point location, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
if (size == default(Size))
{
size = new Size(image.Width, image.Height);
}
if (location == default(Point))
{
location = Point.Empty;
}
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, size, location, options));
source.ApplyProcessor(new DrawImageProcessor<TPixel>(image, location, options));
return source;
}
@ -45,14 +34,14 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float percent)
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return DrawImage(source, image, default(Size), default(Point), options);
options.BlendPercentage = opacity;
return DrawImage(source, image, Point.Empty, options);
}
/// <summary>
@ -62,15 +51,15 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="blender">The blending mode.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent)
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
options.BlendPercentage = opacity;
options.BlenderMode = blender;
return DrawImage(source, image, default(Size), default(Point), options);
return DrawImage(source, image, Point.Empty, options);
}
/// <summary>
@ -79,12 +68,12 @@ namespace SixLabors.ImageSharp
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="options">The options, including the blending type and belnding amount.</param>
/// <param name="options">The options, including the blending type and blending amount.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> Blend<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
return DrawImage(source, image, default(Size), default(Point), options);
return DrawImage(source, image, Point.Empty, options);
}
/// <summary>
@ -93,16 +82,15 @@ namespace SixLabors.ImageSharp
/// <param name="source">The image this method extends.</param>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float percent, Size size, Point location)
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, float opacity, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
options.BlendPercentage = opacity;
return source.DrawImage(image, location, options);
}
/// <summary>
@ -112,17 +100,16 @@ namespace SixLabors.ImageSharp
/// <param name="image">The image to blend with the currently processing image.</param>
/// <typeparam name="TPixel">The pixel format.</typeparam>
/// <param name="blender">The type of bending to apply.</param>
/// <param name="percent">The opacity of the image image to blend. Must be between 0 and 1.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <returns>The <see cref="Image{TPixel}"/>.</returns>
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float percent, Size size, Point location)
public static IImageProcessingContext<TPixel> DrawImage<TPixel>(this IImageProcessingContext<TPixel> source, Image<TPixel> image, PixelBlenderMode blender, float opacity, Point location)
where TPixel : struct, IPixel<TPixel>
{
GraphicsOptions options = GraphicsOptions.Default;
options.BlenderMode = blender;
options.BlendPercentage = percent;
return source.DrawImage(image, size, location, options);
options.BlendPercentage = opacity;
return source.DrawImage(image, location, options);
}
}
}

76
src/ImageSharp.Drawing/Processors/DrawImageProcessor.cs

@ -19,71 +19,60 @@ namespace SixLabors.ImageSharp.Drawing.Processors
internal class DrawImageProcessor<TPixel> : ImageProcessor<TPixel>
where TPixel : struct, IPixel<TPixel>
{
private readonly PixelBlender<TPixel> blender;
/// <summary>
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixel}"/> class.
/// </summary>
/// <param name="image">The image to blend with the currently processing image.</param>
/// <param name="size">The size to draw the blended image.</param>
/// <param name="location">The location to draw the blended image.</param>
/// <param name="options">The opacity of the image to blend. Between 0 and 100.</param>
public DrawImageProcessor(Image<TPixel> image, Size size, Point location, GraphicsOptions options)
/// <param name="options">The opacity of the image to blend. Between 0 and 1.</param>
public DrawImageProcessor(Image<TPixel> image, Point location, GraphicsOptions options)
{
Guard.MustBeBetweenOrEqualTo(options.BlendPercentage, 0, 1, nameof(options.BlendPercentage));
this.Image = image;
this.Size = size;
this.Alpha = options.BlendPercentage;
this.blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
this.Opacity = options.BlendPercentage;
this.Blender = PixelOperations<TPixel>.Instance.GetPixelBlender(options.BlenderMode);
this.Location = location;
}
/// <summary>
/// Gets the image to blend.
/// Gets the image to blend
/// </summary>
public Image<TPixel> Image { get; }
/// <summary>
/// Gets the alpha percentage value.
/// Gets the opacity of the image to blend
/// </summary>
public float Alpha { get; }
public float Opacity { get; }
/// <summary>
/// Gets the size to draw the blended image.
/// Gets the pixel blender
/// </summary>
public Size Size { get; }
public PixelBlender<TPixel> Blender { get; }
/// <summary>
/// Gets the location to draw the blended image.
/// Gets the location to draw the blended image
/// </summary>
public Point Location { get; }
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, Rectangle sourceRectangle, Configuration configuration)
{
Image<TPixel> disposableImage = null;
Image<TPixel> targetImage = this.Image;
PixelBlender<TPixel> blender = this.Blender;
int locationY = this.Location.Y;
try
{
if (targetImage.Size() != this.Size)
{
targetImage = disposableImage = this.Image.Clone(x => x.Resize(this.Size.Width, this.Size.Height));
}
// Align start/end positions.
Rectangle bounds = targetImage.Bounds();
// Align start/end positions.
Rectangle bounds = targetImage.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 targetX = minX - this.Location.X;
int minX = Math.Max(this.Location.X, sourceRectangle.X);
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width);
int targetX = minX - this.Location.X;
int minY = Math.Max(this.Location.Y, sourceRectangle.Y);
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom);
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;
int width = maxX - minX;
MemoryManager memoryManager = this.Image.GetConfiguration().MemoryManager;
@ -91,21 +80,16 @@ namespace SixLabors.ImageSharp.Drawing.Processors
{
amount.Span.Fill(this.Alpha);
Parallel.For(
minY,
maxY,
configuration.ParallelOptions,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - this.Location.Y).Slice(targetX, width);
Parallel.For(
minY,
maxY,
configuration.ParallelOptions,
y =>
{
Span<TPixel> background = source.GetPixelRowSpan(y).Slice(minX, width);
Span<TPixel> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width);
this.blender.Blend(memoryManager, background, background, foreground, amount.Span);
});
}
}
finally
{
disposableImage?.Dispose();
});
}
}
}

2
src/ImageSharp/GraphicsOptions.cs

@ -67,7 +67,7 @@ namespace SixLabors.ImageSharp
// some API thought post V1.
/// <summary>
/// Gets or sets a value indicating the blending percentage to apply to the drawing operation
/// Gets or sets a value indicating the blending mode to apply to the drawing operation
/// </summary>
public PixelBlenderMode BlenderMode
{

5
src/ImageSharp/Processing/Processors/Transforms/AffineTransformProcessor.cs

@ -51,8 +51,6 @@ namespace SixLabors.ImageSharp.Processing.Processors
public AffineTransformProcessor(Matrix3x2 matrix, IResampler sampler, Size targetDimensions)
: base(sampler)
{
// Transforms are inverted else the output is the opposite of the expected.
Matrix3x2.Invert(matrix, out matrix);
this.TransformMatrix = matrix;
this.targetDimensions = targetDimensions;
}
@ -95,6 +93,9 @@ namespace SixLabors.ImageSharp.Processing.Processors
// Since could potentially be resizing the canvas we might need to re-calculate the matrix
Matrix3x2 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);
// Convert from screen to world space.
Matrix3x2.Invert(matrix, out matrix);
if (this.Sampler is NearestNeighborResampler)
{
Parallel.For(

13
src/ImageSharp/Processing/Processors/Transforms/CenteredAffineTransformProcessor.cs

@ -27,23 +27,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override Matrix3x2 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
{
var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F);
return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter;
return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
}
/// <inheritdoc/>
protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix3x2 matrix)
{
var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height);
if (!Matrix3x2.Invert(this.TransformMatrix, out Matrix3x2 sizeMatrix))
{
// TODO: Shouldn't we throw an exception instead?
return sourceDimensions;
}
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix).Size;
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix).Size;
}
}
}

11
src/ImageSharp/Processing/Processors/Transforms/CenteredProjectiveTransformProcessor.cs

@ -27,17 +27,14 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override Matrix4x4 GetProcessingMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle)
{
var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0);
var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0);
return translationToTargetCenter * this.TransformMatrix * translateToSourceCenter;
return TransformHelpers.GetCenteredTransformMatrix(sourceRectangle, destinationRectangle, this.TransformMatrix);
}
/// <inheritdoc/>
protected override Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
protected override Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix)
{
return Matrix4x4.Invert(this.TransformMatrix, out Matrix4x4 sizeMatrix)
? TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, sizeMatrix)
: sourceRectangle;
var sourceRectangle = new Rectangle(0, 0, sourceDimensions.Width, sourceDimensions.Height);
return TransformHelpers.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix).Size;
}
}
}

37
src/ImageSharp/Processing/Processors/Transforms/ProjectiveTransformProcessor.cs

@ -22,8 +22,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
internal class ProjectiveTransformProcessor<TPixel> : InterpolatedTransformProcessorBase<TPixel>
where TPixel : struct, IPixel<TPixel>
{
// TODO: We should use a Size instead! (See AffineTransformProcessor<T>)
private Rectangle targetRectangle;
private Size targetDimensions;
/// <summary>
/// Initializes a new instance of the <see cref="ProjectiveTransformProcessor{TPixel}"/> class.
@ -40,7 +39,7 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler)
: this(matrix, sampler, Rectangle.Empty)
: this(matrix, sampler, Size.Empty)
{
}
@ -49,27 +48,26 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// </summary>
/// <param name="matrix">The transform matrix</param>
/// <param name="sampler">The sampler to perform the transform operation.</param>
/// <param name="rectangle">The rectangle to constrain the transformed image to.</param>
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Rectangle rectangle)
/// <param name="targetDimensions">The target dimensions to constrain the transformed image to.</param>
public ProjectiveTransformProcessor(Matrix4x4 matrix, IResampler sampler, Size targetDimensions)
: base(sampler)
{
// Transforms are inverted else the output is the opposite of the expected.
Matrix4x4.Invert(matrix, out matrix);
this.TransformMatrix = matrix;
this.targetRectangle = rectangle;
this.targetDimensions = targetDimensions;
}
/// <summary>
/// Gets the matrix used to supply the non-affine transform
/// Gets the matrix used to supply the projective transform
/// </summary>
public Matrix4x4 TransformMatrix { get; }
/// <inheritdoc/>
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle)
{
if (this.targetRectangle == Rectangle.Empty)
if (this.targetDimensions == Size.Empty)
{
this.targetRectangle = this.GetTransformedBoundingRectangle(sourceRectangle, this.TransformMatrix);
// TODO: CreateDestination() should not modify the processors state! (kinda CQRS)
this.targetDimensions = this.GetTransformedDimensions(sourceRectangle.Size, this.TransformMatrix);
}
// We will always be creating the clone even for mutate because we may need to resize the canvas
@ -86,12 +84,17 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <inheritdoc/>
protected override void OnApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration)
{
int height = this.targetRectangle.Height;
int width = this.targetRectangle.Width;
int height = this.targetDimensions.Height;
int width = this.targetDimensions.Width;
Rectangle sourceBounds = source.Bounds();
var targetBounds = new Rectangle(0, 0, width, height);
// Since could potentially be resizing the canvas we might need to re-calculate the matrix
Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, this.targetRectangle);
Matrix4x4 matrix = this.GetProcessingMatrix(sourceBounds, targetBounds);
// Convert from screen to world space.
Matrix4x4.Invert(matrix, out matrix);
if (this.Sampler is NearestNeighborResampler)
{
@ -234,12 +237,12 @@ namespace SixLabors.ImageSharp.Processing.Processors
/// <summary>
/// Gets the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// </summary>
/// <param name="sourceRectangle">The source rectangle.</param>
/// <param name="sourceDimensions">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Rectangle"/></returns>
protected virtual Rectangle GetTransformedBoundingRectangle(Rectangle sourceRectangle, Matrix4x4 matrix)
protected virtual Size GetTransformedDimensions(Size sourceDimensions, Matrix4x4 matrix)
{
return sourceRectangle;
return sourceDimensions;
}
}
}

10
src/ImageSharp/Processing/Transforms/Transform.cs

@ -23,7 +23,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
public static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix3x2 matrix)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, KnownResamplers.NearestNeighbor);
=> Transform(source, matrix, KnownResamplers.Bicubic);
/// <summary>
/// Transforms an image by the given matrix using the specified sampling algorithm.
@ -90,7 +90,7 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix)
where TPixel : struct, IPixel<TPixel>
=> Transform(source, matrix, KnownResamplers.NearestNeighbor);
=> Transform(source, matrix, KnownResamplers.Bicubic);
/// <summary>
/// Applies a projective transform to the image by the given matrix using the specified sampling algorithm.
@ -117,6 +117,10 @@ namespace SixLabors.ImageSharp
/// <returns>The <see cref="Image{TPixel}"/></returns>
internal static IImageProcessingContext<TPixel> Transform<TPixel>(this IImageProcessingContext<TPixel> source, Matrix4x4 matrix, IResampler sampler, Rectangle rectangle)
where TPixel : struct, IPixel<TPixel>
=> source.ApplyProcessor(new ProjectiveTransformProcessor<TPixel>(matrix, sampler, rectangle));
{
var t = Matrix4x4.CreateTranslation(new Vector3(-rectangle.Location, 0));
Matrix4x4 combinedMatrix = t * matrix;
return source.ApplyProcessor(new ProjectiveTransformProcessor<TPixel>(combinedMatrix, sampler, rectangle.Size));
}
}
}

48
src/ImageSharp/Processing/Transforms/TransformHelpers.cs

@ -59,7 +59,51 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Returns the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// Gets the centered transform matrix based upon the source and destination rectangles
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="destinationRectangle">The destination image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix3x2"/></returns>
public static Matrix3x2 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix3x2 matrix)
{
// We invert the matrix to handle the transformation from screen to world space.
// This ensures scaling matrices are correct.
Matrix3x2.Invert(matrix, out Matrix3x2 inverted);
var translationToTargetCenter = Matrix3x2.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F);
var translateToSourceCenter = Matrix3x2.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F);
Matrix3x2.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix3x2 centered);
// Translate back to world to pass to the Transform method.
return centered;
}
/// <summary>
/// Gets the centered transform matrix based upon the source and destination rectangles
/// </summary>
/// <param name="sourceRectangle">The source image bounds.</param>
/// <param name="destinationRectangle">The destination image bounds.</param>
/// <param name="matrix">The transformation matrix.</param>
/// <returns>The <see cref="Matrix4x4"/></returns>
public static Matrix4x4 GetCenteredTransformMatrix(Rectangle sourceRectangle, Rectangle destinationRectangle, Matrix4x4 matrix)
{
// We invert the matrix to handle the transformation from screen to world space.
// This ensures scaling matrices are correct.
Matrix4x4.Invert(matrix, out Matrix4x4 inverted);
var translationToTargetCenter = Matrix4x4.CreateTranslation(-destinationRectangle.Width * .5F, -destinationRectangle.Height * .5F, 0);
var translateToSourceCenter = Matrix4x4.CreateTranslation(sourceRectangle.Width * .5F, sourceRectangle.Height * .5F, 0);
Matrix4x4.Invert(translationToTargetCenter * inverted * translateToSourceCenter, out Matrix4x4 centered);
// Translate back to world to pass to the Transform method.
return centered;
}
/// <summary>
/// Returns the bounding rectangle relative to the source for the given transformation matrix.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>
@ -79,7 +123,7 @@ namespace SixLabors.ImageSharp
}
/// <summary>
/// Returns the bounding <see cref="Rectangle"/> relative to the source for the given transformation matrix.
/// Returns the bounding rectangle relative to the source for the given transformation matrix.
/// </summary>
/// <param name="rectangle">The source rectangle.</param>
/// <param name="matrix">The transformation matrix.</param>

38
tests/ImageSharp.Tests/Drawing/DrawImageTest.cs

@ -1,17 +1,16 @@
// Copyright (c) Six Labors and contributors.
// Licensed under the Apache License, Version 2.0.
using System.IO;
using System.Linq;
using System;
using System.Numerics;
using SixLabors.ImageSharp.Helpers;
using SixLabors.ImageSharp.Processing;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.Primitives;
using Xunit;
namespace SixLabors.ImageSharp.Tests
{
using System;
using System.Numerics;
public class DrawImageTest : FileTestBase
{
private const PixelTypes PixelTypes = Tests.PixelTypes.Rgba32;
@ -23,8 +22,6 @@ namespace SixLabors.ImageSharp.Tests
TestImages.Gif.Rings
};
object[][] Modes = System.Enum.GetValues(typeof(PixelBlenderMode)).Cast<PixelBlenderMode>().Select(x => new object[] { x }).ToArray();
[Theory]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Normal)]
[WithFileCollection(nameof(TestFiles), PixelTypes, PixelBlenderMode.Multiply)]
@ -39,9 +36,10 @@ namespace SixLabors.ImageSharp.Tests
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using (Image<TPixel> blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
using (var blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
{
image.Mutate(x => x.DrawImage(blend, mode, .75f, new Size(image.Width / 2, image.Height / 2), new Point(image.Width / 4, image.Height / 4)));
blend.Mutate(x => x.Resize(image.Width / 2, image.Height / 2));
image.Mutate(x => x.DrawImage(blend, mode, .75f, new Point(image.Width / 4, image.Height / 4)));
image.DebugSave(provider, new { mode });
}
}
@ -52,15 +50,25 @@ namespace SixLabors.ImageSharp.Tests
where TPixel : struct, IPixel<TPixel>
{
using (Image<TPixel> image = provider.GetImage())
using (Image<TPixel> blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
using (var blend = Image.Load<TPixel>(TestFile.Create(TestImages.Bmp.Car).Bytes))
{
Matrix3x2 rotate = Matrix3x2Extensions.CreateRotationDegrees(45F);
Matrix3x2 scale = Matrix3x2Extensions.CreateScale(new SizeF(.25F, .25F));
Matrix3x2 matrix = rotate * scale;
// Lets center the matrix so we can tell whether any cut-off issues we may have belong to the drawing processor
Rectangle srcBounds = blend.Bounds();
Rectangle destBounds = TransformHelpers.GetTransformedBoundingRectangle(srcBounds, matrix);
Matrix3x2 centeredMatrix = TransformHelpers.GetCenteredTransformMatrix(srcBounds, destBounds, matrix);
blend.Mutate(x => x.Transform(rotate * scale));
// We pass a new rectangle here based on the dest bounds since we've offset the matrix
blend.Mutate(x => x.Transform(
centeredMatrix,
KnownResamplers.Bicubic,
new Rectangle(0, 0, destBounds.Width, destBounds.Height)));
var position = new Point((image.Width - blend.Width) / 2, (image.Height - blend.Height) / 2);
image.Mutate(x => x.DrawImage(blend, mode, .75F, new Size(blend.Width, blend.Height), position));
image.Mutate(x => x.DrawImage(blend, mode, .75F, position));
image.DebugSave(provider, new[] { "Transformed" });
}
}
@ -78,7 +86,7 @@ namespace SixLabors.ImageSharp.Tests
Rgba32 backgroundPixel = background[0, 0];
Rgba32 overlayPixel = overlay[Math.Abs(xy) + 1, Math.Abs(xy) + 1];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy)));
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy)));
Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[0, 0]);
@ -100,7 +108,7 @@ namespace SixLabors.ImageSharp.Tests
Rgba32 backgroundPixel = background[xy - 1, xy - 1];
Rgba32 overlayPixel = overlay[0, 0];
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Size(overlay.Width, overlay.Height), new Point(xy, xy)));
background.Mutate(x => x.DrawImage(overlay, PixelBlenderMode.Normal, 1F, new Point(xy, xy)));
Assert.Equal(Rgba32.White, backgroundPixel);
Assert.Equal(overlayPixel, background[xy, xy]);
@ -109,4 +117,4 @@ namespace SixLabors.ImageSharp.Tests
}
}
}
}
}
Loading…
Cancel
Save