첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.

728x90
반응형
728x170

TestProject.zip
0.02MB

▶ 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
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요