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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two images together by blending the pixels.
|
|||
/// </summary>
|
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Buffers; |
|||
using System.Threading.Tasks; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.ParallelUtils; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.Memory; |
|||
using SixLabors.Primitives; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Drawing |
|||
{ |
|||
/// <summary>
|
|||
/// Combines two images together by blending the pixels.
|
|||
/// </summary>
|
|||
/// <typeparam name="TPixelDst">The pixel format of destination image.</typeparam>
|
|||
/// <typeparam name="TPixelSrc">The pixel format os source image.</typeparam>
|
|||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
|||
/// <typeparam name="TPixelSrc">The pixel format of source image.</typeparam>
|
|||
internal class DrawImageProcessor<TPixelDst, TPixelSrc> : ImageProcessor<TPixelDst> |
|||
where TPixelDst : struct, IPixel<TPixelDst> |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
where TPixelSrc : struct, IPixel<TPixelSrc> |
|||
{ |
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
|||
/// </summary>
|
|||
/// <param name="image">The image to blend with the currently processing image.</param>
|
|||
/// <summary>
|
|||
/// Initializes a new instance of the <see cref="DrawImageProcessor{TPixelDst, TPixelSrc}"/> class.
|
|||
/// </summary>
|
|||
/// <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="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="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) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
|||
|
|||
this.Image = image; |
|||
this.Opacity = opacity; |
|||
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
|||
this.Location = location; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the image to blend
|
|||
/// </summary>
|
|||
public Image<TPixelSrc> Image { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the opacity of the image to blend
|
|||
/// </summary>
|
|||
public float Opacity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel blender
|
|||
/// </summary>
|
|||
public PixelBlender<TPixelDst> Blender { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the location to draw the blended image
|
|||
/// </summary>
|
|||
public Point Location { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
Image<TPixelSrc> targetImage = this.Image; |
|||
PixelBlender<TPixelDst> blender = this.Blender; |
|||
int locationY = this.Location.Y; |
|||
|
|||
// Align start/end positions.
|
|||
Rectangle bounds = targetImage.Bounds(); |
|||
|
|||
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
|||
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
|||
int targetX = minX - this.Location.X; |
|||
|
|||
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
|||
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
|||
|
|||
int width = maxX - minX; |
|||
|
|||
/// <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) |
|||
{ |
|||
Guard.MustBeBetweenOrEqualTo(opacity, 0, 1, nameof(opacity)); |
|||
|
|||
this.Image = image; |
|||
this.Opacity = opacity; |
|||
this.Blender = PixelOperations<TPixelDst>.Instance.GetPixelBlender(colorBlendingMode, alphaCompositionMode); |
|||
this.Location = location; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the image to blend
|
|||
/// </summary>
|
|||
public Image<TPixelSrc> Image { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the opacity of the image to blend
|
|||
/// </summary>
|
|||
public float Opacity { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the pixel blender
|
|||
/// </summary>
|
|||
public PixelBlender<TPixelDst> Blender { get; } |
|||
|
|||
/// <summary>
|
|||
/// Gets the location to draw the blended image
|
|||
/// </summary>
|
|||
public Point Location { get; } |
|||
|
|||
/// <inheritdoc/>
|
|||
protected override void OnFrameApply(ImageFrame<TPixelDst> source, Rectangle sourceRectangle, Configuration configuration) |
|||
{ |
|||
Image<TPixelSrc> targetImage = this.Image; |
|||
PixelBlender<TPixelDst> blender = this.Blender; |
|||
int locationY = this.Location.Y; |
|||
|
|||
// Align start/end positions.
|
|||
Rectangle bounds = targetImage.Bounds(); |
|||
|
|||
int minX = Math.Max(this.Location.X, sourceRectangle.X); |
|||
int maxX = Math.Min(this.Location.X + bounds.Width, sourceRectangle.Width); |
|||
int targetX = minX - this.Location.X; |
|||
|
|||
int minY = Math.Max(this.Location.Y, sourceRectangle.Y); |
|||
int maxY = Math.Min(this.Location.Y + bounds.Height, sourceRectangle.Bottom); |
|||
|
|||
int width = maxX - minX; |
|||
|
|||
MemoryAllocator memoryAllocator = this.Image.GetConfiguration().MemoryAllocator; |
|||
|
|||
ParallelFor.WithConfiguration( |
|||
minY, |
|||
maxY, |
|||
configuration, |
|||
y => |
|||
{ |
|||
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); |
|||
}); |
|||
} |
|||
} |
|||
var workingRect = Rectangle.FromLTRB(minX, minY, maxX, maxY); |
|||
|
|||
ParallelHelper.IterateRows( |
|||
workingRect, |
|||
configuration, |
|||
rows => |
|||
{ |
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
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.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
|
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
using SixLabors.ImageSharp.Processing; |
|||
using SixLabors.ImageSharp.Tests.TestUtilities.ImageComparison; |
|||
using SixLabors.Primitives; |
|||
|
|||
using Xunit; |
|||
|
|||
namespace SixLabors.ImageSharp.Tests.Processing.Processors.Transforms |
|||
{ |
|||
[GroupOutput("Transforms")] |
|||
public class CropTest : FileTestBase |
|||
{ |
|||
[Theory] |
|||
[WithFileCollection(nameof(DefaultFiles), DefaultPixelType)] |
|||
public void ImageShouldCrop<TPixel>(TestImageProvider<TPixel> provider) |
|||
[WithTestPatternImages(70, 30, PixelTypes.Rgba32, 0, 0, 70, 30)] |
|||
[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> |
|||
{ |
|||
using (Image<TPixel> image = provider.GetImage()) |
|||
{ |
|||
image.Mutate(x => x.Crop(image.Width / 2, image.Height / 2)); |
|||
image.DebugSave(provider); |
|||
} |
|||
var rect = new Rectangle(x, y, w, h); |
|||
FormattableString info = $"X{x}Y{y}.W{w}H{h}"; |
|||
provider.RunValidatingProcessorTest( |
|||
ctx => ctx.Crop(rect), |
|||
info, |
|||
appendPixelTypeToFileName: false, |
|||
comparer: ImageComparer.Exact); |
|||
} |
|||
} |
|||
} |
|||
@ -1 +1 @@ |
|||
Subproject commit 6abc3bc0ac253a24c9e88e68d7b7d853350a85da |
|||
Subproject commit 7f4d2d64c6b820ca2b6827e6a8540a1013305ccf |
|||
Loading…
Reference in new issue