Shape에 Effect 넣기

예전에 PowerPoint 2007을 설치하고 글자와 도형에 멋진 효과를 넣을 수 있는 것을 보고 직접 구현해 보았다. GDI+을 이용하였으며 Native C++로 구현하였다. 필요한 자료를 찾던 중에 윈도우즈 개발 환경은 이제 C#으로 넘어가야하나라는 생각이 강하게 들었다. 같은 GDI+라도 .NET과 Native Win32에서 제공되는 클래스가 차별화되어있고 많은 GDI+ 관련 자료가 C#으로 되어 있는 이유이다.

위의 실행 결과를 보면 Shape에 입체효과(베벨 또는 엠보싱)와 그림자를 넣어주었다. 이미지 처리에서 사용되는 Convolution Filter로써 Blur Filter와 Emboss Filter를 사용하여 효과를 주었다.

간단이 과정을 살명하면 먼저 도형을 GraphicsPath를 통해 만들고 이 Path를 임시 버퍼에 검정색으로 그린뒤, 이 버퍼에 Blur 효과를 주고 화면상에 그리면 그림자가 그려지게 된다. 여기에 사용한 Convolution Filter는 다음과 같다.

const int filterHalfSize = 4;
int convolutionFilter[(filterHalfSize*2+1)*(filterHalfSize*2+1)] = { 
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
	1, 1, 1, 1, 1, 1, 1, 1, 1,
};

Divider 값은 필터의 크기가 9이므로 그 두배인 81로 했다. 이렇게 그림자를 그린후에 이 위에 그대로 Path를 그린다. 이제 다시 Emboss 효과를 줘서 만든 이미지를 생성하는데 여기에 사용한 Filter는 다음과 같다.

const int filterHalfSize2 = 1;
int convolutionFilter2[
	(filterHalfSize2*2+1)*(filterHalfSize2*2+1)] = { 
	1, 0, 0,
	0, 0, 0,
	0, 0, -1,
};

Divider 값은 1로 했으며(즉, 평균을 내기 위해 나누지 않았으며) 결과값에 32를 더했다. Emboss 효과를 적용해서 만들어진 이미지는 다음과 같다.


이것을 그대로 사용하면 않되고 원래 도형의 모양으로 Clipping 해야한다. GDI+의 grfx의 SetClip 함수를 이용해 Clipping해서 그려주게 되면 완료된다.

과거에는 이러한 효과를 실시간으로 적용하기에는 연산 속도가 느려 전문분야에나 활용되다가 이제는 윈도우즈 비스타의 UI에 기본적으로 사용되고 있다. 갑작이 드는 생각은 OS에서 버전업이라는 개념은 그 속내의 변화보다는 겉모양의 변화가 발생했을때 이루어지고 있다는 것이다. 속내의 변화는 서비스팩이나 업데이트를 통해 이루어지고 있고 새로운 제품으로써 버전업은 일반 사용자에게 가장 강하게 어필할 수 있는 UI 부분의 진보이다. 이러한 UI의 중요성을 절대 무시해서는 않될 것 같다.

STL의 map 컨테이너 테스트

STL의 컨테이너 중에서 Hash 검색 알고리즘을 사용하고 있는 map 컨테이너를 테스트 해보았다. Hash 검색 알고리즘은 검색 항목의 개수에 상관없이 검색시간은 항상 일정하다라고 되어 있다. 바로 이것을 테스트해 보고자 했다.

N의 개수는 천만(10,000,000)개를 생성했으며 hash key는 unsigned long 타입으로 했다. 0, 2000000, 5000000, 7000000, 9999999 hash key 값을 검색하여 그곳에 -1을 넣었다. 과연 상수 시간이 나올 것인가? 나올 경우 시간은 얼마나 걸릴것인가…

아래는 그 결과다..


소요 시간은 분명 상수이긴 한데…. 0초다. 분명 0초는 아니겠지만 거의 0초가 걸린다. 사실 믿기지가 않는다. 아래는 테스트한 코드인데… 잘못된 부분이라도 있는건가? 옳바르다면 stl의 map 컨테이너는 정말 물건이다…

<#include 
#include <sys/timeb.h>
#include 

using namespace std;

void PerformanceTest(bool bStart, const char *szTitle="") {
    static timeb start;
    static timeb stop;

    if(bStart) {
        ftime(&start);
    } else {
        ftime(&stop);

        double gab;
        gab = 1.0e3 * difftime(stop.time, start.time);
        gab+=(double)(stop.millitm - start.millitm);

        double ReturnValue = gab/1.0e3;
        printf( "%s: %.10lf sec required.\n", szTitle, ReturnValue);
    }
}

map container_;

int _tmain(int argc, _TCHAR* argv[])
{
    PerformanceTest(true);
    for(unsigned long i=0; i<10000000; i++) {
        container_[i] = i;
    }
    PerformanceTest(false, "generating 10000000 data");

    printf("Now container has %ld items.\n\n", container_.size());

    for(size_t i=0; i<5; i++) {
        PerformanceTest(true);
        container_[0] = -1;
        PerformanceTest(false, "finding index 0:");

        PerformanceTest(true);
        container_[2000000] = -1;
        PerformanceTest(false, "finding index 2000000:");

        PerformanceTest(true);
        container_[5000000] = -1;
        PerformanceTest(false, "finding index 5000000:");

        PerformanceTest(true);
        container_[7000000] = -1;
        PerformanceTest(false, "finding index 7000000:");

        PerformanceTest(true);
        container_[10000000-1] = -1;
        PerformanceTest(false, "finding index 9999999:");
    }

    printf("\n[%ld %ld %ld %ld %ld]\n", 
        container_[0], 
        container_[2000000], 
        container_[5000000], 
        container_[7000000], 
        container_[9999999]
    );

    return 0;
}

GDI+에서 화면에 표시될 String(문자, Text)의 정확한 Width(폭) 구하기

GDI+에서 graphics의 MeasureString 함수를 통해 출력된 문자열의 폭을 구해보면 기대했던 폭 보다 다소 넓게 구해진다. MeasureString 보다 훨씬 정확한 폭을 구하는 방법에 대한 코드이다.

static public int MeasureDisplayStringWidth(Graphics graphics,
    string text, Font font)
{
    System.Drawing.StringFormat format = 
        new System.Drawing.StringFormat();

    System.Drawing.RectangleF rect = 
        new System.Drawing.RectangleF(0, 0, 1000, 1000);

    System.Drawing.CharacterRange[] ranges  = { 
        new System.Drawing.CharacterRange0,text.Length) 
    };

    System.Drawing.Region[] regions = new System.Drawing.Region[1];

    format.SetMeasurableCharacterRanges(ranges);

    regions = graphics.MeasureCharacterRanges(text, font, rect, format);
    rect = regions[0].GetBounds(graphics);

    return (int)(rect.Right + 1.0f);
}
옮겨온 곳 : Code Project, Pierre Arnaud의 글

 

참고로, 이 방법을 알기 전에는 필자는 String에 대한 Path를 만든 후, 즉 GraphicsPath를 만들고  GetBounds 매서드를 통해 크기를 얻었다. 하지만 이 방법은 MeasureString에 비해 정확하기는 하지만 역시 다소 부정확하다. 하지만 여전이 실제 프로젝트 적용에 대해서는 Path를 이용한 방법을 사용하고 있다.

[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에 해당한다.






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