1 /* 2 * PROJECT: PAINT for ReactOS 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Loading/Saving an image file with getting/setting resolution 5 * COPYRIGHT: Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8 #pragma once 9 10 #include <atlimage.h> 11 12 namespace GPDE = Gdiplus::DllExports; 13 14 class CImageDx 15 { 16 protected: 17 HBITMAP m_hBitmap = NULL; 18 19 public: CImageDx()20 CImageDx() 21 { 22 _shared()->AddRef(); 23 } 24 ~CImageDx()25 ~CImageDx() 26 { 27 if (m_hBitmap) 28 ::DeleteObject(m_hBitmap); 29 30 _shared()->Release(); 31 } 32 Attach(HBITMAP hbm)33 void Attach(HBITMAP hbm) 34 { 35 if (m_hBitmap) 36 ::DeleteObject(m_hBitmap); 37 m_hBitmap = hbm; 38 } 39 Detach()40 HBITMAP Detach() 41 { 42 HBITMAP hbmOld = m_hBitmap; 43 m_hBitmap = NULL; 44 return hbmOld; 45 } 46 GetResolution(Gdiplus::GpImage * pImage,float * pxDpi,float * pyDpi)47 BOOL GetResolution(Gdiplus::GpImage *pImage, float *pxDpi, float *pyDpi) 48 { 49 if (!get_fn(_shared()->m_GetImageHorizontalResolution, "GdipGetImageHorizontalResolution") || 50 !get_fn(_shared()->m_GetImageVerticalResolution, "GdipGetImageVerticalResolution")) 51 { 52 return FALSE; 53 } 54 55 if (pxDpi) 56 _shared()->m_GetImageHorizontalResolution(pImage, pxDpi); 57 if (pyDpi) 58 _shared()->m_GetImageVerticalResolution(pImage, pyDpi); 59 60 return TRUE; 61 } 62 SetResolution(Gdiplus::GpBitmap * pBitmap,float xDpi,float yDpi)63 BOOL SetResolution(Gdiplus::GpBitmap *pBitmap, float xDpi, float yDpi) 64 { 65 if (!get_fn(_shared()->m_BitmapSetResolution, "GdipBitmapSetResolution")) 66 return FALSE; 67 68 _shared()->m_BitmapSetResolution(pBitmap, xDpi, yDpi); 69 return TRUE; 70 } 71 LoadDx(LPCWSTR pszFileName,float * pxDpi,float * pyDpi)72 HRESULT LoadDx(LPCWSTR pszFileName, float *pxDpi, float *pyDpi) throw() 73 { 74 using namespace Gdiplus; 75 76 _shared()->AddRef(); 77 78 if (!get_fn(_shared()->m_CreateBitmapFromFile, "GdipCreateBitmapFromFile") || 79 !get_fn(_shared()->m_CreateHBITMAPFromBitmap, "GdipCreateHBITMAPFromBitmap") || 80 !get_fn(_shared()->m_DisposeImage, "GdipDisposeImage")) 81 { 82 _shared()->Release(); 83 return E_FAIL; 84 } 85 86 // create a GpBitmap object from file 87 GpBitmap *pBitmap = NULL; 88 if (_shared()->m_CreateBitmapFromFile(pszFileName, &pBitmap) != Ok) 89 { 90 _shared()->Release(); 91 return E_FAIL; 92 } 93 94 // get an HBITMAP 95 HBITMAP hbm = NULL; 96 Color color(0xFF, 0xFF, 0xFF); 97 Status status = _shared()->m_CreateHBITMAPFromBitmap(pBitmap, &hbm, color.GetValue()); 98 99 // get the resolution 100 if (pxDpi || pyDpi) 101 GetResolution((GpImage*)pBitmap, pxDpi, pyDpi); 102 103 // delete GpBitmap 104 _shared()->m_DisposeImage(pBitmap); 105 106 // attach it 107 if (status == Ok) 108 Attach(hbm); 109 110 _shared()->Release(); 111 return (status == Ok ? S_OK : E_FAIL); 112 } 113 114 HRESULT SaveDx(LPCWSTR pszFileName, REFGUID guidFileType = GUID_NULL, throw()115 float xDpi = 0, float yDpi = 0) throw() 116 { 117 using namespace Gdiplus; 118 119 _shared()->AddRef(); 120 121 if (!get_fn(_shared()->m_CreateBitmapFromHBITMAP, "GdipCreateBitmapFromHBITMAP") || 122 !get_fn(_shared()->m_SaveImageToFile, "GdipSaveImageToFile") || 123 !get_fn(_shared()->m_DisposeImage, "GdipDisposeImage")) 124 { 125 _shared()->Release(); 126 return E_FAIL; 127 } 128 129 // create a GpBitmap from HBITMAP 130 GpBitmap *pBitmap = NULL; 131 _shared()->m_CreateBitmapFromHBITMAP(m_hBitmap, NULL, &pBitmap); 132 133 // set the resolution 134 SetResolution(pBitmap, xDpi, yDpi); 135 136 // Get encoders 137 UINT cEncoders = 0; 138 ImageCodecInfo* pEncoders = GetAllEncoders(cEncoders); 139 140 // if the file type is null, get the file type from extension 141 CLSID clsid; 142 if (::IsEqualGUID(guidFileType, GUID_NULL)) 143 { 144 CStringW strExt(PathFindExtensionW(pszFileName)); 145 clsid = FindCodecForExtension(strExt, pEncoders, cEncoders); 146 } 147 else 148 { 149 clsid = FindCodecForFileType(guidFileType, pEncoders, cEncoders); 150 } 151 152 delete[] pEncoders; 153 154 // save to file 155 Status status = _shared()->m_SaveImageToFile(pBitmap, pszFileName, &clsid, NULL); 156 157 // destroy GpBitmap 158 _shared()->m_DisposeImage(pBitmap); 159 160 _shared()->Release(); 161 162 return (status == Ok ? S_OK : E_FAIL); 163 } 164 IsExtensionSupported(PWCHAR pchDotExt)165 static BOOL IsExtensionSupported(PWCHAR pchDotExt) 166 { 167 _shared()->AddRef(); 168 169 UINT cEncoders; 170 Gdiplus::ImageCodecInfo* pEncoders = GetAllEncoders(cEncoders); 171 172 CLSID clsid = FindCodecForExtension(pchDotExt, pEncoders, cEncoders); 173 BOOL ret = !::IsEqualGUID(clsid, CLSID_NULL); 174 delete[] pEncoders; 175 176 _shared()->Release(); 177 return ret; 178 } 179 180 protected: 181 using FN_Startup = decltype(&Gdiplus::GdiplusStartup); 182 using FN_Shutdown = decltype(&Gdiplus::GdiplusShutdown); 183 using FN_GetImageHorizontalResolution = decltype(&GPDE::GdipGetImageHorizontalResolution); 184 using FN_GetImageVerticalResolution = decltype(&GPDE::GdipGetImageVerticalResolution); 185 using FN_BitmapSetResolution = decltype(&GPDE::GdipBitmapSetResolution); 186 using FN_CreateBitmapFromHBITMAP = decltype(&GPDE::GdipCreateBitmapFromHBITMAP); 187 using FN_CreateBitmapFromFile = decltype(&GPDE::GdipCreateBitmapFromFile); 188 using FN_CreateHBITMAPFromBitmap = decltype(&GPDE::GdipCreateHBITMAPFromBitmap); 189 using FN_SaveImageToFile = decltype(&GPDE::GdipSaveImageToFile); 190 using FN_DisposeImage = decltype(&GPDE::GdipDisposeImage); 191 using FN_GetImageEncodersSize = decltype(&GPDE::GdipGetImageEncodersSize); 192 using FN_GetImageEncoders = decltype(&GPDE::GdipGetImageEncoders); 193 194 struct SHARED 195 { 196 HINSTANCE m_hGdiPlus = NULL; 197 LONG m_cRefs = 0; 198 ULONG_PTR m_dwToken = 0; 199 FN_Shutdown m_Shutdown = NULL; 200 FN_GetImageHorizontalResolution m_GetImageHorizontalResolution = NULL; 201 FN_GetImageVerticalResolution m_GetImageVerticalResolution = NULL; 202 FN_BitmapSetResolution m_BitmapSetResolution = NULL; 203 FN_CreateBitmapFromHBITMAP m_CreateBitmapFromHBITMAP = NULL; 204 FN_CreateBitmapFromFile m_CreateBitmapFromFile = NULL; 205 FN_CreateHBITMAPFromBitmap m_CreateHBITMAPFromBitmap = NULL; 206 FN_SaveImageToFile m_SaveImageToFile = NULL; 207 FN_DisposeImage m_DisposeImage = NULL; 208 FN_GetImageEncodersSize m_GetImageEncodersSize = NULL; 209 FN_GetImageEncoders m_GetImageEncoders = NULL; 210 InitSHARED211 HINSTANCE Init() 212 { 213 if (m_hGdiPlus) 214 return m_hGdiPlus; 215 216 m_hGdiPlus = ::LoadLibraryW(L"gdiplus.dll"); 217 if (!m_hGdiPlus) 218 return NULL; 219 220 FN_Startup Startup = (FN_Startup)GetProcAddress(m_hGdiPlus, "GdiplusStartup"); 221 m_Shutdown = (FN_Shutdown)GetProcAddress(m_hGdiPlus, "GdiplusShutdown"); 222 if (!Startup || !m_Shutdown) 223 { 224 ::FreeLibrary(m_hGdiPlus); 225 m_hGdiPlus = NULL; 226 return NULL; 227 } 228 229 Gdiplus::GdiplusStartupInput gdiplusStartupInput; 230 Startup(&m_dwToken, &gdiplusStartupInput, NULL); 231 232 return m_hGdiPlus; 233 } 234 FreeSHARED235 void Free() 236 { 237 ::FreeLibrary(m_hGdiPlus); 238 ZeroMemory(this, sizeof(*this)); 239 } 240 AddRefSHARED241 LONG AddRef() 242 { 243 return ++m_cRefs; 244 } 245 ReleaseSHARED246 LONG Release() 247 { 248 if (--m_cRefs == 0) 249 { 250 Free(); 251 return 0; 252 } 253 return m_cRefs; 254 } 255 }; 256 _shared()257 static SHARED* _shared() 258 { 259 static SHARED s_shared; 260 return &s_shared; 261 } 262 GetAllEncoders(UINT & cEncoders)263 static Gdiplus::ImageCodecInfo* GetAllEncoders(UINT& cEncoders) 264 { 265 Gdiplus::ImageCodecInfo *ret = NULL; 266 UINT total_size; 267 268 if (!get_fn(_shared()->m_GetImageEncodersSize, "GdipGetImageEncodersSize") || 269 !get_fn(_shared()->m_GetImageEncoders, "GdipGetImageEncoders")) 270 { 271 cEncoders = 0; 272 return NULL; 273 } 274 275 _shared()->m_GetImageEncodersSize(&cEncoders, &total_size); 276 if (total_size == 0) 277 return NULL; 278 279 ret = new Gdiplus::ImageCodecInfo[total_size / sizeof(ret[0])]; 280 if (ret == NULL) 281 { 282 cEncoders = 0; 283 return NULL; 284 } 285 286 _shared()->m_GetImageEncoders(cEncoders, total_size, ret); 287 288 return ret; // needs delete[] 289 } 290 291 template <typename FN_T> get_fn(FN_T & fn,const char * name)292 static bool get_fn(FN_T& fn, const char *name) 293 { 294 if (fn) 295 return true; 296 HINSTANCE hGdiPlus = _shared()->Init(); 297 fn = reinterpret_cast<FN_T>(::GetProcAddress(hGdiPlus, name)); 298 return fn != NULL; 299 } 300 301 // CImage::FindCodecForExtension is private. We have to duplicate it at here... 302 static CLSID FindCodecForExtension(LPCWSTR dotext,const Gdiplus::ImageCodecInfo * pCodecs,UINT nCodecs)303 FindCodecForExtension(LPCWSTR dotext, const Gdiplus::ImageCodecInfo *pCodecs, UINT nCodecs) 304 { 305 for (UINT i = 0; i < nCodecs; ++i) 306 { 307 CStringW strSpecs(pCodecs[i].FilenameExtension); 308 int ichOld = 0, ichSep; 309 for (;;) 310 { 311 ichSep = strSpecs.Find(L';', ichOld); 312 313 CStringW strSpec; 314 if (ichSep < 0) 315 strSpec = strSpecs.Mid(ichOld); 316 else 317 strSpec = strSpecs.Mid(ichOld, ichSep - ichOld); 318 319 int ichDot = strSpec.ReverseFind(L'.'); 320 if (ichDot >= 0) 321 strSpec = strSpec.Mid(ichDot); 322 323 if (!dotext || strSpec.CompareNoCase(dotext) == 0) 324 return pCodecs[i].Clsid; 325 326 if (ichSep < 0) 327 break; 328 329 ichOld = ichSep + 1; 330 } 331 } 332 return CLSID_NULL; 333 } 334 335 // CImage::FindCodecForFileType is private. We have to duplicate it at here... 336 static CLSID FindCodecForFileType(REFGUID guidFileType,const Gdiplus::ImageCodecInfo * pCodecs,UINT nCodecs)337 FindCodecForFileType(REFGUID guidFileType, const Gdiplus::ImageCodecInfo *pCodecs, UINT nCodecs) 338 { 339 for (UINT iInfo = 0; iInfo < nCodecs; ++iInfo) 340 { 341 if (::IsEqualGUID(pCodecs[iInfo].FormatID, guidFileType)) 342 return pCodecs[iInfo].Clsid; 343 } 344 return CLSID_NULL; 345 } 346 }; 347