mirror of https://github.com/SixLabors/ImageSharp
8 changed files with 326 additions and 267 deletions
@ -0,0 +1,188 @@ |
|||||
|
// Copyright (c) Six Labors and contributors.
|
||||
|
// Licensed under the Apache License, Version 2.0.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Buffers; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using System.Numerics; |
||||
|
using System.Runtime.CompilerServices; |
||||
|
using System.Runtime.InteropServices; |
||||
|
|
||||
|
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.Transforms |
||||
|
{ |
||||
|
// The non-generic processor is responsible for:
|
||||
|
// - Encapsulating the parameters of the processor
|
||||
|
// - Implementing a factory method to create the pixel-specific processor that contains the implementation
|
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides methods that allow the resizing of images using various algorithms.
|
||||
|
/// Adapted from <see href="http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/filter_rcg.c"/>
|
||||
|
/// </summary>
|
||||
|
/// <typeparam name="TPixel">The pixel format.</typeparam>
|
||||
|
internal class ResizeProcessorImplementation<TPixel> : TransformProcessorBase<TPixel> |
||||
|
where TPixel : struct, IPixel<TPixel> |
||||
|
{ |
||||
|
// The following fields are not immutable but are optionally created on demand.
|
||||
|
private ResizeKernelMap horizontalKernelMap; |
||||
|
private ResizeKernelMap verticalKernelMap; |
||||
|
|
||||
|
private readonly ResizeProcessor parameterSource; |
||||
|
|
||||
|
public ResizeProcessorImplementation(ResizeProcessor parameterSource) |
||||
|
{ |
||||
|
this.parameterSource = parameterSource; |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the sampler to perform the resize operation.
|
||||
|
/// </summary>
|
||||
|
public IResampler Sampler => this.parameterSource.Sampler; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the target width.
|
||||
|
/// </summary>
|
||||
|
public int Width => this.parameterSource.Width; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the target height.
|
||||
|
/// </summary>
|
||||
|
public int Height => this.parameterSource.Height; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the resize rectangle.
|
||||
|
/// </summary>
|
||||
|
public Rectangle TargetRectangle => this.parameterSource.TargetRectangle; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether to compress or expand individual pixel color values on processing.
|
||||
|
/// </summary>
|
||||
|
public bool Compand => this.parameterSource.Compand; |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override Image<TPixel> CreateDestination(Image<TPixel> source, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
// We will always be creating the clone even for mutate because we may need to resize the canvas
|
||||
|
IEnumerable<ImageFrame<TPixel>> frames = source.Frames.Select(x => new ImageFrame<TPixel>(source.GetConfiguration(), this.Width, this.Height, x.Metadata.DeepClone())); |
||||
|
|
||||
|
// Use the overload to prevent an extra frame being added
|
||||
|
return new Image<TPixel>(source.GetConfiguration(), source.Metadata.DeepClone(), frames); |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void BeforeImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
if (!(this.Sampler is NearestNeighborResampler)) |
||||
|
{ |
||||
|
// Since all image frame dimensions have to be the same we can calculate this for all frames.
|
||||
|
MemoryAllocator memoryAllocator = source.GetMemoryAllocator(); |
||||
|
this.horizontalKernelMap = ResizeKernelMap.Calculate( |
||||
|
this.Sampler, |
||||
|
this.TargetRectangle.Width, |
||||
|
sourceRectangle.Width, |
||||
|
memoryAllocator); |
||||
|
|
||||
|
this.verticalKernelMap = ResizeKernelMap.Calculate( |
||||
|
this.Sampler, |
||||
|
this.TargetRectangle.Height, |
||||
|
sourceRectangle.Height, |
||||
|
memoryAllocator); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <inheritdoc/>
|
||||
|
protected override void OnFrameApply(ImageFrame<TPixel> source, ImageFrame<TPixel> destination, Rectangle sourceRectangle, Configuration configuration) |
||||
|
{ |
||||
|
// Handle resize dimensions identical to the original
|
||||
|
if (source.Width == destination.Width && source.Height == destination.Height && sourceRectangle == this.TargetRectangle) |
||||
|
{ |
||||
|
// The cloned will be blank here copy all the pixel data over
|
||||
|
source.GetPixelSpan().CopyTo(destination.GetPixelSpan()); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int width = this.Width; |
||||
|
int height = this.Height; |
||||
|
int sourceX = sourceRectangle.X; |
||||
|
int sourceY = sourceRectangle.Y; |
||||
|
int startY = this.TargetRectangle.Y; |
||||
|
int startX = this.TargetRectangle.X; |
||||
|
|
||||
|
var targetWorkingRect = Rectangle.Intersect( |
||||
|
this.TargetRectangle, |
||||
|
new Rectangle(0, 0, width, height)); |
||||
|
|
||||
|
if (this.Sampler is NearestNeighborResampler) |
||||
|
{ |
||||
|
// Scaling factors
|
||||
|
float widthFactor = sourceRectangle.Width / (float)this.TargetRectangle.Width; |
||||
|
float heightFactor = sourceRectangle.Height / (float)this.TargetRectangle.Height; |
||||
|
|
||||
|
ParallelHelper.IterateRows( |
||||
|
targetWorkingRect, |
||||
|
configuration, |
||||
|
rows => |
||||
|
{ |
||||
|
for (int y = rows.Min; y < rows.Max; y++) |
||||
|
{ |
||||
|
// Y coordinates of source points
|
||||
|
Span<TPixel> sourceRow = |
||||
|
source.GetPixelRowSpan((int)(((y - startY) * heightFactor) + sourceY)); |
||||
|
Span<TPixel> targetRow = destination.GetPixelRowSpan(y); |
||||
|
|
||||
|
for (int x = targetWorkingRect.Left; x < targetWorkingRect.Right; x++) |
||||
|
{ |
||||
|
// X coordinates of source points
|
||||
|
targetRow[x] = sourceRow[(int)(((x - startX) * widthFactor) + sourceX)]; |
||||
|
} |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
int sourceHeight = source.Height; |
||||
|
|
||||
|
PixelConversionModifiers conversionModifiers = |
||||
|
PixelConversionModifiers.Premultiply.ApplyCompanding(this.Compand); |
||||
|
|
||||
|
BufferArea<TPixel> sourceArea = source.PixelBuffer.GetArea(sourceRectangle); |
||||
|
|
||||
|
// To reintroduce parallel processing, we to launch multiple workers
|
||||
|
// for different row intervals of the image.
|
||||
|
using (var worker = new ResizeWorker<TPixel>( |
||||
|
configuration, |
||||
|
sourceArea, |
||||
|
conversionModifiers, |
||||
|
this.horizontalKernelMap, |
||||
|
this.verticalKernelMap, |
||||
|
width, |
||||
|
targetWorkingRect, |
||||
|
this.TargetRectangle.Location)) |
||||
|
{ |
||||
|
worker.Initialize(); |
||||
|
|
||||
|
var workingInterval = new RowInterval(targetWorkingRect.Top, targetWorkingRect.Bottom); |
||||
|
worker.FillDestinationPixels(workingInterval, destination.PixelBuffer); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override void AfterImageApply(Image<TPixel> source, Image<TPixel> destination, Rectangle sourceRectangle) |
||||
|
{ |
||||
|
base.AfterImageApply(source, destination, sourceRectangle); |
||||
|
|
||||
|
// TODO: An exception in the processing chain can leave these buffers undisposed. We should consider making image processors IDisposable!
|
||||
|
this.horizontalKernelMap?.Dispose(); |
||||
|
this.horizontalKernelMap = null; |
||||
|
this.verticalKernelMap?.Dispose(); |
||||
|
this.verticalKernelMap = null; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue