C# How to: Stained Glass Image Filter

Article Purpose

This article serves to provides a detailed discussion and implementation of a Stained Glass Image Filter. Primary topics explored include: Creating , Pixel Coordinate distance calculations implementing , and methods. In addition, this article explores Gradient Based implementing thresholds.

Zurich: Block Size 15, Factor 4, Euclidean

Zurich Block Size 15 Factor 4 Euclidean

Sample Source Code

This article is accompanied by a sample source code Visual Studio project which is available for download .

Using the Sample Application

This article’s accompanying sample source code includes a based sample application. The sample application provides an implementation of the concepts explored by this article. Concepts discussed can be easily replicated and tested by using the sample application.

Source/input files can be specified from the local system when clicking the Load Image button. Additionally users also have the option to save resulting filtered by clicking the Save Image button.

The sample application through its user interface allows a user to specify several filter configuration options. Two main categories of configuration options have been defined as Block Properties and Edge Properties.

Block Properties relate to  the process of rendering  . The following configuration options have been implemented:

  • Block Size – During the process of rendering a regions or blocks of equal shape and size have to be defined. These uniform regions/blocks form the basis of rendering uniquely shaped regions later on. The Block Size option determines the width and height of an individual region/block. Larger values result in larger non-uniform regions being rendered. Smaller values in return result in smaller non-uniform regions being rendered.
  • Distance Factor – The Distance Factor option determines the extent to which a pixel’s containing region will be calculated. Possible values range from 1 to 4 inclusive. A Distance Factor value of 4 equates to precise calculation of a pixel’s containing region, whereas a value of 1 results in containing regions often registering pixels that should be part of a neighbouring region. Values closer to 4 result in more varied region shapes. Values closer to 1 result in regions being rendered having more of a uniform shape/pattern.
  • Distance Formula – The distance between a pixel’s coordinates and a region’s outline determines whether that pixel should be considered part of a region. The sample application implements three different methods of calculating pixel distance: , and methods. Each result in region shapes being rendered differently.

Salzburg: Block Size 20, Factor 1, Chebyshev, Edge Threshold 2 

Saltzburg Block Size 20 Factor 1 Chebyshev Edge Threshold 2

Edge Properties relate to the implementation of Image Gradient Based Edge Detection. is an optional filter and can be enabled/disabled through the user interface, The implementation of serves to highlight/outline regions rendered as part of a . The configuration options implemented are:

  • Highlight Edges – Boolean value indicating whether or not should be applied
  • Threshold – In calculating a threshold value determines if a pixel forms part of an edge. Higher threshold values result in less being expressed. Lower threshold values result in more being expressed.
  • Colour – If a pixel has been determined as forming part of an , the resulting pixel colour will be determined by the colour value specified by the user.

The following image is a screenshot of the Stained Glass Image Filter sample application in action:

Stained Glass Image Filter Sample Application 

Locarno: Block Size 10, Factor 4, Euclidean

Locarno Block Size 10 Factor 4 Euclidean

Stained Glass

The Stained Glass Image Filter detailed in this article operates on the basis of implementing modifications upon a specified sample/input , producing resulting which resemble the appearance of stained glass artwork.

A common variant of stained glass artwork comes in the form of several individual pieces of coloured glass being combined in order to create an . The sample source code employs a similar  method of combining what appears to be non-uniform puzzle pieces. The following list provides a broad overview of the steps involved in applying a Stained Glass Image Filter:

  1. Render a Voronoi Diagram – Through rendering a the resulting will be divided into a number of regions. Each region being intended to represent an individual glass puzzle piece. The following section of this article provides a detailed discussion on rendering .
  2. Assign each Pixel to a Voronoi Diagram Region – Each pixel forming part of the source/input should be iterated. Whilst iterating pixels determine the region to which a pixel should be associated. A pixel should be associated to the region whose border has been determined the nearest to the pixel. In a following section of this article a detailed discussion regarding Pixel Coordinate Distance Calculations can be found.
  3. Determine each Region’s Colour Mean – Each region will only express a single colour value. A region’s colour equates to the average colour as expressed by all the pixels forming part of a region. Once the average colour value of a region has been determined every pixel forming part of that region should be set to the average colour.
  4. Implement Edge Detection – If the user configuration option indicates that should be implemented, apply Gradient Based Edge Detection. This method of has been discussed in detailed in a following section of this article.

Bad Ragaz: Block Size 10, Factor 1, Manhattan 

Bad Ragaz Block Size 10 Factor 1 Manhattan

Voronoi Diagrams

represent a fairly uncomplicated concept. In contrast, the implementation of prove somewhat more of a challenge. From we gain the following :

In mathematics, a Voronoi diagram is a way of dividing space into a number of regions. A set of points (called seeds, sites, or generators) is specified beforehand and for each seed there will be a corresponding region consisting of all points closer to that seed than to any other. The regions are called Voronoi cells. It is dual to the Delaunay triangulation.

In this article are generated resulting in regions expressing random shapes. Although region shapes are randomly generated, the parameters or ranges within which random values are selected are fixed/constant. The steps required in generating a can be detailed as follows:

  1. Define fixed size square regions – By making use of the user specified Block/Region Size value, group pixels together into square regions.
  2. Determine a Seed Value for Random number generation – Determine the sum total of pixel colour components of all the pixels forming part of a square region. The colour sum total value should be used as a seed value when generating random numbers in the next step.
  3. Determine a Random XY coordinate within each square region – Generate two random numbers, specifying each region’s coordinate boundaries as minimum and maximum boundaries in generating random numbers. Keep record of every new randomly generated XY-Coordinate value.
  4. Associate Pixels and Regions – A pixel should be associated to the Random Coordinate point nearest to that pixel. Determine the Random Coordinate nearest to each pixel in the source/input image. The method implemented in calculating coordinate distance depends on the configuration value specified by the user.
  5. Set Region Colours – Each pixel forming part of the same region should be set to the same colour. The colour assigned to a region’s pixels will be determined by the average colour value of the region’s pixels.

The following image illustrates an example consisting of 10 regions:

2Ddim-L2norm-10site

Port Edward: Block Size 10, Factor 1, Chebyshev, Edge Threshold 2

Port Edward Block Size 10 Factor 1 Chebyshev Edge Threshold 2

Calculating Pixel Coordinate Distances

The sample source code provides three different coordinate distance calculation methods. The supported methods are: , and . A pixel’s nearest randomly generated coordinate depends on the distance between that pixel and the random coordinate. Each method of calculating distance in most instances would be likely to produce different output values, which in turn influences the region to which a pixel will be associated.

The most common method of distance calculation, , has been described by as follows:

In mathematics, the Euclidean distance or Euclidean metric is the "ordinary" distance between two points that one would measure with a ruler, and is given by the Pythagorean formula. By using this formula as distance, Euclidean space (or even any inner product space) becomes a metric space. The associated norm is called the Euclidean norm. Older literature refers to the metric as Pythagorean metric.

When calculating the algorithm implemented can be expressed as follows:

Euclidean Distance Algorithm

Zurich: Block Size 10, Factor 1, Euclidean

Zurich Block Size 10 Factor 1 Euclidean

As an alternative to calculating , the sample source code also implements calculation. Often calculation will be referred to as , or . From we gain the following :

Taxicab geometry, considered by Hermann Minkowski in the 19th century, is a form of geometry in which the usual distance function or metric of Euclidean geometry is replaced by a new metric in which the distance between two points is the sum of the absolute differences of their coordinates. The taxicab metric is also known as rectilinear distance, L1 distance or \ell_1 norm (see Lp space), city block distance, Manhattan distance, or Manhattan length, with corresponding variations in the name of the geometry.[1] The latter names allude to the grid layout of most streets on the island of Manhattan, which causes the shortest path a car could take between two intersections in the borough to have length equal to the intersections’ distance in taxicab geometry

When calculating the algorithm implemented can be expressed as follows:

Manhattan Distance Algorithm

Port Edward: Block Size 10, Factor 4, Euclidean

Port Edward Block Size 10 Factor 4 Euclidean

, a distance algorithm resembling the way in which a King Chess piece may move on a chess board. The following we gain from :

In mathematics, Chebyshev distance (or Tchebychev distance), Maximum metric, or L∞ metric[1] is a metric defined on a vector space where the distance between two vectors is the greatest of their differences along any coordinate dimension.[2] It is named after Pafnuty Chebyshev.

It is also known as chessboard distance, since in the game of chess the minimum number of moves needed by a king to go from one square on a chessboard to another equals the Chebyshev distance between the centers of the squares, if the squares have side length one, as represented in 2-D spatial coordinates with axes aligned to the edges of the board.[3] For example, the Chebyshev distance between f6 and e2 equals 4.

When calculating the algorithm implemented can be expressed as follows:

Chebyshev Distance Algorithm

Salzburg: Block Size 20, Factor 1, Chebyshev

Salzburg Block Size 20 Factor 1 Chebyshev

Gradient Based Edge Detection

Various methods of can easily be implemented in C#. Each method of provides a set of benefits, usually weighed against a set of trade-offs. In this article and the accompanying sample source code the Gradient Based Edge Detection method has been implement.

Take into regard that every region within the rendered will only express a single colour, although most regions differ in the single colour they express. Once all pixels have been associated to a region and all pixel colour values have been updated the resulting defines mostly clearly distinguishable  colour gradients. A method of performs efficiently at detecting . The edges detected are defined between different regions.

An can be considered as a difference in colour intensity relating to a specific direction. Only once all tasks related to applying the Stained Glass Filter have been completed should the Gradient Based Edge Detection be applied. The steps involved in applying Gradient Based Edge Detection can be described as follows:

  1. Iterate each pixel – Each pixel forming part of a source/input image should be iterated.
  2. Determine Horizontal and Vertical Gradients – Calculate the colour value difference between the currently iterated pixel’s left and right neighbour pixel as well as the top and bottom neighbour pixel. If the gradient exceeds the specified threshold continue to step 8.
  3. Determine Horizontal Gradient – Calculate the colour value difference between the currently iterated pixel’s left and right neighbour pixel. If the gradient exceeds the specified threshold continue to step 8.
  4. Determine Vertical Gradient – Calculate the colour value difference between the currently iterated pixel’s top and bottom neighbour pixel. If the gradient exceeds the specified threshold continue to step 8.
  5. Determine Diagonal Gradients – Calculate the colour value difference between the currently iterated pixel’s North-Western and South-Eastern neighbour pixel as well as the North-Eastern and South-Western neighbour pixel. If the gradient exceeds the specified threshold continue to step 8.
  6. Determine NW-SE Gradient – Calculate the colour value difference between the currently iterated pixel’s North-Western and South-Eastern neighbour pixel. If the gradient exceeds the specified threshold continue to step 8.
  7. Determine NE-SW Gradient  – Calculate the colour value difference between the currently iterated pixel’s North-Eastern and South-Western neighbour pixel.
  8. Determine and set result pixel value – If any of the six gradients calculated exceeded the specified threshold value set the related pixel in the resulting image to the Edge Colour specified by the user, if not, set the related pixel equal to the source pixel colour value.

Zurich: Block Size 10, Factor 4, Chebyshev

Zurich Block Size 10 Factor 4 Chebyshev 

Implementing a Stained Glass Image Filter

The sample source code defines two helper classes, both implemented when applying the Stained Glass Image Filter. The Pixel class represents a single pixel in terms of an XY-Coordinate and Red, Green and Blue values. The definition as follows:

public class Pixel  
{
    private int xOffset = 0; 
    public int XOffset 
    {
        get { return xOffset; } set { xOffset = value; } 
    }

private int yOffset = 0; public int YOffset { get { return yOffset; } set { yOffset = value; } }
private byte blue = 0; public byte Blue { get { return blue; } set { blue = value; } }
private byte green = 0; public byte Green { get { return green; } set { green = value; } }
private byte red = 0; public byte Red { get { return red; } set { red = value; } } }

Zurich: Block Size 10, Factor 1, Chebyshev, Edge Threshold 1

Zurich Block Size 10 Factor 1 Chebyshev Edge Threshold 1

The VoronoiPoint class serves as method of recording randomly generated coordinates and referencing a region’s associated pixels. The definition as follows:

public class VoronoiPoint 
{
    private int xOffset = 0; 
    public int XOffset 
    {
        get  { return xOffset; } set { xOffset = value; }
    }

private int yOffset = 0; public int YOffset { get { return yOffset; } set { yOffset = value; } }
private int blueTotal = 0; public int BlueTotal { get { return blueTotal; } set { blueTotal = value; } }
private int greenTotal = 0; public int GreenTotal { get {return greenTotal; } set { greenTotal = value; } }
private int redTotal = 0; public int RedTotal { get { return redTotal; } set { redTotal = value; } }
public void CalculateAverages() { if (pixelCollection.Count > 0) { blueAverage = blueTotal / pixelCollection.Count; greenAverage = greenTotal / pixelCollection.Count; redAverage = redTotal / pixelCollection.Count; } }
private int blueAverage = 0; public int BlueAverage { get { return blueAverage; } }
private int greenAverage = 0; public int GreenAverage { get { return greenAverage; } }
private int redAverage = 0; public int RedAverage { get { return redAverage; } }
private List<Pixel> pixelCollection = new List<Pixel>(); public List<Pixel> PixelCollection { get { return pixelCollection; } }
public void AddPixel(Pixel pixel) { blueTotal += pixel.Blue; greenTotal += pixel.Green; redTotal += pixel.Red;
pixelCollection.Add(pixel); } }

Zurich: Block Size 20, Factor 1, Euclidean, Edge Threshold 1

Zurich Block Size 20 Factor 1 Euclidean Edge Threshold 1

From the perspective of a filter implementation code base the only requirement comes in the form of having to invoke the StainedGlassColorFilter , no additional work is required from external code consumers. The StainedGlassColorFilter method has been defined as an targeting the class. The StainedGlassColorFilter method definition as follows:

public static Bitmap StainedGlassColorFilter(this Bitmap sourceBitmap,  
                                             int blockSize, double blockFactor, 
                                             DistanceFormulaType distanceType, 
                                             bool highlightEdges,  
                                             byte edgeThreshold, Color edgeColor) 
{
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle(0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height];
byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int neighbourHoodTotal = 0; int sourceOffset = 0; int resultOffset = 0; int currentPixelDistance = 0; int nearestPixelDistance = 0; int nearesttPointIndex = 0;
Random randomizer = new Random();
List<VoronoiPoint> randomPointList = new List<VoronoiPoint>();
for (int row = 0; row < sourceBitmap.Height - blockSize; row += blockSize) { for (int col = 0; col < sourceBitmap.Width - blockSize; col += blockSize) { sourceOffset = row * sourceData.Stride + col * 4;
neighbourHoodTotal = 0;
for (int y = 0; y < blockSize; y++) { for (int x = 0; x < blockSize; x++) { resultOffset = sourceOffset + y * sourceData.Stride + x * 4; neighbourHoodTotal += pixelBuffer[resultOffset]; neighbourHoodTotal += pixelBuffer[resultOffset + 1]; neighbourHoodTotal += pixelBuffer[resultOffset + 2]; } }
randomizer = new Random(neighbourHoodTotal);
VoronoiPoint randomPoint = new VoronoiPoint(); randomPoint.XOffset = randomizer.Next(0, blockSize) + col; randomPoint.YOffset = randomizer.Next(0, blockSize) + row;
randomPointList.Add(randomPoint); } }
int rowOffset = 0; int colOffset = 0;
for (int bufferOffset = 0; bufferOffset < pixelBuffer.Length - 4; bufferOffset += 4) { rowOffset = bufferOffset / sourceData.Stride; colOffset = (bufferOffset % sourceData.Stride) / 4;
currentPixelDistance = 0; nearestPixelDistance = blockSize * 4; nearesttPointIndex = 0;
List<VoronoiPoint> pointSubset = new List<VoronoiPoint>();
pointSubset.AddRange(from t in randomPointList where rowOffset >= t.YOffset - blockSize * 2 && rowOffset <= t.YOffset + blockSize * 2 select t);
for (int k = 0; k < pointSubset.Count; k++) { if (distanceType == DistanceFormulaType.Euclidean) { currentPixelDistance = CalculateDistanceEuclidean(pointSubset[k].XOffset, colOffset, pointSubset[k].YOffset, rowOffset); } else if (distanceType == DistanceFormulaType.Manhattan) { currentPixelDistance = CalculateDistanceManhattan(pointSubset[k].XOffset, colOffset, pointSubset[k].YOffset, rowOffset); } else if (distanceType == DistanceFormulaType.Chebyshev) { currentPixelDistance = CalculateDistanceChebyshev(pointSubset[k].XOffset, colOffset, pointSubset[k].YOffset, rowOffset); } if (currentPixelDistance <= nearestPixelDistance) { nearestPixelDistance = currentPixelDistance; nearesttPointIndex = k; if (nearestPixelDistance <= blockSize / blockFactor) { break; } } }
Pixel tmpPixel = new Pixel (); tmpPixel.XOffset = colOffset; tmpPixel.YOffset = rowOffset; tmpPixel.Blue = pixelBuffer[bufferOffset]; tmpPixel.Green = pixelBuffer[bufferOffset + 1]; tmpPixel.Red = pixelBuffer[bufferOffset + 2];
pointSubset[nearesttPointIndex].AddPixel(tmpPixel); }
for (int k = 0; k < randomPointList.Count; k++) { randomPointList[k].CalculateAverages();
for (int i = 0; i < randomPointList[k].PixelCollection.Count; i++) { resultOffset = randomPointList[k].PixelCollection[i].YOffset * sourceData.Stride + randomPointList[k].PixelCollection[i].XOffset * 4;
resultBuffer[resultOffset] = (byte)randomPointList[k].BlueAverage; resultBuffer[resultOffset + 1] = (byte)randomPointList[k].GreenAverage; resultBuffer[resultOffset + 2] = (byte)randomPointList[k].RedAverage;
resultBuffer[resultOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode.WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length);
resultBitmap.UnlockBits(resultData);
if (highlightEdges == true ) { resultBitmap = resultBitmap.GradientBasedEdgeDetectionFilter(edgeColor, edgeThreshold); }
return resultBitmap; }

Locarno: Block Size 10, Factor 4, Euclidean, Edge Threshold 1

Locarno Block Size 10 Factor 4 Euclidean Edge Threshold 1

Implementing Pixel Coordinate Distance Calculations

As mentioned earlier, this article and the accompanying sample source code support coordinate distance calculations through three different calculation methods, namely , and . The method of distance calculation implemented depends on the configuration option specified by the user.

The CalculateDistanceEuclidean method calculates distance implementing the Calculation method. In order to aid faster execution this method will calculate the square root of a specific value only once. Once a square root has been calculated the result is kept in memory. The following code snippet lists the definition of the CalculateDistanceEuclidean method:

private static Dictionary <int,int> squareRoots = new Dictionary<int,int>(); 

private static int CalculateDistanceEuclidean(int x1, int x2, int y1, int y2) { int square = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
if(squareRoots.ContainsKey(square) == false) { squareRoots.Add(square, (int)Math.Sqrt(square)); }
return squareRoots[square]; }

The two other methods of calculating distance are implemented through the CalculateDistanceManhattan and CalculateDistanceChebyshev methods. The definition as follows:

private static int CalculateDistanceManhattan(int x1, int x2, int y1, int y2) 
{
    return Math.Abs(x1 - x2) + Math.Abs(y1 - y2); 
}

private static int CalculateDistanceChebyshev(int x1, int x2, int y1, int y2) { return Math.Max(Math.Abs(x1 - x2), Math.Abs(y1 - y2)); }

Bad Ragaz: Block Size 12, Factor 1, Chebyshev

Bad Ragaz Block Size 12 Factor 1 Chebyshev

Implementing Gradient Based Edge Detection

Did you notice the very last step performed by the StainedGlassColorFilter method involves implementing Gradient Based Edge Detection, depending on whether had been specified by the user.

The following code snippet provides the implementation of the GradientBasedEdgeDetectionFilter extension method:

public static Bitmap GradientBasedEdgeDetectionFilter( 
                this Bitmap sourceBitmap, 
                Color edgeColour, 
                byte threshold = 0) 
{
    BitmapData sourceData = 
               sourceBitmap.LockBits(new Rectangle (0, 0, 
               sourceBitmap.Width, sourceBitmap.Height), 
               ImageLockMode.ReadOnly, 
               PixelFormat.Format32bppArgb); 

byte[] pixelBuffer = new byte[sourceData.Stride * sourceData.Height]; byte[] resultBuffer = new byte[sourceData.Stride * sourceData.Height];
Marshal.Copy(sourceData.Scan0, pixelBuffer, 0, pixelBuffer.Length); Marshal.Copy(sourceData.Scan0, resultBuffer, 0, resultBuffer.Length);
sourceBitmap.UnlockBits(sourceData);
int sourceOffset = 0, gradientValue = 0; bool exceedsThreshold = false;
for(int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++) { for(int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++) { sourceOffset = offsetY * sourceData.Stride + offsetX * 4; gradientValue = 0; exceedsThreshold = true;
// Horizontal Gradient CheckThreshold(pixelBuffer, sourceOffset - 4, sourceOffset + 4, ref gradientValue, threshold, 2); // Vertical Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride, sourceOffset + sourceData.Stride, ref gradientValue, threshold, 2);
if (exceedsThreshold == false) { gradientValue = 0;
// Horizontal Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - 4, sourceOffset + 4, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Vertical Gradient exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride, sourceOffset + sourceData.Stride, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NW-SE CheckThreshold(pixelBuffer, sourceOffset - 4 - sourceData.Stride, sourceOffset + 4 + sourceData.Stride, ref gradientValue, threshold, 2); // Diagonal Gradient : NE-SW exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride + 4, sourceOffset - 4 + sourceData.Stride, ref gradientValue, threshold, 2);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NW-SE exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - 4 - sourceData.Stride, sourceOffset + 4 + sourceData.Stride, ref gradientValue, threshold);
if (exceedsThreshold == false) { gradientValue = 0;
// Diagonal Gradient : NE-SW exceedsThreshold = CheckThreshold(pixelBuffer, sourceOffset - sourceData.Stride + 4, sourceOffset + sourceData.Stride - 4, ref gradientValue, threshold); } } } } }
if (exceedsThreshold == true) { resultBuffer[sourceOffset] = edgeColour.B; resultBuffer[sourceOffset + 1] = edgeColour.G; resultBuffer[sourceOffset + 2] = edgeColour.R; }
resultBuffer[sourceOffset + 3] = 255; } }
Bitmap resultBitmap = new Bitmap (sourceBitmap.Width, sourceBitmap.Height); BitmapData resultData = resultBitmap.LockBits(new Rectangle (0, 0, resultBitmap.Width, resultBitmap.Height), ImageLockMode .WriteOnly, PixelFormat.Format32bppArgb);
Marshal.Copy(resultBuffer, 0, resultData.Scan0, resultBuffer.Length); resultBitmap.UnlockBits(resultData);
return resultBitmap; }

Zurich: Block Size 15, Factor 1, Manhattan, Edge Threshold 1

Zurich Block Size 15 Factor 1 Manhattan Edge Threshold 1

Sample Images

This article features a rendered graphic illustrating an example which has been released into the public domain by its author, Augochy at the wikipedia project. This applies worldwide. The original can be downloaded from .

All of the photos that appear in this article were taken by myself. Photos listed under Zurich, Locarno and Bad Ragaz were shot in Switzerland. The photo listed as Salzburg had been shot in Austria and the photo listed under Port Edward had been shot in South Africa. In order to fully realize the extent to which had been modified the following section details the original photos.

Zurich, Switzerland

Zurich, Switzerland

Salzburg, Austria

Salzburg, Austria

Locarno, Switzerland

Locarno, Switzerland

Bad Ragaz, Switzerland

Bad Ragaz, Switzerland

Port Edward, South Africa

Port Edward, South Africa

Zurich, Switzerland

Zurich, Switzerland

Zurich, Switzerland

Zurich, Switzerland

Zurich, Switzerland

Zurich, Switzerland

Bad Ragaz, Switzerland

Bad Ragaz, Switzerland

Related Articles and Feedback

Feedback and questions are always encouraged. If you know of an alternative implementation or have ideas on a more efficient implementation please share in the comments section.

I’ve published a number of articles related to imaging and images of which you can find URL links here:

26 Responses to “C# How to: Stained Glass Image Filter”



  1. 1 C# How to: Image Transform Shear | Software by Default Trackback on June 30, 2013 at 1:15 PM
  2. 2 C# How to: Oil Painting and Cartoon Filter | Software by Default Trackback on June 30, 2013 at 1:20 PM
  3. 3 C# How to: Image Transform Rotate | Software by Default Trackback on June 30, 2013 at 1:25 PM
  4. 4 C# How to: Image Blur | Software by Default Trackback on June 30, 2013 at 1:35 PM
  5. 5 C# How to: Calculating Gaussian Kernels | Software by Default Trackback on June 30, 2013 at 1:40 PM
  6. 6 C# How to: Image Cartoon Effect | Software by Default Trackback on June 30, 2013 at 1:45 PM
  7. 7 C# How to: Boolean Edge Detection | Software by Default Trackback on June 30, 2013 at 1:56 PM
  8. 8 C# How to: Morphological Edge Detection | Software by Default Trackback on June 30, 2013 at 2:02 PM
  9. 9 C# How to: Image Erosion and Dilation | Software by Default Trackback on June 30, 2013 at 2:11 PM
  10. 10 C# How to: Image Colour Average | Software by Default Trackback on June 30, 2013 at 2:20 PM
  11. 11 C# How to: Image Unsharp Mask | Software by Default Trackback on June 30, 2013 at 2:29 PM
  12. 12 C# How to: Image Median Filter | Software by Default Trackback on June 30, 2013 at 3:17 PM
  13. 13 C# How to: Difference Of Gaussians | Software by Default Trackback on June 30, 2013 at 3:28 PM
  14. 14 C# How to: Image Edge Detection | Software by Default Trackback on June 30, 2013 at 3:33 PM
  15. 15 C# How to: Image Convolution | Software by Default Trackback on June 30, 2013 at 3:55 PM
  16. 16 C# How to: Generate a Web Service from WSDL | Software by Default Trackback on June 30, 2013 at 4:08 PM
  17. 17 C# How to: Decoding/Converting Base64 strings to Bitmap images | Software by Default Trackback on June 30, 2013 at 4:14 PM
  18. 18 C# How to: Bitmap Colour Substitution implementing thresholds | Software by Default Trackback on July 6, 2013 at 4:33 PM
  19. 19 C# How to: Swapping Bitmap ARGB Colour Channels | Software by Default Trackback on July 6, 2013 at 5:02 PM
  20. 20 C# How to: Image filtering by directly manipulating Pixel ARGB values | Software by Default Trackback on July 8, 2013 at 2:58 AM
  21. 21 C# How to: Image ASCII Art | Software by Default Trackback on July 14, 2013 at 7:23 AM
  22. 22 C# How to: Weighted Difference of Gaussians | Software by Default Trackback on July 14, 2013 at 8:12 PM
  23. 23 C# How to: Image Boundary Extraction | Software by Default Trackback on July 21, 2013 at 10:24 AM
  24. 24 C# How to: Image Abstract Colours Filter | Software by Default Trackback on July 28, 2013 at 7:42 PM
  25. 25 C# How to: Fuzzy Blur Filter | Software by Default Trackback on August 9, 2013 at 6:40 AM
  26. 26 C# How to: Image Distortion Blur | Software by Default Trackback on August 9, 2013 at 10:14 PM

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




about.me :: Dewald Esterhuizen

Dewald Esterhuizen

Blog Stats

  • 163,039 hits

Gravatar :: Dewald Esterhuizen

SoftwareByDefault QR Code

SoftwareByDefault QR Code

I review for the O'Reilly Blogger Review Program

Enter your email address to follow and receive notifications of new posts by email.

Join 152 other followers

Archives

Twitter feed

RSS SoftwareByDefault on MSDN

  • An error has occurred; the feed is probably down. Try again later.

del.icio.us Links

http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com
http://softwarebydefault.com

Follow

Get every new post delivered to your Inbox.

Join 152 other followers

%d bloggers like this: