Image.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Image.cpp
  3. * ---------
  4. * Purpose: Bitmap and Vector image file handling using GDI+.
  5. * Notes : (currently none)
  6. * Authors: OpenMPT Devs
  7. * The OpenMPT source code is released under the BSD license. Read LICENSE for more details.
  8. */
  9. #include "stdafx.h"
  10. #include "MPTrackUtil.h"
  11. #include "Image.h"
  12. #include "../common/FileReader.h"
  13. #include "../common/ComponentManager.h"
  14. // GDI+
  15. #include <atlbase.h>
  16. #define max(a, b) (((a) > (b)) ? (a) : (b))
  17. #define min(a, b) (((a) < (b)) ? (a) : (b))
  18. #if MPT_COMPILER_MSVC
  19. #pragma warning(push)
  20. #pragma warning(disable : 4458) // declaration of 'x' hides class member
  21. #endif
  22. #include <gdiplus.h>
  23. #if MPT_COMPILER_MSVC
  24. #pragma warning(pop)
  25. #endif
  26. #undef min
  27. #undef max
  28. OPENMPT_NAMESPACE_BEGIN
  29. GdiplusRAII::GdiplusRAII()
  30. {
  31. Gdiplus::GdiplusStartupInput gdiplusStartupInput;
  32. Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
  33. }
  34. GdiplusRAII::~GdiplusRAII()
  35. {
  36. Gdiplus::GdiplusShutdown(gdiplusToken);
  37. gdiplusToken = 0;
  38. }
  39. RawGDIDIB::RawGDIDIB(uint32 width, uint32 height)
  40. : width(width)
  41. , height(height)
  42. , pixels(width * height)
  43. {
  44. MPT_ASSERT(width > 0);
  45. MPT_ASSERT(height > 0);
  46. }
  47. namespace GDIP
  48. {
  49. static CComPtr<IStream> GetStream(mpt::const_byte_span data)
  50. {
  51. CComPtr<IStream> stream;
  52. #if (_WIN32_WINNT >= _WIN32_WINNT_VISTA)
  53. stream.Attach(SHCreateMemStream(mpt::byte_cast<const unsigned char *>(data.data()), mpt::saturate_cast<UINT>(data.size())));
  54. #else
  55. HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE | GMEM_ZEROINIT, data.size());
  56. if(hGlobal == NULL)
  57. {
  58. throw bad_image();
  59. }
  60. void * mem = GlobalLock(hGlobal);
  61. if(!mem)
  62. {
  63. hGlobal = GlobalFree(hGlobal);
  64. throw bad_image();
  65. }
  66. std::memcpy(mem, data.data(), data.size());
  67. GlobalUnlock(hGlobal);
  68. if(CreateStreamOnHGlobal(hGlobal, TRUE, &stream) != S_OK)
  69. {
  70. hGlobal = GlobalFree(hGlobal);
  71. throw bad_image();
  72. }
  73. hGlobal = NULL;
  74. #endif
  75. if(!stream)
  76. {
  77. throw bad_image();
  78. }
  79. return stream;
  80. }
  81. std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(mpt::const_byte_span file)
  82. {
  83. CComPtr<IStream> stream = GetStream(file);
  84. std::unique_ptr<Gdiplus::Bitmap> result = std::make_unique<Gdiplus::Bitmap>(stream, FALSE);
  85. if(result->GetLastStatus() != Gdiplus::Ok)
  86. {
  87. throw bad_image();
  88. }
  89. if(result->GetWidth() == 0 || result->GetHeight() == 0)
  90. {
  91. throw bad_image();
  92. }
  93. return result;
  94. }
  95. std::unique_ptr<Gdiplus::Bitmap> LoadPixelImage(FileReader file)
  96. {
  97. FileReader::PinnedView view = file.GetPinnedView();
  98. return LoadPixelImage(view.span());
  99. }
  100. std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(mpt::const_byte_span file)
  101. {
  102. CComPtr<IStream> stream = GetStream(file);
  103. std::unique_ptr<Gdiplus::Metafile> result = std::make_unique<Gdiplus::Metafile>(stream);
  104. if(result->GetLastStatus() != Gdiplus::Ok)
  105. {
  106. throw bad_image();
  107. }
  108. if(result->GetWidth() == 0 || result->GetHeight() == 0)
  109. {
  110. throw bad_image();
  111. }
  112. return result;
  113. }
  114. std::unique_ptr<Gdiplus::Metafile> LoadVectorImage(FileReader file)
  115. {
  116. FileReader::PinnedView view = file.GetPinnedView();
  117. return LoadVectorImage(view.span());
  118. }
  119. static std::unique_ptr<Gdiplus::Bitmap> DoResize(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
  120. {
  121. const int width = src.GetWidth(), height = src.GetHeight();
  122. int newWidth = 0, newHeight = 0, newSpriteWidth = 0, newSpriteHeight = 0;
  123. if(spriteWidth <= 0 || spriteHeight <= 0)
  124. {
  125. newWidth = mpt::saturate_round<int>(width * scaling);
  126. newHeight = mpt::saturate_round<int>(height * scaling);
  127. } else
  128. {
  129. // Sprite mode: Source images consists of several sprites / icons that should be scaled individually
  130. newSpriteWidth = mpt::saturate_round<int>(spriteWidth * scaling);
  131. newSpriteHeight = mpt::saturate_round<int>(spriteHeight * scaling);
  132. newWidth = width * newSpriteWidth / spriteWidth;
  133. newHeight = height * newSpriteHeight / spriteHeight;
  134. }
  135. std::unique_ptr<Gdiplus::Bitmap> resizedImage = std::make_unique<Gdiplus::Bitmap>(newWidth, newHeight, PixelFormat32bppARGB);
  136. std::unique_ptr<Gdiplus::Graphics> resizedGraphics(Gdiplus::Graphics::FromImage(resizedImage.get()));
  137. if(scaling >= 1.5)
  138. {
  139. // Prefer crisp look on real high-DPI devices
  140. resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeNearestNeighbor);
  141. resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHalf);
  142. resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeNone);
  143. } else
  144. {
  145. resizedGraphics->SetInterpolationMode(Gdiplus::InterpolationModeHighQualityBicubic);
  146. resizedGraphics->SetPixelOffsetMode(Gdiplus::PixelOffsetModeHighQuality);
  147. resizedGraphics->SetSmoothingMode(Gdiplus::SmoothingModeHighQuality);
  148. }
  149. if(spriteWidth <= 0 || spriteHeight <= 0)
  150. {
  151. resizedGraphics->DrawImage(&src, 0, 0, newWidth, newHeight);
  152. } else
  153. {
  154. // Draw each source sprite individually into separate image to avoid neighbouring source sprites bleeding in
  155. std::unique_ptr<Gdiplus::Bitmap> spriteImage = std::make_unique<Gdiplus::Bitmap>(spriteWidth, spriteHeight, PixelFormat32bppARGB);
  156. std::unique_ptr<Gdiplus::Graphics> spriteGraphics(Gdiplus::Graphics::FromImage(spriteImage.get()));
  157. for(int srcY = 0, destY = 0; srcY < height; srcY += spriteHeight, destY += newSpriteHeight)
  158. {
  159. for(int srcX = 0, destX = 0; srcX < width; srcX += spriteWidth, destX += newSpriteWidth)
  160. {
  161. spriteGraphics->Clear({0, 0, 0, 0});
  162. spriteGraphics->DrawImage(&src, Gdiplus::Rect(0, 0, spriteWidth, spriteHeight), srcX, srcY, spriteWidth, spriteHeight, Gdiplus::UnitPixel);
  163. resizedGraphics->DrawImage(spriteImage.get(), destX, destY, newSpriteWidth, newSpriteHeight);
  164. }
  165. }
  166. }
  167. return resizedImage;
  168. }
  169. std::unique_ptr<Gdiplus::Image> ResizeImage(Gdiplus::Image &src, double scaling, int spriteWidth, int spriteHeight)
  170. {
  171. return DoResize(src, scaling, spriteWidth, spriteHeight);
  172. }
  173. std::unique_ptr<Gdiplus::Bitmap> ResizeImage(Gdiplus::Bitmap &src, double scaling, int spriteWidth, int spriteHeight)
  174. {
  175. return DoResize(src, scaling, spriteWidth, spriteHeight);
  176. }
  177. } // namespace GDIP
  178. std::unique_ptr<RawGDIDIB> ToRawGDIDIB(Gdiplus::Bitmap &bitmap)
  179. {
  180. Gdiplus::BitmapData bitmapData;
  181. Gdiplus::Rect rect{Gdiplus::Point{0, 0}, Gdiplus::Size{static_cast<INT>(bitmap.GetWidth()), static_cast<INT>(bitmap.GetHeight())}};
  182. std::unique_ptr<RawGDIDIB> result = std::make_unique<RawGDIDIB>(bitmap.GetWidth(), bitmap.GetHeight());
  183. if(bitmap.LockBits(&rect, Gdiplus::ImageLockModeRead, PixelFormat32bppARGB, &bitmapData) != Gdiplus::Ok)
  184. {
  185. throw bad_image();
  186. }
  187. RawGDIDIB::Pixel *dst = result->Pixels().data();
  188. for(uint32 y = 0; y < result->Height(); ++y)
  189. {
  190. const GDIP::Pixel *src = GDIP::GetScanline(bitmapData, y);
  191. for(uint32 x = 0; x < result->Width(); ++x)
  192. {
  193. *dst = GDIP::ToRawGDIDIB(*src);
  194. src++;
  195. dst++;
  196. }
  197. }
  198. bitmap.UnlockBits(&bitmapData);
  199. return result;
  200. }
  201. std::unique_ptr<RawGDIDIB> LoadPixelImage(mpt::const_byte_span file, double scaling, int spriteWidth, int spriteHeight)
  202. {
  203. auto bitmap = GDIP::LoadPixelImage(file);
  204. if(scaling != 1.0)
  205. bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
  206. return ToRawGDIDIB(*bitmap);
  207. }
  208. std::unique_ptr<RawGDIDIB> LoadPixelImage(FileReader file, double scaling, int spriteWidth, int spriteHeight)
  209. {
  210. auto bitmap = GDIP::LoadPixelImage(file);
  211. if(scaling != 1.0)
  212. bitmap = GDIP::ResizeImage(*bitmap, scaling, spriteWidth, spriteHeight);
  213. return ToRawGDIDIB(*bitmap);
  214. }
  215. // Create a DIB for the current device from our PNG.
  216. bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, const RawGDIDIB &src)
  217. {
  218. BITMAPINFOHEADER bi;
  219. MemsetZero(bi);
  220. bi.biSize = sizeof(BITMAPINFOHEADER);
  221. bi.biWidth = src.Width();
  222. bi.biHeight = -static_cast<LONG>(src.Height());
  223. bi.biPlanes = 1;
  224. bi.biBitCount = 32;
  225. bi.biCompression = BI_RGB;
  226. bi.biSizeImage = src.Width() * src.Height() * 4;
  227. if(!dst.CreateCompatibleBitmap(&dc, src.Width(), src.Height()))
  228. {
  229. return false;
  230. }
  231. if(!SetDIBits(dc.GetSafeHdc(), dst, 0, src.Height(), src.Pixels().data(), reinterpret_cast<BITMAPINFO *>(&bi), DIB_RGB_COLORS))
  232. {
  233. return false;
  234. }
  235. return true;
  236. }
  237. bool CopyToCompatibleBitmap(CBitmap &dst, CDC &dc, Gdiplus::Image &src)
  238. {
  239. if(!dst.CreateCompatibleBitmap(&dc, src.GetWidth(), src.GetHeight()))
  240. {
  241. return false;
  242. }
  243. CDC memdc;
  244. if(!memdc.CreateCompatibleDC(&dc))
  245. {
  246. return false;
  247. }
  248. memdc.SelectObject(dst);
  249. Gdiplus::Graphics gfx(memdc);
  250. if(gfx.DrawImage(&src, 0, 0) != Gdiplus::Ok)
  251. {
  252. return false;
  253. }
  254. return true;
  255. }
  256. bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, mpt::const_byte_span file)
  257. {
  258. try
  259. {
  260. std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
  261. if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
  262. {
  263. return false;
  264. }
  265. } catch(...)
  266. {
  267. return false;
  268. }
  269. return true;
  270. }
  271. bool LoadCompatibleBitmapFromPixelImage(CBitmap &dst, CDC &dc, FileReader file)
  272. {
  273. try
  274. {
  275. std::unique_ptr<Gdiplus::Bitmap> pBitmap = GDIP::LoadPixelImage(file);
  276. if(!CopyToCompatibleBitmap(dst, dc, *pBitmap))
  277. {
  278. return false;
  279. }
  280. } catch(...)
  281. {
  282. return false;
  283. }
  284. return true;
  285. }
  286. OPENMPT_NAMESPACE_END