728x90
반응형
728x170
▶ WordMarginMethod.cs
namespace TestProject
{
/// <summary>
/// 워드 마진 메소드
/// </summary>
public enum WordMarginMethod
{
/// <summary>
/// 해당 무
/// </summary>
None,
/// <summary>
/// 스트로크
/// </summary>
Stroke,
/// <summary>
/// BLUR
/// </summary>
Blur,
/// <summary>
/// 테두리
/// </summary>
Boundary
}
}
728x90
▶ WordRectangle.cs
using System.Windows;
namespace TestProject
{
/// <summary>
/// 워드 사각형
/// </summary>
public class WordRectangle
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Public
#region Field
/// <summary>
/// 테두리 사각형
/// </summary>
public Rect BoundRectangle;
/// <summary>
/// 최소 폰트 크기
/// </summary>
public int MinimumFontSize;
/// <summary>
/// 최대 폰트 크기
/// </summary>
public int MaximumFontSize;
#endregion
}
}
300x250
▶ WordCloudControl.cs
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
namespace TestProject
{
/// <summary>
/// 워드 클라우드 컨트롤
/// </summary>
public class WordCloudControl : Canvas
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Routed Event
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 그리기 완료시 이벤트 - DrawCompletedEvent
/// <summary>
/// 그리기 완료시 이벤트
/// </summary>
public static readonly RoutedEvent DrawCompletedEvent = EventManager.RegisterRoutedEvent
(
"DrawCompleted",
RoutingStrategy.Direct,
typeof(RoutedEventHandler),
typeof(WordCloudControl)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Dependency Property
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 이미지 URI 속성 - ImageURIProperty
/// <summary>
/// 이미지 URI 속성
/// </summary>
public static readonly DependencyProperty ImageURIProperty = DependencyProperty.Register
(
"ImageURI",
typeof(Uri),
typeof(WordCloudControl)
);
#endregion
#region 폰트 문화 속성 - FontCultureProperty
/// <summary>
/// 폰트 문화 속성
/// </summary>
public static readonly DependencyProperty FontCultureProperty = DependencyProperty.Register
(
"FontCulture",
typeof(string),
typeof(WordCloudControl),
new PropertyMetadata("ko-kr")
);
#endregion
#region 폰트명 속성 - FontNameProperty
/// <summary>
/// 폰트명 속성
/// </summary>
public static readonly DependencyProperty FontNameProperty = DependencyProperty.Register
(
"FontName",
typeof(string),
typeof(WordCloudControl),
new PropertyMetadata("나눔고딕")
);
#endregion
#region 최소 폰트 크기 속성 - MinimumFontSizeProperty
/// <summary>
/// 최소 폰트 크기 속성
/// </summary>
public static readonly DependencyProperty MinimumFontSizeProperty = DependencyProperty.Register
(
"MinimumFontSize",
typeof(int),
typeof(WordCloudControl),
new PropertyMetadata(1)
);
#endregion
#region 최대 폰트 크기 속성 - MaximumFontSizeProperty
/// <summary>
/// 최대 폰트 크기 속성
/// </summary>
public static readonly DependencyProperty MaximumFontSizeProperty = DependencyProperty.Register
(
"MaximumFontSize",
typeof(int),
typeof(WordCloudControl),
new PropertyMetadata(100)
);
#endregion
#region 텍스트 리스트 속성 - TextListProperty
/// <summary>
/// 텍스트 리스트 속성
/// </summary>
public static readonly DependencyProperty TextListProperty = DependencyProperty.Register
(
"TextList",
typeof(List<string>),
typeof(WordCloudControl),
new PropertyMetadata(new List<string>())
);
#endregion
#region 반복 카운트 속성 - RepeatCountProperty
/// <summary>
/// 반복 카운트 속성
/// </summary>
public static readonly DependencyProperty RepeatCountProperty = DependencyProperty.Register
(
"RepeatCount",
typeof(int),
typeof(WordCloudControl),
new PropertyMetadata(150)
);
#endregion
#region 각도 속성 - AngleProperty
/// <summary>
/// 각도 속성
/// </summary>
public static readonly DependencyProperty AngleProperty = DependencyProperty.Register
(
"Angle",
typeof(int),
typeof(WordCloudControl),
new PropertyMetadata(0)
);
#endregion
#region 워드 마진 메소드 속성 - WordMarginMethodProperty
/// <summary>
/// 워드 마진 메소드 속성
/// </summary>
public static readonly DependencyProperty WordMarginMethodProperty = DependencyProperty.Register
(
"WordMarginMethod",
typeof(WordMarginMethod),
typeof(WordCloudControl),
new PropertyMetadata(WordMarginMethod.Blur)
);
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Event
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 그리기 완료시 이벤트 - DrawCompleted
/// <summary>
/// 그리기 완료시 이벤트
/// </summary>
public event RoutedEventHandler DrawCompleted
{
add
{
AddHandler(DrawCompletedEvent, value);
}
remove
{
RemoveHandler(DrawCompletedEvent, value);
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// DPI X
/// </summary>
private double dpiX = 96.0;
/// <summary>
/// DPI Y
/// </summary>
private double dpiY = 96.0;
/// <summary>
/// 현재 폰트 크기
/// </summary>
private double currentFontSize;
/// <summary>
/// 텍스트 인덱스
/// </summary>
private int textIndex;
/// <summary>
/// 사각형 인덱스
/// </summary>
private int rectangleIndex;
/// <summary>
/// 임시 픽셀 바이트 배열
/// </summary>
private byte[] tempiraryPixelByteArray;
/// <summary>
/// 이미지 픽셀 바이트 배열
/// </summary>
private byte[] imagePixelByteArray;
/// <summary>
/// 난수기
/// </summary>
private Random random;
/// <summary>
/// 임시 비트맵
/// </summary>
private WriteableBitmap tempiraryBitmap;
/// <summary>
/// 대상 비트맵
/// </summary>
private WriteableBitmap targetBitmap;
/// <summary>
/// 타이머
/// </summary>
private DispatcherTimer timer;
/// <summary>
/// 워드 사각형 리스트
/// </summary>
private List<WordRectangle> wordRectangleList;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 이미지 URI - ImageURI
/// <summary>
/// 이미지 URI
/// </summary>
public Uri ImageURI
{
get
{
return (Uri)GetValue(ImageURIProperty);
}
set
{
SetValue(ImageURIProperty, value);
}
}
#endregion
#region 폰트 문화 - FontCulture
/// <summary>
/// 폰트 문화
/// </summary>
public string FontCulture
{
get
{
return (string)GetValue(FontCultureProperty);
}
set
{
SetValue(FontCultureProperty, value);
}
}
#endregion
#region 폰트명 - FontName
/// <summary>
/// 폰트명
/// </summary>
public string FontName
{
get
{
return (string)GetValue(FontNameProperty);
}
set
{
SetValue(FontNameProperty, value);
}
}
#endregion
#region 최소 폰트 크기 - MinimumFontSize
/// <summary>
/// 최소 폰트 크기
/// </summary>
public int MinimumFontSize
{
get
{
return (int)GetValue(MinimumFontSizeProperty);
}
set
{
SetValue(MinimumFontSizeProperty, value);
}
}
#endregion
#region 최대 폰트 크기 - MaximumFontSize
/// <summary>
/// 최대 폰트 크기
/// </summary>
public int MaximumFontSize
{
get
{
return (int)GetValue(MaximumFontSizeProperty);
}
set
{
SetValue(MaximumFontSizeProperty, value);
}
}
#endregion
#region 텍스트 리스트 - TextList
/// <summary>
/// 텍스트 리스트
/// </summary>
public List<string> TextList
{
get
{
return (List<string>)GetValue(TextListProperty);
}
set
{
SetValue(TextListProperty, value);
}
}
#endregion
#region 반복 카운트 - RepeatCount
/// <summary>
/// 반복 카운트
/// </summary>
public int RepeatCount
{
get
{
return (int)GetValue(RepeatCountProperty);
}
set
{
SetValue(RepeatCountProperty, value);
}
}
#endregion
#region 각도 - Angle
/// <summary>
/// 각도
/// </summary>
public int Angle
{
get
{
return (int)GetValue(AngleProperty);
}
set
{
SetValue(AngleProperty, value);
}
}
#endregion
#region 워드 마진 메소드 - WordMarginMethod
/// <summary>
/// 워드 마진 메소드
/// </summary>
public WordMarginMethod WordMarginMethod
{
get
{
return (WordMarginMethod)GetValue(WordMarginMethodProperty);
}
set
{
SetValue(WordMarginMethodProperty, value);
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - WordCloudControl()
/// <summary>
/// 생성자
/// </summary>
public WordCloudControl()
{
SetDPI();
this.targetBitmap = new WriteableBitmap(10, 10, this.dpiX, this.dpiY, PixelFormats.Pbgra32, null);
this.random = new Random(DateTime.Today.TimeOfDay.Milliseconds);
this.timer = new DispatcherTimer();
this.timer.Interval = TimeSpan.FromSeconds(1.0 / 60.0);
this.timer.Tick += timer_Tick;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 그리기 - Draw(wordRectangleList)
/// <summary>
/// 그리기
/// </summary>
/// <param name="wordRectangleList">워드 사각형 리스트</param>
public void Draw(List<WordRectangle> wordRectangleList = null)
{
if(TextList.Count == 0 || ImageURI == null || !File.Exists(ImageURI.OriginalString))
{
return;
}
this.timer.Stop();
this.textIndex = 0;
this.rectangleIndex = 0;
this.wordRectangleList = wordRectangleList;
InitializeBitmap();
InitializeRectangle();
this.timer.Start();
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 렌더링시 처리하기 - OnRender(drawingContext)
/// <summary>
/// 렌더링시 처리하기
/// </summary>
/// <param name="drawingContext">드로잉 컨텍스트</param>
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
drawingContext.DrawImage
(
this.targetBitmap,
new Rect
(
0,
0,
this.targetBitmap.PixelWidth,
this.targetBitmap.PixelHeight
)
);
}
#endregion
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 타이머 틱 처리하기 - timer_Tick(sender, e)
/// <summary>
/// 타이머 틱 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void timer_Tick(object sender, EventArgs e)
{
if(this.currentFontSize > MinimumFontSize && this.currentFontSize > 1.0)
{
if(this.textIndex >= int.MaxValue)
{
this.textIndex = 0;
}
string text = TextList[this.textIndex++ % TextList.Count];
if(!WriteText(this.currentFontSize, text))
{
this.currentFontSize *= 0.95;
}
else
{
InvalidateVisual();
}
}
else
{
this.rectangleIndex++;
if(this.rectangleIndex < this.wordRectangleList.Count)
{
WordRectangle wordRectangle = this.wordRectangleList[this.rectangleIndex];
this.currentFontSize = wordRectangle.MaximumFontSize;
}
else
{
this.timer.Stop();
RaiseEvent(new RoutedEventArgs(DrawCompletedEvent));
}
}
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region DPI 설정하기 - SetDPI()
/// <summary>
/// DPI 설정하기
/// </summary>
private void SetDPI()
{
if(!DesignerProperties.GetIsInDesignMode(this))
{
PresentationSource presentationSource = PresentationSource.FromVisual(MainApplication.Current.MainWindow);
if(presentationSource != null)
{
this.dpiX = 96.0 * presentationSource.CompositionTarget.TransformToDevice.M11;
this.dpiY = 96.0 * presentationSource.CompositionTarget.TransformToDevice.M22;
}
}
}
#endregion
#region 비트맵 초기화하기 - InitializeBitmap()
/// <summary>
/// 비트맵 초기화하기
/// </summary>
private void InitializeBitmap()
{
BitmapImage bitmapImage = new BitmapImage(ImageURI);
Width = bitmapImage.PixelWidth;
Height = bitmapImage.PixelHeight;
WriteableBitmap writeableBitmap = new WriteableBitmap(bitmapImage);
this.imagePixelByteArray = new byte[writeableBitmap.PixelHeight * writeableBitmap.PixelWidth * 4];
writeableBitmap.CopyPixels(this.imagePixelByteArray, writeableBitmap.BackBufferStride, 0);
this.targetBitmap = new WriteableBitmap
(
bitmapImage.PixelWidth,
bitmapImage.PixelHeight,
this.dpiX,
this.dpiY,
PixelFormats.Pbgra32,
null
);
this.tempiraryBitmap = this.targetBitmap.Clone();
this.tempiraryPixelByteArray = new byte[bitmapImage.PixelHeight * bitmapImage.PixelWidth * 4];
}
#endregion
#region 사각형 초기화하기 - InitializeRectangle()
/// <summary>
/// 사각형 초기화하기
/// </summary>
private void InitializeRectangle()
{
WordRectangle wordRectangle = new WordRectangle
{
BoundRectangle = new Rect(0, 0, this.targetBitmap.PixelWidth, this.targetBitmap.PixelHeight),
MinimumFontSize = MinimumFontSize,
MaximumFontSize = MaximumFontSize
};
if(this.wordRectangleList == null || (this.wordRectangleList != null && this.wordRectangleList.Count == 0))
{
this.wordRectangleList = new List<WordRectangle>();
this.wordRectangleList.Add(wordRectangle);
}
IEnumerable<WordRectangle> wordRectangleEnumerable = from source in this.wordRectangleList
where source.BoundRectangle == wordRectangle.BoundRectangle
select source;
if(wordRectangleEnumerable.Count() == 0)
{
this.wordRectangleList.Insert(0, wordRectangle);
}
WordRectangle firstWordRectangle = this.wordRectangleList[0];
this.currentFontSize = firstWordRectangle.MaximumFontSize;
}
#endregion
#region 스트로크 그리기 - DrawStroke(drawingContext, formattedText, originPoint)
/// <summary>
/// 스트로크 그리기
/// </summary>
/// <param name="drawingContext">드로잉 컨텍스트</param>
/// <param name="formattedText">포맷 텍스트</param>
/// <param name="originPoint">원점</param>
private void DrawStroke(DrawingContext drawingContext, FormattedText formattedText, Point originPoint)
{
Geometry geometry = formattedText.BuildGeometry(originPoint);
drawingContext.DrawGeometry(Brushes.Black, new Pen(Brushes.Black, 15), geometry);
}
#endregion
#region BLUR 그리기 - DrawBlur(drawingVisual, drawingContext, formattedText, originPoint)
/// <summary>
/// BLUR 그리기
/// </summary>
/// <param name="drawingVisual">드로잉 비주얼</param>
/// <param name="drawingContext">드로잉 컨텍스트</param>
/// <param name="formattedText">포맷 텍스트</param>
/// <param name="originPoint">원점</param>
private void DrawBlur
(
DrawingVisual drawingVisual,
DrawingContext drawingContext,
FormattedText formattedText,
Point originPoint
)
{
drawingContext.DrawText(formattedText, originPoint);
DropShadowBitmapEffect effect = new DropShadowBitmapEffect
{
ShadowDepth = 0,
Softness = 0.4
};
drawingVisual.BitmapEffect = effect;
}
#endregion
#region 테두리 그리기 - DrawBoundary(drawingContext, formattedText, originPoint)
/// <summary>
/// 테두리 그리기
/// </summary>
/// <param name="drawingContext">드로잉 컨텍스트</param>
/// <param name="formattedText">포맷 텍스트</param>
/// <param name="originPoint">원점</param>
private void DrawBoundary(DrawingContext drawingContext, FormattedText formattedText, Point originPoint)
{
Geometry geometry = formattedText.BuildGeometry(new Point());
drawingContext.DrawRectangle
(
Brushes.Black,
new Pen(),
new Rect
(
geometry.Bounds.X + originPoint.X,
geometry.Bounds.Y + originPoint.Y,
geometry.Bounds.Width,
geometry.Bounds.Height
)
);
}
#endregion
#region 텍스트 처리하기 - ProcessingText(drawingVisual, drawingContext, formattedText, originPoint)
/// <summary>
/// 텍스트 처리하기
/// </summary>
/// <param name="drawingVisual">드로잉 비주얼</param>
/// <param name="drawingContext">드로잉 컨텍스트</param>
/// <param name="formattedText">포맷 텍스트</param>
/// <param name="originPoint">원점</param>
private void ProcessingText
(
DrawingVisual drawingVisual,
DrawingContext drawingContext,
FormattedText formattedText,
Point originPoint
)
{
switch(WordMarginMethod)
{
case WordMarginMethod.Stroke :
DrawStroke(drawingContext, formattedText, originPoint);
break;
case WordMarginMethod.Blur :
DrawBlur(drawingVisual, drawingContext, formattedText, originPoint);
break;
case WordMarginMethod.Boundary :
DrawBoundary(drawingContext, formattedText, originPoint);
break;
case WordMarginMethod.None :
drawingContext.DrawText(formattedText, originPoint);
break;
}
}
#endregion
#region 스트라이드 구하기 - GetStride(pixelWidth, pixelFormat)
/// <summary>
/// 스트라이드 구하기
/// </summary>
/// <param name="pixelWidth">픽셀 너비</param>
/// <param name="pixelFormat">픽셀 포맷</param>
/// <returns>스트라이드</returns>
private int GetStride(int pixelWidth, PixelFormat pixelFormat)
{
return (pixelWidth * pixelFormat.BitsPerPixel + 7) / 8;
}
#endregion
#region 픽셀 쓰기 - WritePixel(textPixelByteArray, writePixelByteArray, x, y, width, height)
/// <summary>
/// 픽셀 쓰기
/// </summary>
/// <param name="textPixelByteArray">텍스트 픽셀 바이트 배열</param>
/// <param name="writePixelByteArray">쓰기 픽셀 바이트 배열</param>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <param name="width">너비</param>
/// <param name="height">높이</param>
private void WritePixel(byte[] textPixelByteArray, byte[] writePixelByteArray, int x, int y, int width, int height)
{
byte[] pixelByteArray = new byte[4];
for(int h = 0; h < height; h++)
{
for(int w = 0; w < width; w++)
{
var i = h * (width * 4) + w * 4;
if(writePixelByteArray[i + 3] > 0)
{
pixelByteArray[0] = writePixelByteArray[i ];
pixelByteArray[1] = writePixelByteArray[i + 1];
pixelByteArray[2] = writePixelByteArray[i + 2];
pixelByteArray[3] = writePixelByteArray[i + 3];
this.tempiraryBitmap.WritePixels(new Int32Rect(x + w, y + h, 1, 1), pixelByteArray, 4, 0);
}
if(textPixelByteArray[i + 3] > 0)
{
pixelByteArray[0] = textPixelByteArray[i ];
pixelByteArray[1] = textPixelByteArray[i + 1];
pixelByteArray[2] = textPixelByteArray[i + 2];
pixelByteArray[3] = textPixelByteArray[i + 3];
this.targetBitmap.WritePixels(new Int32Rect(x + w, y + h, 1, 1), pixelByteArray, 4, 0);
}
}
}
int stride = GetStride(this.tempiraryBitmap.PixelWidth, this.tempiraryBitmap.Format);
this.tempiraryBitmap.CopyPixels(this.tempiraryPixelByteArray, stride, 0);
}
#endregion
#region 텍스트 쓰기 - WriteText(fontSize, text)
/// <summary>
/// 텍스트 쓰기
/// </summary>
/// <param name="fontSize">폰트 크기</param>
/// <param name="text">텍스트</param>
/// <returns>처리 결과</returns>
private bool WriteText(double fontSize, string text)
{
FormattedText formattedText = new FormattedText
(
text,
CultureInfo.GetCultureInfo(FontCulture),
FlowDirection.LeftToRight,
new Typeface(FontName),
fontSize,
Brushes.Black
);
double angle = this.random.Next(0, Angle + 1);
Geometry geometry = formattedText.BuildHighlightGeometry(new Point());
geometry.Transform = new RotateTransform
(
angle,
geometry.Bounds.Width / 2,
geometry.Bounds.Height / 2
);
int textWidth = (int)geometry.Bounds.Width;
int textHeight = (int)geometry.Bounds.Height;
if(textWidth <= 0 || textHeight <= 0)
{
return false;
}
Color fontColor = new Color();
WordRectangle wordRectangle = this.wordRectangleList[this.rectangleIndex];
int startX = (int)wordRectangle.BoundRectangle.X;
int startY = (int)wordRectangle.BoundRectangle.Y;
int deltaX = (int)wordRectangle.BoundRectangle.Width - textWidth + startX;
int deltaY = (int)wordRectangle.BoundRectangle.Height - textHeight + startY;
for(int count = 0; count < this.RepeatCount; count++)
{
if
(
wordRectangle.BoundRectangle.Width < textWidth ||
wordRectangle.BoundRectangle.Height < textHeight ||
startX > deltaX ||
startY > deltaY
)
{
continue;
}
int x = this.random.Next(startX, deltaX);
int y = this.random.Next(startY, deltaY);
int center = (y + textHeight / 2) * (this.targetBitmap.PixelWidth * 4) + (x + textWidth / 2) * 4;
fontColor.B = this.imagePixelByteArray[center ];
fontColor.G = this.imagePixelByteArray[center + 1];
fontColor.R = this.imagePixelByteArray[center + 2];
fontColor.A = this.imagePixelByteArray[center + 3];
int fontColorSummary = (fontColor.B + fontColor.G + fontColor.R + fontColor.A) / 4;
bool successed = true;
for(int th = 0; th < textHeight && successed; th++)
{
for(int tw = 0; tw < textWidth && successed; tw++)
{
int i = (y + th) * (this.targetBitmap.PixelWidth * 4) + (x + tw) * 4;
successed = !(255 - this.imagePixelByteArray[i ] <= 2 &&
255 - this.imagePixelByteArray[i + 1] <= 2 &&
255 - this.imagePixelByteArray[i + 2] <= 2 &&
255 - this.imagePixelByteArray[i + 3] <= 2);
if(successed)
{
int current = (y + th) * (this.targetBitmap.PixelWidth * 4) + (x + tw) * 4;
byte blue = this.imagePixelByteArray[current ];
byte green = this.imagePixelByteArray[current + 1];
byte red = this.imagePixelByteArray[current + 2];
byte alpha = this.imagePixelByteArray[current + 3];
int summary = (blue + green + red + alpha) / 4;
if(Math.Abs(fontColorSummary - summary) > 10)
{
successed = false;
}
}
}
}
if(successed)
{
RotateTransform rotateTransform = new RotateTransform
(
angle,
geometry.Bounds.Width / 2,
geometry.Bounds.Height / 2
);
Point originPoint = new Point(-geometry.Bounds.X, -geometry.Bounds.Y);
DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext drawingContext = drawingVisual.RenderOpen();
drawingContext.PushTransform(rotateTransform);
drawingContext.DrawText(formattedText, originPoint);
drawingContext.Close();
RenderTargetBitmap textBitmap = new RenderTargetBitmap
(
textWidth,
textHeight,
this.dpiX,
this.dpiY,
PixelFormats.Pbgra32
);
textBitmap.Render(drawingVisual);
int stride = GetStride(textBitmap.PixelWidth, textBitmap.Format);
byte[] textPixelByteArray = new byte[stride * textBitmap.PixelHeight];
textBitmap.CopyPixels(textPixelByteArray, stride, 0);
for(int th = 0; th < textHeight && successed; th++)
{
for(int tw = 0; tw < textWidth && successed; tw++)
{
int textIndex = th * (textWidth * 4) + tw * 4;
int imageIndex = (y + th) * (this.tempiraryBitmap.PixelWidth * 4) + (x + tw) * 4;
if(textPixelByteArray[textIndex + 3] > 0)
{
successed = this.tempiraryPixelByteArray[imageIndex + 3] == 0;
}
}
}
if(successed)
{
formattedText.SetForegroundBrush(new SolidColorBrush(fontColor));
drawingVisual = new DrawingVisual();
drawingContext = drawingVisual.RenderOpen();
drawingContext.PushTransform(rotateTransform);
drawingContext.DrawText(formattedText, originPoint);
drawingContext.Close();
textBitmap.Clear();
textBitmap.Render(drawingVisual);
textBitmap.CopyPixels(textPixelByteArray, stride, 0);
drawingVisual = new DrawingVisual();
drawingContext = drawingVisual.RenderOpen();
drawingContext.PushTransform(rotateTransform);
ProcessingText(drawingVisual, drawingContext, formattedText, originPoint);
drawingContext.Close();
textBitmap.Clear();
textBitmap.Render(drawingVisual);
byte[] writePixelByteArray = new byte[textPixelByteArray.Length];
textBitmap.CopyPixels(writePixelByteArray, stride, 0);
WritePixel(textPixelByteArray, writePixelByteArray, x, y, textWidth, textHeight);
return true;
}
}
}
return false;
}
#endregion
}
}
▶ MainWindow.xaml
<Window x:Class="TestProject.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:TestProject"
Width="800"
Height="600"
Title="워드 클라우드(Word Cloud) 이미지 만들기"
FontFamily="나눔고딕코딩"
FontSize="16">
<Grid Margin="10">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="10" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<TextBlock Grid.Row="0" Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10 0 0 0"
Text="폰트" />
<ComboBox Name="fontComboBox" Grid.Row="0" Grid.Column="2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10 0 0 0"
Height="24"
SelectedIndex="1"
ItemsSource="{x:Static Fonts.SystemFontFamilies}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock
FontFamily="{Binding}"
Text="{Binding}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
<TextBlock Grid.Row="0" Grid.Column="3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10 0 0 0"
Text="폰트 크기" />
<StackPanel Grid.Row="0" Grid.Column="4"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10 0 0 0"
Orientation="Horizontal">
<TextBox Name="minimumFontSizeTextBox"
VerticalAlignment="Center"
Width="50"
Height="24"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="2" />
<TextBlock
VerticalAlignment="Center"
Margin="5 0 5 0"
Text="~" />
<TextBox Name="maximumFontSizeTextBox"
VerticalAlignment="Center"
Width="50"
Height="24"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="100" />
</StackPanel>
<TextBlock Grid.Row="0" Grid.Column="5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10 0 0 0"
Text="텍스트" />
<TextBox Name="textTextBox" Grid.Row="0" Grid.Column="6"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10 0 0 0"
Width="120"
Height="24"
HorizontalContentAlignment="Left"
VerticalContentAlignment="Center"
Text="테스트,문자열" />
<TextBlock Grid.Row="2" Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="카운트" />
<TextBox Name="repeatCountTextBox" Grid.Row="2" Grid.Column="2"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10 0 0 0"
Width="50"
Height="24"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="300" />
<TextBlock Grid.Row="2" Grid.Column="3"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="각도" />
<TextBox Name="angleTextBox" Grid.Row="2" Grid.Column="4"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Margin="10 0 0 0"
Width="50"
Height="24"
HorizontalContentAlignment="Right"
VerticalContentAlignment="Center"
Text="0" />
<TextBlock Grid.Row="2" Grid.Column="5"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Margin="10 0 0 0"
Text="워드 마진" />
<ComboBox Name="wordMarginMethodComboBox" Grid.Row="2" Grid.Column="6"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Height="24"
Margin="10 0 0 0"
SelectedIndex="1">
<ComboBoxItem Content="None" />
<ComboBoxItem Content="Blur" />
<ComboBoxItem Content="Stroke" />
<ComboBoxItem Content="Boundary" />
</ComboBox>
<Grid Grid.Row="0" Grid.RowSpan="3" Grid.Column="7"
Margin="10 0 0 0">
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Horizontal">
<Button Name="imageButton"
Width="80"
Height="30"
Content="이미지" />
<Button Name="drawButton"
Margin="10 0 0 0"
Width="80"
Height="30"
Content="그리기" />
</StackPanel>
</Grid>
</Grid>
<Grid Grid.Row="1"
Margin="0 10 0 0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="10" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0"
BorderThickness="1"
BorderBrush="Black"
SnapsToDevicePixels="True">
<Image Name="image"
Stretch="Uniform" />
</Border>
<Border Grid.Column="2"
BorderThickness="1"
BorderBrush="Black"
SnapsToDevicePixels="True">
<Viewbox>
<local:WordCloudControl x:Name="wordCloudControl" />
</Viewbox>
</Border>
</Grid>
</Grid>
</Window>
▶ MainWindow.xaml.cs
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media.Imaging;
namespace TestProject
{
/// <summary>
/// 메인 윈도우
/// </summary>
public partial class MainWindow : Window
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - MainWindow()
/// <summary>
/// 생성자
/// </summary>
public MainWindow()
{
InitializeComponent();
this.imageButton.Click += imageButton_Click;
this.drawButton.Click += drawButton_Click;
this.wordCloudControl.DrawCompleted += wordCloudControl_DrawCompleted;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Private
//////////////////////////////////////////////////////////////////////////////// Event
#region 이미지 버튼 클릭시 처리하기 - imageButton_Click(sender, RoutedEventArgs e)
/// <summary>
/// 이미지 버튼 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void imageButton_Click(object sender, RoutedEventArgs e)
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.Filter = "이미지 파일|*.jpg;*.png|JPG 파일|*.jpg|PNG 파일|*.png";
if(openFileDialog.ShowDialog().GetValueOrDefault())
{
Uri uri = new Uri(openFileDialog.FileName, UriKind.Absolute);
this.image.Source = new BitmapImage(uri);
this.wordCloudControl.ImageURI = uri;
}
}
#endregion
#region 그리기 버튼 클릭시 처리하기 - drawButton_Click(sender, e)
/// <summary>
/// 그리기 버튼 클릭시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void drawButton_Click(object sender, RoutedEventArgs e)
{
#region 폰트명을 설정한다.
if(this.fontComboBox.SelectedIndex == -1)
{
MessageBox.Show(this, "폰트 항목을 선택해 주시기 바랍니다.", "Information");
this.fontComboBox.Focus();
return;
}
string fontName = this.fontComboBox.SelectedItem.ToString();
#endregion
#region 최소 폰트 크기를 설정한다.
int minimumFontSize;
if(!int.TryParse(this.minimumFontSizeTextBox.Text, out minimumFontSize))
{
MessageBox.Show(this, "최소 폰트 크기를 입력해 주시기 바랍니다.", "Information");
this.minimumFontSizeTextBox.SelectAll();
this.minimumFontSizeTextBox.Focus();
return;
}
#endregion
#region 최대 폰트 크기를 설정한다.
int maximumFontSize;
if(!int.TryParse(this.maximumFontSizeTextBox.Text, out maximumFontSize))
{
MessageBox.Show(this, "최대 폰트 크기를 입력해 주시기 바랍니다.", "Information");
this.maximumFontSizeTextBox.SelectAll();
this.maximumFontSizeTextBox.Focus();
return;
}
#endregion
#region 텍스트 리스트를 설정한다.
string text = this.textTextBox.Text.Trim();
if(string.IsNullOrWhiteSpace(text))
{
MessageBox.Show(this, "텍스트 항목을 입력해 주시기 바랍니다.", "Information");
this.textTextBox.SelectAll();
this.textTextBox.Focus();
return;
}
string[] textArray = text.Split(',');
List<string> textList = new List<string>(textArray);
#endregion
#region 반복 카운트를 설정한다.
int repeatCount;
if(!int.TryParse(this.repeatCountTextBox.Text, out repeatCount))
{
MessageBox.Show(this, "반복 카운트를 입력해 주시기 바랍니다.", "Information");
this.repeatCountTextBox.SelectAll();
this.repeatCountTextBox.Focus();
return;
}
#endregion
#region 각도를 설정한다.
int angle;
if(!int.TryParse(this.angleTextBox.Text, out angle))
{
MessageBox.Show(this, "각도를 입력해 주시기 바랍니다.", "Information");
this.angleTextBox.SelectAll();
this.angleTextBox.Focus();
return;
}
#endregion
#region 워드 마진 메소드를 설정한다.
if(this.wordMarginMethodComboBox.SelectedIndex == -1)
{
MessageBox.Show(this, "워드 마진을 선택해 주시기 바랍니다.", "Information");
this.wordMarginMethodComboBox.Focus();
return;
}
string wordMarginMethodString = (this.wordMarginMethodComboBox.SelectedItem as ComboBoxItem).Content as string;
WordMarginMethod wordMarginMethod = (WordMarginMethod)Enum.Parse(typeof(WordMarginMethod), wordMarginMethodString);
#endregion
Draw(fontName, minimumFontSize, maximumFontSize, textList, repeatCount, angle, wordMarginMethod);
}
#endregion
#region 워드 클라우드 컨트롤 그리기 완료시 처리하기 - wordCloudControl_DrawCompleted(sender, e)
/// <summary>
/// 워드 클라우드 컨트롤 그리기 완료시 처리하기
/// </summary>
/// <param name="sender">이벤트 발생자</param>
/// <param name="e">이벤트 인자</param>
private void wordCloudControl_DrawCompleted(object sender, RoutedEventArgs e)
{
MessageBox.Show(this, "작업을 완료했습니다.", "Information");
}
#endregion
//////////////////////////////////////////////////////////////////////////////// Function
#region 그리기 - Draw(fontName, minimumFontSize, maximumFontSize, textList, repeatCount, angle, wordMarginMethod)
/// <summary>
/// 그리기
/// </summary>
/// <param name="fontName">폰트명</param>
/// <param name="minimumFontSize">최소 폰트 크기</param>
/// <param name="maximumFontSize">최대 폰트 크기</param>
/// <param name="textList">텍스트 리스트</param>
/// <param name="repeatCount">반복 카운트</param>
/// <param name="angle">각도</param>
/// <param name="wordMarginMethod">워드 마진 메소드</param>
private void Draw
(
string fontName,
int minimumFontSize,
int maximumFontSize,
List<string> textList,
int repeatCount,
int angle,
WordMarginMethod wordMarginMethod
)
{
this.wordCloudControl.FontName = fontName;
this.wordCloudControl.MinimumFontSize = minimumFontSize;
this.wordCloudControl.MaximumFontSize = maximumFontSize;
this.wordCloudControl.TextList = textList;
this.wordCloudControl.RepeatCount = repeatCount;
this.wordCloudControl.Angle = angle;
this.wordCloudControl.WordMarginMethod = wordMarginMethod;
this.wordCloudControl.Draw();
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > WPF' 카테고리의 다른 글
[C#/WPF] Image 클래스 : 마우스를 사용해 이미지 회전시키기 (0) | 2021.02.20 |
---|---|
[C#/WPF] ComponentDispatcher 클래스 : ThreadFilterMessage 정적 이벤트를 사용해 프로세스 간 메시지 송수신하기 (0) | 2021.02.20 |
[C#/WPF] Slider 클래스 : 커스텀 슬라이더 사용하기 (0) | 2021.02.20 |
[C#/WPF] Window 클래스 : 반투명 윈도우 사용하기 (0) | 2021.02.19 |
[C#/WPF] MediaElement 클래스 : 움직이는 GIF 파일 재생하기 (0) | 2021.02.19 |
[C#/WPF] 워드 클라우드(Word Cloud) 이미지 만들기 (0) | 2021.02.19 |
[C#/WPF] 로딩 패널 표시하기 (0) | 2021.02.18 |
[C#/WPF] BitmapImage 클래스 : 바이트 배열에서 비트맵 이미지 구하기 (0) | 2021.02.18 |
[C#/WPF] BitmapSource 클래스 : 바이트 배열에서 비트맵 소스 구하기 (0) | 2021.02.18 |
[C#/WPF] BitmapImage 클래스 : 바이트 배열 구하기 (0) | 2021.02.17 |
[C#/WPF] Slider 엘리먼트 : 커스텀 슬라이더 사용하기 (0) | 2021.02.16 |
댓글을 달아 주세요