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