mirror of https://github.com/SixLabors/ImageSharp
88 changed files with 3839 additions and 2358 deletions
@ -1,95 +1,101 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
using System; |
using System; |
||||
using System.Buffers; |
using System.Buffers; |
||||
using System.Threading.Tasks; |
using System.Threading.Tasks; |
||||
using SixLabors.ImageSharp.Advanced; |
using SixLabors.ImageSharp.Advanced; |
||||
using SixLabors.ImageSharp.Memory; |
using SixLabors.ImageSharp.Memory; |
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.ParallelUtils; |
||||
using SixLabors.Memory; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.Primitives; |
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|
||||
{ |
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
||||
/// <summary>
|
{ |
||||
/// Combines two images together by blending the pixels.
|
/// <summary>
|
||||
/// </summary>
|
/// Combines two images together by blending the pixels.
|
||||
|
/// </summary>
|
||||
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
||||
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
|
/// <typeparam name="TPixelSrc">The pixel format of source image.</typeparam>
|
||||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
||||
where TPixelDst : struct, IPixel<TPixelDst> |
where TPixelDst : struct, IPixel<TPixelDst> |
||||
where TPixelSrc : struct, IPixel<TPixelSrc> |
where TPixelSrc : struct, IPixel<TPixelSrc> |
||||
{ |
{ |
||||
/// <summary>
|
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
||||
/// </summary>
|
/// </summary>
|
||||
/// <param name="image">The image to blend with the currently processing image.</param>
|
/// <param name="image">The image to blend with the currently processing image.</param>
|
||||
/// <param name="location">The location to draw the blended image.</param>
|
/// <param name="location">The location to draw the blended image.</param>
|
||||
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
|
/// <param name="colorBlendingMode">The blending mode to use when drawing the image.</param>
|
||||
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
|
/// <param name="alphaCompositionMode">The Alpha blending mode to use when drawing the image.</param>
|
||||
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
/// <param name="opacity">The opacity of the image to blend. Must be between 0 and 1.</param>
|
||||
public DrawImageProcessor(Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) |
public DrawImageProcessor(Image<TPixelSrc> image, Point location, PixelColorBlendingMode colorBlendingMode, PixelAlphaCompositionMode alphaCompositionMode, float opacity) |
||||
{ |
{ |
||||
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
||||
|
|
||||
this.Image = image; |
this.Image = image; |
||||
this.Opacity = opacity; |
this.Opacity = opacity; |
||||
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
||||
this.Location = location; |
this.Location = location; |
||||
} |
} |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the image to blend
|
/// Gets the image to blend
|
||||
/// </summary>
|
/// </summary>
|
||||
public Image<TPixelSrc> Image { get; } |
public Image<TPixelSrc> Image { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the opacity of the image to blend
|
/// Gets the opacity of the image to blend
|
||||
/// </summary>
|
/// </summary>
|
||||
public float Opacity { get; } |
public float Opacity { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the pixel blender
|
/// Gets the pixel blender
|
||||
/// </summary>
|
/// </summary>
|
||||
public PixelBlender<TPixelDst> Blender { get; } |
public PixelBlender<TPixelDst> Blender { get; } |
||||
|
|
||||
/// <summary>
|
/// <summary>
|
||||
/// Gets the location to draw the blended image
|
/// Gets the location to draw the blended image
|
||||
/// </summary>
|
/// </summary>
|
||||
public Point Location { get; } |
public Point Location { get; } |
||||
|
|
||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
||||
{ |
{ |
||||
Image<TPixelSrc> targetImage = this.Image; |
Image<TPixelSrc> targetImage = this.Image; |
||||
PixelBlender<TPixelDst> blender = this.Blender; |
PixelBlender<TPixelDst> blender = this.Blender; |
||||
int locationY = this.Location.Y; |
int locationY = this.Location.Y; |
||||
|
|
||||
// Align start/end positions.
|
// Align start/end positions.
|
||||
Rectangle bounds = targetImage.Bounds(); |
Rectangle bounds = targetImage.Bounds(); |
||||
|
|
||||
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
||||
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
||||
int targetX = minX - this.Location.X; |
int targetX = minX - this.Location.X; |
||||
|
|
||||
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
||||
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
||||
|
|
||||
int width = maxX - minX; |
int width = maxX - minX; |
||||
|
|
||||
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
||||
|
|
||||
ParallelFor.WithConfiguration( |
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); |
||||
minY, |
|
||||
maxY, |
ParallelHelper.IterateRows( |
||||
configuration, |
workingRect, |
||||
y => |
configuration, |
||||
{ |
rows => |
||||
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width); |
{ |
||||
Span<TPixelSrc> foreground = targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
for (int y = rows.Min; y < rows.Max; y++) |
||||
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity); |
{ |
||||
}); |
Span<TPixelDst> background = source.GetPixelRowSpan(y).Slice(minX, width); |
||||
} |
Span<TPixelSrc> foreground = |
||||
} |
targetImage.GetPixelRowSpan(y - locationY).Slice(targetX, width); |
||||
|
blender.Blend<TPixelSrc>(memoryAllocator, background, background, foreground, this.Opacity); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
} |
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
#if !NETCOREAPP2_1
|
||||
|
using System; |
||||
|
using System.Text; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Extension methods for the <see cref="Encoder"/> type.
|
||||
|
/// </summary>
|
||||
|
internal static unsafe class EncoderExtensions |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets a string from the provided buffer data.
|
||||
|
/// </summary>
|
||||
|
/// <param name="encoding">The encoding.</param>
|
||||
|
/// <param name="buffer">The buffer.</param>
|
||||
|
/// <returns>The string.</returns>
|
||||
|
public static string GetString(this Encoding encoding, ReadOnlySpan<byte> buffer) |
||||
|
{ |
||||
|
#if NETSTANDARD1_1
|
||||
|
return encoding.GetString(buffer.ToArray()); |
||||
|
#else
|
||||
|
fixed (byte* bytes = buffer) |
||||
|
{ |
||||
|
return encoding.GetString(bytes, buffer.Length); |
||||
|
} |
||||
|
#endif
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
#endif
|
||||
@ -1,61 +0,0 @@ |
|||||
using System; |
|
||||
using System.Buffers; |
|
||||
using System.Threading.Tasks; |
|
||||
using SixLabors.Memory; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Utility methods for Parallel.For() execution. Use this instead of raw <see cref="Parallel"/> calls!
|
|
||||
/// </summary>
|
|
||||
internal static class ParallelFor |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Helper method to execute Parallel.For using the settings in <paramref name="configuration"/>
|
|
||||
/// </summary>
|
|
||||
public static void WithConfiguration(int fromInclusive, int toExclusive, Configuration configuration, Action<int> body) |
|
||||
{ |
|
||||
Parallel.For(fromInclusive, toExclusive, configuration.GetParallelOptions(), body); |
|
||||
} |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Helper method to execute Parallel.For with temporary worker buffer shared between executing tasks.
|
|
||||
/// The buffer is not guaranteed to be clean!
|
|
||||
/// </summary>
|
|
||||
/// <typeparam name="T">The value type of the buffer</typeparam>
|
|
||||
/// <param name="fromInclusive">The start index, inclusive.</param>
|
|
||||
/// <param name="toExclusive">The end index, exclusive.</param>
|
|
||||
/// <param name="configuration">The <see cref="Configuration"/> used for getting the <see cref="MemoryAllocator"/> and <see cref="ParallelOptions"/></param>
|
|
||||
/// <param name="bufferLength">The length of the requested parallel buffer</param>
|
|
||||
/// <param name="body">The delegate that is invoked once per iteration.</param>
|
|
||||
public static void WithTemporaryBuffer<T>( |
|
||||
int fromInclusive, |
|
||||
int toExclusive, |
|
||||
Configuration configuration, |
|
||||
int bufferLength, |
|
||||
Action<int, IMemoryOwner<T>> body) |
|
||||
where T : struct |
|
||||
{ |
|
||||
MemoryAllocator memoryAllocator = configuration.MemoryAllocator; |
|
||||
ParallelOptions parallelOptions = configuration.GetParallelOptions(); |
|
||||
|
|
||||
IMemoryOwner<T> InitBuffer() |
|
||||
{ |
|
||||
return memoryAllocator.Allocate<T>(bufferLength); |
|
||||
} |
|
||||
|
|
||||
void CleanUpBuffer(IMemoryOwner<T> buffer) |
|
||||
{ |
|
||||
buffer.Dispose(); |
|
||||
} |
|
||||
|
|
||||
IMemoryOwner<T> BodyFunc(int i, ParallelLoopState state, IMemoryOwner<T> buffer) |
|
||||
{ |
|
||||
body(i, buffer); |
|
||||
return buffer; |
|
||||
} |
|
||||
|
|
||||
Parallel.For(fromInclusive, toExclusive, parallelOptions, InitBuffer, BodyFunc, CleanUpBuffer); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,71 @@ |
|||||
|
// Copyright(c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
using SixLabors.Memory; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.ParallelUtils |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct ParallelExecutionSettings |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Default value for <see cref="MinimumPixelsProcessedPerTask"/>.
|
||||
|
/// </summary>
|
||||
|
public const int DefaultMinimumPixelsProcessedPerTask = 4096; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
|
||||
|
/// </summary>
|
||||
|
public ParallelExecutionSettings( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minimumPixelsProcessedPerTask, |
||||
|
MemoryAllocator memoryAllocator) |
||||
|
{ |
||||
|
this.MaxDegreeOfParallelism = maxDegreeOfParallelism; |
||||
|
this.MinimumPixelsProcessedPerTask = minimumPixelsProcessedPerTask; |
||||
|
this.MemoryAllocator = memoryAllocator; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="ParallelExecutionSettings"/> struct.
|
||||
|
/// </summary>
|
||||
|
public ParallelExecutionSettings(int maxDegreeOfParallelism, MemoryAllocator memoryAllocator) |
||||
|
: this(maxDegreeOfParallelism, DefaultMinimumPixelsProcessedPerTask, memoryAllocator) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the MemoryAllocator
|
||||
|
/// </summary>
|
||||
|
public MemoryAllocator MemoryAllocator { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the value used for initializing <see cref="ParallelOptions.MaxDegreeOfParallelism"/> when using TPL.
|
||||
|
/// </summary>
|
||||
|
public int MaxDegreeOfParallelism { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the minimum number of pixels being processed by a single task when parallelizing operations with TPL.
|
||||
|
/// Launching tasks for pixel regions below this limit is not worth the overhead.
|
||||
|
/// Initialized with <see cref="DefaultMinimumPixelsProcessedPerTask"/> by default,
|
||||
|
/// the optimum value is operation specific. (The cheaper the operation, the larger the value is.)
|
||||
|
/// </summary>
|
||||
|
public int MinimumPixelsProcessedPerTask { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Creates a new instance of <see cref="ParallelExecutionSettings"/>
|
||||
|
/// having <see cref="MinimumPixelsProcessedPerTask"/> multiplied by <paramref name="multiplier"/>
|
||||
|
/// </summary>
|
||||
|
public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier) |
||||
|
{ |
||||
|
return new ParallelExecutionSettings( |
||||
|
this.MaxDegreeOfParallelism, |
||||
|
this.MinimumPixelsProcessedPerTask * multiplier, |
||||
|
this.MemoryAllocator); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,146 @@ |
|||||
|
// Copyright(c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Threading.Tasks; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.ParallelUtils |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Utility methods for batched processing of pixel row intervals.
|
||||
|
/// Parallel execution is optimized for image processing.
|
||||
|
/// Use this instead of direct <see cref="Parallel"/> calls!
|
||||
|
/// </summary>
|
||||
|
internal static class ParallelHelper |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Get the default <see cref="ParallelExecutionSettings"/> for a <see cref="Configuration"/>
|
||||
|
/// </summary>
|
||||
|
public static ParallelExecutionSettings GetParallelSettings(this Configuration configuration) |
||||
|
{ |
||||
|
return new ParallelExecutionSettings(configuration.MaxDegreeOfParallelism, configuration.MemoryAllocator); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
|
||||
|
/// </summary>
|
||||
|
public static void IterateRows(Rectangle rectangle, Configuration configuration, Action<RowInterval> body) |
||||
|
{ |
||||
|
ParallelExecutionSettings parallelSettings = configuration.GetParallelSettings(); |
||||
|
|
||||
|
IterateRows(rectangle, parallelSettings, body); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
|
||||
|
/// </summary>
|
||||
|
public static void IterateRows( |
||||
|
Rectangle rectangle, |
||||
|
in ParallelExecutionSettings parallelSettings, |
||||
|
Action<RowInterval> body) |
||||
|
{ |
||||
|
DebugGuard.MustBeGreaterThan(rectangle.Width, 0, nameof(rectangle)); |
||||
|
|
||||
|
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); |
||||
|
|
||||
|
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); |
||||
|
|
||||
|
// Avoid TPL overhead in this trivial case:
|
||||
|
if (numOfSteps == 1) |
||||
|
{ |
||||
|
var rows = new RowInterval(rectangle.Top, rectangle.Bottom); |
||||
|
body(rows); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int verticalStep = DivideCeil(rectangle.Height, numOfSteps); |
||||
|
|
||||
|
var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
numOfSteps, |
||||
|
parallelOptions, |
||||
|
i => |
||||
|
{ |
||||
|
int yMin = rectangle.Top + (i * verticalStep); |
||||
|
int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); |
||||
|
|
||||
|
var rows = new RowInterval(yMin, yMax); |
||||
|
body(rows); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
|
||||
|
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
|
||||
|
/// </summary>
|
||||
|
public static void IterateRowsWithTempBuffer<T>( |
||||
|
Rectangle rectangle, |
||||
|
in ParallelExecutionSettings parallelSettings, |
||||
|
Action<RowInterval, Memory<T>> body) |
||||
|
where T : struct |
||||
|
{ |
||||
|
int maxSteps = DivideCeil(rectangle.Width * rectangle.Height, parallelSettings.MinimumPixelsProcessedPerTask); |
||||
|
|
||||
|
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps); |
||||
|
|
||||
|
MemoryAllocator memoryAllocator = parallelSettings.MemoryAllocator; |
||||
|
|
||||
|
// Avoid TPL overhead in this trivial case:
|
||||
|
if (numOfSteps == 1) |
||||
|
{ |
||||
|
var rows = new RowInterval(rectangle.Top, rectangle.Bottom); |
||||
|
using (IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>(rectangle.Width)) |
||||
|
{ |
||||
|
body(rows, buffer.Memory); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int verticalStep = DivideCeil(rectangle.Height, numOfSteps); |
||||
|
|
||||
|
var parallelOptions = new ParallelOptions() { MaxDegreeOfParallelism = numOfSteps }; |
||||
|
|
||||
|
Parallel.For( |
||||
|
0, |
||||
|
numOfSteps, |
||||
|
parallelOptions, |
||||
|
i => |
||||
|
{ |
||||
|
int yMin = rectangle.Top + (i * verticalStep); |
||||
|
int yMax = Math.Min(yMin + verticalStep, rectangle.Bottom); |
||||
|
|
||||
|
var rows = new RowInterval(yMin, yMax); |
||||
|
|
||||
|
using (IMemoryOwner<T> buffer = memoryAllocator.Allocate<T>(rectangle.Width)) |
||||
|
{ |
||||
|
body(rows, buffer.Memory); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
|
||||
|
/// instantiating a temporary buffer for each <paramref name="body"/> invocation.
|
||||
|
/// </summary>
|
||||
|
public static void IterateRowsWithTempBuffer<T>( |
||||
|
Rectangle rectangle, |
||||
|
Configuration configuration, |
||||
|
Action<RowInterval, Memory<T>> body) |
||||
|
where T : struct |
||||
|
{ |
||||
|
IterateRowsWithTempBuffer(rectangle, configuration.GetParallelSettings(), body); |
||||
|
} |
||||
|
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,56 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Constants and helper methods for the Adam7 interlacing algorithm.
|
||||
|
/// </summary>
|
||||
|
internal static class Adam7 |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The amount to increment when processing each column per scanline for each interlaced pass.
|
||||
|
/// </summary>
|
||||
|
public static readonly int[] ColumnIncrement = { 8, 8, 4, 4, 2, 2, 1 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The index to start at when processing each column per scanline for each interlaced pass.
|
||||
|
/// </summary>
|
||||
|
public static readonly int[] FirstColumn = { 0, 4, 0, 2, 0, 1, 0 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The index to start at when processing each row per scanline for each interlaced pass.
|
||||
|
/// </summary>
|
||||
|
public static readonly int[] FirstRow = { 0, 0, 4, 0, 2, 0, 1 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The amount to increment when processing each row per scanline for each interlaced pass.
|
||||
|
/// </summary>
|
||||
|
public static readonly int[] RowIncrement = { 8, 8, 8, 4, 4, 2, 2 }; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Returns the correct number of columns for each interlaced pass.
|
||||
|
/// </summary>
|
||||
|
/// <param name="width">The line width.</param>
|
||||
|
/// <param name="passIndex">The current pass index.</param>
|
||||
|
/// <returns>The <see cref="int"/></returns>
|
||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)] |
||||
|
public static int ComputeColumns(int width, int passIndex) |
||||
|
{ |
||||
|
switch (passIndex) |
||||
|
{ |
||||
|
case 0: return (width + 7) / 8; |
||||
|
case 1: return (width + 3) / 8; |
||||
|
case 2: return (width + 3) / 4; |
||||
|
case 3: return (width + 1) / 4; |
||||
|
case 4: return (width + 1) / 2; |
||||
|
case 5: return width / 2; |
||||
|
case 6: return width; |
||||
|
default: throw new ArgumentException($"Not a valid pass index: {passIndex}"); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,107 @@ |
|||||
|
using System; |
||||
|
using System.Buffers.Binary; |
||||
|
using SixLabors.ImageSharp.Common.Helpers; |
||||
|
using SixLabors.ImageSharp.MetaData; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png.Chunks |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// The pHYs chunk specifies the intended pixel size or aspect ratio for display of the image.
|
||||
|
/// </summary>
|
||||
|
internal readonly struct PhysicalChunkData |
||||
|
{ |
||||
|
public const int Size = 9; |
||||
|
|
||||
|
public PhysicalChunkData(uint x, uint y, byte unitSpecifier) |
||||
|
{ |
||||
|
this.XAxisPixelsPerUnit = x; |
||||
|
this.YAxisPixelsPerUnit = y; |
||||
|
this.UnitSpecifier = unitSpecifier; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of pixels per unit on the X axis.
|
||||
|
/// </summary>
|
||||
|
public uint XAxisPixelsPerUnit { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the number of pixels per unit on the Y axis.
|
||||
|
/// </summary>
|
||||
|
public uint YAxisPixelsPerUnit { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the unit specifier.
|
||||
|
/// 0: unit is unknown
|
||||
|
/// 1: unit is the meter
|
||||
|
/// When the unit specifier is 0, the pHYs chunk defines pixel aspect ratio only; the actual size of the pixels remains unspecified.
|
||||
|
/// </summary>
|
||||
|
public byte UnitSpecifier { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Parses the PhysicalChunkData from the given buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="data">The data buffer.</param>
|
||||
|
/// <returns>The parsed PhysicalChunkData.</returns>
|
||||
|
public static PhysicalChunkData Parse(ReadOnlySpan<byte> data) |
||||
|
{ |
||||
|
uint hResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(0, 4)); |
||||
|
uint vResolution = BinaryPrimitives.ReadUInt32BigEndian(data.Slice(4, 4)); |
||||
|
byte unit = data[8]; |
||||
|
|
||||
|
return new PhysicalChunkData(hResolution, vResolution, unit); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Constructs the PngPhysicalChunkData from the provided metadata.
|
||||
|
/// If the resolution units are not in meters, they are automatically convereted.
|
||||
|
/// </summary>
|
||||
|
/// <param name="meta">The metadata.</param>
|
||||
|
/// <returns>The constructed PngPhysicalChunkData instance.</returns>
|
||||
|
public static PhysicalChunkData FromMetadata(ImageMetaData meta) |
||||
|
{ |
||||
|
byte unitSpecifier = 0; |
||||
|
uint x; |
||||
|
uint y; |
||||
|
|
||||
|
switch (meta.ResolutionUnits) |
||||
|
{ |
||||
|
case PixelResolutionUnit.AspectRatio: |
||||
|
unitSpecifier = 0; // Unspecified
|
||||
|
x = (uint)Math.Round(meta.HorizontalResolution); |
||||
|
y = (uint)Math.Round(meta.VerticalResolution); |
||||
|
break; |
||||
|
|
||||
|
case PixelResolutionUnit.PixelsPerInch: |
||||
|
unitSpecifier = 1; // Per meter
|
||||
|
x = (uint)Math.Round(UnitConverter.InchToMeter(meta.HorizontalResolution)); |
||||
|
y = (uint)Math.Round(UnitConverter.InchToMeter(meta.VerticalResolution)); |
||||
|
break; |
||||
|
|
||||
|
case PixelResolutionUnit.PixelsPerCentimeter: |
||||
|
unitSpecifier = 1; // Per meter
|
||||
|
x = (uint)Math.Round(UnitConverter.CmToMeter(meta.HorizontalResolution)); |
||||
|
y = (uint)Math.Round(UnitConverter.CmToMeter(meta.VerticalResolution)); |
||||
|
break; |
||||
|
|
||||
|
default: |
||||
|
unitSpecifier = 1; // Per meter
|
||||
|
x = (uint)Math.Round(meta.HorizontalResolution); |
||||
|
y = (uint)Math.Round(meta.VerticalResolution); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
return new PhysicalChunkData(x, y, unitSpecifier); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Writes the data to the given buffer.
|
||||
|
/// </summary>
|
||||
|
/// <param name="buffer">The buffer.</param>
|
||||
|
public void WriteTo(Span<byte> buffer) |
||||
|
{ |
||||
|
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(0, 4), this.XAxisPixelsPerUnit); |
||||
|
BinaryPrimitives.WriteUInt32BigEndian(buffer.Slice(4, 4), this.YAxisPixelsPerUnit); |
||||
|
buffer[8] = this.UnitSpecifier; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,600 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers.Binary; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using SixLabors.ImageSharp.PixelFormats; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Formats.Png |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Provides methods to allow the decoding of raw scanlines to image rows of different pixel formats.
|
||||
|
/// </summary>
|
||||
|
internal static class PngScanlineProcessor |
||||
|
{ |
||||
|
public static void ProcessGrayscaleScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
bool hasTrans, |
||||
|
ushort luminance16Trans, |
||||
|
byte luminanceTrans) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); |
||||
|
|
||||
|
if (!hasTrans) |
||||
|
{ |
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += 2) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
rgb48.R = luminance; |
||||
|
rgb48.G = luminance; |
||||
|
rgb48.B = luminance; |
||||
|
|
||||
|
pixel.PackFromRgb48(rgb48); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
|
||||
|
var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); |
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); |
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += 2) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
rgba64.R = luminance; |
||||
|
rgba64.G = luminance; |
||||
|
rgba64.B = luminance; |
||||
|
rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgba32 rgba32 = default; |
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); |
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessInterlacedGrayscaleScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int pixelOffset, |
||||
|
int increment, |
||||
|
bool hasTrans, |
||||
|
ushort luminance16Trans, |
||||
|
byte luminanceTrans) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
int scaleFactor = 255 / (ImageMaths.GetColorCountForBitDepth(header.BitDepth) - 1); |
||||
|
|
||||
|
if (!hasTrans) |
||||
|
{ |
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
rgb48.R = luminance; |
||||
|
rgb48.G = luminance; |
||||
|
rgb48.B = luminance; |
||||
|
|
||||
|
pixel.PackFromRgb48(rgb48); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// TODO: We should really be using Rgb24 here but IPixel does not have a PackFromRgb24 method.
|
||||
|
var rgba32 = new Rgba32(0, 0, 0, byte.MaxValue); |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) |
||||
|
{ |
||||
|
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, o) * scaleFactor); |
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 2) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
rgba64.R = luminance; |
||||
|
rgba64.G = luminance; |
||||
|
rgba64.B = luminance; |
||||
|
rgba64.A = luminance.Equals(luminance16Trans) ? ushort.MinValue : ushort.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgba32 rgba32 = default; |
||||
|
for (int x = pixelOffset; x < header.Width; x += increment) |
||||
|
{ |
||||
|
byte luminance = (byte)(Unsafe.Add(ref scanlineSpanRef, x) * scaleFactor); |
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
rgba32.A = luminance.Equals(luminanceTrans) ? byte.MinValue : byte.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessGrayscaleWithAlphaScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += 4) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); |
||||
|
rgba64.R = luminance; |
||||
|
rgba64.G = luminance; |
||||
|
rgba64.B = luminance; |
||||
|
rgba64.A = alpha; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgba32 rgba32 = default; |
||||
|
int bps = bytesPerSample; |
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
int offset = x * bytesPerPixel; |
||||
|
byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); |
||||
|
byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bps); |
||||
|
|
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
rgba32.A = alpha; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessInterlacedGrayscaleWithAlphaScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int pixelOffset, |
||||
|
int increment, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += 4) |
||||
|
{ |
||||
|
ushort luminance = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, 2)); |
||||
|
ushort alpha = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + 2, 2)); |
||||
|
rgba64.R = luminance; |
||||
|
rgba64.G = luminance; |
||||
|
rgba64.B = luminance; |
||||
|
rgba64.A = alpha; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgba32 rgba32 = default; |
||||
|
for (int x = pixelOffset; x < header.Width; x += increment) |
||||
|
{ |
||||
|
int offset = x * bytesPerPixel; |
||||
|
byte luminance = Unsafe.Add(ref scanlineSpanRef, offset); |
||||
|
byte alpha = Unsafe.Add(ref scanlineSpanRef, offset + bytesPerSample); |
||||
|
rgba32.R = luminance; |
||||
|
rgba32.G = luminance; |
||||
|
rgba32.B = luminance; |
||||
|
rgba32.A = alpha; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessPaletteScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
ReadOnlySpan<byte> palette, |
||||
|
byte[] paletteAlpha) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
ReadOnlySpan<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(palette); |
||||
|
ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); |
||||
|
|
||||
|
if (paletteAlpha?.Length > 0) |
||||
|
{ |
||||
|
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
|
||||
|
// channel and we should try to read it.
|
||||
|
Rgba32 rgba = default; |
||||
|
ref byte paletteAlphaRef = ref paletteAlpha[0]; |
||||
|
|
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
int index = Unsafe.Add(ref scanlineSpanRef, x); |
||||
|
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); |
||||
|
rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// TODO: We should have PackFromRgb24.
|
||||
|
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); |
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
int index = Unsafe.Add(ref scanlineSpanRef, x); |
||||
|
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessInterlacedPaletteScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int pixelOffset, |
||||
|
int increment, |
||||
|
ReadOnlySpan<byte> palette, |
||||
|
byte[] paletteAlpha) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
ReadOnlySpan<Rgb24> palettePixels = MemoryMarshal.Cast<byte, Rgb24>(palette); |
||||
|
ref Rgb24 palettePixelsRef = ref MemoryMarshal.GetReference(palettePixels); |
||||
|
|
||||
|
if (paletteAlpha?.Length > 0) |
||||
|
{ |
||||
|
// If the alpha palette is not null and has one or more entries, this means, that the image contains an alpha
|
||||
|
// channel and we should try to read it.
|
||||
|
Rgba32 rgba = default; |
||||
|
ref byte paletteAlphaRef = ref paletteAlpha[0]; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) |
||||
|
{ |
||||
|
int index = Unsafe.Add(ref scanlineSpanRef, o); |
||||
|
rgba.A = paletteAlpha.Length > index ? Unsafe.Add(ref paletteAlphaRef, index) : byte.MaxValue; |
||||
|
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o++) |
||||
|
{ |
||||
|
int index = Unsafe.Add(ref scanlineSpanRef, o); |
||||
|
rgba.Rgb = Unsafe.Add(ref palettePixelsRef, index); |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessRgbScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample, |
||||
|
bool hasTrans, |
||||
|
Rgb48 rgb48Trans, |
||||
|
Rgb24 rgb24Trans) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (!hasTrans) |
||||
|
{ |
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgb48(rgb48); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PixelOperations<TPixel>.Instance.PackFromRgb24Bytes(scanlineSpan, rowSpan, header.Width); |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
rgba64.Rgb = rgb48; |
||||
|
rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
ReadOnlySpan<Rgb24> rgb24Span = MemoryMarshal.Cast<byte, Rgb24>(scanlineSpan); |
||||
|
ref Rgb24 rgb24SpanRef = ref MemoryMarshal.GetReference(rgb24Span); |
||||
|
for (int x = 0; x < header.Width; x++) |
||||
|
{ |
||||
|
ref readonly Rgb24 rgb24 = ref Unsafe.Add(ref rgb24SpanRef, x); |
||||
|
Rgba32 rgba32 = default; |
||||
|
rgba32.Rgb = rgb24; |
||||
|
rgba32.A = rgb24.Equals(rgb24Trans) ? byte.MinValue : byte.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba32); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessInterlacedRgbScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int pixelOffset, |
||||
|
int increment, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample, |
||||
|
bool hasTrans, |
||||
|
Rgb48 rgb48Trans, |
||||
|
Rgb24 rgb24Trans) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
if (hasTrans) |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
rgba64.Rgb = rgb48; |
||||
|
rgba64.A = rgb48.Equals(rgb48Trans) ? ushort.MinValue : ushort.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgb48 rgb48 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgb48.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgb48.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgb48.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgb48(rgb48); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
if (hasTrans) |
||||
|
{ |
||||
|
Rgba32 rgba = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgba.R = Unsafe.Add(ref scanlineSpanRef, o); |
||||
|
rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); |
||||
|
rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); |
||||
|
rgba.A = rgb24Trans.Equals(rgba.Rgb) ? byte.MinValue : byte.MaxValue; |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var rgba = new Rgba32(0, 0, 0, byte.MaxValue); |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgba.R = Unsafe.Add(ref scanlineSpanRef, o); |
||||
|
rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); |
||||
|
rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessRgbaScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = 0, o = 0; x < header.Width; x++, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
PixelOperations<TPixel>.Instance.PackFromRgba32Bytes(scanlineSpan, rowSpan, header.Width); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public static void ProcessInterlacedRgbaScanline<TPixel>( |
||||
|
in PngHeader header, |
||||
|
ReadOnlySpan<byte> scanlineSpan, |
||||
|
Span<TPixel> rowSpan, |
||||
|
int pixelOffset, |
||||
|
int increment, |
||||
|
int bytesPerPixel, |
||||
|
int bytesPerSample) |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
TPixel pixel = default; |
||||
|
ref byte scanlineSpanRef = ref MemoryMarshal.GetReference(scanlineSpan); |
||||
|
ref TPixel rowSpanRef = ref MemoryMarshal.GetReference(rowSpan); |
||||
|
|
||||
|
if (header.BitDepth == 16) |
||||
|
{ |
||||
|
Rgba64 rgba64 = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgba64.R = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o, bytesPerSample)); |
||||
|
rgba64.G = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + bytesPerSample, bytesPerSample)); |
||||
|
rgba64.B = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (2 * bytesPerSample), bytesPerSample)); |
||||
|
rgba64.A = BinaryPrimitives.ReadUInt16BigEndian(scanlineSpan.Slice(o + (3 * bytesPerSample), bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgba64(rgba64); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Rgba32 rgba = default; |
||||
|
for (int x = pixelOffset, o = 0; x < header.Width; x += increment, o += bytesPerPixel) |
||||
|
{ |
||||
|
rgba.R = Unsafe.Add(ref scanlineSpanRef, o); |
||||
|
rgba.G = Unsafe.Add(ref scanlineSpanRef, o + bytesPerSample); |
||||
|
rgba.B = Unsafe.Add(ref scanlineSpanRef, o + (2 * bytesPerSample)); |
||||
|
rgba.A = Unsafe.Add(ref scanlineSpanRef, o + (3 * bytesPerSample)); |
||||
|
|
||||
|
pixel.PackFromRgba32(rgba); |
||||
|
Unsafe.Add(ref rowSpanRef, x) = pixel; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
namespace SixLabors.ImageSharp |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// A generic interface for a deeply cloneable type.
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="T">The type of object to clone.</typeparam>
|
||||
|
public interface IDeepCloneable<out T> |
||||
|
where T : class |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new <typeparamref name="T"/> that is a deep copy of the current instance.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <typeparamref name="T"/>.</returns>
|
||||
|
T DeepClone(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An interface for objects that can be cloned. This creates a deep copy of the object.
|
||||
|
/// </summary>
|
||||
|
public interface IDeepCloneable |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Creates a new object that is a deep copy of the current instance.
|
||||
|
/// </summary>
|
||||
|
/// <returns>The <see cref="IDeepCloneable"/>.</returns>
|
||||
|
IDeepCloneable DeepClone(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,3 @@ |
|||||
|
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> |
||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common/@EntryIndexedValue">True</s:Boolean> |
||||
|
<s:Boolean x:Key="/Default/CodeInspection/NamespaceProvider/NamespaceFoldersToSkip/=common_005Cexceptions/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> |
||||
@ -0,0 +1,45 @@ |
|||||
|
// Copyright(c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Memory |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Represents an interval of rows in a <see cref="Rectangle"/> and/or <see cref="Buffer2D{T}"/>
|
||||
|
/// </summary>
|
||||
|
internal readonly struct RowInterval |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Initializes a new instance of the <see cref="RowInterval"/> struct.
|
||||
|
/// </summary>
|
||||
|
public RowInterval(int min, int max) |
||||
|
{ |
||||
|
DebugGuard.MustBeLessThan(min, max, nameof(min)); |
||||
|
|
||||
|
this.Min = min; |
||||
|
this.Max = max; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the INCLUSIVE minimum
|
||||
|
/// </summary>
|
||||
|
public int Min { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the EXCLUSIVE maximum
|
||||
|
/// </summary>
|
||||
|
public int Max { get; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the difference (<see cref="Max"/> - <see cref="Min"/>)
|
||||
|
/// </summary>
|
||||
|
public int Height => this.Max - this.Min; |
||||
|
|
||||
|
/// <inheritdoc />
|
||||
|
public override string ToString() |
||||
|
{ |
||||
|
return $"RowInterval [{this.Min}->{this.Max}["; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,115 +0,0 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
|
||||
// Licensed under the Apache License, Version 2.0.
|
|
||||
|
|
||||
using System; |
|
||||
using System.Threading.Tasks; |
|
||||
|
|
||||
using BenchmarkDotNet.Attributes; |
|
||||
|
|
||||
using SixLabors.ImageSharp.Advanced; |
|
||||
using SixLabors.ImageSharp.Memory; |
|
||||
using SixLabors.ImageSharp.PixelFormats; |
|
||||
|
|
||||
namespace SixLabors.ImageSharp.Benchmarks.Codecs |
|
||||
{ |
|
||||
public class CopyPixels : BenchmarkBase |
|
||||
{ |
|
||||
[Benchmark(Baseline = true, Description = "PixelAccessor Copy by indexer")] |
|
||||
public Rgba32 CopyByPixelAccesor() |
|
||||
{ |
|
||||
using (var source = new Image<Rgba32>(1024, 768)) |
|
||||
using (var target = new Image<Rgba32>(1024, 768)) |
|
||||
{ |
|
||||
Buffer2D<Rgba32> sourcePixels = source.GetRootFramePixelBuffer(); |
|
||||
Buffer2D<Rgba32> targetPixels = target.GetRootFramePixelBuffer(); |
|
||||
ParallelFor.WithConfiguration( |
|
||||
0, |
|
||||
source.Height, |
|
||||
Configuration.Default, |
|
||||
y => |
|
||||
{ |
|
||||
for (int x = 0; x < source.Width; x++) |
|
||||
{ |
|
||||
targetPixels[x, y] = sourcePixels[x, y]; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return targetPixels[0, 0]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Benchmark(Description = "PixelAccessor Copy by Span")] |
|
||||
public Rgba32 CopyByPixelAccesorSpan() |
|
||||
{ |
|
||||
using (var source = new Image<Rgba32>(1024, 768)) |
|
||||
using (var target = new Image<Rgba32>(1024, 768)) |
|
||||
{ |
|
||||
Buffer2D<Rgba32> sourcePixels = source.GetRootFramePixelBuffer(); |
|
||||
Buffer2D<Rgba32> targetPixels = target.GetRootFramePixelBuffer(); |
|
||||
ParallelFor.WithConfiguration( |
|
||||
0, |
|
||||
source.Height, |
|
||||
Configuration.Default, |
|
||||
y => |
|
||||
{ |
|
||||
Span<Rgba32> sourceRow = sourcePixels.GetRowSpan(y); |
|
||||
Span<Rgba32> targetRow = targetPixels.GetRowSpan(y); |
|
||||
|
|
||||
for (int x = 0; x < source.Width; x++) |
|
||||
{ |
|
||||
targetRow[x] = sourceRow[x]; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return targetPixels[0, 0]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Benchmark(Description = "Copy by indexer")] |
|
||||
public Rgba32 Copy() |
|
||||
{ |
|
||||
using (var source = new Image<Rgba32>(1024, 768)) |
|
||||
using (var target = new Image<Rgba32>(1024, 768)) |
|
||||
{ |
|
||||
ParallelFor.WithConfiguration( |
|
||||
0, |
|
||||
source.Height, |
|
||||
Configuration.Default, |
|
||||
y => |
|
||||
{ |
|
||||
for (int x = 0; x < source.Width; x++) |
|
||||
{ |
|
||||
target[x, y] = source[x, y]; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return target[0, 0]; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
[Benchmark(Description = "Copy by Span")] |
|
||||
public Rgba32 CopySpan() |
|
||||
{ |
|
||||
using (var source = new Image<Rgba32>(1024, 768)) |
|
||||
using (var target = new Image<Rgba32>(1024, 768)) |
|
||||
{ |
|
||||
ParallelFor.WithConfiguration( |
|
||||
0, |
|
||||
source.Height, |
|
||||
Configuration.Default, |
|
||||
y => |
|
||||
{ |
|
||||
Span<Rgba32> sourceRow = source.Frames.RootFrame.GetPixelRowSpan(y); |
|
||||
Span<Rgba32> targetRow = target.Frames.RootFrame.GetPixelRowSpan(y); |
|
||||
|
|
||||
for (int x = 0; x < source.Width; x++) |
|
||||
{ |
|
||||
targetRow[x] = sourceRow[x]; |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
return target[0, 0]; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Bmp; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Bmp |
||||
|
{ |
||||
|
public class BmpMetaDataTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void CloneIsDeep() |
||||
|
{ |
||||
|
var meta = new BmpMetaData() { BitsPerPixel = BmpBitsPerPixel.Pixel24 }; |
||||
|
var clone = (BmpMetaData)meta.DeepClone(); |
||||
|
|
||||
|
clone.BitsPerPixel = BmpBitsPerPixel.Pixel32; |
||||
|
|
||||
|
Assert.False(meta.BitsPerPixel.Equals(clone.BitsPerPixel)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Gif; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Gif |
||||
|
{ |
||||
|
public class GifFrameMetaDataTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void CloneIsDeep() |
||||
|
{ |
||||
|
var meta = new GifFrameMetaData() |
||||
|
{ |
||||
|
FrameDelay = 1, |
||||
|
DisposalMethod = GifDisposalMethod.RestoreToBackground, |
||||
|
ColorTableLength = 2 |
||||
|
}; |
||||
|
|
||||
|
var clone = (GifFrameMetaData)meta.DeepClone(); |
||||
|
|
||||
|
clone.FrameDelay = 2; |
||||
|
clone.DisposalMethod = GifDisposalMethod.RestoreToPrevious; |
||||
|
clone.ColorTableLength = 1; |
||||
|
|
||||
|
Assert.False(meta.FrameDelay.Equals(clone.FrameDelay)); |
||||
|
Assert.False(meta.DisposalMethod.Equals(clone.DisposalMethod)); |
||||
|
Assert.False(meta.ColorTableLength.Equals(clone.ColorTableLength)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,32 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Gif; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Gif |
||||
|
{ |
||||
|
public class GifMetaDataTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void CloneIsDeep() |
||||
|
{ |
||||
|
var meta = new GifMetaData() |
||||
|
{ |
||||
|
RepeatCount = 1, |
||||
|
ColorTableMode = GifColorTableMode.Global, |
||||
|
GlobalColorTableLength = 2 |
||||
|
}; |
||||
|
|
||||
|
var clone = (GifMetaData)meta.DeepClone(); |
||||
|
|
||||
|
clone.RepeatCount = 2; |
||||
|
clone.ColorTableMode = GifColorTableMode.Local; |
||||
|
clone.GlobalColorTableLength = 1; |
||||
|
|
||||
|
Assert.False(meta.RepeatCount.Equals(clone.RepeatCount)); |
||||
|
Assert.False(meta.ColorTableMode.Equals(clone.ColorTableMode)); |
||||
|
Assert.False(meta.GlobalColorTableLength.Equals(clone.GlobalColorTableLength)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Jpeg; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Jpg |
||||
|
{ |
||||
|
public class JpegMetaDataTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void CloneIsDeep() |
||||
|
{ |
||||
|
var meta = new JpegMetaData() { Quality = 50 }; |
||||
|
var clone = (JpegMetaData)meta.DeepClone(); |
||||
|
|
||||
|
clone.Quality = 99; |
||||
|
|
||||
|
Assert.False(meta.Quality.Equals(clone.Quality)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using SixLabors.ImageSharp.Formats.Png; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Formats.Png |
||||
|
{ |
||||
|
public class PngMetaDataTests |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void CloneIsDeep() |
||||
|
{ |
||||
|
var meta = new PngMetaData() |
||||
|
{ |
||||
|
BitDepth = PngBitDepth.Bit16, |
||||
|
ColorType = PngColorType.GrayscaleWithAlpha, |
||||
|
Gamma = 2 |
||||
|
}; |
||||
|
var clone = (PngMetaData)meta.DeepClone(); |
||||
|
|
||||
|
clone.BitDepth = PngBitDepth.Bit2; |
||||
|
clone.ColorType = PngColorType.Palette; |
||||
|
clone.Gamma = 1; |
||||
|
|
||||
|
Assert.False(meta.BitDepth.Equals(clone.BitDepth)); |
||||
|
Assert.False(meta.ColorType.Equals(clone.ColorType)); |
||||
|
Assert.False(meta.Gamma.Equals(clone.Gamma)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,338 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Collections.Concurrent; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Threading; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.ImageSharp.ParallelUtils; |
||||
|
using SixLabors.Memory; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
|
using Xunit; |
||||
|
using Xunit.Abstractions; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Helpers |
||||
|
{ |
||||
|
public class ParallelHelperTests |
||||
|
{ |
||||
|
private readonly ITestOutputHelper Output; |
||||
|
|
||||
|
public ParallelHelperTests(ITestOutputHelper output) |
||||
|
{ |
||||
|
this.Output = output; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// maxDegreeOfParallelism, minY, maxY, expectedStepLength, expectedLastStepLength
|
||||
|
/// </summary>
|
||||
|
public static TheoryData<int, int, int, int, int> IterateRows_OverMinimumPixelsLimit_Data = |
||||
|
new TheoryData<int, int, int, int, int>() |
||||
|
{ |
||||
|
{ 1, 0, 100, -1, 100 }, |
||||
|
{ 2, 0, 9, 5, 4 }, |
||||
|
{ 4, 0, 19, 5, 4 }, |
||||
|
{ 2, 10, 19, 5, 4 }, |
||||
|
{ 4, 0, 200, 50, 50 }, |
||||
|
{ 4, 123, 323, 50, 50 }, |
||||
|
{ 4, 0, 1201, 301, 298 }, |
||||
|
{ 8, 10, 236, 29, 23 } |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
||||
|
public void IterateRows_OverMinimumPixelsLimit_IntervalsAreCorrect( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minY, |
||||
|
int maxY, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
1, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
||||
|
|
||||
|
int actualNumberOfSteps = 0; |
||||
|
|
||||
|
ParallelHelper.IterateRows( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
rows => |
||||
|
{ |
||||
|
Assert.True(rows.Min >= minY); |
||||
|
Assert.True(rows.Max <= maxY); |
||||
|
|
||||
|
int step = rows.Max - rows.Min; |
||||
|
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; |
||||
|
|
||||
|
Interlocked.Increment(ref actualNumberOfSteps); |
||||
|
Assert.Equal(expected, step); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
||||
|
public void IterateRows_OverMinimumPixelsLimit_ShouldVisitAllRows( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minY, |
||||
|
int maxY, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
1, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
||||
|
|
||||
|
|
||||
|
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); |
||||
|
int[] actualData = new int[maxY]; |
||||
|
|
||||
|
ParallelHelper.IterateRows( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
rows => |
||||
|
{ |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
actualData[y] = y; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(expectedData, actualData); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
||||
|
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minY, |
||||
|
int maxY, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
1, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
||||
|
|
||||
|
var bufferHashes = new ConcurrentBag<int>(); |
||||
|
|
||||
|
int actualNumberOfSteps = 0; |
||||
|
ParallelHelper.IterateRowsWithTempBuffer( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
(RowInterval rows, Memory<Vector4> buffer) => |
||||
|
{ |
||||
|
Assert.True(rows.Min >= minY); |
||||
|
Assert.True(rows.Max <= maxY); |
||||
|
|
||||
|
bufferHashes.Add(buffer.GetHashCode()); |
||||
|
|
||||
|
int step = rows.Max - rows.Min; |
||||
|
int expected = rows.Max < maxY ? expectedStepLength : expectedLastStepLength; |
||||
|
|
||||
|
Interlocked.Increment(ref actualNumberOfSteps); |
||||
|
Assert.Equal(expected, step); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(maxDegreeOfParallelism, actualNumberOfSteps); |
||||
|
|
||||
|
int numberOfDifferentBuffers = bufferHashes.Distinct().Count(); |
||||
|
Assert.Equal(actualNumberOfSteps, numberOfDifferentBuffers); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_OverMinimumPixelsLimit_Data))] |
||||
|
public void IterateRowsWithTempBuffer_OverMinimumPixelsLimit_ShouldVisitAllRows( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minY, |
||||
|
int maxY, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
1, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, minY, 10, maxY - minY); |
||||
|
|
||||
|
int[] expectedData = Enumerable.Repeat(0, minY).Concat(Enumerable.Range(minY, maxY - minY)).ToArray(); |
||||
|
int[] actualData = new int[maxY]; |
||||
|
|
||||
|
ParallelHelper.IterateRowsWithTempBuffer( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
(RowInterval rows, Memory<Vector4> buffer) => |
||||
|
{ |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
actualData[y] = y; |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(expectedData, actualData); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public static TheoryData<int, int, int, int, int, int, int> IterateRows_WithEffectiveMinimumPixelsLimit_Data = |
||||
|
new TheoryData<int, int, int, int, int, int, int>() |
||||
|
{ |
||||
|
{ 2, 200, 50, 2, 1, -1, 2 }, |
||||
|
{ 2, 200, 200, 1, 1, -1, 1 }, |
||||
|
{ 4, 200, 100, 4, 2, 2, 2 }, |
||||
|
{ 4, 300, 100, 8, 3, 3, 2 }, |
||||
|
{ 2, 5000, 1, 4500, 1, -1, 4500 }, |
||||
|
{ 2, 5000, 1, 5000, 1, -1, 5000 }, |
||||
|
{ 2, 5000, 1, 5001, 2, 2501, 2500 }, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] |
||||
|
public void IterateRows_WithEffectiveMinimumPixelsLimit( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minimumPixelsProcessedPerTask, |
||||
|
int width, |
||||
|
int height, |
||||
|
int expectedNumberOfSteps, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
minimumPixelsProcessedPerTask, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, 0, width, height); |
||||
|
|
||||
|
int actualNumberOfSteps = 0; |
||||
|
|
||||
|
ParallelHelper.IterateRows( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
rows => |
||||
|
{ |
||||
|
Assert.True(rows.Min >= 0); |
||||
|
Assert.True(rows.Max <= height); |
||||
|
|
||||
|
int step = rows.Max - rows.Min; |
||||
|
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; |
||||
|
|
||||
|
Interlocked.Increment(ref actualNumberOfSteps); |
||||
|
Assert.Equal(expected, step); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); |
||||
|
} |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRows_WithEffectiveMinimumPixelsLimit_Data))] |
||||
|
public void IterateRowsWithTempBuffer_WithEffectiveMinimumPixelsLimit( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int minimumPixelsProcessedPerTask, |
||||
|
int width, |
||||
|
int height, |
||||
|
int expectedNumberOfSteps, |
||||
|
int expectedStepLength, |
||||
|
int expectedLastStepLength) |
||||
|
{ |
||||
|
var parallelSettings = new ParallelExecutionSettings( |
||||
|
maxDegreeOfParallelism, |
||||
|
minimumPixelsProcessedPerTask, |
||||
|
Configuration.Default.MemoryAllocator); |
||||
|
|
||||
|
var rectangle = new Rectangle(0, 0, width, height); |
||||
|
|
||||
|
int actualNumberOfSteps = 0; |
||||
|
ParallelHelper.IterateRowsWithTempBuffer( |
||||
|
rectangle, |
||||
|
parallelSettings, |
||||
|
(RowInterval rows, Memory<Vector4> buffer) => |
||||
|
{ |
||||
|
Assert.True(rows.Min >= 0); |
||||
|
Assert.True(rows.Max <= height); |
||||
|
|
||||
|
int step = rows.Max - rows.Min; |
||||
|
int expected = rows.Max < height ? expectedStepLength : expectedLastStepLength; |
||||
|
|
||||
|
Interlocked.Increment(ref actualNumberOfSteps); |
||||
|
Assert.Equal(expected, step); |
||||
|
}); |
||||
|
|
||||
|
Assert.Equal(expectedNumberOfSteps, actualNumberOfSteps); |
||||
|
} |
||||
|
|
||||
|
public static readonly TheoryData<int, int, int, int, int, int, int> IterateRectangularBuffer_Data = |
||||
|
new TheoryData<int, int, int, int, int, int, int>() |
||||
|
{ |
||||
|
{ 8, 582, 453, 10, 10, 291, 226 }, // boundary data from DetectEdgesTest.DetectEdges_InBox
|
||||
|
{ 2, 582, 453, 10, 10, 291, 226 }, |
||||
|
{ 16, 582, 453, 10, 10, 291, 226 }, |
||||
|
{ 16, 582, 453, 10, 10, 1, 226 }, |
||||
|
{ 16, 1, 453, 0, 10, 1, 226 }, |
||||
|
}; |
||||
|
|
||||
|
[Theory] |
||||
|
[MemberData(nameof(IterateRectangularBuffer_Data))] |
||||
|
public void IterateRectangularBuffer( |
||||
|
int maxDegreeOfParallelism, |
||||
|
int bufferWidth, |
||||
|
int bufferHeight, |
||||
|
int rectX, |
||||
|
int rectY, |
||||
|
int rectWidth, |
||||
|
int rectHeight) |
||||
|
{ |
||||
|
MemoryAllocator memoryAllocator = Configuration.Default.MemoryAllocator; |
||||
|
|
||||
|
using (Buffer2D<Point> expected = memoryAllocator.Allocate2D<Point>(bufferWidth, bufferHeight, AllocationOptions.Clean)) |
||||
|
using (Buffer2D<Point> actual = memoryAllocator.Allocate2D<Point>(bufferWidth, bufferHeight, AllocationOptions.Clean)) |
||||
|
{ |
||||
|
var rect = new Rectangle(rectX, rectY, rectWidth, rectHeight); |
||||
|
|
||||
|
void FillRow(int y, Buffer2D<Point> buffer) |
||||
|
{ |
||||
|
for (int x = rect.Left; x < rect.Right; x++) |
||||
|
{ |
||||
|
buffer[x, y] = new Point(x, y); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
// Fill Expected data:
|
||||
|
for (int y = rectY; y < rect.Bottom; y++) |
||||
|
{ |
||||
|
FillRow(y, expected); |
||||
|
} |
||||
|
|
||||
|
// Fill actual data using IterateRows:
|
||||
|
var settings = new ParallelExecutionSettings(maxDegreeOfParallelism, memoryAllocator); |
||||
|
|
||||
|
ParallelHelper.IterateRows(rect, settings, |
||||
|
rows => |
||||
|
{ |
||||
|
this.Output.WriteLine(rows.ToString()); |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
FillRow(y, actual); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
// Assert:
|
||||
|
TestImageExtensions.CompareBuffers(expected.Span, actual.Span); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,38 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
|
||||
|
using SixLabors.ImageSharp.Memory; |
||||
|
using SixLabors.Memory; |
||||
|
|
||||
|
using Xunit; |
||||
|
|
||||
|
namespace SixLabors.ImageSharp.Tests.Helpers |
||||
|
{ |
||||
|
public class RowIntervalTests |
||||
|
{ |
||||
|
[Theory] |
||||
|
[InlineData(10, 20, 5, 10)] |
||||
|
[InlineData(1, 10, 0, 10)] |
||||
|
[InlineData(1, 10, 5, 8)] |
||||
|
[InlineData(1, 1, 0, 1)] |
||||
|
[InlineData(10, 20, 9, 10)] |
||||
|
[InlineData(10, 20, 0, 1)] |
||||
|
public void GetMultiRowSpan(int width, int height, int min, int max) |
||||
|
{ |
||||
|
using (Buffer2D<int> buffer = Configuration.Default.MemoryAllocator.Allocate2D<int>(width, height)) |
||||
|
{ |
||||
|
var rows = new RowInterval(min, max); |
||||
|
|
||||
|
Span<int> span = buffer.GetMultiRowSpan(rows); |
||||
|
|
||||
|
ref int expected0 = ref buffer.Span[min * width]; |
||||
|
int expectedLength = (max - min) * width; |
||||
|
|
||||
|
ref int actual0 = ref span[0]; |
||||
|
|
||||
|
Assert.Equal(span.Length, expectedLength); |
||||
|
Assert.True(Unsafe.AreSame(ref expected0, ref actual0)); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,24 +1,34 @@ |
|||||
// Copyright (c) Six Labors and contributors.
|
// Copyright (c) Six Labors and contributors.
|
||||
// Licensed under the Apache License, Version 2.0.
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
|
||||
using SixLabors.ImageSharp.PixelFormats; |
using SixLabors.ImageSharp.PixelFormats; |
||||
using SixLabors.ImageSharp.Processing; |
using SixLabors.ImageSharp.Processing; |
||||
|
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
||||
|
using SixLabors.Primitives; |
||||
|
|
||||
using Xunit; |
using Xunit; |
||||
|
|
||||
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
||||
{ |
{ |
||||
|
[GroupOutput("Transforms")] |
||||
public class CropTest : FileTestBase |
public class CropTest : FileTestBase |
||||
{ |
{ |
||||
[Theory] |
[Theory] |
||||
[WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] |
[WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] |
||||
public void ImageShouldCrop<TPixel>(TestImageProvider<TPixel> provider) |
[WithTestPatternImages(50, 50, PixelTypes.Rgba32, -1, -1, 100, 200)] |
||||
|
[WithTestPatternImages(30, 70, PixelTypes.Rgba32, 7, 13, 20, 50)] |
||||
|
public void Crop<TPixel>(TestImageProvider<TPixel> provider, int x, int y, int w, int h) |
||||
where TPixel : struct, IPixel<TPixel> |
where TPixel : struct, IPixel<TPixel> |
||||
{ |
{ |
||||
using (Image<TPixel> image = provider.GetImage()) |
var rect = new Rectangle(x, y, w, h); |
||||
{ |
FormattableString info = $"X{x}Y{y}.W{w}H{h}"; |
||||
image.Mutate(x => x.Crop(image.Width / 2, image.Height / 2)); |
provider.RunValidatingProcessorTest( |
||||
image.DebugSave(provider); |
ctx => ctx.Crop(rect), |
||||
} |
info, |
||||
|
appendPixelTypeToFileName: false, |
||||
|
comparer: ImageComparer.Exact); |
||||
} |
} |
||||
} |
} |
||||
} |
} |
||||
@ -1 +1 @@ |
|||||
Subproject commit 6abc3bc0ac253a24c9e88e68d7b7d853350a85da |
Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf |
||||
Loading…
Reference in new issue