상세 컨텐츠

본문 제목

GDI+ 설정방법, 이미지출력, 투명화, 더블버퍼링, 이미지회전, jpg생성, 글자 출력, GDI와 데이터형 변환 관련

프로그래밍 관련/MFC

by AlrepondTech 2017. 3. 21. 15:59

본문

반응형
728x170

 

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

 

출처 : 

http://blog.naver.com/najira00/60058229279

 

여지껏 GDI를 사용하다가 vc++ 2005로 갈아타게 되는 김에 GDI+를 한번 공부해보았습니다.
GDI+는 GDI에 업그레이드 버젼이며, 
자세한 정보및 강좌는 제 블로그 Library란에 보시면 강좌가 링크되어있습니다.
저는 잘 못느끼겠지만..속도는 GDI보다 느리다고 하더군요.
그렇지만 한번 GDI+로 코딩하면서 느낀것은 너무너무 간편하다는 점이였습니다.
코딩이 달랑 몇줄에 끝나기도 하고...
별다른 코딩없이 JPG등의 파일을 생성하기도 하고 제어하기도 한다는점이 매력적이였습니다.
왠만한 함수들은 이미 구현이 되어있더군요.
어쨌든 gdi+를 처음 해보시려고 하는 분들에게 작은 도움이 되지 않을까 해서 이렇게 글을 올려봅니다.
참고로 제 환경은 vc++ 2005입니다.

GDI+를 사용하기 위한 설정방법
먼저 GDI+를 사용하기 위해서는 h파일에 다음과 같이 선언해주셔야합니다.
#include <GdiPlus.h> //header 포함
using namespace Gdiplus; //네임스페이스 설정

그후 cpp 파일에 전역 변수로 토큰을 생성합니다.
참고로 토큰은 각 클래스 별로 이름이 달라야합니다.
ULONG_PTR g_GdiPlusTokenBoxData; //다른 class에서 만들때는 g_GdiPlusTokenAnother; 이런식으로 이전과 동일하면 안됩니다.
그후 class의 생성자와 소멸자 부분에 각각 gdi+ 사용 함수와 제거 함수를 넣어주어야합니다.
생성자 부분에는
//->gdi+ start
GdiplusStartupInput GdiplusStartupInput;
GdiplusStartup(&토큰변수이름(여기서는 g_GdiPlusTokenBoxData), &GdiplusStartupInput, NULL);

소멸자 부분에는
//->gdi+ shutdown 
GdiplusShutdown(토큰변수이름(여기서는 g_GdiPlusTokenBoxData));

이렇게 하면 gdi+를 사용하실 준비가 끝난것입니다.

이미지 출력 (JPG 파일(혹은 외부 이미지)을 가져와 GDI+로 출력)
기존 GDI에서는 dc얻어오고, dc만들고, bitmap 만들고 벼라별 코딩을 다해야했습니다.
사실 그게 어렵거나 긴 코딩은 아니지만..GDI+에서는 확연할 정도로 코딩이 짧아집니다.
단 몇줄이면 이미지를 출력할수 있고, 복잡하지 않아서 나중에 수정할때도 편리합니다.
CPaintDC dc(this); //혹은 CCLientDC dc(this); //On_Paint라면 처음부터 존재하고, OnPaint가 아니라면 CClient로 생성합니다.
Graphics ScreenG(dc.GetSafeHdc()); //dc에서 dc핸들을 가져와서 Graphics에 연결합니다. 
                                                      //이제 이곳에다 그리면 바로바로 출력됩니다. 여기서 ScreenG변수가 모니터라고 보시면됩니다.
Bitmap *pBitmap; //Bitmap 포인터변수입니다. 기존 GDI에서 쓰던게 아니니 대소문자에 조심해주세요
pBitmap = (Bitmap*)Image::FromFile(가져올JPG경로); //외부 이미지를 가져옵니다.
ScreenG.DrawImage(pBitmap, 0, 0); //모니터에 가져온 이미지를 뿌려줍니다. 
                                                   //여기서 2,3번째 인자에 0이 들어가면 이미지 전체가 출력됩니다.
                                                   //당연히 스크린좌표는 아니고 해당 핸들에 부여된 영역을 의미합니다.
delete pBitmap; //delete 안해주시면 메모리에 남아 코딩에 따라서 어마어마한 메모리 누수가 발생할수 있습니다.
                       //포인터가 아닌 Bitmap은 지우실 필요없고, 또한 해당 변수에 이미지랑 연결도 안해놓고 delete 하면 에러납니다.

이미지 출력 (RESOURCE에서 이미지를 가져와 GDI+로 출력)
위에 처럼 경로에 있는걸 가져올수도 있지만, 당연히 RESOURCE에 이미지도 가져올수 있습니다.
참고로 리소스에 이미지를 추가하시려면 vc++왼쪽탭에 리소스뷰에 오른쪽 마우스 버튼 클릭->리소스 추가->가져오기를 선택하시고 원하는 이미지를 선택하시고 이름 수정 원하시면 수정하시면 됩니다.),
어쨌든..이미지 하나를 IDB_BITMAP에 저장해두었다면
위 코드에서 한줄만 바꿔주시면 됩니다.
CPaintDC dc(this);
Graphics ScreenG(dc.GetSafeHdc());
Bitmap *pBitmap;
pBitmap = Bitmap::FromResource(AfxGetInstanceHandle(),(WCHAR*)MAKEINTRESOURCE(리소스명(여기서는 IDB_BITMAP)));
ScreenG.DrawImage(pBitmap, 0, 0);
delete pBitmap;
이렇게 하시면 리소스에 있는 이미지도 가져와서 뿌려주실수 있습니다.

이미지 출력 (원하는 영역에 원하는 크기만큼 출력)
위에 코드는 단순히 절대크기의 이미지를 출력하는 방법입니다.
해당 핸들에 부여된 시작점 0,0 부터 이미지 크기 그대로 출력하는 방법입니다.
원하는 영역에서 원하는 크기만큼 출력하고 싶다면 DrawImage 인자만 바꾸어 넣으시면 됩니다.
ScreenG.DrawImage(pBitmap, Rect(x, y, sizex, sizey), 0, 0, sizex, sizey, UnitPixel);
여기서 x와 y는 모니터에 뿌려줄 시작 위치입니다.
sizex와 sizey는 해당 이미지의 크기입니다.
이 코딩을 해석하면 pBitmap에 연결된 이미지를,
원본 이미지의 0, 0 위치부터 sizex, sizey크기만큼을,
모니터에(dc에 핸들에 부여된 영역기준) x, y 위치부터 sizex, sizey 만큼 그려줘라.
라는 의미입니다.
여기서 Rect(x, y, sizex, sizey)를 수정하면 이미지를 작게, 혹은 크게 그려줄수 있습니다.
0, 0, sizex, sizey 부분도 역시 수정하면 원본 이미지의 원하는 위치를 선택할수 있습니다.

참고로 Bitmap 변수는 pBitmap->GetWidth(), pBitmap->GetHeight()를 통해 이미지 가로 세로 크기를 구할수 있습니다.
예) ScreenG.DrawImage(pBitmap, Rect(x, y, sizex, sizey), 0, 0, pBitmap->GetWidth(), pBitmap->GetHeight(), UnitPixel);

이미지 회전 (원하는 이미지를 회전)
이미지를 회전 시킬수 있습니다.
원하는 포인터를 제자리에서 회전할수 있고, 원하는 위치를 기준으로도 회전 시킬수 있습니다.
회전을 위해서는 가장 쉬운게 RotateFlip()이란느 함수입니다.
그런데, 이 함수는... 정해진 각밖에 못움직입니다.
만약 자유롭게 회전 시키려고 하면 쓸수가 없습니다.
그래서 RotateAt()함수와 Matrix 변수를 이용해야합니다.
CPaintDC dc(this);
Graphics ScreenG(dc.GetSafeHdc());
Bitmap *pBitmap;
pBitmap = (Bitmap*)Image::FromFile(가져올JPG경로);
Gdiplus::Matrix matrix;
matrix.RotateAt(원하는 각도(예:235.7), Gdiplus::PointF((float)(pBitmap->GetWidth()/2), (float)(pBitmap->GetHeight()/2)));
                           //해당 이미지의 정중앙을 기준으로 235.7도 돌리라는 얘기입니다. 
                           //참고로 가운데를 기준으로 돌리기 위해서는 이미지의 가로세로 사이즈가 홀수여야 깔끔하게 돌아갈겁니다.
ScreenG.SetTransform(&matrix); //ScreenG를 위에 규칙대로 돌려버렸습니다.
ScreenG.DrawImage(pBitmap, 0, 0); //이미 돌아간 ScreenG에 pBitmap 이미지를 그려줍니다. 알아서 같이 돌아가게됩니다.
delete pBitmap;

이미지 틀기 (원하는 이미지를 사각형이 아닌 평행사변형으로 출력)
이방법은 제 블로그 Library에 가셔서 GDI+강좌 링크에 가서 보세요.
저는 쓸데가 없어서 안해보았습니다.

이미지 출력 (Bitmap 이미지 투명화)
투명화도 쉽게 할수 있습니다.
ImageAttributes 변수를 만들고 low와 high colorkey를 설정합니다.
RGB()가 아닌 Color 변수가 인자로 들어가며, low와 high 사이에 존재하는 모든 색은 자동으로 투명화 처리됩니다.
한 색깔만 투명화 시키고자 할때는 둘다 똑같은 같으로 넣어주면 됩니다.
ImageAttributes imageAttr;
imageAttr.SetColorKey(투명화시킬 low Color(예:Color(243,109,109)), 투명화시킬 high Color(예:Color(243,109,109)));
ScreenG.DrawImage(pBitmap, Rect(0, 0, sizex, sizey), 0, 0, sizex, sizey, UnitPixel, &imageAttr);

Bitmap 변수 제거(메모리 누수 방지)
위에 쓴것처럼 포인터로 만들어진 Bitmap 변수에 값이 존재한다면, 반드시 delete 해주어야 합니다.
그러나 코딩에 따라 해당 변수가 멤버 변수라서 값을 넣어줄때도 있고 아닐때도 있다면, delete 시킬 기준이 필요합니다.
해당 번지가 비었는지 아닌지를..알 방법이 없더군요..
그래서 편법을 사용했습니다.
Bitmap *m_pBitmap; 선언후에,
m_pBitmap = NULL; 을 넣어서 초기화 해주고
포인터 값을 넣어줄때는 그냥 넣어주고, 
delete 시킬때는 반드시 NULL인지 확인하고 NULL이면 지워주고 다시 NULL을 넣어줍니다.
if (m_pBitmap != NULL) {
    delete m_pBitmap;
    m_pBitmap = NULL;
}
그러면 변수가 비워졌는지 아닌지 판별이 가능해집니다.

더블버퍼링(GDI+를 이용한 더블버퍼링)
더블 버퍼링이란 이미지를 출력할때 바로 모니터에 내보내면서 깜빡이는 문제를 해결하기 위한 방법입니다.
메모리에 먼저 그리고 싶은걸 모두 그려주고, 이미 그려진 메모리에 이미지를 단한번 출력함으로써 깜빡임을 방지합니다.
GDI+를 이용해서 더블버퍼링을 하는 법 역시 GDI+에 비해 간단합니다.
CPaintDC dc(this);
Graphics ScreenG(dc.GetSafeHdc()); //dc의 핸들을 가져와 실제 모니터에 내보낼 Graphics변수입니다
Bitmap *pBitmap; //출력할 이미지 변수입니다
pBitmap = (Bitmap*)Image::FromFile(가져올JPG경로);
Bitmap memBitmap(pBitmap->Width(), pBitmap->Height()); //가져온 이미지의 크기만큼 Bitmap 파일을 하나 만듭니다.
Graphics memG(&memBitmap); //메모리에 그려줄 Graphics변수입니다.
memG.DrawImage(pBitmap, 0, 0); //메모리에 먼저 이미지를 그려줍니다.
ScreenG.DrawImage(&memBitmap, 0, 0); //메모리에 이미 그려진걸 가져옵니다.
delete pBitmapBack; //memBitmap은 delete해줄수 없습니다. 자동으로 메모리에서 사라지니 포인터 변수만 delete해주세요

JPG 파일 생성 (GDI+로 이미지를 JPG파일로 생성)
그냥 가지고 가셔서 붙여넣으시면 됩니다. -.-;
CLSID Clsid;
EncoderParameters Para;
UINT num, size;
ImageCodecInfo *arCod;
GetImageEncodersSize(&num, &size);
arCod=(ImageCodecInfo *)malloc(size);
GetImageEncoders(num,size,arCod);
for (UINT i = 0;i < num; i++) {
        if(wcscmp(arCod[i].MimeType,L"image/jpeg") == 0) {
            Clsid=arCod[i].Clsid;
            break;
        }    
}
free(arCod);
ULONG Quality = 100; //jpg 화질입니다. 100이 가장 높으며, 수가 줄어들수록 퀄리티가 떨어집니다.
Para.Count = 1;
Para.Parameter[0].Guid = EncoderQuality;
Para.Parameter[0].Type = EncoderParameterValueTypeLong;
Para.Parameter[0].NumberOfValues = 1;
Para.Parameter[0].Value = &Quality;
pBitmap->Save(생성할 파일경로와이름, &Clsid, &Para); //pBitmap의 이미지를 jpg 파일로 생성합니다.

글자 출력 (GDI+ 글자 출력)
CString strTemp = 원하는 글자;
FontFamily  fontFamily(L"Verdana");
Font font(&fontFamily, 9, FontStyleBold, UnitPoint); //verdana라는 글자체로 9의 크기로, 굵은 font를 생성합니다.
StringFormat stringFormat;
stringFormat.SetAlignment(StringAlignmentNear); //다 써진 글자가 영역에 상단에 맞춰지게합니다.
stringFormat.SetLineAlignment(StringAlignmentNear); //글이 다음줄로 갱신될때 왼쪽부터 쓰게합니다.
stringFormat.SetTrimming(StringTrimmingNone);
SolidBrush  brush(Color(48,58,118)); //글자의 색상을 설정합니다.
memG.DrawString(strTemp, 글자수, &font, RectF(x, y, sizex, sizey), &stringFormat, &brush); 
                                                                              //x, y위치에서 sizex, sizey만큼 크기로 글자를 출력합니다.

데이터 형 변환 (GDI -> GDI+ 데이터형 변환 : CBitmap을 -> Bitmap으로)
부득이하게 CBitmap 파일을 Bitmap으로 바꿔야 한다면, 다음 방법으로 바꿔줄수 있습니다.
Bitmap포인터변수 = Bitmap::FromHBITMAP((HBITMAP)CBitmap변수.m_hObject, NULL);

데이터 형 변환 (GDI+ -> GDI 데이터형 변환 : Bitmap -> CBitmap으로)
위에 방법처럼 쉽게 바꾸는 법이 있는지 몰라서 -_-;; 
진짜 한참 찾고 알아봤는데... 도저히 모르겠더군요 --;
그냥 CBitmap에다가 직접 Bitmap을 그려버렸습니다. ㅋㅋ
무식한 방법인지는 몰라도 문제는 없더군요 -.-;; 혹시 그냥 바꾸는 법 아시는 분은 알려주시면 너무너무 감사드립니다.
여기서 중요한건 Graphics에 Bitmap이 아닌 CBitmap을 연결해서 그려주면 Graphics로도 CBitmap을 활용할수 있다는점입니다.

Bitmap *pBitmap ; 
pBitmap = (Bitmap*)Image::FromFile(가져올JPG경로);

CClientDC dc(this);
CDC memDC;
CBitmap memBitmap; //Bitmap을 받아서 그리게될 CBitmap입니다.
memDC.CreateCompatibleDC(&dc);
memBitmap.CreateCompatibleBitmap(&dc, pBitmap->GetWidth(), pBitmap->GetHeight());
memDC.SelectObject(memBitmap);

Graphics graphicsMem(memDC.GetSafeHdc());
graphicsMem.DrawImage(pBitmap, 0, 0); //CBitmap 변수에 Bitmap 이미지를 그려줍니다.
delete pBitmap; //기존에 pBitmap이 존재할 이유가 없으므로 delete 시킵니다.

 

[출처] gdi+|작성자 najira00

 

 

 

 

=================================

=================================

=================================

 

 

 

 

 

반응형
그리드형


관련글 더보기

댓글 영역