//
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageProcessorCore.Processors
{
using System;
using System.Numerics;
using System.Threading.Tasks;
using GenericImage;
///
/// Provides methods that allow the resizing of images using various algorithms.
///
public class ResizeProcessor : ImageProcessor
{
///
/// Initializes a new instance of the class.
///
///
/// The sampler to perform the resize operation.
///
public ResizeProcessor(IResampler sampler)
{
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
}
///
/// Gets the sampler to perform the resize operation.
///
public IResampler Sampler { get; }
///
/// Gets or sets the horizontal weights.
///
protected Weights[] HorizontalWeights { get; set; }
///
/// Gets or sets the vertical weights.
///
protected Weights[] VerticalWeights { get; set; }
///
protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
if (!(this.Sampler is NearestNeighborResampler))
{
this.HorizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width);
this.VerticalWeights = this.PrecomputeWeights(targetRectangle.Height, sourceRectangle.Height);
}
}
///
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
// Jump out, we'll deal with that later.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
{
return;
}
int width = target.Width;
int height = target.Height;
int sourceHeight = sourceRectangle.Height;
int targetX = target.Bounds.X;
int targetY = target.Bounds.Y;
int targetRight = target.Bounds.Right;
int targetBottom = target.Bounds.Bottom;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
bool compand = this.Compand;
if (this.Sampler is NearestNeighborResampler)
{
// Scaling factors
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height;
using (IPixelAccessor sourcePixels = source.Lock())
using (IPixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
if (targetY <= y && y < targetBottom)
{
// Y coordinates of source points
int originY = (int)((y - startY) * heightFactor);
for (int x = startX; x < endX; x++)
{
if (targetX <= x && x < targetRight)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
targetPixels[x, y] = sourcePixels[originX, originY];
}
}
this.OnRowProcessed();
}
});
}
// Break out now.
return;
}
// Interpolate the image using the calculated weights.
// A 2-pass 1D algorithm appears to be faster than splitting a 1-pass 2D algorithm
// First process the columns. Since we are not using multiple threads startY and endY
// are the upper and lower bounds of the source rectangle.
Image firstPass = new Image(target.Width, source.Height);
using (IPixelAccessor sourcePixels = source.Lock())
using (IPixelAccessor firstPassPixels = firstPass.Lock())
using (IPixelAccessor targetPixels = target.Lock())
{
Parallel.For(
0,
sourceHeight,
y =>
{
for (int x = startX; x < endX; x++)
{
if (x >= 0 && x < width)
{
// Ensure offsets are normalised for cropping and padding.
int offsetX = x - startX;
float sum = this.HorizontalWeights[offsetX].Sum;
Weight[] horizontalValues = this.HorizontalWeights[offsetX].Values;
// Destination color components
//Color destination = new Color();
//for (int i = 0; i < sum; i++)
//{
// Weight xw = horizontalValues[i];
// int originX = xw.Index;
// Color sourceColor = compand
// ? Color.Expand(sourcePixels[originX, y])
// : sourcePixels[originX, y];
// destination += sourceColor * xw.Value;
//}
//if (compand)
//{
// destination = Color.Compress(destination);
//}
//firstPassPixels[x, y] = destination;
TColor sourceColor;
TColor destination = default(TColor);
for (int i = 0; i < sum; i++)
{
Weight xw = horizontalValues[i];
int originX = xw.Index;
sourceColor = sourcePixels[originX, y];
//Color sourceColor = compand
// ? Color.Expand(sourcePixels[originX, y])
// : sourcePixels[originX, y];
//sourceColor.Multiply(xw.Value);
//destination.Add(sourceColor);
//destination += sourceColor * xw.Value;
sourceColor.Multiply(xw.Value);
destination.Add(sourceColor);
}
//if (compand)
//{
// destination = Color.Compress(destination);
//}
//T packed = default(T);
//packed.PackVector(destination);
firstPassPixels[x, y] = destination;
}
}
});
// Now process the rows.
Parallel.For(
startY,
endY,
y =>
{
if (y >= 0 && y < height)
{
// Ensure offsets are normalised for cropping and padding.
int offsetY = y - startY;
float sum = this.VerticalWeights[offsetY].Sum;
Weight[] verticalValues = this.VerticalWeights[offsetY].Values;
for (int x = 0; x < width; x++)
{
// Destination color components
TColor sourceColor;
TColor destination = default(TColor);
for (int i = 0; i < sum; i++)
{
Weight yw = verticalValues[i];
int originY = yw.Index;
sourceColor = firstPassPixels[x, originY];
//Color sourceColor = compand
// ? Color.Expand(firstPassPixels[x, originY])
// : firstPassPixels[x, originY];
//Vector4 sourceColor = firstPassPixels[x, originY].ToVector4();
//destination += sourceColor * yw.Value;
sourceColor.Multiply(yw.Value);
destination.Add(sourceColor);
}
//if (compand)
//{
// destination = Color.Compress(destination);
//}
//T packed = default(T);
//packed.PackVector(destination);
targetPixels[x, y] = destination;
}
}
this.OnRowProcessed();
});
}
}
///
protected override void AfterApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Copy the pixels over.
if (source.Bounds == target.Bounds && sourceRectangle == targetRectangle)
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
}
///
/// Computes the weights to apply at each pixel when resizing.
///
/// The destination section size.
/// The source section size.
///
/// The .
///
protected Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{
float scale = (float)destinationSize / sourceSize;
IResampler sampler = this.Sampler;
float radius = sampler.Radius;
double left;
double right;
float weight;
int index;
int sum;
Weights[] result = new Weights[destinationSize];
// When shrinking, broaden the effective kernel support so that we still
// visit every source pixel.
if (scale < 1)
{
float width = radius / scale;
float filterScale = 1 / scale;
// Make the weights slices, one source for each column or row.
for (int i = 0; i < destinationSize; i++)
{
float centre = i / scale;
left = Math.Ceiling(centre - width);
right = Math.Floor(centre + width);
result[i] = new Weights
{
Values = new Weight[(int)(right - left + 1)]
};
for (double j = left; j <= right; j++)
{
weight = sampler.GetValue((float)((centre - j) / filterScale)) / filterScale;
if (j < 0)
{
index = (int)-j;
}
else if (j >= sourceSize)
{
index = (int)((sourceSize - j) + sourceSize - 1);
}
else
{
index = (int)j;
}
sum = (int)result[i].Sum++;
result[i].Values[sum] = new Weight(index, weight);
}
}
}
else
{
// Make the weights slices, one source for each column or row.
for (int i = 0; i < destinationSize; i++)
{
float centre = i / scale;
left = Math.Ceiling(centre - radius);
right = Math.Floor(centre + radius);
result[i] = new Weights
{
Values = new Weight[(int)(right - left + 1)]
};
for (double j = left; j <= right; j++)
{
weight = sampler.GetValue((float)(centre - j));
if (j < 0)
{
index = (int)-j;
}
else if (j >= sourceSize)
{
index = (int)((sourceSize - j) + sourceSize - 1);
}
else
{
index = (int)j;
}
sum = (int)result[i].Sum++;
result[i].Values[sum] = new Weight(index, weight);
}
}
}
return result;
}
///
/// Represents the weight to be added to a scaled pixel.
///
protected struct Weight
{
///
/// Initializes a new instance of the struct.
///
/// The index.
/// The value.
public Weight(int index, float value)
{
this.Index = index;
this.Value = value;
}
///
/// Gets the pixel index.
///
public int Index { get; }
///
/// Gets the result of the interpolation algorithm.
///
public float Value { get; }
}
///
/// Represents a collection of weights and their sum.
///
protected class Weights
{
///
/// Gets or sets the values.
///
public Weight[] Values { get; set; }
///
/// Gets or sets the sum.
///
public float Sum { get; set; }
}
}
}