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의 중요성을 절대 무시해서는 않될 것 같다.

C++/CLI의 Dispose Pattern에 대한 고찰

리소스 해제를 위한 .NET 개발환경에서 제공하는 Dispose 패턴을 파악하기 위해 테스트용으로 적용할 클래스 정의는 다음과 같으며 총 세가지의 경우로 시험을 해보았다.

ref class T : public IDisposable {
public:
    T() {
        Console::WriteLine(L"T() invoked");
    }

    ~T() {
        Console::WriteLine(L"~T() invoked");
    }

    !T() {
        Console::WriteLine(L"!T() invoked");
    }
};

첫번째 시험 코드는 마치 지역변수처럼 할당하는 경우이다. 하지만 절대 지역 변수가 아니라는 점.. CLR Heap에 할당된다.

int main(array ^args)
{
    T a;
    return 0;
}

실행 결과는 다음과 같다. 지역변수처럼 변수의 유효 Scope를 벗어나는 순간 소멸자가 호출되었다. 실제 IL에 의해 구현된 내부 흐름은 소멸자 호출이 아니라 IDisposable::Dispose 매서드의 호출이다. 즉, 소멸자가 IDisposable::Dispose의 재구현이다. 또한 내부적으로 GC에 의해 호출되어져야했을 Finalize 매서드는 호출되지 않게 조치된다. C#과는 다르게 Finalize가 호출되지 않도록 자동화되었다는 점이 매우 특이하다.

T() invoked
~T() invoked

두번째 시험 코드는 C++에서는 포인터 개념으로 생각되는, 즉 C++/CLI는 Handle 개념으로 CLR의 Heap에 객체를 생성하였고 delete를 호출하지 않은 경우이다.

int main(array ^args)
{
    T ^a = gcnew T();
    return 0;
}

실행 결과는 다음과 같다. Finalize에 해당하는 !T()가 호출되었다는 점에 유의하자.

T() invoked
!T() invoked

세번째 시험 코드는 두번째와 다르게 delete 연산자를 적용해 주었다.

int main(array ^args)
{
    T ^a = gcnew T();
    delete a;
    return 0;
}

실행 결과는 다음과 같다. Finalize가 아닌 Dispose가 호출되었다.

T() invoked
~T() invoked

여기서 얻을 수 있는 가장 중요한 한가지 결론은 ~T()에 해당하는 Dispose()와 !T()에 해당하는 Finalize()의 코드는 절대로 같이 호출되지 않는다는 점이다. C++/CLI의 사용자가 .NET의 참조형 변수를 지역변수처럼 사용하든지, gcnew에 의해 할당하여 사용하든지.. 또한 사용한 후 delete를 했든지, 하지 않았든지 간에 ~T()와 !T() 둘중에 하나는 반드시 실행되다는 점이다. ~T()는 기존의 C++ 개념으로써 호출되며 !T()는 .NET의 GC에 의해 호출된다. 즉, 리소스 해제를 위한 코드는 ~T()와 !T()에 똑 같이 중복적으로 와야한다고 생각한다.

[C++] LPCSTR Type을 LPOLESTR Type으로 변환

HRESULT __fastcall AnsiToUnicode(LPCSTR pszA, LPOLESTR* ppszW) { 
    ULONG cCharacters;
    DWORD dwError;

    // If input is null then just return the same.
    if (NULL == pszA)
    {
        *ppszW = NULL;
        return NOERROR;
    }

    // Determine number of wide characters to be allocated for the
    // Unicode string.
    cCharacters =  strlen(pszA)+1;

    // Use of the OLE allocator is required if the resultant Unicode
    // string will be passed to another COM component and if that
    // component will free it. Otherwise you can use your own allocator.
    *ppszW = (LPOLESTR) CoTaskMemAlloc(cCharacters*2);
    if (NULL == *ppszW)
        return E_OUTOFMEMORY;

    // Covert to Unicode.
    if (0 == MultiByteToWideChar(CP_ACP, 0, pszA, cCharacters,
                  *ppszW, cCharacters))
    {
        dwError = GetLastError();
        CoTaskMemFree(*ppszW);
        *ppszW = NULL;
        return HRESULT_FROM_WIN32(dwError);
    }

    return NOERROR;
}

SOAP, SOAP, SOAP, SOAP.. 지금 내 머리속엔 비누방울이 아른 아른~ 아침 출근할때 감기 바이러스가 몸에 침투를 했나부다. 몸에 열… 점심때 후식으로 먹은 빵이 불량식품이였나부다. 속이 부글부글.. 뼈를 싹인다는 Coco Cola로 일단 속을 달래는 중….

으.. 아까 콜라 사러 1층 편의점에 가려고 엘리베이터타려고 기다리는데 (사무실은 11층).. 이 놈의 건물의 엘리베이터의 움직이는 속도가 정말 장난이 아니다.. 가끔 엘리베이터를 기다리는 건지… 한없이 감감 무소식으로 오지 않는 지하철을 기다리는건지….. 성질 급한 사람은 가끔 화가 날 법도 하다.. 나같이 오늘처럼 말이다..