Browse Source

Resizer updates

Getting closer but still not quite right.


Former-commit-id: 7df2d4c45591ba8e71e073c29d61b73e25c38cf0
Former-commit-id: cb63ea1d1341f15a3b6d82fb6b0a6c842107bcf9
Former-commit-id: e3a69acf2949c0081381ca71214c673581712e93
pull/17/head
James Jackson-South 11 years ago
parent
commit
e3a2334ede
  1. 4
      src/ImageProcessor/Common/Helpers/PixelOperations.cs
  2. 2
      src/ImageProcessor/ParallelImageProcessor.cs
  3. 107
      src/ImageProcessor/Samplers/Resize.cs
  4. 12
      tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs
  5. 2
      tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs
  6. 1
      tests/ImageProcessor.Tests/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg.REMOVED.git-id
  7. BIN
      tests/ImageProcessor.Tests/TestImages/Formats/Png/gamma-1.0-or-2.2.png

4
src/ImageProcessor/Common/Helpers/PixelOperations.cs

@ -38,7 +38,7 @@ namespace ImageProcessor
// Create only once and lazily. // Create only once and lazily.
byte[] ramp = LinearBytes.Value; byte[] ramp = LinearBytes.Value;
return new Bgra(composite.B, ramp[composite.G], ramp[composite.R], ramp[composite.A]); return new Bgra(ramp[composite.B], ramp[composite.G], ramp[composite.R], composite.A);
} }
/// <summary> /// <summary>
@ -55,7 +55,7 @@ namespace ImageProcessor
// Create only once and lazily. // Create only once and lazily.
byte[] ramp = SrgbBytes.Value; byte[] ramp = SrgbBytes.Value;
return new Bgra(linear.B, ramp[linear.G], ramp[linear.R], ramp[linear.A]); return new Bgra(ramp[linear.B], ramp[linear.G], ramp[linear.R], linear.A);
} }
/// <summary> /// <summary>

2
src/ImageProcessor/ParallelImageProcessor.cs

@ -67,7 +67,7 @@ namespace ImageProcessor
{ {
sourceRectangle = source.Bounds; sourceRectangle = source.Bounds;
} }
this.Parallelism = 1;
if (this.Parallelism > 1) if (this.Parallelism > 1)
{ {
int partitionCount = this.Parallelism; int partitionCount = this.Parallelism;

107
src/ImageProcessor/Samplers/Resize.cs

@ -63,8 +63,10 @@ namespace ImageProcessor.Samplers
int targetSectionHeight = endY - startY; int targetSectionHeight = endY - startY;
int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5); int sourceSectionHeight = (int)((targetSectionHeight * heightFactor) + .5);
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width, this.Sampler); int offsetY = this.CalculateOffset(startY, targetSectionHeight, sourceSectionHeight);
Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight, this.Sampler); int offsetX = this.CalculateOffset(startX, targetRectangle.Width, sourceRectangle.Width);
Weights[] horizontalWeights = this.PrecomputeWeights(targetRectangle.Width, sourceRectangle.Width);
Weights[] verticalWeights = this.PrecomputeWeights(targetSectionHeight, sourceSectionHeight);
// Width and height decreased by 1 // Width and height decreased by 1
int maxHeight = sourceHeight - 1; int maxHeight = sourceHeight - 1;
@ -97,8 +99,8 @@ namespace ImageProcessor.Samplers
continue; continue;
} }
// TODO: This is wrong. Adding (int)((startY * heightFactor) - .5) gets close but no cigar. // TODO: This offset is wrong.
int originY = yw.Index + (int)((startY * heightFactor) - .5); int originY = offsetY == 0 ? yw.Index : yw.Index + offsetY;
originY = originY.Clamp(0, maxHeight); originY = originY.Clamp(0, maxHeight);
foreach (Weight xw in horizontalValues) foreach (Weight xw in horizontalValues)
@ -108,11 +110,13 @@ namespace ImageProcessor.Samplers
continue; continue;
} }
// TODO: This need updating to take into account the target rectangle. // TODO: This offset is wrong.
int originX = xw.Index; int originX = xw.Index + offsetX;
originX = originX.Clamp(0, maxWidth); originX = originX.Clamp(0, maxWidth);
Bgra sourceColor = source[originX, originY]; Bgra sourceColor = source[originX, originY];
sourceColor = PixelOperations.ToLinear(sourceColor);
r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum); r += sourceColor.R * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum); g += sourceColor.G * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum); b += sourceColor.B * (yw.Value / verticalSum) * (xw.Value / horizontalSum);
@ -121,6 +125,7 @@ namespace ImageProcessor.Samplers
} }
Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte()); Bgra destinationColor = new Bgra(b.ToByte(), g.ToByte(), r.ToByte(), a.ToByte());
destinationColor = PixelOperations.ToSrgb(destinationColor);
target[x, y] = destinationColor; target[x, y] = destinationColor;
} }
} }
@ -131,22 +136,16 @@ namespace ImageProcessor.Samplers
/// <summary> /// <summary>
/// Computes the weights to apply at each pixel when resizing. /// Computes the weights to apply at each pixel when resizing.
/// </summary> /// </summary>
/// <param name="destinationSize"> /// <param name="destinationSize">The destination section size.</param>
/// The destination section size. /// <param name="sourceSize">The source section size.</param>
/// </param>
/// <param name="sourceSize">
/// The source section size.
/// </param>
/// <param name="sampler">
/// The <see cref="IResampler"/> containing the resampling algorithm.
/// </param>
/// <returns> /// <returns>
/// The <see cref="T:Weights[]"/>. /// The <see cref="T:Weights[]"/>.
/// </returns> /// </returns>
private Weights[] PrecomputeWeights(int destinationSize, int sourceSize, IResampler sampler) private Weights[] PrecomputeWeights(int destinationSize, int sourceSize)
{ {
float du = sourceSize / (float)destinationSize; IResampler sampler = this.Sampler;
float scale = du; double du = sourceSize / (double)destinationSize;
double scale = du;
if (scale < 1) if (scale < 1)
{ {
@ -193,28 +192,92 @@ namespace ImageProcessor.Samplers
return result; return result;
} }
protected struct Weight /// <summary>
/// Calculates the scaled offset caused by parallelism.
/// </summary>
/// <param name="offset">The offset position.</param>
/// <param name="destinationSize">The destination size.</param>
/// <param name="sourceSize">The source size.</param>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
private int CalculateOffset(int offset, int destinationSize, int sourceSize)
{ {
public Weight(int index, double value) if (offset == 0)
{ {
this.Index = index; return 0;
this.Value = value; }
IResampler sampler = this.Sampler;
double du = sourceSize / (double)destinationSize;
double scale = du;
if (scale < 1)
{
scale = 1;
}
double ru = Math.Ceiling(scale * sampler.Radius);
double fu = ((offset + .5) * du) - 0.5;
int result = (int)Math.Ceiling(fu - ru);
if (result < 0)
{
return 0;
} }
return result;
}
/// <summary>
/// Represents the weight to be added to a scaled pixel.
/// </summary>
protected struct Weight
{
/// <summary>
/// The pixel index.
/// </summary>
public readonly int Index; public readonly int Index;
/// <summary>
/// The result of the interpolation algorithm.
/// </summary>
public readonly double Value; public readonly double Value;
/// <summary>
/// Initializes a new instance of the <see cref="Weight"/> struct.
/// </summary>
/// <param name="index">The index.</param>
/// <param name="value">The value.</param>
public Weight(int index, double value)
{
this.Index = index;
this.Value = value;
}
} }
/// <summary>
/// Represents a collection of weights and their sum.
/// </summary>
protected class Weights protected class Weights
{ {
/// <summary>
/// Initializes a new instance of the <see cref="Weights"/> class.
/// </summary>
public Weights() public Weights()
{ {
this.Values = new List<Weight>(); this.Values = new List<Weight>();
} }
/// <summary>
/// Gets or sets the values.
/// </summary>
public List<Weight> Values { get; set; } public List<Weight> Values { get; set; }
/// <summary>
/// Gets or sets the sum.
/// </summary>
public double Sum { get; set; } public double Sum { get; set; }
} }
} }

12
tests/ImageProcessor.Tests/Processors/ProcessorTestBase.cs

@ -19,11 +19,13 @@ namespace ImageProcessor.Tests
/// </summary> /// </summary>
public static readonly List<string> Files = new List<string> public static readonly List<string> Files = new List<string>
{ {
//"../../TestImages/Formats/Jpg/Backdrop.jpg", "../../TestImages/Formats/Jpg/Backdrop.jpg",
//"../../TestImages/Formats/Jpg/Calliphora.jpg", "../../TestImages/Formats/Jpg/Calliphora.jpg",
//"../../TestImages/Formats/Bmp/Car.bmp", "../../TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg",
//"../../TestImages/Formats/Png/cmyk.png", "../../TestImages/Formats/Bmp/Car.bmp",
//"../../TestImages/Formats/Gif/leaf.gif" "../../TestImages/Formats/Png/cmyk.png",
"../../TestImages/Formats/Png/gamma-1.0-or-2.2.png",
"../../TestImages/Formats/Gif/leaf.gif",
"../../TestImages/Formats/Gif/rings.gif" "../../TestImages/Formats/Gif/rings.gif"
// { "../../TestImages/Formats/Gif/ani.gif" }, // { "../../TestImages/Formats/Gif/ani.gif" },

2
tests/ImageProcessor.Tests/Processors/Samplers/SamplerTests.cs

@ -45,7 +45,7 @@ namespace ImageProcessor.Tests
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file); string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"Resized/{filename}")) using (FileStream output = File.OpenWrite($"Resized/{filename}"))
{ {
image.Resize(100, 100, sampler).Save(output); image.Resize(500, 500, sampler).Save(output);
} }
Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms"); Trace.WriteLine($"{name}: {watch.ElapsedMilliseconds}ms");

1
tests/ImageProcessor.Tests/TestImages/Formats/Jpg/gamma_dalai_lama_gray.jpg.REMOVED.git-id

@ -0,0 +1 @@
56cbc3371def2882d1ead5d4d2456550f2b8d72c

BIN
tests/ImageProcessor.Tests/TestImages/Formats/Png/gamma-1.0-or-2.2.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Loading…
Cancel
Save