xref: /reactos/base/applications/mspaint/dib.cpp (revision 6a6b5ec2)
1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Some DIB related functions
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  */
7 
8 #include "precomp.h"
9 
10 INT g_fileSize = 0;
11 float g_xDpi = 96;
12 float g_yDpi = 96;
13 SYSTEMTIME g_fileTime;
14 
15 #define WIDTHBYTES(i) (((i) + 31) / 32 * 4)
16 
17 struct BITMAPINFOEX : BITMAPINFO
18 {
19     RGBQUAD bmiColorsExtra[256 - 1];
20 };
21 
22 /* FUNCTIONS ********************************************************/
23 
24 // Convert DPI (dots per inch) into PPCM (pixels per centimeter)
25 float PpcmFromDpi(float dpi)
26 {
27     // 1 DPI is 0.0254 meter. 1 centimeter is 1/100 meter.
28     return dpi / (0.0254f * 100.0f);
29 }
30 
31 HBITMAP
32 CreateDIBWithProperties(int width, int height)
33 {
34     BITMAPINFO bmi;
35     ZeroMemory(&bmi, sizeof(BITMAPINFO));
36     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
37     bmi.bmiHeader.biWidth = width;
38     bmi.bmiHeader.biHeight = height;
39     bmi.bmiHeader.biPlanes = 1;
40     bmi.bmiHeader.biBitCount = 24;
41     return CreateDIBSection(NULL, &bmi, DIB_RGB_COLORS, NULL, NULL, 0);
42 }
43 
44 HBITMAP
45 CreateMonoBitmap(int width, int height, BOOL bWhite)
46 {
47     HBITMAP hbm = CreateBitmap(width, height, 1, 1, NULL);
48     if (hbm == NULL)
49         return NULL;
50 
51     if (bWhite)
52     {
53         HDC hdc = CreateCompatibleDC(NULL);
54         HGDIOBJ hbmOld = SelectObject(hdc, hbm);
55         RECT rc = { 0, 0, width, height };
56         FillRect(hdc, &rc, (HBRUSH)GetStockObject(WHITE_BRUSH));
57         SelectObject(hdc, hbmOld);
58         DeleteDC(hdc);
59     }
60 
61     return hbm;
62 }
63 
64 HBITMAP
65 CreateColorDIB(int width, int height, COLORREF rgb)
66 {
67     HBITMAP ret = CreateDIBWithProperties(width, height);
68     if (!ret)
69         return NULL;
70 
71     if (rgb)
72     {
73         HDC hdc = CreateCompatibleDC(NULL);
74         HGDIOBJ hbmOld = SelectObject(hdc, ret);
75         RECT rc;
76         SetRect(&rc, 0, 0, width, height);
77         HBRUSH hbr = CreateSolidBrush(rgb);
78         FillRect(hdc, &rc, hbr);
79         DeleteObject(hbr);
80         SelectObject(hdc, hbmOld);
81         DeleteDC(hdc);
82     }
83 
84     return ret;
85 }
86 
87 HBITMAP CopyMonoImage(HBITMAP hbm, INT cx, INT cy)
88 {
89     BITMAP bm;
90     if (!::GetObjectW(hbm, sizeof(bm), &bm))
91         return NULL;
92 
93     if (cx == 0 || cy == 0)
94     {
95         cx = bm.bmWidth;
96         cy = bm.bmHeight;
97     }
98 
99     HBITMAP hbmNew = ::CreateBitmap(cx, cy, 1, 1, NULL);
100     if (!hbmNew)
101         return NULL;
102 
103     HDC hdc1 = ::CreateCompatibleDC(NULL);
104     HDC hdc2 = ::CreateCompatibleDC(NULL);
105     HGDIOBJ hbm1Old = ::SelectObject(hdc1, hbm);
106     HGDIOBJ hbm2Old = ::SelectObject(hdc2, hbmNew);
107     ::StretchBlt(hdc2, 0, 0, cx, cy, hdc1, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
108     ::SelectObject(hdc1, hbm1Old);
109     ::SelectObject(hdc2, hbm2Old);
110     ::DeleteDC(hdc1);
111     ::DeleteDC(hdc2);
112     return hbmNew;
113 }
114 
115 HBITMAP CachedBufferDIB(HBITMAP hbm, int minimalWidth, int minimalHeight)
116 {
117     if (minimalWidth <= 0)
118         minimalWidth = 1;
119     if (minimalHeight <= 0)
120         minimalHeight = 1;
121 
122     BITMAP bm;
123     if (!GetObjectW(hbm, sizeof(bm), &bm))
124         hbm = NULL;
125 
126     if (hbm && minimalWidth <= bm.bmWidth && minimalHeight <= bm.bmHeight)
127         return hbm;
128 
129     if (hbm)
130         DeleteObject(hbm);
131 
132     return CreateDIBWithProperties((minimalWidth * 3) / 2, (minimalHeight * 3) / 2);
133 }
134 
135 int
136 GetDIBWidth(HBITMAP hBitmap)
137 {
138     BITMAP bm;
139     ::GetObjectW(hBitmap, sizeof(BITMAP), &bm);
140     return bm.bmWidth;
141 }
142 
143 int
144 GetDIBHeight(HBITMAP hBitmap)
145 {
146     BITMAP bm;
147     ::GetObjectW(hBitmap, sizeof(BITMAP), &bm);
148     return bm.bmHeight;
149 }
150 
151 BOOL SaveDIBToFile(HBITMAP hBitmap, LPCWSTR FileName, BOOL fIsMainFile, REFGUID guidFileType)
152 {
153     CWaitCursor waitCursor;
154 
155     CImageDx img;
156     img.Attach(hBitmap);
157     HRESULT hr = img.SaveDx(FileName, guidFileType, g_xDpi, g_yDpi);
158     img.Detach();
159 
160     if (FAILED(hr))
161     {
162         ShowError(IDS_SAVEERROR, FileName);
163         return FALSE;
164     }
165 
166     if (!fIsMainFile)
167         return TRUE;
168 
169     WIN32_FIND_DATAW find;
170     HANDLE hFind = ::FindFirstFileW(FileName, &find);
171     if (hFind == INVALID_HANDLE_VALUE)
172     {
173         ShowError(IDS_SAVEERROR, FileName);
174         return FALSE;
175     }
176     ::FindClose(hFind);
177 
178     SetFileInfo(FileName, &find, TRUE);
179     g_imageSaved = TRUE;
180     return TRUE;
181 }
182 
183 void SetFileInfo(LPCWSTR name, LPWIN32_FIND_DATAW pFound, BOOL isAFile)
184 {
185     // update file time and size
186     if (pFound)
187     {
188         FILETIME ft;
189         ::FileTimeToLocalFileTime(&pFound->ftLastWriteTime, &ft);
190         ::FileTimeToSystemTime(&ft, &g_fileTime);
191 
192         g_fileSize = pFound->nFileSizeLow;
193     }
194     else
195     {
196         ZeroMemory(&g_fileTime, sizeof(g_fileTime));
197         g_fileSize = 0;
198     }
199 
200     // update g_szFileName
201     if (name && name[0])
202     {
203         CStringW strName = name;
204         ::GetFullPathNameW(strName, _countof(g_szFileName), g_szFileName, NULL);
205         // The following code won't work correctly when (name == g_szFileName):
206         //   ::GetFullPathNameW(name, _countof(g_szFileName), g_szFileName, NULL);
207     }
208     else
209     {
210         ::LoadStringW(g_hinstExe, IDS_DEFAULTFILENAME, g_szFileName, _countof(g_szFileName));
211     }
212 
213     // set title
214     CStringW strTitle;
215     strTitle.Format(IDS_WINDOWTITLE, PathFindFileNameW(g_szFileName));
216     mainWindow.SetWindowText(strTitle);
217 
218     // update file info and recent
219     g_isAFile = isAFile;
220     if (g_isAFile)
221         registrySettings.SetMostRecentFile(g_szFileName);
222 
223     g_imageSaved = TRUE;
224 }
225 
226 HBITMAP InitializeImage(LPCWSTR name, LPWIN32_FIND_DATAW pFound, BOOL isFile)
227 {
228     COLORREF white = RGB(255, 255, 255);
229     HBITMAP hBitmap = CreateColorDIB(registrySettings.BMPWidth, registrySettings.BMPHeight, white);
230     if (hBitmap == NULL)
231     {
232         ShowOutOfMemory();
233         return NULL;
234     }
235 
236     HDC hScreenDC = ::GetDC(NULL);
237     g_xDpi = (float)::GetDeviceCaps(hScreenDC, LOGPIXELSX);
238     g_yDpi = (float)::GetDeviceCaps(hScreenDC, LOGPIXELSY);
239     ::ReleaseDC(NULL, hScreenDC);
240 
241     return SetBitmapAndInfo(hBitmap, name, pFound, isFile);
242 }
243 
244 HBITMAP SetBitmapAndInfo(HBITMAP hBitmap, LPCWSTR name, LPWIN32_FIND_DATAW pFound, BOOL isFile)
245 {
246     // update image
247     canvasWindow.updateScrollPos();
248     imageModel.PushImageForUndo(hBitmap);
249     imageModel.ClearHistory();
250 
251     SetFileInfo(name, pFound, isFile);
252     g_imageSaved = TRUE;
253     return hBitmap;
254 }
255 
256 HBITMAP DoLoadImageFile(HWND hwnd, LPCWSTR name, BOOL fIsMainFile)
257 {
258     CWaitCursor waitCursor;
259 
260     // find the file
261     WIN32_FIND_DATAW find;
262     HANDLE hFind = ::FindFirstFileW(name, &find);
263     if (hFind == INVALID_HANDLE_VALUE) // does not exist
264     {
265         ShowError(IDS_LOADERRORTEXT, name);
266         return NULL;
267     }
268     ::FindClose(hFind);
269 
270     // is file empty?
271     if (find.nFileSizeLow == 0 && find.nFileSizeHigh == 0)
272     {
273         if (fIsMainFile)
274             return InitializeImage(name, &find, TRUE);
275     }
276 
277     // load the image
278     CImageDx img;
279     float xDpi = 0, yDpi = 0;
280     HRESULT hr = img.LoadDx(name, &xDpi, &yDpi);
281     if (FAILED(hr) && fIsMainFile)
282     {
283         imageModel.ClearHistory();
284         hr = img.LoadDx(name, &xDpi, &yDpi);
285     }
286     if (FAILED(hr))
287     {
288         ATLTRACE("hr: 0x%08lX\n", hr);
289         ShowError(IDS_LOADERRORTEXT, name);
290         return NULL;
291     }
292 
293     HBITMAP hBitmap = img.Detach();
294     if (!fIsMainFile)
295         return hBitmap;
296 
297     if (xDpi <= 0 || yDpi <= 0)
298     {
299         HDC hDC = ::GetDC(NULL);
300         xDpi = (float)::GetDeviceCaps(hDC, LOGPIXELSX);
301         yDpi = (float)::GetDeviceCaps(hDC, LOGPIXELSY);
302         ::ReleaseDC(NULL, hDC);
303     }
304 
305     g_xDpi = xDpi;
306     g_yDpi = yDpi;
307 
308     SetBitmapAndInfo(hBitmap, name, &find, TRUE);
309     return hBitmap;
310 }
311 
312 HBITMAP Rotate90DegreeBlt(HDC hDC1, INT cx, INT cy, BOOL bRight, BOOL bMono)
313 {
314     HBITMAP hbm2;
315     if (bMono)
316         hbm2 = ::CreateBitmap(cy, cx, 1, 1, NULL);
317     else
318         hbm2 = CreateDIBWithProperties(cy, cx);
319     if (!hbm2)
320         return NULL;
321 
322     HDC hDC2 = CreateCompatibleDC(NULL);
323     HGDIOBJ hbm2Old = SelectObject(hDC2, hbm2);
324     if (bRight)
325     {
326         for (INT y = 0; y < cy; ++y)
327         {
328             for (INT x = 0; x < cx; ++x)
329             {
330                 COLORREF rgb = GetPixel(hDC1, x, y);
331                 SetPixelV(hDC2, cy - (y + 1), x, rgb);
332             }
333         }
334     }
335     else
336     {
337         for (INT y = 0; y < cy; ++y)
338         {
339             for (INT x = 0; x < cx; ++x)
340             {
341                 COLORREF rgb = GetPixel(hDC1, x, y);
342                 SetPixelV(hDC2, y, cx - (x + 1), rgb);
343             }
344         }
345     }
346     SelectObject(hDC2, hbm2Old);
347     DeleteDC(hDC2);
348     return hbm2;
349 }
350 
351 HBITMAP SkewDIB(HDC hDC1, HBITMAP hbm, INT nDegree, BOOL bVertical, BOOL bMono)
352 {
353     CWaitCursor waitCursor;
354 
355     if (nDegree == 0)
356         return CopyDIBImage(hbm);
357 
358     const double eTan = tan(abs(nDegree) * M_PI / 180);
359 
360     BITMAP bm;
361     ::GetObjectW(hbm, sizeof(bm), &bm);
362     INT cx = bm.bmWidth, cy = bm.bmHeight, dx = 0, dy = 0;
363     if (bVertical)
364         dy = INT(cx * eTan);
365     else
366         dx = INT(cy * eTan);
367 
368     if (dx == 0 && dy == 0)
369         return CopyDIBImage(hbm);
370 
371     HBITMAP hbmNew;
372     if (bMono)
373         hbmNew = CreateMonoBitmap(cx + dx, cy + dy, FALSE);
374     else
375         hbmNew = CreateColorDIB(cx + dx, cy + dy, RGB(255, 255, 255));
376     if (!hbmNew)
377         return NULL;
378 
379     HDC hDC2 = CreateCompatibleDC(NULL);
380     HGDIOBJ hbm2Old = SelectObject(hDC2, hbmNew);
381     if (bVertical)
382     {
383         for (INT x = 0; x < cx; ++x)
384         {
385             INT delta = INT(x * eTan);
386             if (nDegree > 0)
387                 ::BitBlt(hDC2, x, (dy - delta), 1, cy, hDC1, x, 0, SRCCOPY);
388             else
389                 ::BitBlt(hDC2, x, delta, 1, cy, hDC1, x, 0, SRCCOPY);
390         }
391     }
392     else
393     {
394         for (INT y = 0; y < cy; ++y)
395         {
396             INT delta = INT(y * eTan);
397             if (nDegree > 0)
398                 ::BitBlt(hDC2, (dx - delta), y, cx, 1, hDC1, 0, y, SRCCOPY);
399             else
400                 ::BitBlt(hDC2, delta, y, cx, 1, hDC1, 0, y, SRCCOPY);
401         }
402     }
403 
404     SelectObject(hDC2, hbm2Old);
405     DeleteDC(hDC2);
406     return hbmNew;
407 }
408 
409 struct BITMAPINFODX : BITMAPINFO
410 {
411     RGBQUAD bmiColorsAdditional[256 - 1];
412 };
413 
414 HGLOBAL BitmapToClipboardDIB(HBITMAP hBitmap)
415 {
416     CWaitCursor waitCursor;
417 
418     BITMAP bm;
419     if (!GetObjectW(hBitmap, sizeof(BITMAP), &bm))
420         return NULL;
421 
422     BITMAPINFODX bmi;
423     ZeroMemory(&bmi, sizeof(bmi));
424     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
425     bmi.bmiHeader.biWidth = bm.bmWidth;
426     bmi.bmiHeader.biHeight = bm.bmHeight;
427     bmi.bmiHeader.biPlanes = 1;
428     bmi.bmiHeader.biBitCount = bm.bmBitsPixel;
429     bmi.bmiHeader.biCompression = BI_RGB;
430     bmi.bmiHeader.biSizeImage = bm.bmWidthBytes * bm.bmHeight;
431 
432     INT cColors;
433     if (bm.bmBitsPixel < 16)
434         cColors = 1 << bm.bmBitsPixel;
435     else
436         cColors = 0;
437 
438     HDC hDC = CreateCompatibleDC(NULL);
439 
440     if (cColors)
441     {
442         HGDIOBJ hbmOld = SelectObject(hDC, hBitmap);
443         cColors = GetDIBColorTable(hDC, 0, cColors, bmi.bmiColors);
444         SelectObject(hDC, hbmOld);
445     }
446 
447     DWORD cbColors = cColors * sizeof(RGBQUAD);
448     DWORD dwSize = sizeof(BITMAPINFOHEADER) + cbColors + bmi.bmiHeader.biSizeImage;
449     HGLOBAL hGlobal = GlobalAlloc(GHND | GMEM_SHARE, dwSize);
450     if (hGlobal)
451     {
452         LPBYTE pb = (LPBYTE)GlobalLock(hGlobal);
453         if (pb)
454         {
455             CopyMemory(pb, &bmi, sizeof(BITMAPINFOHEADER));
456             pb += sizeof(BITMAPINFOHEADER);
457 
458             CopyMemory(pb, bmi.bmiColors, cbColors);
459             pb += cbColors;
460 
461             GetDIBits(hDC, hBitmap, 0, bm.bmHeight, pb, &bmi, DIB_RGB_COLORS);
462 
463             GlobalUnlock(hGlobal);
464         }
465         else
466         {
467             GlobalFree(hGlobal);
468             hGlobal = NULL;
469         }
470     }
471 
472     DeleteDC(hDC);
473 
474     return hGlobal;
475 }
476 
477 HBITMAP BitmapFromClipboardDIB(HGLOBAL hGlobal)
478 {
479     CWaitCursor waitCursor;
480 
481     LPBYTE pb = (LPBYTE)GlobalLock(hGlobal);
482     if (!pb)
483         return NULL;
484 
485     LPBITMAPINFO pbmi = (LPBITMAPINFO)pb;
486     pb += pbmi->bmiHeader.biSize;
487 
488     INT cColors = 0, cbColors = 0;
489     if (pbmi->bmiHeader.biSize == sizeof(BITMAPCOREHEADER))
490     {
491         LPBITMAPCOREINFO pbmci = (LPBITMAPCOREINFO)pbmi;
492         WORD BitCount = pbmci->bmciHeader.bcBitCount;
493         if (BitCount < 16)
494         {
495             cColors = (1 << BitCount);
496             cbColors = cColors * sizeof(RGBTRIPLE);
497             pb += cbColors;
498         }
499     }
500     else if (pbmi->bmiHeader.biSize >= sizeof(BITMAPINFOHEADER))
501     {
502         WORD BitCount = pbmi->bmiHeader.biBitCount;
503         if (BitCount < 16)
504         {
505             cColors = (1 << BitCount);
506             cbColors = cColors * sizeof(RGBQUAD);
507             pb += cbColors;
508         }
509     }
510 
511     HDC hDC = CreateCompatibleDC(NULL);
512     HBITMAP hBitmap = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, NULL, NULL, 0);
513     if (hBitmap)
514     {
515         SetDIBits(hDC, hBitmap, 0, labs(pbmi->bmiHeader.biHeight), pb, pbmi, DIB_RGB_COLORS);
516     }
517     DeleteDC(hDC);
518 
519     GlobalUnlock(hGlobal);
520 
521     return hBitmap;
522 }
523 
524 HBITMAP BitmapFromHEMF(HENHMETAFILE hEMF)
525 {
526     CWaitCursor waitCursor;
527 
528     ENHMETAHEADER header;
529     if (!GetEnhMetaFileHeader(hEMF, sizeof(header), &header))
530         return NULL;
531 
532     CRect rc = *(LPRECT)&header.rclBounds;
533     INT cx = rc.Width(), cy = rc.Height();
534     HBITMAP hbm = CreateColorDIB(cx, cy, RGB(255, 255, 255));
535     if (!hbm)
536         return NULL;
537 
538     HDC hDC = CreateCompatibleDC(NULL);
539     HGDIOBJ hbmOld = SelectObject(hDC, hbm);
540     PlayEnhMetaFile(hDC, hEMF, &rc);
541     SelectObject(hDC, hbmOld);
542     DeleteDC(hDC);
543 
544     return hbm;
545 }
546 
547 BOOL IsBitmapBlackAndWhite(HBITMAP hbm)
548 {
549     CWaitCursor waitCursor;
550 
551     BITMAP bm;
552     if (!::GetObjectW(hbm, sizeof(bm), &bm))
553         return FALSE;
554 
555     if (bm.bmBitsPixel == 1)
556         return TRUE;
557 
558     BITMAPINFOEX bmi;
559     ZeroMemory(&bmi, sizeof(bmi));
560     bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
561     bmi.bmiHeader.biWidth = bm.bmWidth;
562     bmi.bmiHeader.biHeight = bm.bmHeight;
563     bmi.bmiHeader.biPlanes = 1;
564     bmi.bmiHeader.biBitCount = 24;
565 
566     DWORD widthbytes = WIDTHBYTES(24 * bm.bmWidth);
567     DWORD cbBits = widthbytes * bm.bmHeight;
568     LPBYTE pbBits = new BYTE[cbBits];
569 
570     HDC hdc = ::CreateCompatibleDC(NULL);
571     ::GetDIBits(hdc, hbm, 0, bm.bmHeight, pbBits, &bmi, DIB_RGB_COLORS);
572     ::DeleteDC(hdc);
573 
574     BOOL bBlackAndWhite = TRUE;
575     for (LONG y = 0; y < bm.bmHeight; ++y)
576     {
577         LPBYTE pbLine = &pbBits[widthbytes * y];
578         for (LONG x = 0; x < bm.bmWidth; ++x)
579         {
580             BYTE Blue = *pbLine++;
581             BYTE Green = *pbLine++;
582             BYTE Red = *pbLine++;
583             COLORREF rgbColor = RGB(Red, Green, Blue);
584             if (rgbColor != RGB(0, 0, 0) && rgbColor != RGB(255, 255, 255))
585             {
586                 bBlackAndWhite = FALSE;
587                 goto Finish;
588             }
589         }
590     }
591 
592 Finish:
593     delete[] pbBits;
594 
595     return bBlackAndWhite;
596 }
597 
598 HBITMAP ConvertToBlackAndWhite(HBITMAP hbm)
599 {
600     CWaitCursor waitCursor;
601 
602     BITMAP bm;
603     if (!::GetObjectW(hbm, sizeof(bm), &bm))
604         return NULL;
605 
606     BITMAPINFOEX bmi;
607     ZeroMemory(&bmi, sizeof(bmi));
608     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
609     bmi.bmiHeader.biWidth = bm.bmWidth;
610     bmi.bmiHeader.biHeight = bm.bmHeight;
611     bmi.bmiHeader.biPlanes = 1;
612     bmi.bmiHeader.biBitCount = 1;
613     bmi.bmiColors[1].rgbBlue = 255;
614     bmi.bmiColors[1].rgbGreen = 255;
615     bmi.bmiColors[1].rgbRed = 255;
616     HDC hdc = ::CreateCompatibleDC(NULL);
617     LPVOID pvMonoBits;
618     HBITMAP hMonoBitmap = ::CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pvMonoBits, NULL, 0);
619     if (!hMonoBitmap)
620     {
621         ::DeleteDC(hdc);
622         return NULL;
623     }
624 
625     HBITMAP hNewBitmap = CreateDIBWithProperties(bm.bmWidth, bm.bmHeight);
626     if (hNewBitmap)
627     {
628         ::GetDIBits(hdc, hbm, 0, bm.bmHeight, pvMonoBits, &bmi, DIB_RGB_COLORS);
629         ::SetDIBits(hdc, hNewBitmap, 0, bm.bmHeight, pvMonoBits, &bmi, DIB_RGB_COLORS);
630     }
631     ::DeleteObject(hMonoBitmap);
632     ::DeleteDC(hdc);
633 
634     return hNewBitmap;
635 }
636