Browse Source

Refactoring + Convolution filter update

Former-commit-id: db48e9ad325b90b5b2648aaaa05636de05d7e9fb
pull/17/head
James South 12 years ago
parent
commit
77a59c25bf
  1. 0
      src/ImageProcessor.Playground/App.config
  2. 0
      src/ImageProcessor.Playground/ImageProcessor.Playground.csproj
  3. 4
      src/ImageProcessor.Playground/Program.cs
  4. 0
      src/ImageProcessor.Playground/Properties/AssemblyInfo.cs
  5. 0
      src/ImageProcessor.Playground/images/input/120430.gif.REMOVED.git-id
  6. 0
      src/ImageProcessor.Playground/images/input/1aaa.jpg.REMOVED.git-id
  7. 0
      src/ImageProcessor.Playground/images/input/2006-citybus.jpg.REMOVED.git-id
  8. 0
      src/ImageProcessor.Playground/images/input/2008.jpg.REMOVED.git-id
  9. 0
      src/ImageProcessor.Playground/images/input/2012-citybus.jpg.REMOVED.git-id
  10. 0
      src/ImageProcessor.Playground/images/input/4.sm.webp
  11. 0
      src/ImageProcessor.Playground/images/input/Arc-de-Triomphe-France.jpg.REMOVED.git-id
  12. 0
      src/ImageProcessor.Playground/images/input/Bikesgray.png.REMOVED.git-id
  13. 0
      src/ImageProcessor.Playground/images/input/IC580196.jpg.REMOVED.git-id
  14. 0
      src/ImageProcessor.Playground/images/input/Tl4Yb.gif.REMOVED.git-id
  15. 0
      src/ImageProcessor.Playground/images/input/Turtle.jpg
  16. 0
      src/ImageProcessor.Playground/images/input/Valve_original_(1).PNG.REMOVED.git-id
  17. 0
      src/ImageProcessor.Playground/images/input/arc_de_triomphe_paris_france.jpg.REMOVED.git-id
  18. 0
      src/ImageProcessor.Playground/images/input/circle.png
  19. 0
      src/ImageProcessor.Playground/images/input/circle2.png
  20. BIN
      src/ImageProcessor.Playground/images/input/crop-base-300x200.jpg
  21. 0
      src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id
  22. 0
      src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id
  23. 0
      src/ImageProcessor.Playground/images/input/mountain.jpg.REMOVED.git-id
  24. 0
      src/ImageProcessor.Playground/images/input/nLpfllv.gif.REMOVED.git-id
  25. 0
      src/ImageProcessor.Playground/images/input/new-york.jpg.REMOVED.git-id
  26. 0
      src/ImageProcessor.Playground/images/input/night-bridge.png.REMOVED.git-id
  27. 0
      src/ImageProcessor.Playground/images/input/pixel.png3
  28. 0
      src/ImageProcessor.Playground/images/input/rotate.jpg.REMOVED.git-id
  29. 0
      src/ImageProcessor.Playground/images/input/sample1.jpg
  30. 0
      src/ImageProcessor.Playground/images/input/shutterstock_19173982_Arc_de_triomphe-square1.jpg.REMOVED.git-id
  31. 0
      src/ImageProcessor.Playground/images/input/test.jpg.REMOVED.git-id
  32. 0
      src/ImageProcessor.Playground/images/input/test.webp
  33. 0
      src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id
  34. 0
      src/ImageProcessor.Playground/images/input/tower.jpg.REMOVED.git-id
  35. 2
      src/ImageProcessor.sln
  36. 11
      src/ImageProcessor/ImageFactory.cs
  37. 9
      src/ImageProcessor/ImageProcessor.csproj
  38. 183
      src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs
  39. 5
      src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs
  40. 134
      src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs
  41. 23
      src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs
  42. 5
      src/ImageProcessor/Imaging/Filters/EdgeDetection/IEdgeFilter.cs
  43. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/KayyaliEdgeFilter.cs
  44. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/KirschEdgeFilter.cs
  45. 36
      src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3x3Filter.cs
  46. 38
      src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5Filter.cs
  47. 38
      src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianFilter.cs
  48. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/PrewittEdgeFilter.cs
  49. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/RobertsCrossEdgeFilter.cs
  50. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/ScharrEdgeFilter.cs
  51. 2
      src/ImageProcessor/Imaging/Filters/EdgeDetection/SobelEdgeFilter.cs
  52. 149
      src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs
  53. 1
      src/ImageProcessor/Imaging/Formats/FormatUtilities.cs
  54. 2
      src/ImageProcessor/Imaging/MetaData/ExifPropertyTag.cs
  55. 2
      src/ImageProcessor/Imaging/MetaData/ExifPropertyTagType.cs
  56. 2
      src/ImageProcessor/Processors/AutoRotate.cs
  57. 28
      src/ImageProcessor/Processors/DetectEdges.cs
  58. 4
      src/ImageProcessor/Processors/EntropyCrop.cs
  59. 16
      src/ImageProcessor/Processors/Hue.cs
  60. 16
      src/ImageProcessor/Processors/Pixelate.cs
  61. 6
      src/ImageProcessor/Processors/ReplaceColor.cs
  62. 1
      src/ImageProcessor/Settings.StyleCop
  63. 2
      src/ImageProcessor_Mono.sln

0
src/ImageProcessorConsole/App.config → src/ImageProcessor.Playground/App.config

0
src/ImageProcessorConsole/ImageProcessor.Playground.csproj → src/ImageProcessor.Playground/ImageProcessor.Playground.csproj

4
src/ImageProcessorConsole/Program.cs → src/ImageProcessor.Playground/Program.cs

@ -19,6 +19,7 @@ namespace ImageProcessor.PlayGround
using ImageProcessor;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.Filters.EdgeDetection;
using ImageProcessor.Imaging.Filters.Photo;
/// <summary>
@ -76,8 +77,9 @@ namespace ImageProcessor.PlayGround
//.ReplaceColor(Color.FromArgb(255, 1, 107, 165), Color.FromArgb(255, 1, 165, 13), 80)
//.Resize(layer)
//.DetectEdges(new KirschEdgeFilter())
//.DetectEdges(new LaplacianOfGaussianFilter())
//.EntropyCrop()
.Filter(MatrixFilters.Polaroid)
.Filter(MatrixFilters.Comic)
//.Filter(MatrixFilters.Comic)
//.Filter(MatrixFilters.HiSatch)
//.Pixelate(8)

0
src/ImageProcessorConsole/Properties/AssemblyInfo.cs → src/ImageProcessor.Playground/Properties/AssemblyInfo.cs

0
src/ImageProcessorConsole/images/input/120430.gif.REMOVED.git-id → src/ImageProcessor.Playground/images/input/120430.gif.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/1aaa.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/1aaa.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/2006-citybus.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/2006-citybus.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/2008.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/2008.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/2012-citybus.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/2012-citybus.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/4.sm.webp → src/ImageProcessor.Playground/images/input/4.sm.webp

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 20 KiB

0
src/ImageProcessorConsole/images/input/Arc-de-Triomphe-France.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/Arc-de-Triomphe-France.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/Bikesgray.png.REMOVED.git-id → src/ImageProcessor.Playground/images/input/Bikesgray.png.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/IC580196.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/IC580196.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/Tl4Yb.gif.REMOVED.git-id → src/ImageProcessor.Playground/images/input/Tl4Yb.gif.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/Turtle.jpg → src/ImageProcessor.Playground/images/input/Turtle.jpg

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 54 KiB

0
src/ImageProcessorConsole/images/input/Valve_original_(1).PNG.REMOVED.git-id → src/ImageProcessor.Playground/images/input/Valve_original_(1).PNG.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/arc_de_triomphe_paris_france.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/arc_de_triomphe_paris_france.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/circle.png → src/ImageProcessor.Playground/images/input/circle.png

Before

Width:  |  Height:  |  Size: 6.8 KiB

After

Width:  |  Height:  |  Size: 6.8 KiB

0
src/ImageProcessorConsole/images/input/circle2.png → src/ImageProcessor.Playground/images/input/circle2.png

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
src/ImageProcessor.Playground/images/input/crop-base-300x200.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

0
src/ImageProcessorConsole/images/input/monster-whitebg.png2.REMOVED.git-id → src/ImageProcessor.Playground/images/input/monster-whitebg.png2.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/monster.png.REMOVED.git-id → src/ImageProcessor.Playground/images/input/monster.png.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/mountain.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/mountain.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/nLpfllv.gif.REMOVED.git-id → src/ImageProcessor.Playground/images/input/nLpfllv.gif.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/new-york.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/new-york.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/night-bridge.png.REMOVED.git-id → src/ImageProcessor.Playground/images/input/night-bridge.png.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/pixel.png3 → src/ImageProcessor.Playground/images/input/pixel.png3

Before

Width:  |  Height:  |  Size: 166 B

After

Width:  |  Height:  |  Size: 166 B

0
src/ImageProcessorConsole/images/input/rotate.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/rotate.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/sample1.jpg → src/ImageProcessor.Playground/images/input/sample1.jpg

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

0
src/ImageProcessorConsole/images/input/shutterstock_19173982_Arc_de_triomphe-square1.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/shutterstock_19173982_Arc_de_triomphe-square1.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/test.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/test.jpg.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/test.webp → src/ImageProcessor.Playground/images/input/test.webp

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

0
src/ImageProcessorConsole/images/input/tower - Copy.jpg2.REMOVED.git-id → src/ImageProcessor.Playground/images/input/tower - Copy.jpg2.REMOVED.git-id

0
src/ImageProcessorConsole/images/input/tower.jpg.REMOVED.git-id → src/ImageProcessor.Playground/images/input/tower.jpg.REMOVED.git-id

2
src/ImageProcessor.sln

@ -22,7 +22,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web", "Image
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Plugins.WebP", "Plugins\ImageProcessor\ImageProcessor.Plugins.WebP\ImageProcessor.Plugins.WebP.csproj", "{2CF69699-959A-44DC-A281-4E2596C25043}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessorConsole\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessor.Playground\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web.UnitTests", "ImageProcessor.Web.UnitTests\ImageProcessor.Web.UnitTests.csproj", "{961340C8-8C93-401D-A0A2-FF9EC61E5260}"
EndProject

11
src/ImageProcessor/ImageFactory.cs

@ -288,11 +288,20 @@ namespace ImageProcessor
return this;
}
/// <summary>
/// Crops an image to the area of greatest entropy.
/// </summary>
/// <param name="threshold">
/// The threshold in bytes to control the entropy.
/// </param>
/// <returns>
/// The current instance of the <see cref="T:ImageProcessor.ImageFactory"/> class.
/// </returns>
public ImageFactory EntropyCrop(byte threshold = 128)
{
if (this.ShouldProcess)
{
EntropyCrop autoCrop = new EntropyCrop() { DynamicParameter = threshold };
EntropyCrop autoCrop = new EntropyCrop { DynamicParameter = threshold };
this.CurrentImageFormat.ApplyProcessor(autoCrop.ProcessImage, this);
}

9
src/ImageProcessor/ImageProcessor.csproj

@ -76,9 +76,14 @@
<Compile Include="Imaging\Colors\HslaColor.cs" />
<Compile Include="Imaging\Colors\RgbaColor.cs" />
<Compile Include="Imaging\Colors\YCbCrColor.cs" />
<Compile Include="Imaging\Filters\Artistic\OilPaintingFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\ConvolutionFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\I2DEdgeFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\IEdgeFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\KirschEdgeFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\Laplacian5X5Filter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\Laplacian3x3Filter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\LaplacianOfGaussianFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\ScharrEdgeFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\RobertsCrossEdgeFilter.cs" />
<Compile Include="Imaging\Filters\EdgeDetection\PrewittEdgeFilter.cs" />
@ -92,11 +97,11 @@
<Compile Include="Common\Exceptions\ImageFormatException.cs" />
<Compile Include="ImageFactory.cs" />
<Compile Include="Imaging\AnchorPosition.cs" />
<Compile Include="Imaging\ExifPropertyTagType.cs" />
<Compile Include="Imaging\MetaData\ExifPropertyTagType.cs" />
<Compile Include="Imaging\Convolution.cs" />
<Compile Include="Imaging\CropLayer.cs" />
<Compile Include="Imaging\CropMode.cs" />
<Compile Include="Imaging\ExifPropertyTag.cs" />
<Compile Include="Imaging\MetaData\ExifPropertyTag.cs" />
<Compile Include="Imaging\Filters\Photo\MatrixFilterBase.cs" />
<Compile Include="Imaging\Filters\Photo\MatrixFilters.cs" />
<Compile Include="Imaging\Formats\BitmapFormat.cs" />

183
src/ImageProcessor/Imaging/Filters/Artistic/OilPaintingFilter.cs

@ -0,0 +1,183 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="OilPaintingFilter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The oil painting filter.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.Artistic
{
using System;
using System.Drawing;
using ImageProcessor.Common.Extensions;
/// <summary>
/// The oil painting filter.
/// </summary>
public class OilPaintingFilter
{
/// <summary>
/// The levels.
/// </summary>
private int levels;
/// <summary>
/// The brush size.
/// </summary>
private int brushSize;
/// <summary>
/// Initializes a new instance of the <see cref="OilPaintingFilter"/> class.
/// </summary>
/// <param name="levels">
/// The number of levels.
/// </param>
/// <param name="brushSize">
/// The brush size.
/// </param>
public OilPaintingFilter(int levels, int brushSize)
{
this.levels = levels;
this.brushSize = brushSize;
}
/// <summary>
/// Gets or sets the number of levels.
/// </summary>
public int Levels
{
get
{
return this.levels;
}
set
{
if (value > 0)
{
this.levels = value;
}
}
}
/// <summary>
/// Gets or sets the brush size.
/// </summary>
public int BrushSize
{
get
{
return this.brushSize;
}
set
{
if (value > 0)
{
this.brushSize = value;
}
}
}
/// <summary>
/// Applies the filter. TODO: Make this class implement an interface?
/// </summary>
/// <param name="source">
/// The source.
/// </param>
/// <returns>
/// The <see cref="Bitmap"/>.
/// </returns>
public Bitmap ApplyFilter(Bitmap source)
{
int width = source.Width;
int height = source.Height;
int radius = this.brushSize >> 1;
Bitmap destination = new Bitmap(width, height);
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int maxIntensity = 0;
int maxIndex = 0;
int[] intensityBin = new int[this.levels];
int[] blueBin = new int[this.levels];
int[] greenBin = new int[this.levels];
int[] redBin = new int[this.levels];
for (int i = 0; i <= radius; i++)
{
int ir = i - radius;
int offsetY = y + ir;
// Skip the current row
if (offsetY < 0)
{
continue;
}
// Outwith the current bounds so break.
if (offsetY >= height)
{
break;
}
for (int fx = 0; fx <= radius; fx++)
{
int jr = fx - radius;
int offsetX = x + jr;
// Skip the column
if (offsetX < 0)
{
continue;
}
if (offsetX < width)
{
Color color = sourceBitmap.GetPixel(offsetX, offsetY);
byte sourceBlue = color.B;
byte sourceGreen = color.G;
byte sourceRed = color.R;
int currentIntensity = (int)Math.Round(((sourceBlue + sourceGreen + sourceRed) / 3.0 * (this.levels - 1)) / 255.0);
intensityBin[currentIntensity] += 1;
blueBin[currentIntensity] += sourceBlue;
greenBin[currentIntensity] += sourceGreen;
redBin[currentIntensity] += sourceRed;
if (intensityBin[currentIntensity] > maxIntensity)
{
maxIntensity = intensityBin[currentIntensity];
maxIndex = currentIntensity;
}
}
}
}
byte blue = Math.Abs(blueBin[maxIndex] / maxIntensity).ToByte();
byte green = Math.Abs(greenBin[maxIndex] / maxIntensity).ToByte();
byte red = Math.Abs(redBin[maxIndex] / maxIntensity).ToByte();
destinationBitmap.SetPixel(x, y, Color.FromArgb(red, green, blue));
}
}
}
}
return destination;
}
}
}

5
src/ImageProcessor/Imaging/Filters/Binarization/BinaryThreshold.cs

@ -61,10 +61,9 @@ namespace ImageProcessor.Imaging.Filters.Binarization
using (FastBitmap sourceBitmap = new FastBitmap(source))
{
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
Color color = sourceBitmap.GetPixel(x, y);
sourceBitmap.SetPixel(x, y, color.B >= this.threshold ? Color.White : Color.Black);

134
src/ImageProcessor/Imaging/Filters/EdgeDetection/ConvolutionFilter.cs

@ -83,7 +83,137 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
try
{
double[,] horizontalFilter = this.edgeFilter.HorizontalGradientOperator;
double[,] verticalFilter = this.edgeFilter.VerticalGradientOperator;
int kernelLength = horizontalFilter.GetLength(0);
int radius = kernelLength >> 1;
using (FastBitmap sourceBitmap = new FastBitmap(input))
{
using (FastBitmap destinationBitmap = new FastBitmap(destination))
{
// Loop through the pixels.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
double rX = 0;
double gX = 0;
double bX = 0;
// Apply each matrix multiplier to the color components for each pixel.
for (int fy = 0; fy < kernelLength; fy++)
{
int fyr = fy - radius;
int offsetY = y + fyr;
// Skip the current row
if (offsetY < 0)
{
continue;
}
// Outwith the current bounds so break.
if (offsetY >= height)
{
break;
}
for (int fx = 0; fx < kernelLength; fx++)
{
int fxr = fx - radius;
int offsetX = x + fxr;
// Skip the column
if (offsetX < 0)
{
continue;
}
if (offsetX < width)
{
Color currentColor = sourceBitmap.GetPixel(offsetX, offsetY);
double r = currentColor.R;
double g = currentColor.G;
double b = currentColor.B;
rX += horizontalFilter[fy, fx] * r;
gX += horizontalFilter[fy, fx] * g;
bX += horizontalFilter[fy, fx] * b;
}
}
}
// Apply the equation and sanitize.
byte red = rX.ToByte();
byte green = gX.ToByte();
byte blue = bX.ToByte();
Color newColor = Color.FromArgb(red, green, blue);
destinationBitmap.SetPixel(x, y, newColor);
}
}
}
}
}
finally
{
// We created a new image. Cleanup.
input.Dispose();
}
// Draw a black rectangle around the area to ensure that the first row/column in covered.
using (Graphics graphics = Graphics.FromImage(destination))
{
// Draw an edge around the image.
using (Pen blackPen = new Pen(Color.Black))
{
blackPen.Width = 4;
graphics.DrawRectangle(blackPen, new Rectangle(0, 0, destination.Width, destination.Height));
}
}
return destination;
}
/// <summary>
/// Processes the given bitmap to apply the current instances <see cref="IEdgeFilter"/>.
/// </summary>
/// <param name="source">The image to process.</param>
/// <returns>A processed bitmap.</returns>
public Bitmap Process2DFilter(Bitmap source)
{
int width = source.Width;
int height = source.Height;
Bitmap destination = new Bitmap(width, height, PixelFormat.Format32bppArgb);
Bitmap input = new Bitmap(width, height, PixelFormat.Format32bppArgb);
using (Graphics graphics = Graphics.FromImage(input))
{
Rectangle rectangle = new Rectangle(0, 0, width, height);
if (this.greyscale)
{
// If it's greyscale apply a colormatrix to the image.
using (ImageAttributes attributes = new ImageAttributes())
{
attributes.SetColorMatrix(ColorMatrixes.GreyScale);
graphics.DrawImage(source, rectangle, 0, 0, width, height, GraphicsUnit.Pixel, attributes);
}
}
else
{
// Fixes an issue with transparency not converting properly.
graphics.Clear(Color.Transparent);
graphics.DrawImage(source, rectangle);
}
}
try
{
double[,] horizontalFilter = this.edgeFilter.HorizontalGradientOperator;
double[,] verticalFilter = ((I2DEdgeFilter)this.edgeFilter).VerticalGradientOperator;
int kernelLength = horizontalFilter.GetLength(0);
int radius = kernelLength >> 1;
@ -176,7 +306,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
// Draw an edge around the image.
using (Pen blackPen = new Pen(Color.Black))
{
blackPen.Width = 1;
blackPen.Width = 4;
graphics.DrawRectangle(blackPen, new Rectangle(0, 0, destination.Width, destination.Height));
}
}

23
src/ImageProcessor/Imaging/Filters/EdgeDetection/I2DEdgeFilter.cs

@ -0,0 +1,23 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="IEdgeFilter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// Describes properties for creating 2 dimension edge detection filters.
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.EdgeDetection
{
/// <summary>
/// Describes properties for creating 2 dimension edge detection filters.
/// </summary>
public interface I2DEdgeFilter : IEdgeFilter
{
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
double[,] VerticalGradientOperator { get; }
}
}

5
src/ImageProcessor/Imaging/Filters/EdgeDetection/IEdgeFilter.cs

@ -19,10 +19,5 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// Gets the horizontal gradient operator.
/// </summary>
double[,] HorizontalGradientOperator { get; }
/// <summary>
/// Gets the vertical gradient operator.
/// </summary>
double[,] VerticalGradientOperator { get; }
}
}

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/KayyaliEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Kayyali operator filter.
/// <see href="http://edgedetection.webs.com/"/>
/// </summary>
public class KayyaliEdgeFilter : IEdgeFilter
public class KayyaliEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/KirschEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Kirsch operator filter.
/// <see href="http://en.wikipedia.org/wiki/Kirsch_operator"/>
/// </summary>
public class KirschEdgeFilter : IEdgeFilter
public class KirschEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

36
src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian3x3Filter.cs

@ -0,0 +1,36 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Laplacian3x3Filter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Laplacian 3 x 3 operator filter.
// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.EdgeDetection
{
/// <summary>
/// The Laplacian 3 x 3 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public class Laplacian3X3Filter : IEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public double[,] HorizontalGradientOperator
{
get
{
return new double[,]
{
{ -1, -1, -1 },
{ -1, 8, -1 },
{ -1, -1, -1 }
};
}
}
}
}

38
src/ImageProcessor/Imaging/Filters/EdgeDetection/Laplacian5X5Filter.cs

@ -0,0 +1,38 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Laplacian5X5Filter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Laplacian 5 x 5 operator filter.
// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.EdgeDetection
{
/// <summary>
/// The Laplacian 5 x 5 operator filter.
/// <see href="http://en.wikipedia.org/wiki/Discrete_Laplace_operator"/>
/// </summary>
public class Laplacian5X5Filter : IEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public double[,] HorizontalGradientOperator
{
get
{
return new double[,]
{
{ -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1 },
{ -1, -1, 24, -1, -1 },
{ -1, -1, -1, -1, -1 },
{ -1, -1, -1, -1, -1 }
};
}
}
}
}

38
src/ImageProcessor/Imaging/Filters/EdgeDetection/LaplacianOfGaussianFilter.cs

@ -0,0 +1,38 @@
// --------------------------------------------------------------------------------------------------------------------
// <copyright file="LaplacianOfGaussianFilter.cs" company="James South">
// Copyright (c) James South.
// Licensed under the Apache License, Version 2.0.
// </copyright>
// <summary>
// The Laplacian of Gaussian operator filter.
// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node9.html" />
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging.Filters.EdgeDetection
{
/// <summary>
/// The Laplacian of Gaussian operator filter.
/// <see href="http://fourier.eng.hmc.edu/e161/lectures/gradient/node9.html"/>
/// </summary>
public class LaplacianOfGaussianFilter : IEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.
/// </summary>
public double[,] HorizontalGradientOperator
{
get
{
return new double[,]
{
{ 0, 0, -1, 0, 0 },
{ 0, -1, -2, -1, 0 },
{ -1, -2, 16, -2, -1 },
{ 0, -1, -2, -1, 0 },
{ 0, 0, -1, 0, 0 }
};
}
}
}
}

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/PrewittEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Prewitt operator filter.
/// <see href="http://en.wikipedia.org/wiki/Prewitt_operator"/>
/// </summary>
public class PrewittEdgeFilter : IEdgeFilter
public class PrewittEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/RobertsCrossEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Roberts Cross operator filter.
/// <see href="http://en.wikipedia.org/wiki/Roberts_cross"/>
/// </summary>
public class RobertsCrossEdgeFilter : IEdgeFilter
public class RobertsCrossEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/ScharrEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Scharr operator filter.
/// <see href="http://en.wikipedia.org/wiki/Sobel_operator#Alternative_operators"/>
/// </summary>
public class ScharrEdgeFilter : IEdgeFilter
public class ScharrEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

2
src/ImageProcessor/Imaging/Filters/EdgeDetection/SobelEdgeFilter.cs

@ -15,7 +15,7 @@ namespace ImageProcessor.Imaging.Filters.EdgeDetection
/// The Sobel operator filter.
/// <see href="http://en.wikipedia.org/wiki/Sobel_operator"/>
/// </summary>
public class SobelEdgeFilter : IEdgeFilter
public class SobelEdgeFilter : I2DEdgeFilter
{
/// <summary>
/// Gets the horizontal gradient operator.

149
src/ImageProcessor/Imaging/Filters/Photo/ComicMatrixFilter.cs

@ -10,7 +10,6 @@
namespace ImageProcessor.Imaging.Filters.Photo
{
#region Using
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
@ -18,8 +17,7 @@ namespace ImageProcessor.Imaging.Filters.Photo
using System.Runtime.InteropServices;
using ImageProcessor.Common.Extensions;
#endregion
using ImageProcessor.Imaging.Filters.Artistic;
/// <summary>
/// Encapsulates methods with which to add a comic filter to an image.
@ -62,8 +60,8 @@ namespace ImageProcessor.Imaging.Filters.Photo
highBitmap = new Bitmap(rectangle.Width, rectangle.Height, PixelFormat.Format32bppPArgb);
// Apply a oil painting filter to the image.
highBitmap = OilPaintFilter((Bitmap)image, 3, 5);
highBitmap = new OilPaintingFilter(3, 5).ApplyFilter((Bitmap)image);
// Draw the edges.
edgeBitmap = DrawEdges((Bitmap)image, 120);
@ -109,7 +107,7 @@ namespace ImageProcessor.Imaging.Filters.Photo
using (Graphics graphics = Graphics.FromImage(newImage))
{
// graphics.Clear(Color.Transparent);
graphics.Clear(Color.Transparent);
// Overlay the image.
graphics.DrawImage(highBitmap, 0, 0);
@ -166,135 +164,6 @@ namespace ImageProcessor.Imaging.Filters.Photo
return image;
}
/// <summary>
/// Applies an oil paint filter.
/// TODO: Move this to another class and add to the factory
/// </summary>
/// <param name="sourceBitmap">
/// The source bitmap.
/// </param>
/// <param name="levels">
/// The levels.
/// </param>
/// <param name="filterSize">
/// The filter size.
/// </param>
/// <returns>
/// The <see cref="Bitmap"/>.
/// </returns>
private static Bitmap OilPaintFilter(Bitmap sourceBitmap, int levels, int filterSize)
{
int width = sourceBitmap.Width;
int height = sourceBitmap.Height;
BitmapData sourceData = sourceBitmap.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb);
int strideWidth = sourceData.Stride;
int scanHeight = sourceData.Height;
int bufferSize = strideWidth * scanHeight;
byte[] pixelBuffer = new byte[bufferSize];
byte[] resultBuffer = new byte[bufferSize];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
levels = levels - 1;
int radius = filterSize >> 1;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
int maxIntensity = 0;
int maxIndex = 0;
int[] intensityBin = new int[levels + 1];
int[] blueBin = new int[levels + 1];
int[] greenBin = new int[levels + 1];
int[] redBin = new int[levels + 1];
int byteOffset = (y * strideWidth) + (x * 4);
for (int i = 0; i <= radius; i++)
{
int ir = i - radius;
int offsetY = y + ir;
// Skip the current row
if (offsetY < 0)
{
continue;
}
// Outwith the current bounds so break.
if (offsetY >= height)
{
break;
}
for (int j = 0; j <= radius; j++)
{
int jr = j - radius;
int offsetX = x + jr;
// Skip the column
if (offsetX < 0)
{
continue;
}
if (offsetX < width)
{
int calcOffset = (offsetX * 4) + (offsetY * sourceData.Stride);
byte sourceBlue = pixelBuffer[calcOffset];
byte sourceGreen = pixelBuffer[calcOffset + 1];
byte sourceRed = pixelBuffer[calcOffset + 2];
int currentIntensity = (int)Math.Round(((sourceBlue + sourceGreen + sourceRed) / 3.0 * levels) / 255.0);
intensityBin[currentIntensity] += 1;
blueBin[currentIntensity] += sourceBlue;
greenBin[currentIntensity] += sourceGreen;
redBin[currentIntensity] += sourceRed;
if (intensityBin[currentIntensity] > maxIntensity)
{
maxIntensity = intensityBin[currentIntensity];
maxIndex = currentIntensity;
}
}
}
}
double blue = Math.Abs(blueBin[maxIndex] / maxIntensity);
double green = Math.Abs(greenBin[maxIndex] / maxIntensity);
double red = Math.Abs(redBin[maxIndex] / maxIntensity);
resultBuffer[byteOffset] = blue.ToByte();
resultBuffer[byteOffset + 1] = green.ToByte();
resultBuffer[byteOffset + 2] = red.ToByte();
resultBuffer[byteOffset + 3] = pixelBuffer[byteOffset + 3];
}
}
Bitmap resultBitmap = new Bitmap(width, height);
BitmapData resultData = resultBitmap.LockBits(
new Rectangle(0, 0, width, height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
return resultBitmap;
}
/// <summary>
/// Detects and draws edges.
/// TODO: Move this to another class and do edge detection.
@ -514,16 +383,16 @@ namespace ImageProcessor.Imaging.Filters.Photo
int width = source.Width;
int height = source.Height;
for (int i = 0; i < width; i++)
for (int y = 0; y < height; y++)
{
for (int j = 0; j < height; j++)
for (int x = 0; x < width; x++)
{
Color sourceColor = sourceBitmap.GetPixel(i, j);
Color destinationColor = destinationBitmap.GetPixel(i, j);
Color sourceColor = sourceBitmap.GetPixel(x, y);
Color destinationColor = destinationBitmap.GetPixel(x, y);
if (destinationColor.A != 0)
{
destinationBitmap.SetPixel(i, j, Color.FromArgb(sourceColor.B, destinationColor.R, destinationColor.G, destinationColor.B));
destinationBitmap.SetPixel(x, y, Color.FromArgb(sourceColor.B, destinationColor.R, destinationColor.G, destinationColor.B));
}
}
}

1
src/ImageProcessor/Imaging/Formats/FormatUtilities.cs

@ -19,6 +19,7 @@ namespace ImageProcessor.Imaging.Formats
using System.Reflection;
using ImageProcessor.Configuration;
using ImageProcessor.Imaging.MetaData;
/// <summary>
/// Utility methods for working with supported image formats.

2
src/ImageProcessor/Imaging/ExifPropertyTag.cs → src/ImageProcessor/Imaging/MetaData/ExifPropertyTag.cs

@ -8,7 +8,7 @@
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
namespace ImageProcessor.Imaging.MetaData
{
/// <summary>
/// The following enum gives descriptions of the property items supported by Windows GDI+.

2
src/ImageProcessor/Imaging/ExifPropertyTagType.cs → src/ImageProcessor/Imaging/MetaData/ExifPropertyTagType.cs

@ -9,7 +9,7 @@
// </summary>
// --------------------------------------------------------------------------------------------------------------------
namespace ImageProcessor.Imaging
namespace ImageProcessor.Imaging.MetaData
{
/// <summary>
/// Specifies the data type of the values stored in the value data member of that same PropertyItem object.

2
src/ImageProcessor/Processors/AutoRotate.cs

@ -16,7 +16,7 @@ namespace ImageProcessor.Processors
using System.Drawing;
using ImageProcessor.Common.Exceptions;
using ImageProcessor.Imaging;
using ImageProcessor.Imaging.MetaData;
/// <summary>
/// Performs auto-rotation to ensure that EXIF defined rotation is reflected in

28
src/ImageProcessor/Processors/DetectEdges.cs

@ -66,23 +66,27 @@ namespace ImageProcessor.Processors
IEdgeFilter filter = parameters.Item1;
bool greyscale = parameters.Item2;
//try
//{
try
{
ConvolutionFilter convolutionFilter = new ConvolutionFilter(filter, greyscale);
newImage = convolutionFilter.ProcessFilter((Bitmap)image);
// Check and assign the correct method. Don't use reflection for speed.
newImage = filter is I2DEdgeFilter
? convolutionFilter.Process2DFilter((Bitmap)image)
: convolutionFilter.ProcessFilter((Bitmap)image);
image.Dispose();
image = newImage;
//}
//catch (Exception ex)
//{
// if (newImage != null)
// {
// newImage.Dispose();
// }
}
catch (Exception ex)
{
if (newImage != null)
{
newImage.Dispose();
}
// throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
//}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}

4
src/ImageProcessor/Processors/EntropyCrop.cs

@ -18,7 +18,7 @@ namespace ImageProcessor.Processors
using ImageProcessor.Imaging.Filters.EdgeDetection;
/// <summary>
/// The auto crop.
/// Performs a crop on an image to the area of greatest entropy.
/// </summary>
public class EntropyCrop : IGraphicsProcessor
{
@ -194,7 +194,7 @@ namespace ImageProcessor.Processors
stopX = getMaxX(fastBitmap);
}
return new Rectangle(startX, startY, stopX - startX + 1, stopY - startY + 1);
return new Rectangle(startX + 1, startY + 1, stopX - (startX + 1), stopY - (startY + 1));
}
}
}

16
src/ImageProcessor/Processors/Hue.cs

@ -69,25 +69,25 @@ namespace ImageProcessor.Processors
{
if (!rotate)
{
for (int i = 0; i < width; i++)
for (int y = 0; y < height; y++)
{
for (int j = 0; j < height; j++)
for (int x = 0; x < width; x++)
{
HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(i, j));
HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(x, y));
HslaColor altered = HslaColor.FromHslaColor(degrees / 360f, original.S, original.L, original.A);
fastBitmap.SetPixel(i, j, altered);
fastBitmap.SetPixel(x, y, altered);
}
}
}
else
{
for (int i = 0; i < width; i++)
for (int y = 0; y < height; y++)
{
for (int j = 0; j < height; j++)
for (int x = 0; x < width; x++)
{
HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(i, j));
HslaColor original = HslaColor.FromColor(fastBitmap.GetPixel(x, y));
HslaColor altered = HslaColor.FromHslaColor((original.H + (degrees / 360f)) % 1, original.S, original.L, original.A);
fastBitmap.SetPixel(i, j, altered);
fastBitmap.SetPixel(x, y, altered);
}
}
}

16
src/ImageProcessor/Processors/Pixelate.cs

@ -82,31 +82,31 @@ namespace ImageProcessor.Processors
using (FastBitmap fastBitmap = new FastBitmap(newImage))
{
for (int i = x; i < x + width && i < maxWidth; i += size)
for (int j = y; j < y + height && j < maxHeight; j += size)
{
for (int j = y; j < y + height && j < maxHeight; j += size)
for (int i = x; i < x + width && i < maxWidth; i += size)
{
int offsetX = offset;
int offsetY = offset;
// Make sure that the offset is within the boundary of the image.
while (i + offsetX >= maxWidth)
while (j + offsetY >= maxHeight)
{
offsetX--;
offsetY--;
}
while (j + offsetY >= maxHeight)
while (i + offsetX >= maxWidth)
{
offsetY--;
offsetX--;
}
// Get the pixel color in the centre of the soon to be pixelated area.
Color pixel = fastBitmap.GetPixel(i + offsetX, j + offsetY);
// For each pixel in the pixelate size, set it to the centre color.
for (int k = i; k < i + size && k < maxWidth; k++)
for (int l = j; l < j + size && l < maxHeight; l++)
{
for (int l = j; l < j + size && l < maxHeight; l++)
for (int k = i; k < i + size && k < maxWidth; k++)
{
fastBitmap.SetPixel(k, l, pixel);
}

6
src/ImageProcessor/Processors/ReplaceColor.cs

@ -85,9 +85,9 @@ namespace ImageProcessor.Processors
using (FastBitmap fastBitmap = new FastBitmap(newImage))
{
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
for (int y = 0; y < height; y++)
for (int x = 0; x < width; x++)
{
// Get the pixel color.
Color currentColor = fastBitmap.GetPixel(x, y);
@ -103,7 +103,7 @@ namespace ImageProcessor.Processors
{
if (currentB <= originalB + fuzziness && currentB >= originalB - fuzziness)
{
// Ensure the values are withing an acceptable byte range
// Ensure the values are within an acceptable byte range
// and set the new value.
byte r = (originalR - currentR + replacementR).ToByte();
byte g = (originalG - currentG + replacementG).ToByte();

1
src/ImageProcessor/Settings.StyleCop

@ -7,6 +7,7 @@
<Value>dllimport</Value>
<Value>gps</Value>
<Value>Kayyali</Value>
<Value>Laplacian</Value>
<Value>mmmm</Value>
<Value>orig</Value>
<Value>Scharr</Value>

2
src/ImageProcessor_Mono.sln

@ -16,7 +16,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web_NET4", "
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Web_NET45", "ImageProcessor.Web\NET45\ImageProcessor.Web_NET45.csproj", "{D011A778-59C8-4BFA-A770-C350216BF161}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessorConsole", "ImageProcessorConsole\ImageProcessorConsole.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.Playground", "ImageProcessor.Playground\ImageProcessor.Playground.csproj", "{7BF5274B-56A7-4B62-8105-E9BDF25BAFE7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ImageProcessor.UnitTests", "ImageProcessor.UnitTests\ImageProcessor.UnitTests.csproj", "{03CA9055-F997-428C-BF28-F50F991777C6}"
EndProject

Loading…
Cancel
Save