728x90
반응형
728x170
▶ DistanceType.cs
namespace TestProject
{
/// <summary>
/// 거리 타입
/// </summary>
public enum DistanceType
{
/// <summary>
/// 유클리드
/// </summary>
Euclidean,
/// <summary>
/// 맨하탄
/// </summary>
Manhattan,
/// <summary>
/// 체비쇼프
/// </summary>
Chebyshev
}
}
728x90
▶ Pixel.cs
namespace TestProject
{
/// <summary>
/// 픽셀
/// </summary>
public class Pixel
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 오프셋 X
/// </summary>
private int xOffset = 0;
/// <summary>
/// 오프셋 Y
/// </summary>
private int yOffset = 0;
/// <summary>
/// 청색
/// </summary>
private byte blue = 0;
/// <summary>
/// 녹색
/// </summary>
private byte green = 0;
/// <summary>
/// 적색
/// </summary>
private byte red = 0;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region X 오프셋 - XOffset
/// <summary>
/// X 오프셋
/// </summary>
public int XOffset
{
get
{
return this.xOffset;
}
set
{
this.xOffset = value;
}
}
#endregion
#region Y 오프셋 - YOffset
/// <summary>
/// Y 오프셋
/// </summary>
public int YOffset
{
get
{
return this.yOffset;
}
set
{
this.yOffset = value;
}
}
#endregion
#region 청색 - Blue
/// <summary>
/// 청색
/// </summary>
public byte Blue
{
get
{
return this.blue;
}
set
{
this.blue = value;
}
}
#endregion
#region 녹색 - Green
/// <summary>
/// 녹색
/// </summary>
public byte Green
{
get
{
return this.green;
}
set
{
this.green = value;
}
}
#endregion
#region 적색 - Red
/// <summary>
/// 적색
/// </summary>
public byte Red
{
get
{
return this.red;
}
set
{
this.red = value;
}
}
#endregion
}
}
300x250
▶ VoronoiPoint.cs
using System.Collections.Generic;
namespace TestProject
{
/// <summary>
/// 보로노이 포인트
/// </summary>
public class VoronoiPoint
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 픽셀 리스트
/// </summary>
private List<Pixel> pixelList = new List<Pixel>();
/// <summary>
/// X 오프셋
/// </summary>
private int xOffset = 0;
/// <summary>
/// Y 오프셋
/// </summary>
private int yOffset = 0;
/// <summary>
/// 청색 합계
/// </summary>
private int blueTotal = 0;
/// <summary>
/// 녹색 합계
/// </summary>
private int greenTotal = 0;
/// <summary>
/// 적색 합계
/// </summary>
private int redTotal = 0;
/// <summary>
/// 청색 평균
/// </summary>
private int blueAverage = 0;
/// <summary>
/// 녹색 평균
/// </summary>
private int greenAverage = 0;
/// <summary>
/// 적색 평균
/// </summary>
private int redAverage = 0;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 픽셀 리스트 - PixelList
/// <summary>
/// 픽셀 리스트
/// </summary>
public List<Pixel> PixelList
{
get
{
return this.pixelList;
}
}
#endregion
#region X 오프셋 - XOffset
/// <summary>
/// X 오프셋
/// </summary>
public int XOffset
{
get
{
return this.xOffset;
}
set
{
this.xOffset = value;
}
}
#endregion
#region Y 오프셋 - YOffset
/// <summary>
/// Y 오프셋
/// </summary>
public int YOffset
{
get
{
return this.yOffset;
}
set
{
this.yOffset = value;
}
}
#endregion
#region 청색 합계 - BlueTotal
/// <summary>
/// 청색 합계
/// </summary>
public int BlueTotal
{
get
{
return this.blueTotal;
}
set
{
this.blueTotal = value;
}
}
#endregion
#region 녹색 합계 - GreenTotal
/// <summary>
/// 녹색 합계
/// </summary>
public int GreenTotal
{
get
{
return this.greenTotal;
}
set
{
this.greenTotal = value;
}
}
#endregion
#region 적색 합계 - RedTotal
/// <summary>
/// 적색 합계
/// </summary>
public int RedTotal
{
get
{
return this.redTotal;
}
set
{
this.redTotal = value;
}
}
#endregion
#region 청색 평균 - BlueAverage
/// <summary>
/// 청색 평균
/// </summary>
public int BlueAverage
{
get
{
return this.blueAverage;
}
}
#endregion
#region 녹색 평균 - GreenAverage
/// <summary>
/// 녹색 평균
/// </summary>
public int GreenAverage
{
get
{
return this.greenAverage;
}
}
#endregion
#region 적색 평균 - RedAverage
/// <summary>
/// 적색 평균
/// </summary>
public int RedAverage
{
get
{
return this.redAverage;
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 픽셀 추가하기 - AddPixel(pixel)
/// <summary>
/// 픽셀 추가하기
/// </summary>
/// <param name="pixel">픽셀</param>
public void AddPixel(Pixel pixel)
{
this.blueTotal += pixel.Blue;
this.greenTotal += pixel.Green;
this.redTotal += pixel.Red;
this.pixelList.Add(pixel);
}
#endregion
#region 평균 계산하기 - CalculateAverage()
/// <summary>
/// 평균 계산하기
/// </summary>
public void CalculateAverage()
{
if(this.pixelList.Count > 0)
{
this.blueAverage = this.blueTotal / this.pixelList.Count;
this.greenAverage = this.greenTotal / this.pixelList.Count;
this.redAverage = this.redTotal / this.pixelList.Count;
}
}
#endregion
}
}
▶ BitmapHelper.cs
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
namespace TestProject
{
/// <summary>
/// 비트맵 헬퍼
/// </summary>
public static class BitmapHelper
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 제곱근 딕셔너리
/// </summary>
private static Dictionary <int, int> _squareRootDictionary = new Dictionary<int, int>();
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 비트맵 로드하기 - LoadBitmap(filePath)
/// <summary>
/// 비트맵 로드하기
/// </summary>
/// <param name="filePath">파일 경로</param>
/// <returns>비트맵</returns>
public static Bitmap LoadBitmap(string filePath)
{
using(Bitmap bitmap = new Bitmap(filePath))
{
return new Bitmap(bitmap);
}
}
#endregion
#region 스테인 글라스 필터 적용하기 - ApplyStainedGlassFilter(sourceBitmap, blockSize, blockFactor, distanceType, highlightEdge, edgeThreshold, edgeColor)
/// <summary>
/// 스테인 글라스 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="blockSize">블럭 크기</param>
/// <param name="blockFactor">블럭 팩터</param>
/// <param name="distanceType">거리 타입</param>
/// <param name="highlightEdge">가장자리 강조 여부</param>
/// <param name="edgeThreshold">가장자리 임계치</param>
/// <param name="edgeColor">가장자리 색상</param>
/// <returns>비트맵</returns>
public static Bitmap ApplyStainedGlassFilter
(
Bitmap sourceBitmap,
int blockSize,
double blockFactor,
DistanceType distanceType,
bool highlightEdge,
byte edgeThreshold,
Color edgeColor
)
{
BitmapData sourceBitmapData = sourceBitmap.LockBits
(
new Rectangle(0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
byte[] sourceByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
byte[] targetByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
Marshal.Copy(sourceBitmapData.Scan0, sourceByteArray, 0, sourceByteArray.Length);
sourceBitmap.UnlockBits(sourceBitmapData);
int neighbourHoodTotal = 0;
int sourceOffset = 0;
int targetOffset = 0;
int currentPixelDistance = 0;
int nearestPixelDistance = 0;
int nearestPointIndex = 0;
Random random = new Random();
List<VoronoiPoint> randomPointList = new List<VoronoiPoint>();
for(int row = 0; row < sourceBitmap.Height - blockSize; row += blockSize)
{
for(int column = 0; column < sourceBitmap.Width - blockSize; column += blockSize)
{
sourceOffset = row * sourceBitmapData.Stride + column * 4;
neighbourHoodTotal = 0;
for(int y = 0; y < blockSize; y++)
{
for(int x = 0; x < blockSize; x++)
{
targetOffset = sourceOffset + y * sourceBitmapData.Stride + x * 4;
neighbourHoodTotal += sourceByteArray[targetOffset ];
neighbourHoodTotal += sourceByteArray[targetOffset + 1];
neighbourHoodTotal += sourceByteArray[targetOffset + 2];
}
}
random = new Random(neighbourHoodTotal);
VoronoiPoint randomPoint = new VoronoiPoint();
randomPoint.XOffset = random.Next(0, blockSize) + column;
randomPoint.YOffset = random.Next(0, blockSize) + row;
randomPointList.Add(randomPoint);
}
}
int rowOffset = 0;
int columnOffset = 0;
for(int bufferOffset = 0; bufferOffset < sourceByteArray.Length - 4; bufferOffset += 4)
{
rowOffset = bufferOffset / sourceBitmapData.Stride;
columnOffset = (bufferOffset % sourceBitmapData.Stride) / 4;
currentPixelDistance = 0;
nearestPixelDistance = blockSize * 4;
nearestPointIndex = 0;
List<VoronoiPoint> subsidaryPointList = new List<VoronoiPoint>();
subsidaryPointList.AddRange
(
from randomPoint in randomPointList
where rowOffset >= randomPoint.YOffset - blockSize * 2 &&
rowOffset <= randomPoint.YOffset + blockSize * 2
select randomPoint
);
for(int k = 0; k < subsidaryPointList.Count; k++)
{
if(distanceType == DistanceType.Euclidean)
{
currentPixelDistance = CalculateDistanceEuclidean
(
subsidaryPointList[k].XOffset,
columnOffset,
subsidaryPointList[k].YOffset,
rowOffset
);
}
else if(distanceType == DistanceType.Manhattan)
{
currentPixelDistance = CalculateDistanceManhattan
(
subsidaryPointList[k].XOffset,
columnOffset,
subsidaryPointList[k].YOffset,
rowOffset
);
}
else if(distanceType == DistanceType.Chebyshev)
{
currentPixelDistance = CalculateDistanceChebyshev
(
subsidaryPointList[k].XOffset,
columnOffset,
subsidaryPointList[k].YOffset,
rowOffset
);
}
if(currentPixelDistance <= nearestPixelDistance)
{
nearestPixelDistance = currentPixelDistance;
nearestPointIndex = k;
if(nearestPixelDistance <= blockSize / blockFactor)
{
break;
}
}
}
Pixel pixel = new Pixel ();
pixel.XOffset = columnOffset;
pixel.YOffset = rowOffset;
pixel.Blue = sourceByteArray[bufferOffset ];
pixel.Green = sourceByteArray[bufferOffset + 1];
pixel.Red = sourceByteArray[bufferOffset + 2];
subsidaryPointList[nearestPointIndex].AddPixel(pixel);
}
for(int k = 0; k < randomPointList.Count; k++)
{
randomPointList[k].CalculateAverage();
for(int i = 0; i < randomPointList[k].PixelList.Count; i++)
{
targetOffset = randomPointList[k].PixelList[i].YOffset *
sourceBitmapData.Stride +
randomPointList[k].PixelList[i].XOffset * 4;
targetByteArray[targetOffset ] = (byte)randomPointList[k].BlueAverage;
targetByteArray[targetOffset + 1] = (byte)randomPointList[k].GreenAverage;
targetByteArray[targetOffset + 2] = (byte)randomPointList[k].RedAverage;
targetByteArray[targetOffset + 3] = 255;
}
}
Bitmap targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData targetBitmapData = targetBitmap.LockBits
(
new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb
);
Marshal.Copy(targetByteArray, 0, targetBitmapData.Scan0, targetByteArray.Length);
targetBitmap.UnlockBits(targetBitmapData);
if(highlightEdge == true )
{
targetBitmap = ApplyGradientBasedEdgeDetectionFilter(targetBitmap, edgeColor, edgeThreshold);
}
return targetBitmap;
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Private
#region 유클리드 거리 계산하기 - CalculateDistanceEuclidean(x1, x2, y1, y2)
/// <summary>
/// 유클리드 거리 계산하기
/// </summary>
/// <param name="x1">X1</param>
/// <param name="x2">Y1</param>
/// <param name="y1">X2</param>
/// <param name="y2">Y2</param>
/// <returns>거리</returns>
private static int CalculateDistanceEuclidean(int x1, int x2, int y1, int y2)
{
int square = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
if(_squareRootDictionary.ContainsKey(square) == false)
{
_squareRootDictionary.Add(square, (int)Math.Sqrt(square));
}
return _squareRootDictionary[square];
}
#endregion
#region 맨하탄 거리 계산하기 - CalculateDistanceManhattan(x1, x2, y1, y2)
/// <summary>
/// 맨하탄 거리 계산하기
/// </summary>
/// <param name="x1">X1</param>
/// <param name="x2">Y1</param>
/// <param name="y1">X2</param>
/// <param name="y2">Y2</param>
/// <returns>거리</returns>
private static int CalculateDistanceManhattan(int x1, int x2, int y1, int y2)
{
return Math.Abs(x1 - x2) + Math.Abs(y1 - y2);
}
#endregion
#region 체비쇼프 거리 계산하기 - CalculateDistanceChebyshev(x1, x2, y1, y2)
/// <summary>
/// 체비쇼프 거리 계산하기
/// </summary>
/// <param name="x1">X1</param>
/// <param name="x2">Y1</param>
/// <param name="y1">X2</param>
/// <param name="y2">Y2</param>
/// <returns>거리</returns>
private static int CalculateDistanceChebyshev(int x1, int x2, int y1, int y2)
{
return Math.Max(Math.Abs(x1 - x2), Math.Abs(y1 - y2));
}
#endregion
#region 임계치 체크하기 - CheckThreshold(sourceByteArray, offset1, offset2, gradientValue, threshold, divideBy)
/// <summary>
/// 임계치 체크하기
/// </summary>
/// <param name="sourceByteArray">소스 바이트 배열</param>
/// <param name="offset1">오프셋 1</param>
/// <param name="offset2">오프셋 2</param>
/// <param name="gradientValue">그라디언트 값</param>
/// <param name="threshold">임계치</param>
/// <param name="divideBy">젯수</param>
/// <returns>임계치 체크 결과</returns>
private static bool CheckThreshold
(
byte[] sourceByteArray,
int offset1,
int offset2,
ref int gradientValue,
byte threshold,
int divideBy = 1
)
{
gradientValue += Math.Abs(sourceByteArray[offset1 ] - sourceByteArray[offset2 ]) / divideBy;
gradientValue += Math.Abs(sourceByteArray[offset1 + 1] - sourceByteArray[offset2 + 1]) / divideBy;
gradientValue += Math.Abs(sourceByteArray[offset1 + 2] - sourceByteArray[offset2 + 2]) / divideBy;
return (gradientValue >= threshold);
}
#endregion
#region 그라디언트 기반 가장자리 탐지 필터 적용하기 - ApplyGradientBasedEdgeDetectionFilter(sourceBitmap, edgeColor, threshold)
/// <summary>
/// 그라디언트 기반 가장자리 탐지 필터 적용하기
/// </summary>
/// <param name="sourceBitmap">소스 비트맵</param>
/// <param name="edgeColor">가장자리 색상</param>
/// <param name="threshold">임계치</param>
/// <returns>비트맵</returns>
private static Bitmap ApplyGradientBasedEdgeDetectionFilter(Bitmap sourceBitmap, Color edgeColor, byte threshold = 0)
{
BitmapData sourceBitmapData = sourceBitmap.LockBits
(
new Rectangle (0, 0, sourceBitmap.Width, sourceBitmap.Height),
ImageLockMode.ReadOnly,
PixelFormat.Format32bppArgb
);
byte[] sourceByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
byte[] targetByteArray = new byte[sourceBitmapData.Stride * sourceBitmapData.Height];
Marshal.Copy(sourceBitmapData.Scan0, sourceByteArray, 0, sourceByteArray.Length);
Marshal.Copy(sourceBitmapData.Scan0, targetByteArray, 0, targetByteArray.Length);
sourceBitmap.UnlockBits(sourceBitmapData);
int sourceOffset = 0;
int gradientValue = 0;
bool exceedThreshold = false;
for(int offsetY = 1; offsetY < sourceBitmap.Height - 1; offsetY++)
{
for(int offsetX = 1; offsetX < sourceBitmap.Width - 1; offsetX++)
{
sourceOffset = offsetY * sourceBitmapData.Stride + offsetX * 4;
gradientValue = 0;
exceedThreshold = true;
CheckThreshold
(
sourceByteArray,
sourceOffset - 4,
sourceOffset + 4,
ref gradientValue,
threshold,
2
);
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - sourceBitmapData.Stride,
sourceOffset + sourceBitmapData.Stride,
ref gradientValue,
threshold,
2
);
if(exceedThreshold == false)
{
gradientValue = 0;
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - 4,
sourceOffset + 4,
ref gradientValue,
threshold
);
if(exceedThreshold == false)
{
gradientValue = 0;
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - sourceBitmapData.Stride,
sourceOffset + sourceBitmapData.Stride,
ref gradientValue,
threshold
);
if(exceedThreshold == false)
{
gradientValue = 0;
CheckThreshold
(
sourceByteArray,
sourceOffset - 4 - sourceBitmapData.Stride,
sourceOffset + 4 + sourceBitmapData.Stride,
ref gradientValue,
threshold,
2
);
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - sourceBitmapData.Stride + 4,
sourceOffset - 4 + sourceBitmapData.Stride,
ref gradientValue,
threshold,
2
);
if(exceedThreshold == false)
{
gradientValue = 0;
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - 4 - sourceBitmapData.Stride,
sourceOffset + 4 + sourceBitmapData.Stride,
ref gradientValue,
threshold
);
if(exceedThreshold == false)
{
gradientValue = 0;
exceedThreshold = CheckThreshold
(
sourceByteArray,
sourceOffset - sourceBitmapData.Stride + 4,
sourceOffset + sourceBitmapData.Stride - 4,
ref gradientValue,
threshold
);
}
}
}
}
}
if(exceedThreshold == true)
{
targetByteArray[sourceOffset ] = edgeColor.B;
targetByteArray[sourceOffset + 1] = edgeColor.G;
targetByteArray[sourceOffset + 2] = edgeColor.R;
}
targetByteArray[sourceOffset + 3] = 255;
}
}
Bitmap targetBitmap = new Bitmap(sourceBitmap.Width, sourceBitmap.Height);
BitmapData targetBitmapData = targetBitmap.LockBits
(
new Rectangle(0, 0, targetBitmap.Width, targetBitmap.Height),
ImageLockMode.WriteOnly,
PixelFormat.Format32bppArgb
);
Marshal.Copy(targetByteArray, 0, targetBitmapData.Scan0, targetByteArray.Length);
targetBitmap.UnlockBits(targetBitmapData);
return targetBitmap;
}
#endregion
}
}
▶ MainForm.cs
using System.Drawing;
using System.Windows.Forms;
namespace TestProject
{
/// <summary>
/// 메인 폼
/// </summary>
public partial class MainForm : Form
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainForm()
/// <summary>
/// 생성자
/// </summary>
public MainForm()
{
InitializeComponent();
Bitmap sourceBitmap = BitmapHelper.LoadBitmap("IMAGE\\sample.jpg");
Bitmap targetBitmap = BitmapHelper.ApplyStainedGlassFilter
(
sourceBitmap,
20,
10,
DistanceType.Euclidean,
true,
1,
Color.Black
);
this.pictureBox.SizeMode = PictureBoxSizeMode.Zoom;
this.pictureBox.Image = targetBitmap;
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > WinForm' 카테고리의 다른 글
[C#/WINFORM] Bitmap 클래스 : 투명 비트맵 구하기 (0) | 2021.01.08 |
---|---|
[C#/WINFORM] Bitmap 클래스 : 불선명 필터(Blur Filter) 사용하기 (0) | 2021.01.08 |
[C#/WINFORM] Bitmap 클래스 : 비트맵 회전하기 (0) | 2021.01.08 |
[C#/WINFORM] Bitmap 클래스 : 비트맵 자르기(Shear) (0) | 2021.01.08 |
[C#/WINFORM] Bitmap 클래스 : 컴파스 가장자리 탐지 필터(Compass Edge Detection Filter) 사용하기 (0) | 2021.01.04 |
[C#/WINFORM] Bitmap 클래스 : 경계 추출 필터(Boundary Extraction Filter) 사용하기 (0) | 2021.01.03 |
[C#/WINFORM] Bitmap 클래스 : 카툰 필터(Cartoon Filter) 사용하기 (0) | 2021.01.03 |
[C#/WINFORM] Bitmap 클래스 : 오일 페인트 필터(Oil Paint Filter) 사용하기 (0) | 2021.01.03 |
[C#/WINFORM] Bitmap 클래스 : 이미지 추상 색상 필터 사용하기 (0) | 2021.01.03 |
[C#/WINFORM] Bitmap 클래스 : 바이트 배열 구하기 (0) | 2021.01.03 |
댓글을 달아 주세요