Browse Source

ImageBrush can apply a source image of a different pixel type than the target

af/merge-core
Anton Firszov 7 years ago
parent
commit
659f8b6231
  1. 44
      src/ImageSharp.Drawing/Processing/ImageBrush.cs
  2. 17
      src/ImageSharp/Image.cs
  3. 11
      src/ImageSharp/Image{TPixel}.cs
  4. 2
      tests/ImageSharp.Tests/Drawing/DrawImageTests.cs
  5. 19
      tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs
  6. 6
      tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs
  7. 29
      tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs
  8. 2
      tests/Images/External

44
src/ImageSharp.Drawing/Processing/ImageBrush.cs

@ -3,6 +3,7 @@
using System; using System;
using System.Buffers; using System.Buffers;
using SixLabors.ImageSharp.Advanced; using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory; using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.PixelFormats;
@ -37,8 +38,14 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options) GraphicsOptions options)
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
Image<TPixel> specificImage = (Image<TPixel>)this.image; if (this.image is Image<TPixel> specificImage)
return new ImageBrushApplicator<TPixel>(source, specificImage.Frames.RootFrame, region, options); {
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, false);
}
specificImage = this.image.CloneAs<TPixel>();
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, true);
} }
/// <summary> /// <summary>
@ -47,10 +54,11 @@ namespace SixLabors.ImageSharp.Processing
private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel> private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel> where TPixel : struct, IPixel<TPixel>
{ {
/// <summary> private ImageFrame<TPixel> sourceFrame;
/// The source image.
/// </summary> private Image<TPixel> sourceImage;
private readonly ImageFrame<TPixel> source;
private readonly bool shouldDisposeImage;
/// <summary> /// <summary>
/// The y-length. /// The y-length.
@ -79,10 +87,18 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="image">The image.</param> /// <param name="image">The image.</param>
/// <param name="region">The region.</param> /// <param name="region">The region.</param>
/// <param name="options">The options</param> /// <param name="options">The options</param>
public ImageBrushApplicator(ImageFrame<TPixel> target, ImageFrame<TPixel> image, RectangleF region, GraphicsOptions options) /// <param name="shouldDisposeImage">Whether to dispose the image on disposal of the applicator.</param>
public ImageBrushApplicator(
ImageFrame<TPixel> target,
Image<TPixel> image,
RectangleF region,
GraphicsOptions options,
bool shouldDisposeImage)
: base(target, options) : base(target, options)
{ {
this.source = image; this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
this.shouldDisposeImage = shouldDisposeImage;
this.xLength = image.Width; this.xLength = image.Width;
this.yLength = image.Height; this.yLength = image.Height;
this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0); this.offsetY = (int)MathF.Max(MathF.Floor(region.Top), 0);
@ -103,13 +119,19 @@ namespace SixLabors.ImageSharp.Processing
{ {
int srcX = (x - this.offsetX) % this.xLength; int srcX = (x - this.offsetX) % this.xLength;
int srcY = (y - this.offsetY) % this.yLength; int srcY = (y - this.offsetY) % this.yLength;
return this.source[srcX, srcY]; return this.sourceFrame[srcX, srcY];
} }
} }
/// <inheritdoc /> /// <inheritdoc />
public override void Dispose() public override void Dispose()
{ {
if (this.shouldDisposeImage)
{
this.sourceImage?.Dispose();
this.sourceImage = null;
this.sourceFrame = null;
}
} }
/// <inheritdoc /> /// <inheritdoc />
@ -124,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing
int sourceY = (y - this.offsetY) % this.yLength; int sourceY = (y - this.offsetY) % this.yLength;
int offsetX = x - this.offsetX; int offsetX = x - this.offsetX;
Span<TPixel> sourceRow = this.source.GetPixelRowSpan(sourceY); Span<TPixel> sourceRow = this.sourceFrame.GetPixelRowSpan(sourceY);
for (int i = 0; i < scanline.Length; i++) for (int i = 0; i < scanline.Length; i++)
{ {
@ -137,7 +159,7 @@ namespace SixLabors.ImageSharp.Processing
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length); Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend( this.Blender.Blend(
this.source.Configuration, this.sourceFrame.Configuration,
destinationRow, destinationRow,
destinationRow, destinationRow,
overlaySpan, overlaySpan,

17
src/ImageSharp/Image.cs

@ -87,6 +87,23 @@ namespace SixLabors.ImageSharp
EncodeVisitor visitor = new EncodeVisitor(encoder, stream); EncodeVisitor visitor = new EncodeVisitor(encoder, stream);
this.AcceptVisitor(visitor); this.AcceptVisitor(visitor);
} }
/// <summary>
/// Returns a copy of the image in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.Configuration);
/// <summary>
/// Returns a copy of the image in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="Image{TPixel2}"/>.</returns>
public abstract Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>;
/// <summary> /// <summary>
/// Accept a <see cref="IImageVisitor"/>. /// Accept a <see cref="IImageVisitor"/>.

11
src/ImageSharp/Image{TPixel}.cs

@ -153,22 +153,13 @@ namespace SixLabors.ImageSharp
return new Image<TPixel>(configuration, this.Metadata.DeepClone(), clonedFrames); return new Image<TPixel>(configuration, this.Metadata.DeepClone(), clonedFrames);
} }
/// <summary>
/// Returns a copy of the image in the given pixel format.
/// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <returns>The <see cref="Image{TPixel2}"/></returns>
public Image<TPixel2> CloneAs<TPixel2>()
where TPixel2 : struct, IPixel<TPixel2> => this.CloneAs<TPixel2>(this.Configuration);
/// <summary> /// <summary>
/// Returns a copy of the image in the given pixel format. /// Returns a copy of the image in the given pixel format.
/// </summary> /// </summary>
/// <typeparam name="TPixel2">The pixel format.</typeparam> /// <typeparam name="TPixel2">The pixel format.</typeparam>
/// <param name="configuration">The configuration providing initialization code which allows extending the library.</param> /// <param name="configuration">The configuration providing initialization code which allows extending the library.</param>
/// <returns>The <see cref="Image{TPixel2}"/>.</returns> /// <returns>The <see cref="Image{TPixel2}"/>.</returns>
public Image<TPixel2> CloneAs<TPixel2>(Configuration configuration) public override Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
{ {
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration)); IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration));
return new Image<TPixel2>(configuration, this.Metadata.DeepClone(), clonedFrames); return new Image<TPixel2>(configuration, this.Metadata.DeepClone(), clonedFrames);

2
tests/ImageSharp.Tests/Drawing/DrawImageTests.cs

@ -175,5 +175,7 @@ namespace SixLabors.ImageSharp.Tests.Drawing
} }
} }
} }
} }
} }

19
tests/ImageSharp.Tests/Drawing/FillImageBrushTests.cs

@ -32,5 +32,24 @@ namespace SixLabors.ImageSharp.Tests.Drawing
} }
} }
} }
[Theory]
[WithTestPatternImages(200, 200, PixelTypes.Rgba32 | PixelTypes.Bgra32)]
public void UseBrushOfDifferentPixelType<TPixel>(TestImageProvider<TPixel> provider)
where TPixel : struct, IPixel<TPixel>
{
byte[] data = TestFile.Create(TestImages.Png.Ducky).Bytes;
using (Image<TPixel> background = provider.GetImage())
using (Image overlay = provider.PixelType == PixelTypes.Rgba32
? (Image)Image.Load<Bgra32>(data)
: Image.Load<Rgba32>(data))
{
var brush = new ImageBrush(overlay);
background.Mutate(c => c.Fill(brush));
background.DebugSave(provider, appendSourceFileOrDescription : false);
background.CompareToReferenceOutput(provider, appendSourceFileOrDescription: false);
}
}
} }
} }

6
tests/ImageSharp.Tests/TestUtilities/ImagingTestCaseUtility.cs

@ -152,21 +152,19 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Encodes image by the format matching the required extension, than saves it to the recommended output file. /// Encodes image by the format matching the required extension, than saves it to the recommended output file.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
/// <param name="image">The image instance</param> /// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</param> /// <param name="extension">The requested extension</param>
/// <param name="encoder">Optional encoder</param> /// <param name="encoder">Optional encoder</param>
/// <param name="appendPixelTypeToFileName">A value indicating whether to append the pixel type to the test output file name</param> /// <param name="appendPixelTypeToFileName">A value indicating whether to append the pixel type to the test output file name</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param> /// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
/// <param name="testOutputDetails">Additional information to append to the test output file name</param> /// <param name="testOutputDetails">Additional information to append to the test output file name</param>
public string SaveTestOutputFile<TPixel>( public string SaveTestOutputFile(
Image<TPixel> image, Image image,
string extension = null, string extension = null,
IImageEncoder encoder = null, IImageEncoder encoder = null,
object testOutputDetails = null, object testOutputDetails = null,
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true) bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{ {
string path = this.GetTestOutputFileName( string path = this.GetTestOutputFileName(
extension, extension,

29
tests/ImageSharp.Tests/TestUtilities/TestImageExtensions.cs

@ -55,17 +55,16 @@ namespace SixLabors.ImageSharp.Tests
}); });
} }
public static Image<TPixel> DebugSave<TPixel>( public static void DebugSave(
this Image<TPixel> image, this Image image,
ITestImageProvider provider, ITestImageProvider provider,
FormattableString testOutputDetails, FormattableString testOutputDetails,
string extension = "png", string extension = "png",
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
IImageEncoder encoder = null) IImageEncoder encoder = null)
where TPixel : struct, IPixel<TPixel>
{ {
return image.DebugSave( image.DebugSave(
provider, provider,
(object)testOutputDetails, (object)testOutputDetails,
extension, extension,
@ -77,7 +76,6 @@ namespace SixLabors.ImageSharp.Tests
/// <summary> /// <summary>
/// Saves the image only when not running in the CI server. /// Saves the image only when not running in the CI server.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
/// <param name="image">The image</param> /// <param name="image">The image</param>
/// <param name="provider">The image provider</param> /// <param name="provider">The image provider</param>
/// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param> /// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param>
@ -85,15 +83,14 @@ namespace SixLabors.ImageSharp.Tests
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param> /// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param>
/// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param> /// <param name="appendSourceFileOrDescription">A boolean indicating whether to append <see cref="ITestImageProvider.SourceFileOrDescription"/> to the test output file name.</param>
/// <param name="encoder">Custom encoder to use.</param> /// <param name="encoder">Custom encoder to use.</param>
public static Image<TPixel> DebugSave<TPixel>( public static Image DebugSave(
this Image<TPixel> image, this Image image,
ITestImageProvider provider, ITestImageProvider provider,
object testOutputDetails = null, object testOutputDetails = null,
string extension = "png", string extension = "png",
bool appendPixelTypeToFileName = true, bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true, bool appendSourceFileOrDescription = true,
IImageEncoder encoder = null) IImageEncoder encoder = null)
where TPixel : struct, IPixel<TPixel>
{ {
if (TestEnvironment.RunsOnCI) if (TestEnvironment.RunsOnCI)
{ {
@ -111,37 +108,34 @@ namespace SixLabors.ImageSharp.Tests
return image; return image;
} }
public static Image<TPixel> DebugSave<TPixel>( public static void DebugSave(
this Image<TPixel> image, this Image image,
ITestImageProvider provider, ITestImageProvider provider,
IImageEncoder encoder, IImageEncoder encoder,
FormattableString testOutputDetails, FormattableString testOutputDetails,
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel<TPixel>
{ {
return image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName); image.DebugSave(provider, encoder, (object)testOutputDetails, appendPixelTypeToFileName);
} }
/// <summary> /// <summary>
/// Saves the image only when not running in the CI server. /// Saves the image only when not running in the CI server.
/// </summary> /// </summary>
/// <typeparam name="TPixel">The pixel format</typeparam>
/// <param name="image">The image</param> /// <param name="image">The image</param>
/// <param name="provider">The image provider</param> /// <param name="provider">The image provider</param>
/// <param name="encoder">The image encoder</param> /// <param name="encoder">The image encoder</param>
/// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param> /// <param name="testOutputDetails">Details to be concatenated to the test output file, describing the parameters of the test.</param>
/// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param> /// <param name="appendPixelTypeToFileName">A boolean indicating whether to append the pixel type to the output file name.</param>
public static Image<TPixel> DebugSave<TPixel>( public static void DebugSave(
this Image<TPixel> image, this Image image,
ITestImageProvider provider, ITestImageProvider provider,
IImageEncoder encoder, IImageEncoder encoder,
object testOutputDetails = null, object testOutputDetails = null,
bool appendPixelTypeToFileName = true) bool appendPixelTypeToFileName = true)
where TPixel : struct, IPixel<TPixel>
{ {
if (TestEnvironment.RunsOnCI) if (TestEnvironment.RunsOnCI)
{ {
return image; return;
} }
// We are running locally then we want to save it out // We are running locally then we want to save it out
@ -150,7 +144,6 @@ namespace SixLabors.ImageSharp.Tests
encoder: encoder, encoder: encoder,
testOutputDetails: testOutputDetails, testOutputDetails: testOutputDetails,
appendPixelTypeToFileName: appendPixelTypeToFileName); appendPixelTypeToFileName: appendPixelTypeToFileName);
return image;
} }
public static Image<TPixel> DebugSaveMultiFrame<TPixel>( public static Image<TPixel> DebugSaveMultiFrame<TPixel>(

2
tests/Images/External

@ -1 +1 @@
Subproject commit 55c250ab80f7f9fc26208b9b9d9590cbe8012446 Subproject commit 9d1d9dd53755007ee812907f64304d1925c6a91a
Loading…
Cancel
Save