Browse Source

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

pull/910/head
Anton Firszov 7 years ago
parent
commit
efaa3c05ed
  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.Buffers;
using SixLabors.ImageSharp.Advanced;
using SixLabors.ImageSharp.Memory;
using SixLabors.ImageSharp.PixelFormats;
@ -37,8 +38,14 @@ namespace SixLabors.ImageSharp.Processing
GraphicsOptions options)
where TPixel : struct, IPixel<TPixel>
{
Image<TPixel> specificImage = (Image<TPixel>)this.image;
return new ImageBrushApplicator<TPixel>(source, specificImage.Frames.RootFrame, region, options);
if (this.image is Image<TPixel> specificImage)
{
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, false);
}
specificImage = this.image.CloneAs<TPixel>();
return new ImageBrushApplicator<TPixel>(source, specificImage, region, options, true);
}
/// <summary>
@ -47,10 +54,11 @@ namespace SixLabors.ImageSharp.Processing
private class ImageBrushApplicator<TPixel> : BrushApplicator<TPixel>
where TPixel : struct, IPixel<TPixel>
{
/// <summary>
/// The source image.
/// </summary>
private readonly ImageFrame<TPixel> source;
private ImageFrame<TPixel> sourceFrame;
private Image<TPixel> sourceImage;
private readonly bool shouldDisposeImage;
/// <summary>
/// The y-length.
@ -79,10 +87,18 @@ namespace SixLabors.ImageSharp.Processing
/// <param name="image">The image.</param>
/// <param name="region">The region.</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)
{
this.source = image;
this.sourceImage = image;
this.sourceFrame = image.Frames.RootFrame;
this.shouldDisposeImage = shouldDisposeImage;
this.xLength = image.Width;
this.yLength = image.Height;
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 srcY = (y - this.offsetY) % this.yLength;
return this.source[srcX, srcY];
return this.sourceFrame[srcX, srcY];
}
}
/// <inheritdoc />
public override void Dispose()
{
if (this.shouldDisposeImage)
{
this.sourceImage?.Dispose();
this.sourceImage = null;
this.sourceFrame = null;
}
}
/// <inheritdoc />
@ -124,7 +146,7 @@ namespace SixLabors.ImageSharp.Processing
int sourceY = (y - this.offsetY) % this.yLength;
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++)
{
@ -137,7 +159,7 @@ namespace SixLabors.ImageSharp.Processing
Span<TPixel> destinationRow = this.Target.GetPixelRowSpan(y).Slice(x, scanline.Length);
this.Blender.Blend(
this.source.Configuration,
this.sourceFrame.Configuration,
destinationRow,
destinationRow,
overlaySpan,

17
src/ImageSharp/Image.cs

@ -87,6 +87,23 @@ namespace SixLabors.ImageSharp
EncodeVisitor visitor = new EncodeVisitor(encoder, stream);
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>
/// 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);
}
/// <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 Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
where TPixel2 : struct, IPixel<TPixel2>
public override Image<TPixel2> CloneAs<TPixel2>(Configuration configuration)
{
IEnumerable<ImageFrame<TPixel2>> clonedFrames = this.Frames.Select(x => x.CloneAs<TPixel2>(configuration));
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>
/// Encodes image by the format matching the required extension, than saves it to the recommended output file.
/// </summary>
/// <typeparam name="TPixel">The pixel format of the image</typeparam>
/// <param name="image">The image instance</param>
/// <param name="extension">The requested extension</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="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>
public string SaveTestOutputFile<TPixel>(
Image<TPixel> image,
public string SaveTestOutputFile(
Image image,
string extension = null,
IImageEncoder encoder = null,
object testOutputDetails = null,
bool appendPixelTypeToFileName = true,
bool appendSourceFileOrDescription = true)
where TPixel : struct, IPixel<TPixel>
{
string path = this.GetTestOutputFileName(
extension,

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

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

2
tests/Images/External

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