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:
20     CImageDx()
21     {
22         _shared()->AddRef();
23     }
24 
25     ~CImageDx()
26     {
27         if (m_hBitmap)
28             ::DeleteObject(m_hBitmap);
29 
30         _shared()->Release();
31     }
32 
33     void Attach(HBITMAP hbm)
34     {
35         if (m_hBitmap)
36             ::DeleteObject(m_hBitmap);
37         m_hBitmap = hbm;
38     }
39 
40     HBITMAP Detach()
41     {
42         HBITMAP hbmOld = m_hBitmap;
43         m_hBitmap = NULL;
44         return hbmOld;
45     }
46 
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 
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 
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,
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 
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 
211         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 
235         void Free()
236         {
237             ::FreeLibrary(m_hGdiPlus);
238             ZeroMemory(this, sizeof(*this));
239         }
240 
241         LONG AddRef()
242         {
243             return ++m_cRefs;
244         }
245 
246         LONG Release()
247         {
248             if (--m_cRefs == 0)
249             {
250                 Free();
251                 return 0;
252             }
253             return m_cRefs;
254         }
255     };
256 
257     static SHARED* _shared()
258     {
259         static SHARED s_shared;
260         return &s_shared;
261     }
262 
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>
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
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
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