델파이, C++빌더에서 이미지 파일의 썸네일, 즉 작은 크기로 줄인 이미지를 만들어내는 방법은 비교적 간단하고 또 많이 알려져 있습니다. 바로 TCanvas의 StretchDraw 프로시저를 이용하는 것인데요.
그런데, 이렇게 StretchDraw를 이용해서 썸네일을 만들어보면 품질이 그다지 좋지 않습니다. 그냥 좋지 않다 정도가 아니라 흔히 보는 그래픽 뷰어 등에서 보는 썸네일들과 비교하면 아주 형편없는 수준입니다. 글자가 들어간 이미지일 경우 이렇게 StretchDraw로 썸네일로 만들어보면 원본에서 상당히 큰 글자인 경우에도 전혀 알아볼 수가 없을 정도로 깨집니다.
여러해 전에 결혼정보회사에서 일할 때, 그쪽 시스템에 필요해서 증명사진의 썸네일을 만들 때도, StretchDraw로 만든 썸네일의 품질이 너무 조악해서 아예 썸네일을 만드는 외부 커맨드라인 툴을 호출해서 썼었드랬습니다. 그때는 일정이 넘 바빠서 시간을 들여 더 좋은 방법을 찾아낼 수가 없었거든요.
그런데 오늘, 볼랜드포럼 개편 작업을 위해 다시 찾아보다 우연히 VCL에 내장된 썸네일을 위한 다른 함수를 찾아냈습니다. GraphUtil 유닛에 포함된 ScaleImage 프로시저인데요. 이름 그대로 이미지의 크기를 조절해주는 함수입니다. 선언은 아래와 같습니다.
1 |
procedure ScaleImage(const SourceBitmap, ResizedBitmap: TBitmap; const ScaleAmount: Double); |
1 |
extern PACKAGE void __fastcall ScaleImage(const Graphics::TBitmap* SourceBitmap, const Graphics::TBitmap* ResizedBitmap, const double ScaleAmount); |
인자로는 원본 비트맵과 대상 비트맵, 그리고 크기조절비율을 넘겨주면 되고요.
실제 테스트를 해본 결과, 이 ScaleImage 프로시저로 만들어진 썸네일의 품질이 StretchDraw와는 비교도 되지 않을 정도로 아주 훌륭했습니다. 아래 이미지에서 보시다시피, StretchDraw로 구현한 쪽에서는 이미지에 포함된 글자가 상당히 보기 싫게 깨져보이고 그 외의 이미지 부분도 품질이 떨어집니다. 반면 ScaleImage로 구현한 쪽은 전체적으로 아주 자연스럽게 썸네일이 만들어졌습니다.
아래는 주로 라인들로 이루어진 도면 이미지에서 썸네일을 만들어낸 결과인데요. 이 경우는 품질의 차이가 더 많이 납니다. StretchDraw 쪽은 거의 알아보기가 힘들 정도라 썸네일로 쓸 수가 없는 수준입니다만 ScaleImage의 결과는 전체 윤곽을 제대로 알아볼 수 있을 정도로 깔끔하게 만들어졌죠.
아쉽게도, 이 ScaleImage 프로시저는 델파이 2005부터 추가된 루틴입니다. 따라서 델파이 2005 이상, C++빌더 2006 이상에서만 사용 가능합니다. (GraphUtil 유닛 자체는 델파이/C++빌더 6 버전에서 추가되었습니다)
ScaleImage 프로시저로 썸네일을 만드는 완전한 코드는 좀 더 길어지는데요. 그 이유는, ScaleImage에서는 비트맵만을 인자로 받기 때문입니다. 그래서 소스 이미지가 jpg, gif, png인 경우 그것을 비트맵으로 변환하는 과정이 필요하고, 다시 최종 썸네일 파일도 bmp보다는 png로 저장하도록 했기 때문입니다.
또한 ScaleImage에서는 축소될 이미지의 크기를 지정하는 것이 아니라 축소 비율을 지정하는데요. 일반적으로 썸네일은 비율보다는 특정 크기로 만들기 때문에 크기로부터 비율을 환산하는 코드도 필요하게 됩니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
uses Jpeg, GIFImg, PngImage, GraphUtil; function ThumbnailFromImageFile(ImagePath: string; ThumbnailSize: integer): string; const FileNamePrefix = 'thumb_'; var ImageExt: string; graphicSource: TGraphic; bmpSource: TBitmap; pngThumbnail: TPngImage; bmpThumbmail: TBitmap; fScale: double; begin ImageExt := LowerCase(ExtractFileExt(ImagePath)); if ImageExt='.jpg' then graphicSource := TJPEGImage.Create else if ImageExt='.png' then graphicSource := TPngImage.Create else if ImageExt='.gif' then graphicSource := TGIFImage.Create else if ImageExt='.bmp' then graphicSource := TBitmap.Create else exit; try graphicSource.LoadFromFile(ImagePath); if ImageExt='.bmp' then bmpSource := TBitmap(graphicSource) else begin bmpSource := TBitmap.Create; bmpSource.Assign(graphicSource); end; bmpThumbmail := TBitmap.Create; try if bmpSource.Width >= bmpSource.Height then fScale := ThumbnailSize / bmpSource.Width else fScale := ThumbnailSize / bmpSource.Height; ScaleImage(bmpSource, bmpThumbmail, fScale); pngThumbnail := TPngImage.Create; try pngThumbnail.Assign(bmpThumbmail); result := ExtractFilePath(ImagePath) + FileNamePrefix + ChangeFileExt(ExtractFileName(ImagePath), '.png'); try pngThumbnail.SaveToFile(result); except result := ''; end; finally pngThumbnail.Free; end; finally bmpThumbmail.Free; if graphicSource<>bmpSource then bmpSource.Free; end; finally graphicSource.Free; end; end; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
#include <Jpeg.hpp> #include <GIFImg.hpp> #include <PngImage.hpp> #include <GraphUtil.hpp> String ThumbnailFromImageFile(String ImagePath, int ThumbnailSize) { const String FileNamePrefix = "thumb_"; String ImageExt = LowerCase(ExtractFileExt(ImagePath)); TGraphic *graphicSource; if (ImageExt == ".jpg") graphicSource = new TJPEGImage; else if (ImageExt == ".png") graphicSource = new TPngImage; else if (ImageExt == ".gif") graphicSource = new TGIFImage; else if (ImageExt == ".bmp") graphicSource = new Graphics::TBitmap; else return ""; try { graphicSource->LoadFromFile(ImagePath); Graphics::TBitmap *bmpSource; if (ImageExt == ".bmp") bmpSource = (Graphics::TBitmap*)graphicSource; else { bmpSource = new Graphics::TBitmap; bmpSource->Assign(graphicSource); } Graphics::TBitmap *bmpThumbmail = new Graphics::TBitmap; try { double fScale = bmpSource->Width >= bmpSource->Height ? ThumbnailSize * 1.0 / bmpSource->Width : ThumbnailSize * 1.0 / bmpSource->Height; ScaleImage(bmpSource, bmpThumbmail, fScale); TPngImage *pngThumbnail = new TPngImage; try { pngThumbnail->Assign(bmpThumbmail); String ThumbFilePath = ExtractFilePath(ImagePath) + FileNamePrefix + ChangeFileExt(ExtractFileName(ImagePath), ".png"); pngThumbnail->SaveToFile(ThumbFilePath); return ThumbFilePath; } __finally { delete pngThumbnail; } } __finally { delete bmpThumbmail; if (graphicSource != bmpSource) delete bmpSource; } } __finally { delete graphicSource; } } |
이 예제 코드들에 대해 주의할 점은, ScaleImage 프로시저는 2005 버전부터 지원되지만, 위 코드에서 사용된 TGIFImage는 2007 버전부터 지원되고, TPngImage는 2009 버전부터 지원됩니다. 따라서 위 코드가 완전히 다 동작하려면 2009 버전 이상이어야 하구요, 그 이하 버전이라면 버전에 따라 TPngImage 및 TGIFImage 관련 코드를 제거 및 수정해야 하겠지요. (TJPEGImage는 아주 오래전, 4 버전부터 지원되었었습니다)
아래 링크는 위에서 스크린샷을 보여드린 썸네일 데모 프로그램의 소스입니다. 델파이 코드이며, XE 버전으로 컴파일된 실행파일도 포함되어 있습니다.
ThumbnailTest.zip
델파이, C 빌더에서 이미지 파일의 썸네일, 즉 작은 크기로 줄인 이미지를 만들어내는 방법은 비교적 간단하고 또 많이 알려져 있습니다. 바로 TCanvas의 StretchDraw 프로시저를 이용하는 것인데요.
그런데, 이렇게 StretchDraw를 이용해서 썸네일을 만들어보면 품질이