C++Builder Programming Forum
C++Builder  |  Delphi  |  FireMonkey  |  C/C++  |  Free Pascal  |  Firebird
볼랜드포럼 BorlandForum
 경고! 게시물 작성자의 사전 허락없는 메일주소 추출행위 절대 금지
C++빌더 포럼
Q & A
FAQ
팁&트릭
강좌/문서
자료실
컴포넌트/라이브러리
메신저 프로젝트
볼랜드포럼 홈
헤드라인 뉴스
IT 뉴스
공지사항
자유게시판
해피 브레이크
공동 프로젝트
구인/구직
회원 장터
건의사항
운영진 게시판
회원 메뉴
북마크
볼랜드포럼 광고 모집

C++빌더 팁&트릭
C++Builder Programming Tip&Tricks
[1130] FireMonkey에서 썸네일을 만들려면 (2)
박지훈.임프 [cbuilder] 47404 읽음    2013-03-03 04:23
저번 포스트에서 썼다시피, 파이어몽키에도 썸네일을 만들어주는 TBitmap.CreateThumbnail 메소드가 있습니다. 현재 버전에서 버그가 있지만 그 버그를 회피해서 제대로 썸네일을 만들 방법을 설명했습니다.

그런데, 아래 캡쳐 이미지들에서 보시다시피, GDI+ 캔버스와 맥의 Quartz 캔버스의 경우에는 아주 좋은 품질의 썸네일이 만들어집니다만, Drect2D 캔버스의 경우에는 좀 심하게 이미지가 깨져 보입니다.





물론 위의 결과는 도면 파일에서 썸네일을 만든 결과라 품질의 차이가 더 극적으로 눈에 띄지요. 하지만 사진과 같은 이미지로 테스트를 해봐도 큰 차이가 보입니다.



이건 파이어몽키의 문제라기보다는 아무래도 Direct2D 기능의 문제로 보입니다. CreateThumbnail에서 호출하는 DrawBitmap의 Direct2D쪽 구현을 살펴보면, Direct2D의 ID2D1RenderTarget.DrawBitmap을 호출하는데요, 이 함수 자체가 썸네일로 쓰기에는 품질이 떨어집니다. 이 함수에는 품질을 결정하는 interpolationMode 인자가 있고, 파이어몽키에서 이넘을 호출할 때 고품질로 지정했는데도 저 정도의 결과밖에 나오지 않습니다.

이 썸네일 품질 문제에 대한 해결책으로서 먼저 떠올릴 수 있는 방법은 아예 GDI+를 강제하는 방법일텐데요. 일단 GDI+에서는 항상 깨끗한 썸네일이 만들어지니까요. 이것은 얼마전에 올렸던 글 FireMonkey에서 DirectX 사용을 강제하려면 에서 언급했던 GlobalUseDX10를 false로 설정하면 됩니다. 가장 간단한 방법이겠지만, GDI+보다 훨씬 뛰어난 Direct2D의 기능들을 사용할 수 없게 된다는 큰 단점이 있죠.

다른 한가지 방법은, Direct2D나 GDI+에 의존하지 않고 썸네일을 위해 외부의 라이브러리나 코드를 동원하는 방법도 있겠습니다. 하지만 이러기 위해서는 비용이든 코딩 노력이든 리소스가 만만찮게 더 들어갈 수 있겠지요.


저는 이 두가지 방법 대신 저렴한(?) 꼼수를 생각해냈는데요. 의외로 대단히 효과가 좋았습니다. 기본적인 Direct2D 썸네일 코드를 사용하면서도 썸네일링을 한번이 아닌 여러번에 나누어서 하는 것입니다.

이론적으로는 가로 2000의 이미지를 200으로 한번에 줄이는 것과, 1000으로 줄였다가 다시 200으로 한번 더 줄이는 것은 같은 결과를 내겠지만, Direct2D의 썸네일 메소드의 품질이 좋지 않은 편이기 때문에 썸네일링을 두번에 나누어 하는 것이 더 좋은 결과를 낼 수도 있겠다고 생각했는데, 실제로 그랬습니다. Direct2D 캔버스 상태에서 CreateThumbnail을 두번 이상으로 나누어 호출해봤는데, GDI+의 품질보다는 약간 못하긴 하지만 그래도 한번 호출한 경우보다 훨씬 좋은 품질로 나왔습니다.



잠깐, 그럼 같은 함수를 두번이나 호출했으니 실행 속도에도 신경을 써야 하지 않나, 하고 생각하실텐데요. 썸네일을 만드는 방식에 있어 Direct2D가 GDI+보다 품질은 많이 떨어지지만, 대신 속도는 엄청나게 빠릅니다. 시간 측정을 해보니 Direct2D에서는 0.000479초, GDI+에서는 0.034439초가 나왔습니다. Direct2D가 무려 72배나 빠다는 얘기죠. (시간은 QueryPerformanceCounter로 측정했습니다)

참고로 이건 그래픽 칩셋의 기능을 이용하는 Direct2D의 특성상 각각의 시스템의 그래픽 카드 성능에 따라 결과가 다를텐데요, 제가 테스트한 PC의 그래픽 카드는 팬 없이 방열판만 있는 저성능 그래픽 카드입니다. 즉, 웬만한 PC 사양에서는 이보다 오히려 더 큰 차이가 나겠지요.

어쨌든, 이렇게 Direct2D에서 CreateThumbnail이 GDI+보다 워낙에 엄청나게 빠르기 때문에, 몇번 정도 호출한다고 해도 그로 인한 부하 증가는 그다지 문제가 되지 않습니다. 아마도 서드파티 라이브러리나 자체 썸네일 코드를 구현한다고 해도 이런 속도는 내기 힘들겠지요. 제 샘플에서는 단계적으로 CreateThumbnail을 3번 호출했는데, 걸린 시간은 0.0009117초로서, 한번 한 경우의 2배도 되지 않습니다. (2배까지는 걸리지 않는 이유는 첫번째 호출한 결과로 썸네일을 만들 이미지의 크기가 더 작아졌으므로 두번째, 세번째는 더 빠르게 실행되기 때문이죠)

이렇게 해서 최종적으로 만들어진 썸네일 파일 만들기 코드는 다음과 같습니다.

아래는 델파이 코드입니다.
function CreateThumbnail(SrcBitmap: TBitmap; const Width, Height: Integer): TBitmap;
var
  R: TRectF;
begin
  Result := TBitmap.Create(Width, Height);
  if Result.Canvas.BeginScene then
  try
    R := RectF(0, 0, SrcBitmap.Width, SrcBitmap.Height);
    R.Fit(RectF(0, 0, Width, Height));
    Result.Canvas.DrawBitmap(SrcBitmap, RectF(0, 0, SrcBitmap.Width, SrcBitmap.Height), R, 1.0);
    SrcBitmap.Free;
  finally
    Result.Canvas.EndScene;
  end;
end;

function ThumbnailFromImageFile(ImagePath: string; ThumbnailSize: integer): string;
const
  FileNamePrefix = 'thumb_';
var
  bmpThumbnail: TBitmap;
begin
  bmpThumbnail := TBitmap.CreateFromFile(ImagePath);
  QueryPerformanceFrequency(Freq);
	QueryPerformanceCounter(Tick1);
  if TCanvasManager.DefaultCanvas.ClassName = 'TCanvasD2D' then
    while bmpThumbnail.Width div ThumbnailSize > 2 do
      bmpThumbnail := CreateThumbnail(bmpThumbnail, bmpThumbnail.Width div 2, bmpThumbnail.Height div 2);
  bmpThumbnail := CreateThumbnail(bmpThumbnail, ThumbnailSize, ThumbnailSize);
  result := ExtractFilePath(ImagePath) + FileNamePrefix + ChangeFileExt(ExtractFileName(ImagePath), '.png');
  bmpThumbnail.SaveToFile(result);
  bmpThumbnail.Free;
end;


아래는 C++빌더 코드입니다.
double RectFit(TRectF &OrgRect, TRectF BoundsRect)
{
	if (BoundsRect.Width() * BoundsRect.Height() == 0) return 1;

	float ratio = 1;
	if ((OrgRect.Width() / BoundsRect.Width()) > (OrgRect.Height() / BoundsRect.Height()))
		ratio = OrgRect.Width() / BoundsRect.Width();
	else
		ratio = OrgRect.Height() / BoundsRect.Height();
	if (ratio < 1)
		OrgRect = RectF(0, 0, OrgRect.Width(), OrgRect.Height());
	else
		OrgRect = RectF(0, 0, OrgRect.Width() / ratio, OrgRect.Height() / ratio);
	RectCenter(OrgRect, BoundsRect);
	return ratio;
}

TBitmap *CreateThumbnail(TBitmap *SrcBitmap, const int Width, const int Height)
{
	TBitmap *Result = new TBitmap(Width, Height);
	if (Result->Canvas->BeginScene())
		try
		{
			TRectF R = RectF(0, 0, SrcBitmap->Width, SrcBitmap->Height);
			RectFit(R, RectF(0, 0, Width, Height));
			Result->Canvas->DrawBitmap(SrcBitmap, RectF(0, 0, SrcBitmap->Width, SrcBitmap->Height), R, 1.0);
			delete SrcBitmap;
			return Result;
		}
		__finally
		{
			Result->Canvas->EndScene();
		}
}

String ThumbnailFromImageFile(String ImagePath, int ThumbnailSize)
{
	const String FileNamePrefix = "thumb_";
	TBitmap *bmpThumbnail = new TBitmap(ImagePath);
	if (TCanvasManager::DefaultCanvas->ClassName() == "TCanvasD2D")
		while(bmpThumbnail->Width / ThumbnailSize > 2)
			bmpThumbnail = CreateThumbnail(bmpThumbnail, bmpThumbnail->Width / 2, bmpThumbnail->Height / 2);
	bmpThumbnail = CreateThumbnail(bmpThumbnail, ThumbnailSize, ThumbnailSize);
	String sThumbnailPath = ExtractFilePath(ImagePath) + FileNamePrefix + ChangeFileExt(ExtractFileName(ImagePath), ".png");
	bmpThumbnail->SaveToFile(sThumbnailPath);
	delete bmpThumbnail;
	return sThumbnailPath;
}


잘 보시면, 이전 포스트에서 코딩했던 CreateThumbnail 함수와는 아주 약간 변경된 부분이 있습니다. 후반부 Result.Canvas.DrawBitmap() 호출 뒤에 SrcBitmap.Free; 코드가 추가된 거죠. 이 라인을 추가한 이유는 이 CreateThumbnail 함수를 여러번 반복해서 호출하기 쉽게 하기 위해서입니다. 이 코드가 없으면 for 문으로 반복시킬 때 코드가 좀 지저분해지죠. (C++빌더 코드에서 RectFit 함수는 저번 글에서 설명한 대로, 델파이의 TRectF.Fit() 메소드가 C++빌더에 누락되어 있어서 구현을 추가해준 것입니다)

for 문을 보시면, 이미지의 크기가 최종적으로 원하는 썸네일 크기의 2배가 넘는 동안 계속 원래 크기의 1/2로 썸네일을 만들고 다시 그 썸네일로부터 다시 썸네일을 만드는 동작을 반복합니다. 그리고 마지막으로 원하는 썸네일 크기로 다시 한번 썸네일을 만들죠. 위에서 샘플로 쓰고 있는 평면도의 경우 가로 1754의 크기인데, 최종 썸네일 크기 200로 만들 때까지 CreateThumbnail은 3번 호출됩니다.

다른 비율이 아닌 1/2 비율로 썸네일을 반복하는 이유는 여러번 테스트를 해본 결과 1/2 단위로 반복했을 때의 이미지 품질이 가장 좋았고 속도도 빨랐기 때문입니다. 1/2보다 큰 비율로 썸네일을 했을 때는 품질이 그닥 좋지 않았습니다. 2번보다 3번 하는 것이 더 품질이 좋았구요. 이런 이유로 1/2 단위로 반복하는 방식을 선택하게 되었습니다.

물론 이 방식에서는 원본 이미지가 클 수록, 그리고 최종 썸네일 크기가 작을 수록 CreateThumbnail이 호출되는 횟수가 많아지게 되는데요. 예를 들어 원본이 3200의 크기라면 200의 썸네일로 만드는 동안 CreateThumbnail이 4번 호출되겠지요. 하지만 CreateThumbnail을 여러번 호출하더라도 3회차와 4회차 등 3회차 이후로는 소모하는 시간이 아주 미미해져서 횟수가 많아진다고 해서 전체 시간이 별로 늘어나지 않았습니다.


썸네일 데모 프로그램의 소스를 첨부했으니 참고하시기 바랍니다. 델파이 및 C++빌더 코드이며, XE3 버전으로 컴파일되었습니다.
DCAS [simulica]   2013-05-09 17:11 X
좋은글 감사합니다.

+ -

관련 글 리스트
1130 FireMonkey에서 썸네일을 만들려면 (2) 박지훈.임프 47404 2013/03/03
(링크)     Delphi Tip'N Tricks > FireMonkey에서 썸네일을 만들려면 (2)
(링크)     FireMonkey Tip'N Tricks > FireMonkey에서 썸네일을 만들려면 (2)
Google
Copyright © 1999-2015, borlandforum.com. All right reserved.