[X-File] GDI+ 관련, drawString과 GraphicsPath의 차이

GDI+는 정말 사용하기 편하다고는 말 못해도.. -_-; 사용하면 할 수록 기능면에서는 무척 뛰어난 라이브러리로 생각된다. 하지만 가끔씩 이해하기 힘든 시츄레숀…를 만들곤 해서 날 당황하게 한다. 그래도 그때 그때 잘해결해 나갔는데.. 이번 문제는 해결의 시발점도 찾지 못하것다. 해결 방법을 검색해 볼레도.. 도통 검색 키워드를 뭐로해야할지도 모르겠고… 결국 해결하지 못한 이 문제로 인해 꽤나 많은 시간(대략 5~6시간)을 낭비…


문제는 GDI+를 이용해 문자를 그리는 것으로 그냥 DrawString을 이용해 그리는 것과 GraphicPath를 이용해 문자의 Path를 만든후 그 Path를 그려주는, 두 가지 방법에 대한 결과를 비교한 것이다. “안녕하세요”라는 문자가 보이는데, 노란색은 DrawString을 이용해 그린 것이고, 검정색 외곽선은 Path를 이용해 그린것이다.

똑같은 위치, 폰트, 크기, 정렬, 스타일, 크기단위로 지정해 주었음에도 Path가 문자의 폭이 조금 더 넓게 나온다. 높이는 문제가 없다. 유독 폭만 틀리다.

음…. 이 글을 작성하면서 떠오르는 것이.. 바로 “높이는 문제가 없으나, 폭만 다르다”라는 것에 힌트를 얻을 수 있겠다는 생각이 든다…….

문득 생각이 난 검색어로 “GDI+ drawString GraphicsPath”로 검색해 본 결과 동일한 문제에 대한 Q/A가 있었고 drawString과 GraphicsPath에 대한 속도 테스트 데모를 찾을 수 있었다. 먼저 몇개 Q/A의 경우 drawString과 Path를 이용한 렌더링 방법이 다르다는 점을 들어 다를 수밖에 없다라는 답변이다. 하나의 예로 문자의 경우와 그 외의 그래픽 요소에 대한 렌더링 퀄러티에 대한 옵션 지정이 다르다는 점이다. 개인적으로 볼때 그럴듯하지만 확실하지 않은 답변인데다가 근본적인 해결책을 제시하지 못하는 답변이라 도움이 되지 못했다. 속도 테스트 데모의 경우 실행을 해보니 처음 나타난 drawString과 Path를 이용한 문자열의 크기가 똑 같아 보였으나 폰트를 늘려보니 역시 Path를 이용한 문자열 출력이 drawString보다 더 길게 나왔다. 즉, 동일한 문제점이었다.

유추되는 한가지 원인을 말해보면, 앞서 언급했던 바와 같이 높이는 같으나 폭만 다르다는 점이다. “자간”이 문제가 아닌가 싶다. 즉, drawString의 경우 동일 폰트의 각각의 문자들 마다 다르게 지정되어져 있는 자간값이 고려되어져 화면상에 그리는 반면, Path의 경우 자간이 고려되지 않고 동일한 자간의 값으로 문자들에 대한 Path가 만들어지고 이렇게 만들어진 Path가 그려지게 되기때문이 아닌가 싶다.

여튼, 결국 해결 방법을 찾지 못하고 덮는다.

간단한 프랙탈 그래픽 중 하나인 L-System의 응용

간단한 코드지만 그 결과는 굉장(?)하기로 소문난 프랙탈 그래픽 중에 나무 모양을 그려내는 L-System을 작성해보았다. 아래는 그 코드..

void DrawLSystem(SDL_Surface *screen, int l, int d, int n)
{
    float dx = l * sin(d * (M_PI/180.0));
    float dy = l * cos(d * (M_PI/180.0));

    if(n < 7) 
        LineRel(screen, -dx, -dy, 0, 255, 0); 
    else 
        LineRel(screen, -dx, -dy, 100, 120, 0); 

    if(n > 0) {
        DrawLSystem(screen, l*0.6, d+random()%40+30, n-1);
        DrawLSystem(screen, l*0.6, d-random()%40-30, n-1);
        DrawLSystem(screen, l*0.7, d+random()%30, n-1);
    }

    MoveRel(dx, dy)
}

SDL_Surface는 정확한 비교는 아니지만 Windows에서는 HDC에 해당한다.






그 결과이다. 상당한 짧은 코드에 비해서 그 결과는 자못 화려하다. 🙂

[Linux/Fedora] MP3 Player(Audacious) 설치

기본적으로 페도라에서는 MP3를 연주할 수 없습니다. 플러그인을 설치하라는 힌트와 함께 말입니다. 이 방법은 플러그인 방식은 아닌것같고, 새로운 재생기를 설치하는 것입니다. 그 재생기의 이름은 Audacious입니다. 이 재생기 역시 MP3를 연주하기 위해서는 플로그인을 설치하는 방식입니다. 아래는 yum을 이용해 재생기와 MP3 플러그인은 물론 WMA 플러그인을 설치하는 방법입니다.

su -c “yum install audacious audacious-plugins-nonfree-mp3 audacious-plugins-nonfree-wma”



그런데, 위의 명령으로 해보니 MP3 플러그인은 잘설치가 되는데 wma 플러그인은 설치가 되지 않는듯합니다. 아마도 위의 명령 문자열에 이상이 있는듯합니다. 하지만 뭐… MP3는 잘 연주가 되는군요..

[.NET] C#을 이용한 PropertyGrid 사용법에 대한 Summary

드디어 마지막으로 PropertyGrid의 속성을 변경하기 위한 사용자 정의 UI를 붙여보는 것에 대해 살펴 보겠다. 우리가 구현할 결과물의 최종 화면은 아래와 같다.


즉, MyValue라는 프로퍼티의 값을 설정하기 위해 TrackBar 컨트롤을 이용해 보는 것이다. TrackBar를 이용해 값을 설정한 후에 확인 버튼을 누르면 설정된 값이 프로퍼티에 반영이 되도록한다.

순서야 개발자 나름이겠지만, 설명의 편의를 위해 먼저 사용자 정의 UI에 해당하는, 즉 TrackBar 컨트롤과 확인 버튼, 그리고 라벨을 담고 있는 폼을 하나 정의한다.


여기서 TrackBar 컨트롤의 접근자를 원래의 Private에서 Public으로 변경한다. 이는 외부의 클래스에서 접근할 수 있도록 하기 위해서이다. 그리고 이겋게 추가한 폼의 이름을 frmMyValue라고 정하고, 이 폼의 속성을 아래와 같이 변경한다.

+ FormBorderStyle – None
+ MinimizeBox – False
+ MaximizeBox – False
+ ShowInTaskbar – False

위의 속상은 속성창을 통해서 수정이 가능하다. 하지만 변경해야할 속성이 하나 더 있는데, 이 속성은 속성창에서 볼 수 가 없다. 그 속성은 TopLevel이라는 속성으로 이 값을 이 폼의 색성자에서 false로 설정해야 한다. 이 것은 매우 중요하다. 이 값을 설정하지 않으면 프로퍼티에 우리의 사용자 폼이 붙지 못하기 때문이다.

그리고 변수를 2개 추가한다. PropertyGrid의 특정 프로퍼티와 통신을 할 수 있는 수단이 되는 IWindowsFormEditorService라는 인터페이스 변수인 _wfes와 TrackBar 컨트롤을 조작하여 얻은 값을 저장할 변수인 int 형의 MyValue을 추가한다.

이렇게 변수와 몇가지 설정이 끝났다면 이제 UI의 행위에 해당하는 매서드를 정의해야하는데, 아래의 코드로 그 설명을 대신한다.

public partial class frmMyValue : Form
{
    public int MyValue;
    public IWindowsFormsEditorService _wfes;
        
    public frmMyValue()
    {
        InitializeComponent();
        TopLevel = false;
    }

    private void frmMyValue_Load(object sender, EventArgs e)
    {
        label1.Text = "Value : " + MyValue;
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Close();
    }

    private void trackBar1_Scroll(object sender, EventArgs e)
    {
        MyValue = trackBar1.Value;
        label1.Text = "Value : " + MyValue;
    }

    private void frmMyValue_FormClosed(object sender, 
        FormClosedEventArgs e)
    {
        _wfes.CloseDropDown();
    }
}

잘 살펴보면 어렵지 않게 코드를 이해할 수 있을 것이다. 그러나 한가지 새로운 것이 있는데 폼이 닫힐때(Close)할때 발생하는 이벤트에서 앞서 정의한 IWindowsFormEditorService 타입의 변수인 _wfes의 CloseDropDown 매서드를 호출하였다. 이것은 이 폼이 닫힐때 마친가지로 PropertyGrid의 프로퍼티에 붙은 사용자 정의 폼을 담을 컨테이너을 닫도록 하는 코드이다. 폼이 닫혔으므로 이 폼을 담을 컨테어너 역시 닫여야 하지 않겠는가.

이제 프로퍼티에 대한 정의를 담고 있는 클래스를 만들어보자. 이 클래스의 이름은 편의상 MyProperty라고 하였다. 추후 이 클래스의 인스턴스는 PropertyGrid의 SelectedObject에 할당된다.

public class MyProperty {
    private int _value;

    [Browsable(true)]
    [Editor(typeof(MyValueEditor), typeof(System.Drawing.Design.UITypeEditor))]
    public int MyValue
    {
        get { return _value; }
        set { _value = value; }
    }
}

MyProperty 클래스는 MyValue라는 프로퍼티 하나만 정의하고 있으모 이 프로퍼티에 대한 특성 중에 Editor 특성자에 대한 인자의 내용으로 MyValueEditor와 UITypeEditor의 type을 사용하고 있다. MyValueEditor은 UITypeEditor에서 상속받아 우리가 새롭게 정의할 클래스이다. MyValueEdior 클래스의 코드가 그렇게 길지 않으니 한번에 살펴보기로 하자.

public class MyValueEditor : UITypeEditor
{
    public override UITypeEditorEditStyle GetEditStyle(
        ITypeDescriptorContext context)
    {
        return UITypeEditorEditStyle.DropDown;
    }

    public override object EditValue(ITypeDescriptorContext context,
        IServiceProvider provider, object value)
    {
        IWindowsFormsEditorService wfes = provider.GetService(
            typeof(IWindowsFormsEditorService)) 
                as IWindowsFormsEditorService;

        if (wfes != null)
        {
            frmMyValue _frm = new frmMyValue();

            _frm.trackBar1.Value = (int)value;
            _frm.MyValue = _frm.trackBar1.Value;
            _frm._wfes = wfes;

            wfes.DropDownControl(_frm);
            value = _frm.MyValue;
        }

        return value;
    }
}

이 클래스는 GetEditStyle이라는 매서드와 EditValue라는 매서드를 재정의하고 있다. GetEditStyle은 프로퍼티에 붙을 사용자 정의 UI를 나타내기 위해 누를 버튼의 모양을 결정하는 것으로 할당할 수 있는 값은 모두 3가지 이다. 하나는 DropDown이며 삼각형 문자가 담긴 버튼이고 두번째는 Modal이며 … 문자가 담긴 버튼이며 세번째는 None로써 버튼 자체가 생기지 않는다. 이렇게 되면 사용자 정의 UI를 나타낼 방법이 없다. 여기서는 DropDown을 사용하였다. 그리고 EditValue는 사용자 정의 UI를 보이는 시점에서부터 사라질때까지의 영향을 미치는 매서드이다. 과정을 살펴보면 IWindowFormsEditorService 타입의 서비스 인스턴스를 구해 일단 wfes라는 변수에 담아 놓는데, 이는 앞서 정의한 폼(frmMyValue)의 맴버 변수인 _wfes에 참조시켜 실제적으로 프로퍼티와 사용자 UI을 연결짓는 중요한 역활을 하게된다. 그리고 EditValue는 정의한 frmMyValue 폼을 생성시키고 TrackBar 컨트롤을 초기화한 후에 IWindowsFormsEditorService의 DropDownControl을 호출함으로써, 이 시점에서 사용자 정의 UI를 화면에 보이도록 한다. 이 시점에 사용자 정의 UI가 화면상에서 사라질때까지 Blocking된다. 사용자 폼이 사라지면 EditValue 매서드의 인자인 value에 사용자 정의 UI에서 설정한 값을 담고 반환한다. 다소 무리한 설명이라고 생각되나 다시금 찬찬이 읽어본후 코드를 살펴보면 이해가 수월할 것이다. 이상으로 C#을 이용해 PropertyGrid를 이용하는 것에 대해 정리해보았다.