Browse Source

Use PixelAccessor

Former-commit-id: 9b7174022ac6745534acff3ed69718d143e91e6f
Former-commit-id: f8d1e182dec104b3c77fec79cd4d7c00a299a70f
Former-commit-id: 98f22a9d62b5133aeba4b4525a71b2e6c2b18133
pull/1/head
James Jackson-South 10 years ago
parent
commit
3dbad0c44a
  1. 37
      src/ImageProcessorCore/Common/Helpers/ImageMaths.cs
  2. 32
      src/ImageProcessorCore/Filters/Alpha.cs
  3. 51
      src/ImageProcessorCore/Filters/BackgroundColor.cs
  4. 10
      src/ImageProcessorCore/Filters/Binarization/Threshold.cs
  5. 59
      src/ImageProcessorCore/Filters/Blend.cs
  6. 32
      src/ImageProcessorCore/Filters/Brightness.cs
  7. 9
      src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs
  8. 35
      src/ImageProcessorCore/Filters/Contrast.cs
  9. 16
      src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs
  10. 10
      src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs
  11. 10
      src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs
  12. 31
      src/ImageProcessorCore/Filters/Glow.cs
  13. 31
      src/ImageProcessorCore/Filters/Invert.cs
  14. 68
      src/ImageProcessorCore/Filters/Pixelate.cs
  15. 28
      src/ImageProcessorCore/Filters/Vignette.cs
  16. 37
      src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs
  17. 2
      src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs
  18. 43
      src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs
  19. 21
      src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs
  20. 103
      src/ImageProcessorCore/IImage.cs
  21. 106
      src/ImageProcessorCore/IImageBase.cs
  22. 97
      src/ImageProcessorCore/Image.cs
  23. 207
      src/ImageProcessorCore/ImageBase.cs
  24. 8
      src/ImageProcessorCore/ImageExtensions.cs
  25. 164
      src/ImageProcessorCore/PixelAccessor.cs
  26. 31
      src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs
  27. 36
      src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs
  28. 30
      src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs
  29. 55
      src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs
  30. 185
      src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs
  31. 150
      src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs
  32. 43
      src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs
  33. 43
      src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs
  34. 7
      tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs
  35. 10
      tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs
  36. 10
      tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs
  37. 24
      tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs
  38. 127
      tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs
  39. 10
      tests/ImageProcessorCore.Tests/Formats/PngTests.cs
  40. 16
      tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs
  41. 33
      tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

37
src/ImageProcessorCore/Common/Helpers/ImageMaths.cs

@ -171,35 +171,35 @@ namespace ImageProcessorCore
Point topLeft = new Point();
Point bottomRight = new Point();
Func<ImageBase, int, int, float, bool> delegateFunc;
Func<PixelAccessor, int, int, float, bool> delegateFunc;
// Determine which channel to check against
switch (channel)
{
case RgbaComponent.R:
delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].R - b) > Epsilon;
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].R - b) > Epsilon;
break;
case RgbaComponent.G:
delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].G - b) > Epsilon;
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].G - b) > Epsilon;
break;
case RgbaComponent.A:
delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].A - b) > Epsilon;
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].A - b) > Epsilon;
break;
default:
delegateFunc = (imageBase, x, y, b) => Math.Abs(imageBase[x, y].B - b) > Epsilon;
delegateFunc = (pixels, x, y, b) => Math.Abs(pixels[x, y].B - b) > Epsilon;
break;
}
Func<ImageBase, int> getMinY = imageBase =>
Func<PixelAccessor, int> getMinY = pixels =>
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(imageBase, x, y, componentValue))
if (delegateFunc(pixels, x, y, componentValue))
{
return y;
}
@ -209,13 +209,13 @@ namespace ImageProcessorCore
return 0;
};
Func<ImageBase, int> getMaxY = imageBase =>
Func<PixelAccessor, int> getMaxY = pixels =>
{
for (int y = height - 1; y > -1; y--)
{
for (int x = 0; x < width; x++)
{
if (delegateFunc(imageBase, x, y, componentValue))
if (delegateFunc(pixels, x, y, componentValue))
{
return y;
}
@ -225,13 +225,13 @@ namespace ImageProcessorCore
return height;
};
Func<ImageBase, int> getMinX = imageBase =>
Func<PixelAccessor, int> getMinX = pixels =>
{
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(imageBase, x, y, componentValue))
if (delegateFunc(pixels, x, y, componentValue))
{
return x;
}
@ -241,13 +241,13 @@ namespace ImageProcessorCore
return 0;
};
Func<ImageBase, int> getMaxX = imageBase =>
Func<PixelAccessor, int> getMaxX = pixels =>
{
for (int x = width - 1; x > -1; x--)
{
for (int y = 0; y < height; y++)
{
if (delegateFunc(imageBase, x, y, componentValue))
if (delegateFunc(pixels, x, y, componentValue))
{
return x;
}
@ -257,10 +257,13 @@ namespace ImageProcessorCore
return height;
};
topLeft.Y = getMinY(bitmap);
topLeft.X = getMinX(bitmap);
bottomRight.Y = (getMaxY(bitmap) + 1).Clamp(0, height);
bottomRight.X = (getMaxX(bitmap) + 1).Clamp(0, width);
using (PixelAccessor bitmapPixels = bitmap.Lock())
{
topLeft.Y = getMinY(bitmapPixels);
topLeft.X = getMinX(bitmapPixels);
bottomRight.Y = (getMaxY(bitmapPixels) + 1).Clamp(0, height);
bottomRight.X = (getMaxX(bitmapPixels) + 1).Clamp(0, width);
}
return GetBoundingRectangle(topLeft, bottomRight);
}

32
src/ImageProcessorCore/Filters/Alpha.cs

@ -42,22 +42,28 @@ namespace ImageProcessorCore.Filters
int endX = sourceRectangle.Right;
Vector4 alphaVector = new Vector4(1, 1, 1, alpha);
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
if (y >= sourceY && y < sourceBottom)
{
Vector4 color = Color.ToNonPremultiplied(source[x, y]).ToVector4();
color *= alphaVector;
target[x, y] = Color.FromNonPremultiplied(new Color(color));
for (int x = startX; x < endX; x++)
{
Vector4 color = Color.ToNonPremultiplied(sourcePixels[x, y]).ToVector4();
color *= alphaVector;
targetPixels[x, y] = Color.FromNonPremultiplied(new Color(color));
}
this.OnRowProcessed();
}
this.OnRowProcessed();
}
});
});
}
}
}
}

51
src/ImageProcessorCore/Filters/BackgroundColor.cs

@ -41,33 +41,38 @@ namespace ImageProcessorCore.Filters
int endX = sourceRectangle.Right;
Color backgroundColor = this.Value;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
Color color = source[x, y];
float a = color.A;
if (a < 1 && a > 0)
if (y >= sourceY && y < sourceBottom)
{
color = Color.Lerp(color, backgroundColor, .5f);
}
for (int x = startX; x < endX; x++)
{
Color color = sourcePixels[x, y];
float a = color.A;
if (Math.Abs(a) < Epsilon)
{
color = backgroundColor;
}
if (a < 1 && a > 0)
{
color = Color.Lerp(color, backgroundColor, .5f);
}
target[x, y] = color;
}
this.OnRowProcessed();
}
});
if (Math.Abs(a) < Epsilon)
{
color = backgroundColor;
}
targetPixels[x, y] = color;
}
this.OnRowProcessed();
}
});
}
}
}
}

10
src/ImageProcessorCore/Filters/Binarization/Threshold.cs

@ -60,7 +60,10 @@ namespace ImageProcessorCore.Filters
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Parallel.For(
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
@ -69,14 +72,15 @@ namespace ImageProcessorCore.Filters
{
for (int x = startX; x < endX; x++)
{
Color color = source[x, y];
Color color = sourcePixels[x, y];
// Any channel will do since it's greyscale.
target[x, y] = color.B >= threshold ? upper : lower;
targetPixels[x, y] = color.B >= threshold ? upper : lower;
}
this.OnRowProcessed();
}
});
}
}
}
}

59
src/ImageProcessorCore/Filters/Blend.cs

@ -15,7 +15,7 @@ namespace ImageProcessorCore.Filters
/// <summary>
/// The image to blend.
/// </summary>
private readonly ImageBase toBlend;
private readonly ImageBase blend;
/// <summary>
/// Initializes a new instance of the <see cref="Blend"/> class.
@ -28,7 +28,7 @@ namespace ImageProcessorCore.Filters
public Blend(ImageBase image, int alpha = 100)
{
Guard.MustBeBetweenOrEqualTo(alpha, 0, 100, nameof(alpha));
this.toBlend = image;
this.blend = image;
this.Value = alpha;
}
@ -44,38 +44,43 @@ namespace ImageProcessorCore.Filters
int sourceBottom = sourceRectangle.Bottom;
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Rectangle bounds = this.toBlend.Bounds;
Rectangle bounds = this.blend.Bounds;
float alpha = this.Value / 100f;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x++)
using (PixelAccessor toBlendPixels = this.blend.Lock())
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
Color color = source[x, y];
if (bounds.Contains(x, y))
if (y >= sourceY && y < sourceBottom)
{
Color blendedColor = this.toBlend[x, y];
if (blendedColor.A > 0)
for (int x = startX; x < endX; x++)
{
// Lerping colors is dependent on the alpha of the blended color
float alphaFactor = alpha > 0 ? alpha : blendedColor.A;
color = Color.Lerp(color, blendedColor, alphaFactor);
}
}
Color color = sourcePixels[x, y];
target[x, y] = color;
}
if (bounds.Contains(x, y))
{
Color blendedColor = toBlendPixels[x, y];
this.OnRowProcessed();
}
});
if (blendedColor.A > 0)
{
// Lerping colors is dependent on the alpha of the blended color
float alphaFactor = alpha > 0 ? alpha : blendedColor.A;
color = Color.Lerp(color, blendedColor, alphaFactor);
}
}
targetPixels[x, y] = color;
}
this.OnRowProcessed();
}
});
}
}
}
}

32
src/ImageProcessorCore/Filters/Brightness.cs

@ -41,25 +41,29 @@ namespace ImageProcessorCore.Filters
int startX = sourceRectangle.X;
int endX = sourceRectangle.Right;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
if (y >= sourceY && y < sourceBottom)
{
Color color = Color.Expand(source[x, y]);
for (int x = startX; x < endX; x++)
{
Color color = Color.Expand(sourcePixels[x, y]);
Vector3 vector3 = color.ToVector3();
vector3 += new Vector3(brightness);
Vector3 vector3 = color.ToVector3();
vector3 += new Vector3(brightness);
target[x, y] = Color.Compress(new Color(vector3, color.A));
targetPixels[x, y] = Color.Compress(new Color(vector3, color.A));
}
this.OnRowProcessed();
}
this.OnRowProcessed();
}
});
});
}
}
}
}

9
src/ImageProcessorCore/Filters/ColorMatrix/ColorMatrixFilter.cs

@ -28,7 +28,10 @@ namespace ImageProcessorCore.Filters
int endX = sourceRectangle.Right;
Matrix4x4 matrix = this.Matrix;
Parallel.For(
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
@ -37,11 +40,13 @@ namespace ImageProcessorCore.Filters
{
for (int x = startX; x < endX; x++)
{
target[x, y] = this.ApplyMatrix(source[x, y], matrix);
targetPixels[x, y] = this.ApplyMatrix(sourcePixels[x, y], matrix);
}
this.OnRowProcessed();
}
});
}
}
/// <summary>

35
src/ImageProcessorCore/Filters/Contrast.cs

@ -42,24 +42,29 @@ namespace ImageProcessorCore.Filters
int endX = sourceRectangle.Right;
Vector4 contrastVector = new Vector4(contrast, contrast, contrast, 1);
Vector4 shiftVector = new Vector4(.5f, .5f, .5f, 1);
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
if (y >= sourceY && y < sourceBottom)
{
Vector4 color = Color.Expand(source[x, y]).ToVector4();
color -= shiftVector;
color *= contrastVector;
color += shiftVector;
target[x, y] = Color.Compress(new Color(color));
for (int x = startX; x < endX; x++)
{
Vector4 color = Color.Expand(sourcePixels[x, y]).ToVector4();
color -= shiftVector;
color *= contrastVector;
color += shiftVector;
targetPixels[x, y] = Color.Compress(new Color(color));
}
this.OnRowProcessed();
}
this.OnRowProcessed();
}
});
});
}
}
}
}

16
src/ImageProcessorCore/Filters/Convolution/Convolution2DFilter.cs

@ -42,7 +42,10 @@ namespace ImageProcessorCore.Filters
int maxY = sourceBottom - 1;
int maxX = endX - 1;
Parallel.For(
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
@ -58,8 +61,8 @@ namespace ImageProcessorCore.Filters
float gY = 0;
float bY = 0;
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelYHeight; fy++)
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelYHeight; fy++)
{
int fyr = fy - radiusY;
int offsetY = y + fyr;
@ -73,7 +76,7 @@ namespace ImageProcessorCore.Filters
offsetX = offsetX.Clamp(0, maxX);
Color currentColor = source[offsetX, offsetY];
Color currentColor = sourcePixels[offsetX, offsetY];
float r = currentColor.R;
float g = currentColor.G;
float b = currentColor.B;
@ -98,12 +101,13 @@ namespace ImageProcessorCore.Filters
float green = (float)Math.Sqrt((gX * gX) + (gY * gY));
float blue = (float)Math.Sqrt((bX * bX) + (bY * bY));
Color targetColor = target[x, y];
target[x, y] = new Color(red, green, blue, targetColor.A);
Color targetColor = targetPixels[x, y];
targetPixels[x, y] = new Color(red, green, blue, targetColor.A);
}
this.OnRowProcessed();
}
});
}
}
}
}

10
src/ImageProcessorCore/Filters/Convolution/Convolution2PassFilter.cs

@ -71,7 +71,10 @@ namespace ImageProcessorCore.Filters
int maxY = sourceBottom - 1;
int maxX = endX - 1;
Parallel.For(
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
@ -97,16 +100,17 @@ namespace ImageProcessorCore.Filters
offsetX = offsetX.Clamp(0, maxX);
Color currentColor = source[offsetX, offsetY];
Color currentColor = sourcePixels[offsetX, offsetY];
destination += kernel[fy, fx] * currentColor;
}
}
target[x, y] = destination;
targetPixels[x, y] = destination;
}
this.OnRowProcessed();
}
});
}
}
}
}

10
src/ImageProcessorCore/Filters/Convolution/ConvolutionFilter.cs

@ -31,7 +31,10 @@ namespace ImageProcessorCore.Filters
int maxY = sourceBottom - 1;
int maxX = endX - 1;
Parallel.For(
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
@ -59,7 +62,7 @@ namespace ImageProcessorCore.Filters
offsetX = offsetX.Clamp(0, maxX);
Color currentColor = source[offsetX, offsetY];
Color currentColor = sourcePixels[offsetX, offsetY];
float r = currentColor.R;
float g = currentColor.G;
float b = currentColor.B;
@ -74,11 +77,12 @@ namespace ImageProcessorCore.Filters
float green = gX;
float blue = bX;
target[x, y] = new Color(red, green, blue);
targetPixels[x, y] = new Color(red, green, blue);
}
this.OnRowProcessed();
}
});
}
}
}
}

31
src/ImageProcessorCore/Filters/Glow.cs

@ -15,7 +15,7 @@ namespace ImageProcessorCore.Filters
public class Glow : ParallelImageProcessor
{
/// <summary>
/// Gets or sets the vignette color to apply.
/// Gets or sets the glow color to apply.
/// </summary>
public Color Color { get; set; } = Color.White;
@ -40,19 +40,24 @@ namespace ImageProcessorCore.Filters
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance));
}
this.OnRowProcessed();
});
for (int x = startX; x < endX; x++)
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = sourcePixels[x, y];
targetPixels[x, y] = Color.Lerp(glowColor, sourceColor, .5f * (distance / maxDistance));
}
this.OnRowProcessed();
});
}
}
}
}

31
src/ImageProcessorCore/Filters/Invert.cs

@ -22,22 +22,27 @@ namespace ImageProcessorCore.Filters
int endX = sourceRectangle.Right;
Vector3 inverseVector = Vector3.One;
Parallel.For(
startY,
endY,
y =>
{
if (y >= sourceY && y < sourceBottom)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
if (y >= sourceY && y < sourceBottom)
{
Color color = source[x, y];
Vector3 vector = inverseVector - color.ToVector3();
target[x, y] = new Color(vector, color.A);
for (int x = startX; x < endX; x++)
{
Color color = sourcePixels[x, y];
Vector3 vector = inverseVector - color.ToVector3();
targetPixels[x, y] = new Color(vector, color.A);
}
this.OnRowProcessed();
}
this.OnRowProcessed();
}
});
});
}
}
}
}

68
src/ImageProcessorCore/Filters/Pixelate.cs

@ -48,46 +48,50 @@ namespace ImageProcessorCore.Filters
// Get the range on the y-plane to choose from.
IEnumerable<int> range = EnumerableExtensions.SteppedRange(startY, i => i < endY, size);
Parallel.ForEach(
range,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x += size)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.ForEach(
range,
y =>
{
if (y >= sourceY && y < sourceBottom)
{
for (int x = startX; x < endX; x += size)
{
int offsetX = offset;
int offsetY = offset;
int offsetX = offset;
int offsetY = offset;
// Make sure that the offset is within the boundary of the
// image.
while (y + offsetY >= sourceBottom)
{
offsetY--;
}
// Make sure that the offset is within the boundary of the
// image.
while (y + offsetY >= sourceBottom)
{
offsetY--;
}
while (x + offsetX >= endX)
{
offsetX--;
}
while (x + offsetX >= endX)
{
offsetX--;
}
// Get the pixel color in the centre of the soon to be pixelated area.
// ReSharper disable AccessToDisposedClosure
Color pixel = source[x + offsetX, y + offsetY];
// Get the pixel color in the centre of the soon to be pixelated area.
// ReSharper disable AccessToDisposedClosure
Color pixel = sourcePixels[x + offsetX, y + offsetY];
// For each pixel in the pixelate size, set it to the centre color.
for (int l = y; l < y + size && l < sourceBottom; l++)
{
for (int k = x; k < x + size && k < endX; k++)
{
target[k, l] = pixel;
// For each pixel in the pixelate size, set it to the centre color.
for (int l = y; l < y + size && l < sourceBottom; l++)
{
for (int k = x; k < x + size && k < endX; k++)
{
targetPixels[k, l] = pixel;
}
}
}
this.OnRowProcessed();
}
}
this.OnRowProcessed();
}
});
});
}
}
}
}

28
src/ImageProcessorCore/Filters/Vignette.cs

@ -40,19 +40,23 @@ namespace ImageProcessorCore.Filters
float rY = this.RadiusY > 0 ? this.RadiusY : targetRectangle.Height / 2f;
float maxDistance = (float)Math.Sqrt(rX * rX + rY * rY);
Parallel.For(
startY,
endY,
y =>
{
for (int x = startX; x < endX; x++)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = target[x, y];
target[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance);
}
this.OnRowProcessed();
});
for (int x = startX; x < endX; x++)
{
float distance = Vector2.Distance(centre, new Vector2(x, y));
Color sourceColor = sourcePixels[x, y];
targetPixels[x, y] = Color.Lerp(vignetteColor, sourceColor, 1 - .9f * distance / maxDistance);
}
this.OnRowProcessed();
});
}
}
}
}

37
src/ImageProcessorCore/Formats/Bmp/BmpEncoderCore.cs

@ -132,15 +132,18 @@ namespace ImageProcessorCore.Formats
amount = 4 - amount;
}
switch (this.bmpBitsPerPixel)
using (PixelAccessor pixels = image.Lock())
{
case BmpBitsPerPixel.Pixel32:
this.Write32bit(writer, image, amount);
break;
switch (this.bmpBitsPerPixel)
{
case BmpBitsPerPixel.Pixel32:
this.Write32bit(writer, pixels, amount);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24bit(writer, image, amount);
break;
case BmpBitsPerPixel.Pixel24:
this.Write24bit(writer, pixels, amount);
break;
}
}
}
@ -148,18 +151,18 @@ namespace ImageProcessorCore.Formats
/// Writes the 32bit color palette to the stream.
/// </summary>
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
/// <param name="image">The <see cref="ImageBase"/> containing pixel data.</param>
/// <param name="pixels">The <see cref="PixelAccessor"/> containing pixel data.</param>
/// <param name="amount">The amount to pad each row by.</param>
private void Write32bit(EndianBinaryWriter writer, ImageBase image, int amount)
private void Write32bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount)
{
for (int y = image.Height - 1; y >= 0; y--)
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int x = 0; x < image.Width; x++)
for (int x = 0; x < pixels.Width; x++)
{
// Limit the output range and multiply out from our floating point.
// Convert back to b-> g-> r-> a order.
// Convert to non-premultiplied color.
Bgra32 color = Color.ToNonPremultiplied(image[x, y]);
Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]);
// We can take advantage of BGRA here
writer.Write(color.Bgra);
@ -177,18 +180,18 @@ namespace ImageProcessorCore.Formats
/// Writes the 24bit color palette to the stream.
/// </summary>
/// <param name="writer">The <see cref="EndianBinaryWriter"/> containing the stream to write to.</param>
/// <param name="image">The <see cref="ImageBase"/> containing pixel data.</param>
/// <param name="pixels">The <see cref="PixelAccessor"/> containing pixel data.</param>
/// <param name="amount">The amount to pad each row by.</param>
private void Write24bit(EndianBinaryWriter writer, ImageBase image, int amount)
private void Write24bit(EndianBinaryWriter writer, PixelAccessor pixels, int amount)
{
for (int y = image.Height - 1; y >= 0; y--)
for (int y = pixels.Height - 1; y >= 0; y--)
{
for (int x = 0; x < image.Width; x++)
for (int x = 0; x < pixels.Width; x++)
{
// Limit the output range and multiply out from our floating point.
// Convert back to b-> g-> r-> a order.
// Convert to non-premultiplied color.
Bgra32 color = Color.ToNonPremultiplied(image[x, y]);
Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]);
// Allocate 1 array instead of allocating 3.
writer.Write(new[] { color.B, color.G, color.R });

2
src/ImageProcessorCore/Formats/Jpg/JpegEncoder.cs

@ -90,7 +90,7 @@ namespace ImageProcessorCore.Formats
}
else
{
encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
encode.Encode(stream, image, this.Quality, this.Quality >= 80 ? JpegSubsample.Ratio444 : JpegSubsample.Ratio420);
}
}
}

43
src/ImageProcessorCore/Formats/Jpg/JpegEncoderCore.cs

@ -423,15 +423,15 @@ namespace ImageProcessorCore.Formats
// toYCbCr converts the 8x8 region of m whose top-left corner is p to its
// YCbCr values.
private void toYCbCr(ImageBase m, int x, int y, Block yBlock, Block cbBlock, Block crBlock)
private void toYCbCr(PixelAccessor pixels, int x, int y, Block yBlock, Block cbBlock, Block crBlock)
{
int xmax = m.Width - 1;
int ymax = m.Height - 1;
int xmax = pixels.Width - 1;
int ymax = pixels.Height - 1;
for (int j = 0; j < 8; j++)
{
for (int i = 0; i < 8; i++)
{
YCbCr color = m[Math.Min(x + i, xmax), Math.Min(y + j, ymax)];
YCbCr color = pixels[Math.Min(x + i, xmax), Math.Min(y + j, ymax)];
int index = (8 * j) + i;
yBlock[index] = (int)color.Y;
cbBlock[index] = (int)color.Cb;
@ -486,17 +486,17 @@ namespace ImageProcessorCore.Formats
// writeSOS writes the StartOfScan marker.
private void writeSOS(ImageBase m)
private void writeSOS(PixelAccessor pixels)
{
outputStream.Write(sosHeaderYCbCr, 0, sosHeaderYCbCr.Length);
switch (subsample)
{
case JpegSubsample.Ratio444:
encode444(m);
encode444(pixels);
break;
case JpegSubsample.Ratio420:
encode420(m);
encode420(pixels);
break;
}
@ -504,18 +504,18 @@ namespace ImageProcessorCore.Formats
emit(0x7f, 7);
}
private void encode444(ImageBase m)
private void encode444(PixelAccessor pixels)
{
Block b = new Block();
Block cb = new Block();
Block cr = new Block();
int prevDCY = 0, prevDCCb = 0, prevDCCr = 0;
for (int y = 0; y < m.Height; y += 8)
for (int y = 0; y < pixels.Height; y += 8)
{
for (int x = 0; x < m.Width; x += 8)
for (int x = 0; x < pixels.Width; x += 8)
{
toYCbCr(m, x, y, b, cb, cr);
toYCbCr(pixels, x, y, b, cb, cr);
prevDCY = writeBlock(b, (quantIndex)0, prevDCY);
prevDCCb = writeBlock(cb, (quantIndex)1, prevDCCb);
prevDCCr = writeBlock(cr, (quantIndex)1, prevDCCr);
@ -523,7 +523,7 @@ namespace ImageProcessorCore.Formats
}
}
private void encode420(ImageBase m)
private void encode420(PixelAccessor pixels)
{
Block b = new Block();
Block[] cb = new Block[4];
@ -533,16 +533,16 @@ namespace ImageProcessorCore.Formats
for (int i = 0; i < 4; i++) cb[i] = new Block();
for (int i = 0; i < 4; i++) cr[i] = new Block();
for (int y = 0; y < m.Height; y += 16)
for (int y = 0; y < pixels.Height; y += 16)
{
for (int x = 0; x < m.Width; x += 16)
for (int x = 0; x < pixels.Width; x += 16)
{
for (int i = 0; i < 4; i++)
{
int xOff = (i & 1) * 8;
int yOff = (i & 2) * 4;
toYCbCr(m, x + xOff, y + yOff, b, cb[i], cr[i]);
toYCbCr(pixels, x + xOff, y + yOff, b, cb[i], cr[i]);
prevDCY = writeBlock(b, (quantIndex)0, prevDCY);
}
scale_16x16_8x8(b, cb);
@ -555,7 +555,7 @@ namespace ImageProcessorCore.Formats
// Encode writes the Image m to w in JPEG 4:2:0 baseline format with the given
// options. Default parameters are used if a nil *Options is passed.
public void Encode(Stream stream, ImageBase m, int quality, JpegSubsample subsample)
public void Encode(Stream stream, ImageBase image, int quality, JpegSubsample subsample)
{
this.outputStream = stream;
this.subsample = subsample;
@ -570,9 +570,9 @@ namespace ImageProcessorCore.Formats
quant[i] = new byte[Block.blockSize];
}
if (m.Width >= (1 << 16) || m.Height >= (1 << 16))
if (image.Width >= (1 << 16) || image.Height >= (1 << 16))
{
throw new ImageFormatException($"Image is too large to encode at {m.Width}x{m.Height}.");
throw new ImageFormatException($"Image is too large to encode at {image.Width}x{image.Height}.");
}
if (quality < 1) quality = 1;
@ -614,13 +614,16 @@ namespace ImageProcessorCore.Formats
writeDQT();
// Write the image dimensions.
writeSOF0(m.Width, m.Height, nComponent);
writeSOF0(image.Width, image.Height, nComponent);
// Write the Huffman tables.
writeDHT(nComponent);
// Write the image data.
writeSOS(m);
using (PixelAccessor pixels = image.Lock())
{
writeSOS(pixels);
}
// Write the End Of Image marker.
buf[0] = 0xff;

21
src/ImageProcessorCore/Formats/Png/PngEncoderCore.cs

@ -123,7 +123,12 @@ namespace ImageProcessorCore.Formats
this.WritePaletteChunk(stream, header, image);
this.WritePhysicalChunk(stream, image);
this.WriteGammaChunk(stream);
this.WriteDataChunks(stream, image);
using (PixelAccessor pixels = image.Lock())
{
this.WriteDataChunks(stream, pixels);
}
this.WriteEndChunk(stream);
stream.Flush();
}
@ -292,12 +297,12 @@ namespace ImageProcessorCore.Formats
/// Writes the pixel information to the stream.
/// </summary>
/// <param name="stream">The <see cref="Stream"/> containing image data.</param>
/// <param name="image">The image base.</param>
private void WriteDataChunks(Stream stream, ImageBase image)
/// <param name="pixels">The image pixels.</param>
private void WriteDataChunks(Stream stream, PixelAccessor pixels)
{
byte[] data;
int imageWidth = image.Width;
int imageHeight = image.Height;
int imageWidth = pixels.Width;
int imageHeight = pixels.Height;
// Indexed image.
if (this.Quality <= 256)
@ -327,7 +332,7 @@ namespace ImageProcessorCore.Formats
else
{
// TrueColor image.
data = new byte[(imageWidth * imageHeight * 4) + image.Height];
data = new byte[(imageWidth * imageHeight * 4) + pixels.Height];
int rowLength = (imageWidth * 4) + 1;
@ -343,7 +348,7 @@ namespace ImageProcessorCore.Formats
for (int x = 0; x < imageWidth; x++)
{
Bgra32 color = Color.ToNonPremultiplied(image[x, y]);
Bgra32 color = Color.ToNonPremultiplied(pixels[x, y]);
// Calculate the offset for the new array.
int dataOffset = (y * rowLength) + (x * 4) + 1;
@ -354,7 +359,7 @@ namespace ImageProcessorCore.Formats
if (y > 0)
{
color = Color.ToNonPremultiplied(image[x, y - 1]);
color = Color.ToNonPremultiplied(pixels[x, y - 1]);
data[dataOffset] -= color.R;
data[dataOffset + 1] -= color.G;

103
src/ImageProcessorCore/IImage.cs

@ -1,103 +0,0 @@
// <copyright file="IImage.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Collections.Generic;
using System.IO;
using ImageProcessorCore.Formats;
/// <summary>
/// Encapsulates an image, which consists of the pixel data for a graphics image and its attributes.
/// </summary>
public interface IImage : IImageBase
{
/// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x- direction.</value>
double HorizontalResolution { get; set; }
/// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y- direction.</value>
double VerticalResolution { get; set; }
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The width of the image in inches.</value>
double InchWidth { get; }
/// <summary>
/// Gets the height of the image in inches. It is calculated as the height of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The height of the image in inches.</value>
double InchHeight { get; }
/// <summary>
/// Gets a value indicating whether this image is animated.
/// </summary>
/// <value>
/// <c>True</c> if this image is animated; otherwise, <c>false</c>.
/// </value>
bool IsAnimated { get; }
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// </summary>
ushort RepeatCount { get; set; }
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
IImageFormat CurrentImageFormat { get; }
/// <summary>
/// Gets the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
IList<ImageFrame> Frames { get; }
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
IList<ImageProperty> Properties { get; }
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
void Save(Stream stream);
/// <summary>
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
void Save(Stream stream, IImageFormat format);
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
void Save(Stream stream, IImageEncoder encoder);
}
}

106
src/ImageProcessorCore/IImageBase.cs

@ -1,106 +0,0 @@
// <copyright file="IImageBase.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Runtime.CompilerServices;
/// <summary>
/// Encapsulates the basic properties and methods required to manipulate images.
/// </summary>
public interface IImageBase
{
/// <summary>
/// Gets the image pixels as byte array.
/// </summary>
/// <remarks>
/// The returned array has a length of Width * Height * 4 bytes
/// and stores the red, the green, the blue, and the alpha value for
/// each pixel in this order.
/// </remarks>
float[] Pixels { get; }
/// <summary>
/// Gets the width in pixels.
/// </summary>
int Width { get; }
/// <summary>
/// Gets the height in pixels.
/// </summary>
int Height { get; }
/// <summary>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
double PixelRatio { get; }
/// <summary>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
Rectangle Bounds { get; }
/// <summary>
/// Gets or sets th quality of the image. This affects the output quality of lossy image formats.
/// </summary>
int Quality { get; set; }
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
int FrameDelay { get; set; }
/// <summary>
/// Gets or sets the color of a pixel at the specified position.
/// </summary>
/// <param name="x">
/// The x-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <param name="y">
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="Color"/> at the specified position.</returns>
Color this[int x, int y] { get; set; }
/// <summary>
/// Sets the pixel array of the image to the given value.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
void SetPixels(int width, int height, float[] pixels);
/// <summary>
/// Sets the pixel array of the image to the given value, creating a copy of
/// the original pixels.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
void ClonePixels(int width, int height, float[] pixels);
}
}

97
src/ImageProcessorCore/Image.cs

@ -19,10 +19,10 @@ namespace ImageProcessorCore
/// </summary>
/// <remarks>
/// The image data is always stored in RGBA format, where the red, green, blue, and
/// alpha values are floats.
/// alpha values are floating point numbers.
/// </remarks>
[DebuggerDisplay("Image: {Width}x{Height}")]
public class Image : ImageBase, IImage
public class Image : ImageBase
{
/// <summary>
/// The default horizontal resolution value (dots per inch) in x direction.
@ -97,13 +97,26 @@ namespace ImageProcessorCore
/// </summary>
public IReadOnlyCollection<IImageFormat> Formats { get; } = Bootstrapper.Instance.ImageFormats;
/// <inheritdoc/>
/// <summary>
/// Gets or sets the resolution of the image in x- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in x- direction.</value>
public double HorizontalResolution { get; set; } = DefaultHorizontalResolution;
/// <inheritdoc/>
/// <summary>
/// Gets or sets the resolution of the image in y- direction. It is defined as
/// number of dots per inch and should be an positive value.
/// </summary>
/// <value>The density of the image in y- direction.</value>
public double VerticalResolution { get; set; } = DefaultVerticalResolution;
/// <inheritdoc/>
/// <summary>
/// Gets the width of the image in inches. It is calculated as the width of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The width of the image in inches.</value>
public double InchWidth
{
get
@ -119,7 +132,12 @@ namespace ImageProcessorCore
}
}
/// <inheritdoc/>
/// <summary>
/// Gets the height of the image in inches. It is calculated as the height of the image
/// in pixels multiplied with the density. When the density is equals or less than zero
/// the default value is used.
/// </summary>
/// <value>The height of the image in inches.</value>
public double InchHeight
{
get
@ -135,36 +153,66 @@ namespace ImageProcessorCore
}
}
/// <inheritdoc/>
/// <summary>
/// Gets a value indicating whether this image is animated.
/// </summary>
/// <value>
/// <c>True</c> if this image is animated; otherwise, <c>false</c>.
/// </value>
public bool IsAnimated => this.Frames.Count > 0;
/// <inheritdoc/>
/// <summary>
/// Gets or sets the number of times any animation is repeated.
/// <remarks>0 means to repeat indefinitely.</remarks>
/// </summary>
public ushort RepeatCount { get; set; }
/// <inheritdoc/>
/// <summary>
/// Gets the other frames for the animation.
/// </summary>
/// <value>The list of frame images.</value>
public IList<ImageFrame> Frames { get; } = new List<ImageFrame>();
/// <inheritdoc/>
/// <summary>
/// Gets the list of properties for storing meta information about this image.
/// </summary>
/// <value>A list of image properties.</value>
public IList<ImageProperty> Properties { get; } = new List<ImageProperty>();
/// <inheritdoc/>
/// <summary>
/// Gets the currently loaded image format.
/// </summary>
public IImageFormat CurrentImageFormat { get; internal set; }
/// <inheritdoc/>
/// <summary>
/// Saves the image to the given stream using the currently loaded image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream)
{
Guard.NotNull(stream, nameof(stream));
this.CurrentImageFormat.Encoder.Encode(this, stream);
}
/// <inheritdoc/>
/// <summary>
/// Saves the image to the given stream using the given image format.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="format">The format to save the image as.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageFormat format)
{
Guard.NotNull(stream, nameof(stream));
format.Encoder.Encode(this, stream);
}
/// <inheritdoc/>
/// <summary>
/// Saves the image to the given stream using the given image encoder.
/// </summary>
/// <param name="stream">The stream to save the image to.</param>
/// <param name="encoder">The encoder to save the image with.</param>
/// <exception cref="ArgumentNullException">Thrown if the stream is null.</exception>
public void Save(Stream stream, IImageEncoder encoder)
{
Guard.NotNull(stream, nameof(stream));
@ -186,27 +234,6 @@ namespace ImageProcessorCore
}
}
/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (this.IsDisposed)
{
return;
}
// Dispose of the unmanaged resources for each frame here.
if (this.Frames.Any())
{
foreach (ImageFrame frame in this.Frames)
{
frame.Dispose();
}
this.Frames.Clear();
}
base.Dispose(disposing);
}
/// <summary>
/// Loads the image from the given stream.
/// </summary>

207
src/ImageProcessorCore/ImageBase.cs

@ -6,43 +6,18 @@
namespace ImageProcessorCore
{
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
/// <summary>
/// The base class of all images. Encapsulates the basic properties and methods
/// required to manipulate images.
/// </summary>
public abstract unsafe class ImageBase : IImageBase, IDisposable
public abstract class ImageBase
{
/// <summary>
/// The position of the first pixel in the bitmap.
/// </summary>
private float* pixelsBase;
/// <summary>
/// The array of pixels.
/// </summary>
private float[] pixelsArray;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
private GCHandle pixelsHandle;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
/// </remarks>
internal bool IsDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="ImageBase"/> class.
/// </summary>
@ -68,8 +43,6 @@ namespace ImageProcessorCore
// Assign the pointer and pixels.
this.pixelsArray = new float[width * height * 4];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}
/// <summary>
@ -90,19 +63,11 @@ namespace ImageProcessorCore
this.Quality = other.Quality;
this.FrameDelay = other.FrameDelay;
// Assign the pointer and copy the pixels.
// Copy the pixels.
this.pixelsArray = new float[this.Width * this.Height * 4];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
Array.Copy(other.pixelsArray, this.pixelsArray, other.pixelsArray.Length);
}
/// <inheritdoc/>
~ImageBase()
{
this.Dispose(false);
}
/// <summary>
/// Gets or sets the maximum allowable width in pixels.
/// </summary>
@ -113,67 +78,65 @@ namespace ImageProcessorCore
/// </summary>
public static int MaxHeight { get; set; } = int.MaxValue;
/// <inheritdoc/>
/// <summary>
/// Gets the image pixels as byte array.
/// </summary>
/// <remarks>
/// The returned array has a length of Width * Height * 4 bytes
/// and stores the red, the green, the blue, and the alpha value for
/// each pixel in this order.
/// </remarks>
public float[] Pixels => this.pixelsArray;
/// <inheritdoc/>
/// <summary>
/// Gets the width in pixels.
/// </summary>
public int Width { get; private set; }
/// <inheritdoc/>
/// <summary>
/// Gets the height in pixels.
/// </summary>
public int Height { get; private set; }
/// <inheritdoc/>
/// <summary>
/// Gets the pixel ratio made up of the width and height.
/// </summary>
public double PixelRatio => (double)this.Width / this.Height;
/// <inheritdoc/>
/// <summary>
/// Gets the <see cref="Rectangle"/> representing the bounds of the image.
/// </summary>
public Rectangle Bounds => new Rectangle(0, 0, this.Width, this.Height);
/// <inheritdoc/>
/// <summary>
/// Gets or sets th quality of the image. This affects the output quality of lossy image formats.
/// </summary>
public int Quality { get; set; }
/// <inheritdoc/>
/// <summary>
/// Gets or sets the frame delay for animated images.
/// If not 0, this field specifies the number of hundredths (1/100) of a second to
/// wait before continuing with the processing of the Data Stream.
/// The clock starts ticking immediately after the graphic is rendered.
/// </summary>
public int FrameDelay { get; set; }
/// <inheritdoc/>
public Color this[int x, int y]
{
get
{
#if DEBUG
if ((x < 0) || (x >= this.Width))
{
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4));
}
set
{
#if DEBUG
if ((x < 0) || (x >= this.Width))
{
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
*(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value;
}
}
/// <inheritdoc/>
/// <summary>
/// Sets the pixel array of the image to the given value.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
public void SetPixels(int width, int height, float[] pixels)
{
#if DEBUG
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
@ -188,28 +151,30 @@ namespace ImageProcessorCore
{
throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
}
#endif
this.Width = width;
this.Height = height;
// Ensure nothing is preserved if previously allocated.
if (this.pixelsHandle.IsAllocated)
{
this.pixelsArray = null;
this.pixelsHandle.Free();
this.pixelsBase = null;
}
this.pixelsArray = pixels;
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}
/// <inheritdoc/>
/// <summary>
/// Sets the pixel array of the image to the given value, creating a copy of
/// the original pixels.
/// </summary>
/// <param name="width">The new width of the image. Must be greater than zero.</param>
/// <param name="height">The new height of the image. Must be greater than zero.</param>
/// <param name="pixels">
/// The array with colors. Must be a multiple of four times the width and height.
/// </param>
/// <exception cref="ArgumentOutOfRangeException">
/// Thrown if either <paramref name="width"/> or <paramref name="height"/> are less than or equal to 0.
/// </exception>
/// <exception cref="ArgumentException">
/// Thrown if the <paramref name="pixels"/> length is not equal to Width * Height * 4.
/// </exception>
public void ClonePixels(int width, int height, float[] pixels)
{
#if DEBUG
if (width <= 0)
if (width <= 0)
{
throw new ArgumentOutOfRangeException(nameof(width), "Width must be greater than or equals than zero.");
}
@ -223,55 +188,25 @@ namespace ImageProcessorCore
{
throw new ArgumentException("Pixel array must have the length of Width * Height * 4.");
}
#endif
this.Width = width;
this.Height = height;
// Assign the pointer and copy the pixels.
// Copy the pixels.
this.pixelsArray = new float[pixels.Length];
this.pixelsHandle = GCHandle.Alloc(this.pixelsArray, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
Array.Copy(pixels, this.pixelsArray, pixels.Length);
}
/// <inheritdoc/>
public void Dispose()
{
this.Dispose(true);
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
/// <summary>
/// Disposes the object and frees resources for the Garbage Collector.
/// Locks the image providing access to the pixels.
/// <remarks>
/// It is imperative that the accessor is correctly disposed off after use.
/// </remarks>
/// </summary>
/// <param name="disposing">If true, the object gets disposed.</param>
protected virtual void Dispose(bool disposing)
/// <returns>The <see cref="PixelAccessor"/></returns>
public PixelAccessor Lock()
{
if (this.IsDisposed)
{
return;
}
if (disposing)
{
// Dispose of any managed resources here.
this.pixelsArray = null;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
this.pixelsBase = null;
}
// Note disposing is done.
this.IsDisposed = true;
return new PixelAccessor(this);
}
}
}

8
src/ImageProcessorCore/ImageExtensions.cs

@ -128,11 +128,6 @@ namespace ImageProcessorCore
/// <exception cref="ObjectDisposedException">Thrown if the <paramref name="source"/> has been disposed.</exception>
private static Image PerformAction(Image source, bool clone, Action<ImageBase, ImageBase> action)
{
if (source.IsDisposed)
{
throw new ObjectDisposedException("Image");
}
Image transformedImage = clone
? new Image(source)
: new Image
@ -163,9 +158,6 @@ namespace ImageProcessorCore
}
}
// According to http://stackoverflow.com/questions/37921815/idisposable-unmanaged-fields-reference-types-and-assignments
// There's no need to dispose of the original image as the GC will get around to cleaning it up now there are no references to the original data.
// TODO: Investigate how long this is held onto and try to keep that to a minimum.
source = transformedImage;
return source;
}

164
src/ImageProcessorCore/PixelAccessor.cs

@ -0,0 +1,164 @@
// <copyright file="PixelAccessor.cs" company="James Jackson-South">
// Copyright (c) James Jackson-South and contributors.
// Licensed under the Apache License, Version 2.0.
// </copyright>
namespace ImageProcessorCore
{
using System;
using System.Runtime.InteropServices;
/// <summary>
/// Provides per-pixel access to an images pixels.
/// </summary>
public sealed unsafe class PixelAccessor : IDisposable
{
/// <summary>
/// The position of the first pixel in the bitmap.
/// </summary>
private float* pixelsBase;
/// <summary>
/// Provides a way to access the pixels from unmanaged memory.
/// </summary>
private GCHandle pixelsHandle;
/// <summary>
/// A value indicating whether this instance of the given entity has been disposed.
/// </summary>
/// <value><see langword="true"/> if this instance has been disposed; otherwise, <see langword="false"/>.</value>
/// <remarks>
/// If the entity is disposed, it must not be disposed a second
/// time. The isDisposed field is set the first time the entity
/// is disposed. If the isDisposed field is true, then the Dispose()
/// method will not dispose again. This help not to prolong the entity's
/// life in the Garbage Collector.
/// </remarks>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="PixelAccessor"/> class.
/// </summary>
/// <param name="image">
/// The image to provide pixel access for.
/// </param>
public PixelAccessor(ImageBase image)
{
Guard.NotNull(image, nameof(image));
Guard.MustBeGreaterThan(image.Width, 0, "image width");
Guard.MustBeGreaterThan(image.Height, 0, "image height");
int size = image.Pixels.Length;
this.Width = image.Width;
this.Height = image.Height;
// Assign the pointer.
// If buffer is allocated on Large Object Heap, then we are going to pin it instead of making a copy.
if (size > (85 * 1024))
{
this.pixelsHandle = GCHandle.Alloc(image.Pixels, GCHandleType.Pinned);
this.pixelsBase = (float*)this.pixelsHandle.AddrOfPinnedObject().ToPointer();
}
else
{
fixed (float* pbuffer = image.Pixels)
{
this.pixelsBase = pbuffer;
}
}
}
/// <summary>
/// Finalizes an instance of the <see cref="PixelAccessor"/> class.
/// </summary>
~PixelAccessor()
{
this.Dispose();
}
/// <summary>
/// Gets the width of the image.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height of the image.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets or sets the color of a pixel at the specified position.
/// </summary>
/// <param name="x">
/// The x-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <param name="y">
/// The y-coordinate of the pixel. Must be greater
/// than zero and smaller than the width of the pixel.
/// </param>
/// <returns>The <see cref="Color"/> at the specified position.</returns>
public Color this[int x, int y]
{
get
{
#if DEBUG
if ((x < 0) || (x >= this.Width))
{
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
return *((Color*)(this.pixelsBase + ((y * this.Width) + x) * 4));
}
set
{
#if DEBUG
if ((x < 0) || (x >= this.Width))
{
throw new ArgumentOutOfRangeException(nameof(x), "Value cannot be less than zero or greater than the bitmap width.");
}
if ((y < 0) || (y >= this.Height))
{
throw new ArgumentOutOfRangeException(nameof(y), "Value cannot be less than zero or greater than the bitmap height.");
}
#endif
*(Color*)(this.pixelsBase + (((y * this.Width) + x) * 4)) = value;
}
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
if (this.isDisposed)
{
return;
}
if (this.pixelsHandle.IsAllocated)
{
this.pixelsHandle.Free();
}
this.pixelsBase = null;
// Note disposing is done.
this.isDisposed = true;
// This object will be cleaned up by the Dispose method.
// Therefore, you should call GC.SuppressFinalize to
// take this object off the finalization queue
// and prevent finalization code for this object
// from executing a second time.
GC.SuppressFinalize(this);
}
}
}

31
src/ImageProcessorCore/Quantizers/Octree/Quantizer.cs

@ -50,21 +50,24 @@ namespace ImageProcessorCore.Quantizers
// Get the size of the source image
int height = image.Height;
int width = image.Width;
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(image, width, height);
}
byte[] quantizedPixels = new byte[width * height];
List<Bgra32> palette;
// Get the palette
List<Bgra32> palette = this.GetPalette();
using (PixelAccessor pixels = image.Lock())
{
// Call the FirstPass function if not a single pass algorithm.
// For something like an Octree quantizer, this will run through
// all image pixels, build a data structure, and create a palette.
if (!this.singlePass)
{
this.FirstPass(pixels, width, height);
}
// Get the palette
palette = this.GetPalette();
this.SecondPass(image, quantizedPixels, width, height);
this.SecondPass(pixels, quantizedPixels, width, height);
}
return new QuantizedImage(width, height, palette.ToArray(), quantizedPixels, this.TransparentIndex);
}
@ -75,7 +78,7 @@ namespace ImageProcessorCore.Quantizers
/// <param name="source">The source data</param>
/// <param name="width">The width in pixels of the image.</param>
/// <param name="height">The height in pixels of the image.</param>
protected virtual void FirstPass(ImageBase source, int width, int height)
protected virtual void FirstPass(PixelAccessor source, int width, int height)
{
// Loop through each row
for (int y = 0; y < height; y++)
@ -96,7 +99,7 @@ namespace ImageProcessorCore.Quantizers
/// <param name="output">The output pixel array</param>
/// <param name="width">The width in pixels of the image</param>
/// <param name="height">The height in pixels of the image</param>
protected virtual void SecondPass(ImageBase source, byte[] output, int width, int height)
protected virtual void SecondPass(PixelAccessor source, byte[] output, int width, int height)
{
Parallel.For(
0,

36
src/ImageProcessorCore/Quantizers/Wu/WuQuantizer.cs

@ -123,13 +123,16 @@ namespace ImageProcessorCore.Quantizers
this.Clear();
this.Build3DHistogram(image);
this.Get3DMoments();
using (PixelAccessor imagePixels = image.Lock())
{
this.Build3DHistogram(imagePixels);
this.Get3DMoments();
Box[] cube;
this.BuildCube(out cube, ref colorCount);
Box[] cube;
this.BuildCube(out cube, ref colorCount);
return this.GenerateResult(image, colorCount, cube);
return this.GenerateResult(imagePixels, colorCount, cube);
}
}
/// <summary>
@ -318,7 +321,7 @@ namespace ImageProcessorCore.Quantizers
/// Builds a 3-D color histogram of <c>counts, r/g/b, c^2</c>.
/// </summary>
/// <param name="image">The image.</param>
private void Build3DHistogram(ImageBase image)
private void Build3DHistogram(PixelAccessor image)
{
for (int y = 0; y < image.Height; y++)
{
@ -711,15 +714,17 @@ namespace ImageProcessorCore.Quantizers
/// <summary>
/// Generates the quantized result.
/// </summary>
/// <param name="image">The image.</param>
/// <param name="imagePixels">The image pixels.</param>
/// <param name="colorCount">The color count.</param>
/// <param name="cube">The cube.</param>
/// <returns>The result.</returns>
private QuantizedImage GenerateResult(ImageBase image, int colorCount, Box[] cube)
private QuantizedImage GenerateResult(PixelAccessor imagePixels, int colorCount, Box[] cube)
{
List<Bgra32> pallette = new List<Bgra32>();
byte[] pixels = new byte[image.Width * image.Height];
byte[] pixels = new byte[imagePixels.Width * imagePixels.Height];
int transparentIndex = -1;
int width = imagePixels.Width;
int height = imagePixels.Height;
for (int k = 0; k < colorCount; k++)
{
@ -752,12 +757,12 @@ namespace ImageProcessorCore.Quantizers
Parallel.For(
0,
image.Height,
height,
y =>
{
for (int x = 0; x < image.Width; x++)
for (int x = 0; x < width; x++)
{
Bgra32 color = image[x, y];
Bgra32 color = imagePixels[x, y];
int a = color.A >> (8 - IndexAlphaBits);
int r = color.R >> (8 - IndexBits);
int g = color.G >> (8 - IndexBits);
@ -765,16 +770,17 @@ namespace ImageProcessorCore.Quantizers
if (transparentIndex > -1 && color.A <= this.Threshold)
{
pixels[(y * image.Width) + x] = (byte)transparentIndex;
pixels[(y * width) + x] = (byte)transparentIndex;
continue;
}
int ind = GetPaletteIndex(r + 1, g + 1, b + 1, a + 1);
pixels[(y * image.Width) + x] = this.tag[ind];
pixels[(y * width) + x] = this.tag[ind];
}
});
return new QuantizedImage(image.Width, image.Height, pallette.ToArray(), pixels, transparentIndex);
return new QuantizedImage(width, height, pallette.ToArray(), pixels, transparentIndex);
}
}
}

30
src/ImageProcessorCore/Samplers/Processors/CropProcessor.cs

@ -22,21 +22,25 @@ namespace ImageProcessorCore
int sourceX = sourceRectangle.X;
int sourceY = sourceRectangle.Y;
Parallel.For(
startY,
endY,
y =>
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
if (y >= targetY && y < targetBottom)
{
for (int x = startX; x < endX; x++)
{
target[x, y] = source[x + sourceX, y + sourceY];
}
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
for (int x = startX; x < endX; x++)
{
targetPixels[x, y] = sourcePixels[x + sourceX, y + sourceY];
}
this.OnRowProcessed();
}
});
this.OnRowProcessed();
}
});
}
}
}
}

55
src/ImageProcessorCore/Samplers/Processors/EntropyCropProcessor.cs

@ -8,7 +8,7 @@ namespace ImageProcessorCore
using System;
using System.Threading.Tasks;
using ImageProcessorCore.Filters;
using Filters;
/// <summary>
/// Provides methods to allow the cropping of an image to preserve areas of highest
@ -42,21 +42,20 @@ namespace ImageProcessorCore
/// <inheritdoc/>
protected override void OnApply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle)
{
using (ImageBase temp = new Image(source.Width, source.Height))
{
// Detect the edges.
new Sobel().Apply(temp, source, sourceRectangle);
ImageBase temp = new Image(source.Width, source.Height);
// Apply threshold binarization filter.
new Threshold(.5f).Apply(temp, temp, sourceRectangle);
// Detect the edges.
new Sobel().Apply(temp, source, sourceRectangle);
// Search for the first white pixels
Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
// Apply threshold binarization filter.
new Threshold(.5f).Apply(temp, temp, sourceRectangle);
// Reset the target pixel to the correct size.
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
this.cropRectangle = rectangle;
}
// Search for the first white pixels
Rectangle rectangle = ImageMaths.GetFilteredBoundingRectangle(temp, 0);
// Reset the target pixel to the correct size.
target.SetPixels(rectangle.Width, rectangle.Height, new float[rectangle.Width * rectangle.Height * 4]);
this.cropRectangle = rectangle;
}
/// <inheritdoc/>
@ -73,21 +72,25 @@ namespace ImageProcessorCore
int startX = this.cropRectangle.X;
int endX = this.cropRectangle.Right;
Parallel.For(
startY,
endY,
y =>
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
if (y >= targetY && y < targetBottom)
{
for (int x = startX; x < endX; x++)
{
target[x - startX, y - targetY] = source[x, y];
}
}
Parallel.For(
startY,
endY,
y =>
{
if (y >= targetY && y < targetBottom)
{
for (int x = startX; x < endX; x++)
{
targetPixels[x - startX, y - targetY] = sourcePixels[x, y];
}
}
this.OnRowProcessed();
});
this.OnRowProcessed();
});
}
}
/// <inheritdoc/>

185
src/ImageProcessorCore/Samplers/Processors/ResizeProcessor.cs

@ -62,7 +62,13 @@ namespace ImageProcessorCore
}
/// <inheritdoc/>
protected override void Apply(ImageBase target, ImageBase source, Rectangle targetRectangle, Rectangle sourceRectangle, int startY, int endY)
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)
@ -87,30 +93,34 @@ namespace ImageProcessorCore
float widthFactor = sourceRectangle.Width / (float)targetRectangle.Width;
float heightFactor = sourceRectangle.Height / (float)targetRectangle.Height;
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++)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
startY,
endY,
y =>
{
if (targetX <= x && x < targetRight)
if (targetY <= y && y < targetBottom)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
// Y coordinates of source points
int originY = (int)((y - startY) * heightFactor);
target[x, y] = source[originX, originY];
}
}
for (int x = startX; x < endX; x++)
{
if (targetX <= x && x < targetRight)
{
// X coordinates of source points
int originX = (int)((x - startX) * widthFactor);
this.OnRowProcessed();
}
});
targetPixels[x, y] = sourcePixels[originX, originY];
}
}
this.OnRowProcessed();
}
});
}
// Break out now.
return;
@ -120,78 +130,90 @@ namespace ImageProcessorCore
// 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.
Parallel.For(
0,
sourceHeight,
y =>
{
for (int x = startX; x < endX; x++)
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor firstPassPixels = this.firstPass.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
0,
sourceHeight,
y =>
{
if (x >= 0 && x < width)
for (int x = startX; x < endX; x++)
{
// 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(source[originX, y]) : source[originX, y];
destination += sourceColor * xw.Value;
}
if (compand)
if (x >= 0 && x < width)
{
destination = Color.Compress(destination);
// 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;
}
this.firstPass[x, y] = destination;
}
}
});
});
// Now process the rows.
Parallel.For(
startY,
endY,
y =>
{
if (y >= 0 && y < height)
// Now process the rows.
Parallel.For(
startY,
endY,
y =>
{
// 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++)
if (y >= 0 && y < height)
{
// Destination color components
Color destination = new Color();
for (int i = 0; i < sum; i++)
{
Weight yw = verticalValues[i];
int originY = yw.Index;
Color sourceColor = compand ? Color.Expand(this.firstPass[x, originY]) : this.firstPass[x, originY];
destination += sourceColor * yw.Value;
}
// Ensure offsets are normalised for cropping and padding.
int offsetY = y - startY;
float sum = this.VerticalWeights[offsetY].Sum;
Weight[] verticalValues = this.VerticalWeights[offsetY].Values;
if (compand)
for (int x = 0; x < width; x++)
{
destination = Color.Compress(destination);
// Destination color components
Color destination = new Color();
for (int i = 0; i < sum; i++)
{
Weight yw = verticalValues[i];
int originY = yw.Index;
Color sourceColor = compand
? Color.Expand(firstPassPixels[x, originY])
: firstPassPixels[x, originY];
destination += sourceColor * yw.Value;
}
if (compand)
{
destination = Color.Compress(destination);
}
targetPixels[x, y] = destination;
}
target[x, y] = destination;
}
}
this.OnRowProcessed();
});
this.OnRowProcessed();
});
}
}
/// <inheritdoc/>
@ -202,9 +224,6 @@ namespace ImageProcessorCore
{
target.ClonePixels(target.Width, target.Height, source.Pixels);
}
// Clean up
this.firstPass?.Dispose();
}
/// <summary>

150
src/ImageProcessorCore/Samplers/Processors/RotateFlip.cs

@ -78,20 +78,26 @@ namespace ImageProcessorCore
int height = source.Height;
Image temp = new Image(height, width);
Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
newX = height - newX - 1;
int newY = width - x - 1;
newY = width - newY - 1;
temp[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor tempPixels = temp.Lock())
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
newX = height - newX - 1;
int newY = width - x - 1;
newY = width - newY - 1;
tempPixels[newX, newY] = sourcePixels[x, y];
}
this.OnRowProcessed();
});
}
target.SetPixels(height, width, temp.Pixels);
}
@ -106,18 +112,24 @@ namespace ImageProcessorCore
int width = source.Width;
int height = source.Height;
Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = width - x - 1;
int newY = height - y - 1;
target[newX, newY] = source[x, y];
}
this.OnRowProcessed();
});
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = width - x - 1;
int newY = height - y - 1;
targetPixels[newX, newY] = sourcePixels[x, y];
}
this.OnRowProcessed();
});
}
}
/// <summary>
@ -131,17 +143,23 @@ namespace ImageProcessorCore
int height = source.Height;
Image temp = new Image(height, width);
Parallel.For(0, height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
temp[newX, x] = source[x, y];
}
this.OnRowProcessed();
});
using (PixelAccessor sourcePixels = source.Lock())
using (PixelAccessor tempPixels = temp.Lock())
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
int newX = height - y - 1;
tempPixels[newX, x] = sourcePixels[x, y];
}
this.OnRowProcessed();
});
}
target.SetPixels(height, width, temp.Pixels);
}
@ -159,18 +177,24 @@ namespace ImageProcessorCore
ImageBase temp = new Image(width, height);
temp.ClonePixels(width, height, target.Pixels);
Parallel.For(0, halfHeight,
y =>
{
for (int x = 0; x < width; x++)
using (PixelAccessor targetPixels = target.Lock())
using (PixelAccessor tempPixels = temp.Lock())
{
Parallel.For(
0,
halfHeight,
y =>
{
int newY = height - y - 1;
target[x, y] = temp[x, newY];
target[x, newY] = temp[x, y];
}
this.OnRowProcessed();
});
for (int x = 0; x < width; x++)
{
int newY = height - y - 1;
targetPixels[x, y] = tempPixels[x, newY];
targetPixels[x, newY] = tempPixels[x, y];
}
this.OnRowProcessed();
});
}
}
/// <summary>
@ -186,18 +210,24 @@ namespace ImageProcessorCore
ImageBase temp = new Image(width, height);
temp.ClonePixels(width, height, target.Pixels);
Parallel.For(0, height,
y =>
{
for (int x = 0; x < halfWidth; x++)
{
int newX = width - x - 1;
target[x, y] = temp[newX, y];
target[newX, y] = temp[x, y];
}
this.OnRowProcessed();
});
using (PixelAccessor targetPixels = target.Lock())
using (PixelAccessor tempPixels = temp.Lock())
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < halfWidth; x++)
{
int newX = width - x - 1;
targetPixels[x, y] = tempPixels[newX, y];
targetPixels[newX, y] = tempPixels[x, y];
}
this.OnRowProcessed();
});
}
}
}
}

43
src/ImageProcessorCore/Samplers/Processors/RotateProcessor.cs

@ -104,30 +104,27 @@ namespace ImageProcessorCore
// Since we are not working in parallel we use full height and width
// of the first pass image.
Parallel.For(
0,
height,
y =>
{
for (int x = startX; x < endX; x++)
{
// Rotate at the centre point
Point rotated = Point.Rotate(new Point(x, y), rotation);
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y))
using (PixelAccessor firstPassPixels = this.firstPass.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
0,
height,
y =>
{
target[x, y] = this.firstPass[rotated.X, rotated.Y];
}
}
this.OnRowProcessed();
});
}
/// <inheritdoc/>
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Cleanup.
this.firstPass.Dispose();
for (int x = startX; x < endX; x++)
{
// Rotate at the centre point
Point rotated = Point.Rotate(new Point(x, y), rotation);
if (this.firstPass.Bounds.Contains(rotated.X, rotated.Y))
{
targetPixels[x, y] = firstPassPixels[rotated.X, rotated.Y];
}
}
this.OnRowProcessed();
});
}
}
}
}

43
src/ImageProcessorCore/Samplers/Processors/SkewProcessor.cs

@ -135,30 +135,27 @@ namespace ImageProcessorCore
// Since we are not working in parallel we use full height and width
// of the first pass image.
Parallel.For(
0,
height,
y =>
{
for (int x = startX; x < endX; x++)
{
// Skew at the centre point
Point skewed = Point.Skew(new Point(x, y), skew);
if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y))
using (PixelAccessor firstPassPixels = this.firstPass.Lock())
using (PixelAccessor targetPixels = target.Lock())
{
Parallel.For(
0,
height,
y =>
{
target[x, y] = this.firstPass[skewed.X, skewed.Y];
}
}
this.OnRowProcessed();
});
}
/// <inheritdoc/>
protected override void AfterApply(ImageBase source, ImageBase target, Rectangle targetRectangle, Rectangle sourceRectangle)
{
// Cleanup.
this.firstPass.Dispose();
for (int x = startX; x < endX; x++)
{
// Skew at the centre point
Point skewed = Point.Skew(new Point(x, y), skew);
if (this.firstPass.Bounds.Contains(skewed.X, skewed.Y))
{
targetPixels[x, y] = firstPassPixels[skewed.X, skewed.Y];
}
}
this.OnRowProcessed();
});
}
}
}
}

7
tests/ImageProcessorCore.Benchmarks/Image/GetSetPixel.cs

@ -23,10 +23,11 @@
[Benchmark(Description = "ImageProcessorCore GetSet Pixel")]
public CoreColor ResizeCore()
{
using (CoreImage image = new CoreImage(400, 400))
CoreImage image = new CoreImage(400, 400);
using (PixelAccessor imagePixels = image.Lock())
{
image[200, 200] = CoreColor.White;
return image[200, 200];
imagePixels[200, 200] = CoreColor.White;
return imagePixels[200, 200];
}
}
}

10
tests/ImageProcessorCore.Benchmarks/Samplers/Crop.cs

@ -4,8 +4,6 @@
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using ImageProcessorCore.Processors;
using CoreImage = ImageProcessorCore.Image;
using CoreSize = ImageProcessorCore.Size;
@ -34,11 +32,9 @@
[Benchmark(Description = "ImageProcessorCore Crop")]
public CoreSize CropResizeCore()
{
using (CoreImage image = new CoreImage(400, 400))
{
image.Crop(100, 100);
return new CoreSize(image.Width, image.Height);
}
CoreImage image = new CoreImage(400, 400);
image.Crop(100, 100);
return new CoreSize(image.Width, image.Height);
}
}
}

10
tests/ImageProcessorCore.Benchmarks/Samplers/Resize.cs

@ -4,8 +4,6 @@
using System.Drawing.Drawing2D;
using BenchmarkDotNet.Attributes;
using ImageProcessorCore.Processors;
using CoreImage = ImageProcessorCore.Image;
using CoreSize = ImageProcessorCore.Size;
@ -34,11 +32,9 @@
[Benchmark(Description = "ImageProcessorCore Resize")]
public CoreSize ResizeCore()
{
using (CoreImage image = new CoreImage(400, 400))
{
image.Resize(100, 100);
return new CoreSize(image.Width, image.Height);
}
CoreImage image = new CoreImage(400, 400);
image.Resize(100, 100);
return new CoreSize(image.Width, image.Height);
}
}
}

24
tests/ImageProcessorCore.Tests/Formats/BitmapTests.cs

@ -27,23 +27,23 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
{
string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 });
}
Image image = new Image(stream);
string encodeFilename = "TestOutput/Encode/Bitmap/" + "24-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel24 });
}
encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
encodeFilename = "TestOutput/Encode/Bitmap/" + "32-" + Path.GetFileNameWithoutExtension(file) + ".bmp";
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 });
}
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output, new BmpEncoder { BitsPerPixel = BmpBitsPerPixel.Pixel32 });
}
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms");
}
}

127
tests/ImageProcessorCore.Tests/Formats/EncoderDecoderTests.cs

@ -30,11 +30,10 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
{
string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt";
File.WriteAllText(filename, image.ToString());
}
Image image = new Image(stream);
string filename = "TestOutput/ToString/" + Path.GetFileNameWithoutExtension(file) + ".txt";
File.WriteAllText(filename, image.ToString());
Trace.WriteLine($"{watch.ElapsedMilliseconds}ms");
}
@ -54,14 +53,13 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
{
string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file);
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output);
}
Image image = new Image(stream);
string encodeFilename = "TestOutput/Encode/" + Path.GetFileName(file);
using (FileStream output = File.OpenWrite(encodeFilename))
{
image.Save(output);
}
Trace.WriteLine($"{file} : {watch.ElapsedMilliseconds}ms");
@ -81,37 +79,32 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}"))
{
IQuantizer quantizer = new OctreeQuantizer();
QuantizedImage quantizedImage = quantizer.Quantize(image, 256);
Image qi = quantizedImage.ToImage();
qi.Save(output, image.CurrentImageFormat);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Octree-{Path.GetFileName(file)}"))
{
using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
}
}
quantizer = new WuQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
quantizer = new WuQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
}
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Wu-{Path.GetFileName(file)}"))
{
quantizedImage.ToImage().Save(output, image.CurrentImageFormat);
}
quantizer = new PaletteQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
quantizer = new PaletteQuantizer();
quantizedImage = quantizer.Quantize(image, 256);
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}"))
{
using (Image qi = quantizedImage.ToImage())
{
qi.Save(output, image.CurrentImageFormat);
}
}
using (FileStream output = File.OpenWrite($"TestOutput/Quantize/Palette-{Path.GetFileName(file)}"))
{
Image qi = quantizedImage.ToImage();
qi.Save(output, image.CurrentImageFormat);
}
}
}
@ -129,27 +122,25 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif"))
{
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.gif"))
{
image.SaveAsGif(output);
}
image.SaveAsGif(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp"))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.bmp"))
{
image.SaveAsBmp(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg"))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.jpg"))
{
image.SaveAsJpeg(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png"))
{
image.SaveAsPng(output);
}
using (FileStream output = File.OpenWrite($"TestOutput/Format/{Path.GetFileNameWithoutExtension(file)}.png"))
{
image.SaveAsPng(output);
}
}
}
@ -167,25 +158,21 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
byte[] serialized;
using (MemoryStream memoryStream = new MemoryStream())
{
byte[] serialized;
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream);
memoryStream.Flush();
serialized = memoryStream.ToArray();
}
image.Save(memoryStream);
memoryStream.Flush();
serialized = memoryStream.ToArray();
}
using (MemoryStream memoryStream = new MemoryStream(serialized))
using (MemoryStream memoryStream = new MemoryStream(serialized))
{
Image image2 = new Image(memoryStream);
using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}"))
{
using (Image image2 = new Image(memoryStream))
{
using (FileStream output = File.OpenWrite($"TestOutput/Serialized/{Path.GetFileName(file)}"))
{
image2.Save(output);
}
}
image2.Save(output);
}
}
}

10
tests/ImageProcessorCore.Tests/Formats/PngTests.cs

@ -25,13 +25,11 @@ namespace ImageProcessorCore.Tests
{
using (FileStream stream = File.OpenRead(file))
{
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png"))
{
using (FileStream output = File.OpenWrite($"TestOutput/Encode/Png/{Path.GetFileNameWithoutExtension(file)}.png"))
{
image.Quality = 256;
image.Save(output, new PngFormat());
}
image.Quality = 256;
image.Save(output, new PngFormat());
}
}
}

16
tests/ImageProcessorCore.Tests/Processors/Filters/FilterTests.cs

@ -70,16 +70,16 @@ namespace ImageProcessorCore.Tests
using (FileStream stream = File.OpenRead(file))
{
Stopwatch watch = Stopwatch.StartNew();
using (Image image = new Image(stream))
Image image = new Image(stream);
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}"))
{
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (FileStream output = File.OpenWrite($"TestOutput/Filter/{Path.GetFileName(filename)}"))
{
processor.OnProgress += this.ProgressUpdate;
image.Process(processor).Save(output);
processor.OnProgress -= this.ProgressUpdate;
}
processor.OnProgress += this.ProgressUpdate;
image.Process(processor).Save(output);
processor.OnProgress -= this.ProgressUpdate;
}
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
}

33
tests/ImageProcessorCore.Tests/Processors/Samplers/SamplerTests.cs

@ -70,7 +70,6 @@
processor.OnProgress -= this.ProgressUpdate;
}
image.Dispose();
Trace.WriteLine($"{ name }: { watch.ElapsedMilliseconds}ms");
}
}
@ -92,7 +91,7 @@
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Pad/{filename}"))
{
image.Pad(image.Width + 50, image.Height + 50, this.ProgressUpdate)
@ -120,7 +119,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 2, image.Height / 2, sampler, false, this.ProgressUpdate)
@ -149,7 +148,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(image.Width / 3, 0, new TriangleResampler(), false, this.ProgressUpdate)
@ -178,7 +177,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileNameWithoutExtension(file) + "-" + name + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Resize/{filename}"))
{
image.Resize(0, image.Height / 3, new TriangleResampler(), false, this.ProgressUpdate)
@ -205,7 +204,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizeCrop/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -237,7 +236,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizePad/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -270,7 +269,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizeBoxPad/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -303,7 +302,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizeMax/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -337,7 +336,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizeMin/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -370,7 +369,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/ResizeStretch/{filename}"))
{
ResizeOptions options = new ResizeOptions()
@ -417,8 +416,6 @@
.Save(output);
}
image.Dispose();
Trace.WriteLine($"{filename}: {watch.ElapsedMilliseconds}ms");
}
}
@ -440,7 +437,7 @@
Stopwatch watch = Stopwatch.StartNew();
string filename = Path.GetFileNameWithoutExtension(file) + "-" + rotateType + flipType + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/RotateFlip/{filename}"))
{
image.RotateFlip(rotateType, flipType, this.ProgressUpdate)
@ -468,7 +465,7 @@
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Rotate/{filename}"))
{
image.Rotate(63, this.ProgressUpdate)
@ -497,7 +494,7 @@
string filename = Path.GetFileName(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Skew/{filename}"))
{
image.Skew(20, 10, this.ProgressUpdate)
@ -523,7 +520,7 @@
{
string filename = Path.GetFileNameWithoutExtension(file) + "-EntropyCrop" + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/EntropyCrop/{filename}"))
{
image.EntropyCrop(.5f, this.ProgressUpdate).Save(output);
@ -547,7 +544,7 @@
string filename = Path.GetFileNameWithoutExtension(file) + "-Crop" + Path.GetExtension(file);
using (Image image = new Image(stream))
Image image = new Image(stream);
using (FileStream output = File.OpenWrite($"TestOutput/Crop/{filename}"))
{
image.Crop(image.Width / 2, image.Height / 2, this.ProgressUpdate).Save(output);

Loading…
Cancel
Save