mirror of https://github.com/SixLabors/ImageSharp
32 changed files with 704 additions and 307 deletions
@ -0,0 +1,227 @@ |
|||
// Copyright (c) Six Labors and contributors.
|
|||
// Licensed under the Apache License, Version 2.0.
|
|||
|
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using SixLabors.ImageSharp.Advanced; |
|||
using SixLabors.ImageSharp.Memory; |
|||
using SixLabors.ImageSharp.PixelFormats; |
|||
|
|||
namespace SixLabors.ImageSharp.Processing.Processors.Transforms |
|||
{ |
|||
/// <content>
|
|||
/// Extensions for <see cref="IResampler"/>.
|
|||
/// </content>
|
|||
public static partial class ResamplerExtensions |
|||
{ |
|||
/// <summary>
|
|||
/// Applies an resizing transformation upon an image.
|
|||
/// </summary>
|
|||
/// <typeparam name="TResampler">The type of sampler.</typeparam>
|
|||
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
|||
/// <param name="configuration">The configuration.</param>
|
|||
/// <param name="sampler">The pixel sampler.</param>
|
|||
/// <param name="source">The source image.</param>
|
|||
/// <param name="destination">The destination image.</param>
|
|||
/// <param name="sourceRectangle">The source bounds.</param>
|
|||
/// <param name="destinationRectangle">The destination location.</param>
|
|||
/// <param name="compand">Whether to compress or expand individual pixel color values on processing.</param>
|
|||
public static void ApplyResizeTransform<TResampler, TPixel>( |
|||
Configuration configuration, |
|||
in TResampler sampler, |
|||
Image<TPixel> source, |
|||
Image<TPixel> destination, |
|||
Rectangle sourceRectangle, |
|||
Rectangle destinationRectangle, |
|||
bool compand) |
|||
where TResampler : unmanaged, IResampler |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
// Handle resize dimensions identical to the original
|
|||
if (source.Width == destination.Width |
|||
&& source.Height == destination.Height |
|||
&& sourceRectangle == destinationRectangle) |
|||
{ |
|||
for (int i = 0; i < source.Frames.Count; i++) |
|||
{ |
|||
ImageFrame<TPixel> sourceFrame = source.Frames[i]; |
|||
ImageFrame<TPixel> destinationFrame = destination.Frames[i]; |
|||
|
|||
// The cloned will be blank here copy all the pixel data over
|
|||
sourceFrame.GetPixelMemoryGroup().CopyTo(destinationFrame.GetPixelMemoryGroup()); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
var interest = Rectangle.Intersect(destinationRectangle, destination.Bounds()); |
|||
|
|||
if (sampler is NearestNeighborResampler) |
|||
{ |
|||
for (int i = 0; i < source.Frames.Count; i++) |
|||
{ |
|||
ImageFrame<TPixel> sourceFrame = source.Frames[i]; |
|||
ImageFrame<TPixel> destinationFrame = destination.Frames[i]; |
|||
|
|||
ApplyNNResizeFrameTransform( |
|||
configuration, |
|||
sourceFrame, |
|||
destinationFrame, |
|||
sourceRectangle, |
|||
destinationRectangle, |
|||
interest); |
|||
} |
|||
|
|||
return; |
|||
} |
|||
|
|||
// Since all image frame dimensions have to be the same we can calculate
|
|||
// the kernel maps and reuse for all frames.
|
|||
MemoryAllocator allocator = configuration.MemoryAllocator; |
|||
using var horizontalKernelMap = ResizeKernelMap<TResampler>.Calculate( |
|||
in sampler, |
|||
destinationRectangle.Width, |
|||
sourceRectangle.Width, |
|||
allocator); |
|||
|
|||
using var verticalKernelMap = ResizeKernelMap<TResampler>.Calculate( |
|||
in sampler, |
|||
destinationRectangle.Height, |
|||
sourceRectangle.Height, |
|||
allocator); |
|||
|
|||
for (int i = 0; i < source.Frames.Count; i++) |
|||
{ |
|||
ImageFrame<TPixel> sourceFrame = source.Frames[i]; |
|||
ImageFrame<TPixel> destinationFrame = destination.Frames[i]; |
|||
|
|||
ApplyResizeFrameTransform( |
|||
configuration, |
|||
sourceFrame, |
|||
destinationFrame, |
|||
horizontalKernelMap, |
|||
verticalKernelMap, |
|||
sourceRectangle, |
|||
destinationRectangle, |
|||
interest, |
|||
compand); |
|||
} |
|||
} |
|||
|
|||
private static void ApplyNNResizeFrameTransform<TPixel>( |
|||
Configuration configuration, |
|||
ImageFrame<TPixel> source, |
|||
ImageFrame<TPixel> destination, |
|||
Rectangle sourceRectangle, |
|||
Rectangle destinationRectangle, |
|||
Rectangle interest) |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
// Scaling factors
|
|||
float widthFactor = sourceRectangle.Width / (float)destinationRectangle.Width; |
|||
float heightFactor = sourceRectangle.Height / (float)destinationRectangle.Height; |
|||
|
|||
var operation = new NNRowIntervalOperation<TPixel>( |
|||
sourceRectangle, |
|||
destinationRectangle, |
|||
widthFactor, |
|||
heightFactor, |
|||
source, |
|||
destination); |
|||
|
|||
ParallelRowIterator.IterateRows( |
|||
configuration, |
|||
interest, |
|||
in operation); |
|||
} |
|||
|
|||
private static void ApplyResizeFrameTransform<TResampler, TPixel>( |
|||
Configuration configuration, |
|||
ImageFrame<TPixel> source, |
|||
ImageFrame<TPixel> destination, |
|||
ResizeKernelMap<TResampler> horizontalKernelMap, |
|||
ResizeKernelMap<TResampler> verticalKernelMap, |
|||
Rectangle sourceRectangle, |
|||
Rectangle destinationRectangle, |
|||
Rectangle interest, |
|||
bool compand) |
|||
where TResampler : unmanaged, IResampler |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
PixelConversionModifiers conversionModifiers = |
|||
PixelConversionModifiers.Premultiply.ApplyCompanding(compand); |
|||
|
|||
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); |
|||
|
|||
// To reintroduce parallel processing, we would launch multiple workers
|
|||
// for different row intervals of the image.
|
|||
using (var worker = new ResizeWorker<TResampler, TPixel>( |
|||
configuration, |
|||
sourceArea, |
|||
conversionModifiers, |
|||
horizontalKernelMap, |
|||
verticalKernelMap, |
|||
destination.Width, |
|||
interest, |
|||
destinationRectangle.Location)) |
|||
{ |
|||
worker.Initialize(); |
|||
|
|||
var workingInterval = new RowInterval(interest.Top, interest.Bottom); |
|||
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); |
|||
} |
|||
} |
|||
|
|||
private readonly struct NNRowIntervalOperation<TPixel> : IRowIntervalOperation |
|||
where TPixel : struct, IPixel<TPixel> |
|||
{ |
|||
private readonly Rectangle sourceBounds; |
|||
private readonly Rectangle destinationBounds; |
|||
private readonly float widthFactor; |
|||
private readonly float heightFactor; |
|||
private readonly ImageFrame<TPixel> source; |
|||
private readonly ImageFrame<TPixel> destination; |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public NNRowIntervalOperation( |
|||
Rectangle sourceBounds, |
|||
Rectangle destinationBounds, |
|||
float widthFactor, |
|||
float heightFactor, |
|||
ImageFrame<TPixel> source, |
|||
ImageFrame<TPixel> destination) |
|||
{ |
|||
this.sourceBounds = sourceBounds; |
|||
this.destinationBounds = destinationBounds; |
|||
this.widthFactor = widthFactor; |
|||
this.heightFactor = heightFactor; |
|||
this.source = source; |
|||
this.destination = destination; |
|||
} |
|||
|
|||
[MethodImpl(InliningOptions.ShortMethod)] |
|||
public void Invoke(in RowInterval rows) |
|||
{ |
|||
int sourceX = this.sourceBounds.X; |
|||
int sourceY = this.sourceBounds.Y; |
|||
int destX = this.destinationBounds.X; |
|||
int destY = this.destinationBounds.Y; |
|||
int destLeft = this.destinationBounds.Left; |
|||
int destRight = this.destinationBounds.Right; |
|||
|
|||
for (int y = rows.Min; y < rows.Max; y++) |
|||
{ |
|||
// Y coordinates of source points
|
|||
Span<TPixel> sourceRow = this.source.GetPixelRowSpan((int)(((y - destY) * this.heightFactor) + sourceY)); |
|||
Span<TPixel> targetRow = this.destination.GetPixelRowSpan(y); |
|||
|
|||
for (int x = destLeft; x < destRight; x++) |
|||
{ |
|||
// X coordinates of source points
|
|||
targetRow[x] = sourceRow[(int)(((x - destX) * this.widthFactor) + sourceX)]; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue