//
// Copyright © James South and contributors.
// Licensed under the Apache License, Version 2.0.
//
namespace ImageProcessor.Samplers
{
using System;
using System.Collections.Generic;
///
/// Provides methods that allow the resizing of images using various resampling algorithms.
///
/// TODO: There is a bug in this class. Whenever the processor is set to use parallel processing, the output image becomes distorted
/// at the join points when startY is greater than 0. Uncomment the Parallelism overload and run the ImageShouldResize method in the SamplerTests
/// class to see the error manifest.
/// It is imperative that the issue is solved or resampling will be too slow to be practical and the project will have to cease.
///
///
public class Resize : ParallelImageProcessor
{
///
/// The epsilon for comparing floating point numbers.
///
private const float Epsilon = 0.0001f;
///
/// Initializes a new instance of the class.
///
///
/// The sampler to perform the resize operation.
///
public Resize(IResampler sampler)
{
Guard.NotNull(sampler, nameof(sampler));
this.Sampler = sampler;
}
///
public override int Parallelism => 1; // Uncomment this to see bug.
///
/// Gets the sampler to perform the resize operation.
///
public IResampler Sampler { get; }
///
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
{
int sourceWidth = source.Width;
int sourceHeight = source.Height;
int width = target.Width;
int height = target.Height;
int targetY = targetRectangle.Y;
int startX = targetRectangle.X;
int endX = targetRectangle.Right;
// Scaling factors
double heightFactor = sourceHeight / (double)targetRectangle.Height;
int targetSectionHeight = endY - startY;
int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5);
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler);
Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight, this.Sampler);
// Width and height decreased by 1
int maxHeight = sourceHeight - 1;
int maxWidth = sourceWidth - 1;
for (int y = startY; y < endY; y++)
{
if (y >= 0 && y < height)
{
List verticalValues = verticalWeights[y - startY].Values;
double verticalSum = verticalWeights[y - startY].Sum;
for (int x = startX; x < endX; x++)
{
if (x >= 0 && x < width)
{
List horizontalValues = horizontalWeights[x - startX].Values;
double horizontalSum = horizontalWeights[x - startX].Sum;
// Destination color components
double r = 0;
double g = 0;
double b = 0;
double a = 0;
foreach (Weight yw in verticalValues)
{
if (Math.Abs(yw.Value) < Epsilon)
{
continue;
}
// TODO: This is wrong. Adding (int)((startY * heightFactor) - .5) gets close but no cigar.
int originY = yw.Index + (int)((startY * heightFactor) - .5);
originY = originY.Clamp(0, maxHeight);
foreach (Weight xw in horizontalValues)
{
if (Math.Abs(xw.Value) < Epsilon)
{
continue;
}
// TODO: This need updating to take into account the target rectangle.
int originX = xw.Index;
originX = originX.Clamp(0, maxWidth);
Bgra sourceColor = source[originX, originY];
r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
a += sourceColor.A * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
}
}
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte());
target[x, y] = destinationColor;
}
}
}
}
}
///
/// Computes the weights to apply at each pixel when resizing.
///
///
/// The destination section size.
///
///
/// The source section size.
///
///
/// The containing the resampling algorithm.
///
///
/// The .
///
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize, IResampler sampler)
{
float du = sourceSize / (float)destinationSize;
float scale = du;
if (scale < 1)
{
scale = 1;
}
double ru = Math.Ceiling(scale * sampler.Radius);
Weights[] result = new Weights[destinationSize];
for (int i = 0; i < destinationSize; i++)
{
double fu = ((i + .5) * du) - 0.5;
int startU = (int)Math.Ceiling(fu - ru);
if (startU < 0)
{
startU = 0;
}
int endU = (int)Math.Floor(fu + ru);
if (endU > sourceSize - 1)
{
endU = sourceSize - 1;
}
double sum = 0;
result[i] = new Weights();
for (int a = startU; a <= endU; a++)
{
double w = 255 * sampler.GetValue((a - fu) / scale);
if (Math.Abs(w) > Epsilon)
{
sum += w;
result[i].Values.Add(new Weight(a, w));
}
}
result[i].Sum = sum;
}
return result;
}
protected struct Weight
{
public Weight(int index, double value)
{
this.Index = index;
this.Value = value;
}
public readonly int Index;
public readonly double Value;
}
protected class Weights
{
public Weights()
{
this.Values = new List();
}
public List Values { get; set; }
public double Sum { get; set; }
}
}
}