xref: /reactos/base/applications/mspaint/dib.cpp (revision 86696794)
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 (!GetObject(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 (!GetObject(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     GetObject(hBitmap, sizeof(BITMAP), &bm);
140     return bm.bmWidth;
141 }
142 
143 int
144 GetDIBHeight(HBITMAP hBitmap)
145 {
146     BITMAP bm;
147     GetObject(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     CString strTitle;
215     strTitle.Format(IDS_WINDOWTITLE, PathFindFileName(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         return NULL;
232 
233     HDC hScreenDC = ::GetDC(NULL);
234     g_xDpi = (float)::GetDeviceCaps(hScreenDC, LOGPIXELSX);
235     g_yDpi = (float)::GetDeviceCaps(hScreenDC, LOGPIXELSY);
236     ::ReleaseDC(NULL, hScreenDC);
237 
238     return SetBitmapAndInfo(hBitmap, name, pFound, isFile);
239 }
240 
241 HBITMAP SetBitmapAndInfo(HBITMAP hBitmap, LPCWSTR name, LPWIN32_FIND_DATAW pFound, BOOL isFile)
242 {
243     // update image
244     canvasWindow.resetScrollPos();
245     imageModel.PushImageForUndo(hBitmap);
246     imageModel.ClearHistory();
247 
248     SetFileInfo(name, pFound, isFile);
249     g_imageSaved = TRUE;
250     return hBitmap;
251 }
252 
253 HBITMAP DoLoadImageFile(HWND hwnd, LPCWSTR name, BOOL fIsMainFile)
254 {
255     CWaitCursor waitCursor;
256 
257     // find the file
258     WIN32_FIND_DATA find;
259     HANDLE hFind = ::FindFirstFileW(name, &find);
260     if (hFind == INVALID_HANDLE_VALUE) // does not exist
261     {
262         ShowError(IDS_LOADERRORTEXT, name);
263         return NULL;
264     }
265     ::FindClose(hFind);
266 
267     // is file empty?
268     if (find.nFileSizeLow == 0 && find.nFileSizeHigh == 0)
269     {
270         if (fIsMainFile)
271             return InitializeImage(name, &find, TRUE);
272     }
273 
274     // load the image
275     CImageDx img;
276     float xDpi = 0, yDpi = 0;
277     HRESULT hr = img.LoadDx(name, &xDpi, &yDpi);
278     if (FAILED(hr) && fIsMainFile)
279     {
280         imageModel.ClearHistory();
281         hr = img.LoadDx(name, &xDpi, &yDpi);
282     }
283     if (FAILED(hr))
284     {
285         ATLTRACE("hr: 0x%08lX\n", hr);
286         ShowError(IDS_LOADERRORTEXT, name);
287         return NULL;
288     }
289 
290     HBITMAP hBitmap = img.Detach();
291     if (!fIsMainFile)
292         return hBitmap;
293 
294     if (xDpi <= 0 || yDpi <= 0)
295     {
296         HDC hDC = ::GetDC(NULL);
297         xDpi = (float)::GetDeviceCaps(hDC, LOGPIXELSX);
298         yDpi = (float)::GetDeviceCaps(hDC, LOGPIXELSY);
299         ::ReleaseDC(NULL, hDC);
300     }
301 
302     g_xDpi = xDpi;
303     g_yDpi = yDpi;
304 
305     SetBitmapAndInfo(hBitmap, name, &find, TRUE);
306     return hBitmap;
307 }
308 
309 HBITMAP Rotate90DegreeBlt(HDC hDC1, INT cx, INT cy, BOOL bRight, BOOL bMono)
310 {
311     HBITMAP hbm2;
312     if (bMono)
313         hbm2 = ::CreateBitmap(cy, cx, 1, 1, NULL);
314     else
315         hbm2 = CreateDIBWithProperties(cy, cx);
316     if (!hbm2)
317         return NULL;
318 
319     HDC hDC2 = CreateCompatibleDC(NULL);
320     HGDIOBJ hbm2Old = SelectObject(hDC2, hbm2);
321     if (bRight)
322     {
323         for (INT y = 0; y < cy; ++y)
324         {
325             for (INT x = 0; x < cx; ++x)
326             {
327                 COLORREF rgb = GetPixel(hDC1, x, y);
328                 SetPixelV(hDC2, cy - (y + 1), x, rgb);
329             }
330         }
331     }
332     else
333     {
334         for (INT y = 0; y < cy; ++y)
335         {
336             for (INT x = 0; x < cx; ++x)
337             {
338                 COLORREF rgb = GetPixel(hDC1, x, y);
339                 SetPixelV(hDC2, y, cx - (x + 1), rgb);
340             }
341         }
342     }
343     SelectObject(hDC2, hbm2Old);
344     DeleteDC(hDC2);
345     return hbm2;
346 }
347 
348 HBITMAP SkewDIB(HDC hDC1, HBITMAP hbm, INT nDegree, BOOL bVertical, BOOL bMono)
349 {
350     CWaitCursor waitCursor;
351 
352     if (nDegree == 0)
353         return CopyDIBImage(hbm);
354 
355     const double eTan = tan(abs(nDegree) * M_PI / 180);
356 
357     BITMAP bm;
358     GetObjectW(hbm, sizeof(bm), &bm);
359     INT cx = bm.bmWidth, cy = bm.bmHeight, dx = 0, dy = 0;
360     if (bVertical)
361         dy = INT(cx * eTan);
362     else
363         dx = INT(cy * eTan);
364 
365     if (dx == 0 && dy == 0)
366         return CopyDIBImage(hbm);
367 
368     HBITMAP hbmNew;
369     if (bMono)
370         hbmNew = CreateMonoBitmap(cx + dx, cy + dy, FALSE);
371     else
372         hbmNew = CreateColorDIB(cx + dx, cy + dy, RGB(255, 255, 255));
373     if (!hbmNew)
374         return NULL;
375 
376     HDC hDC2 = CreateCompatibleDC(NULL);
377     HGDIOBJ hbm2Old = SelectObject(hDC2, hbmNew);
378     if (bVertical)
379     {
380         for (INT x = 0; x < cx; ++x)
381         {
382             INT delta = INT(x * eTan);
383             if (nDegree > 0)
384                 BitBlt(hDC2, x, (dy - delta), 1, cy, hDC1, x, 0, SRCCOPY);
385             else
386                 BitBlt(hDC2, x, delta, 1, cy, hDC1, x, 0, SRCCOPY);
387         }
388     }
389     else
390     {
391         for (INT y = 0; y < cy; ++y)
392         {
393             INT delta = INT(y * eTan);
394             if (nDegree > 0)
395                 BitBlt(hDC2, (dx - delta), y, cx, 1, hDC1, 0, y, SRCCOPY);
396             else
397                 BitBlt(hDC2, delta, y, cx, 1, hDC1, 0, y, SRCCOPY);
398         }
399     }
400 
401     SelectObject(hDC2, hbm2Old);
402     DeleteDC(hDC2);
403     return hbmNew;
404 }
405 
406 struct BITMAPINFODX : BITMAPINFO
407 {
408     RGBQUAD bmiColorsAdditional[256 - 1];
409 };
410 
411 HGLOBAL BitmapToClipboardDIB(HBITMAP hBitmap)
412 {
413     CWaitCursor waitCursor;
414 
415     BITMAP bm;
416     if (!GetObject(hBitmap, sizeof(BITMAP), &bm))
417         return NULL;
418 
419     BITMAPINFODX bmi;
420     ZeroMemory(&bmi, sizeof(bmi));
421     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
422     bmi.bmiHeader.biWidth = bm.bmWidth;
423     bmi.bmiHeader.biHeight = bm.bmHeight;
424     bmi.bmiHeader.biPlanes = 1;
425     bmi.bmiHeader.biBitCount = bm.bmBitsPixel;
426     bmi.bmiHeader.biCompression = BI_RGB;
427     bmi.bmiHeader.biSizeImage = bm.bmWidthBytes * bm.bmHeight;
428 
429     INT cColors;
430     if (bm.bmBitsPixel < 16)
431         cColors = 1 << bm.bmBitsPixel;
432     else
433         cColors = 0;
434 
435     HDC hDC = CreateCompatibleDC(NULL);
436 
437     if (cColors)
438     {
439         HGDIOBJ hbmOld = SelectObject(hDC, hBitmap);
440         cColors = GetDIBColorTable(hDC, 0, cColors, bmi.bmiColors);
441         SelectObject(hDC, hbmOld);
442     }
443 
444     DWORD cbColors = cColors * sizeof(RGBQUAD);
445     DWORD dwSize = sizeof(BITMAPINFOHEADER) + cbColors + bmi.bmiHeader.biSizeImage;
446     HGLOBAL hGlobal = GlobalAlloc(GHND | GMEM_SHARE, dwSize);
447     if (hGlobal)
448     {
449         LPBYTE pb = (LPBYTE)GlobalLock(hGlobal);
450         if (pb)
451         {
452             CopyMemory(pb, &bmi, sizeof(BITMAPINFOHEADER));
453             pb += sizeof(BITMAPINFOHEADER);
454 
455             CopyMemory(pb, bmi.bmiColors, cbColors);
456             pb += cbColors;
457 
458             GetDIBits(hDC, hBitmap, 0, bm.bmHeight, pb, &bmi, DIB_RGB_COLORS);
459 
460             GlobalUnlock(hGlobal);
461         }
462         else
463         {
464             GlobalFree(hGlobal);
465             hGlobal = NULL;
466         }
467     }
468 
469     DeleteDC(hDC);
470 
471     return hGlobal;
472 }
473 
474 HBITMAP BitmapFromClipboardDIB(HGLOBAL hGlobal)
475 {
476     CWaitCursor waitCursor;
477 
478     LPBYTE pb = (LPBYTE)GlobalLock(hGlobal);
479     if (!pb)
480         return NULL;
481 
482     LPBITMAPINFO pbmi = (LPBITMAPINFO)pb;
483     pb += pbmi->bmiHeader.biSize;
484 
485     INT cColors = 0, cbColors = 0;
486     if (pbmi->bmiHeader.biSize == sizeof(BITMAPCOREHEADER))
487     {
488         LPBITMAPCOREINFO pbmci = (LPBITMAPCOREINFO)pbmi;
489         WORD BitCount = pbmci->bmciHeader.bcBitCount;
490         if (BitCount < 16)
491         {
492             cColors = (1 << BitCount);
493             cbColors = cColors * sizeof(RGBTRIPLE);
494             pb += cbColors;
495         }
496     }
497     else if (pbmi->bmiHeader.biSize >= sizeof(BITMAPINFOHEADER))
498     {
499         WORD BitCount = pbmi->bmiHeader.biBitCount;
500         if (BitCount < 16)
501         {
502             cColors = (1 << BitCount);
503             cbColors = cColors * sizeof(RGBQUAD);
504             pb += cbColors;
505         }
506     }
507 
508     HDC hDC = CreateCompatibleDC(NULL);
509     HBITMAP hBitmap = CreateDIBSection(hDC, pbmi, DIB_RGB_COLORS, NULL, NULL, 0);
510     if (hBitmap)
511     {
512         SetDIBits(hDC, hBitmap, 0, labs(pbmi->bmiHeader.biHeight), pb, pbmi, DIB_RGB_COLORS);
513     }
514     DeleteDC(hDC);
515 
516     GlobalUnlock(hGlobal);
517 
518     return hBitmap;
519 }
520 
521 HBITMAP BitmapFromHEMF(HENHMETAFILE hEMF)
522 {
523     CWaitCursor waitCursor;
524 
525     ENHMETAHEADER header;
526     if (!GetEnhMetaFileHeader(hEMF, sizeof(header), &header))
527         return NULL;
528 
529     CRect rc = *(LPRECT)&header.rclBounds;
530     INT cx = rc.Width(), cy = rc.Height();
531     HBITMAP hbm = CreateColorDIB(cx, cy, RGB(255, 255, 255));
532     if (!hbm)
533         return NULL;
534 
535     HDC hDC = CreateCompatibleDC(NULL);
536     HGDIOBJ hbmOld = SelectObject(hDC, hbm);
537     PlayEnhMetaFile(hDC, hEMF, &rc);
538     SelectObject(hDC, hbmOld);
539     DeleteDC(hDC);
540 
541     return hbm;
542 }
543 
544 BOOL IsBitmapBlackAndWhite(HBITMAP hbm)
545 {
546     CWaitCursor waitCursor;
547 
548     BITMAP bm;
549     if (!::GetObjectW(hbm, sizeof(bm), &bm))
550         return FALSE;
551 
552     if (bm.bmBitsPixel == 1)
553         return TRUE;
554 
555     BITMAPINFOEX bmi;
556     ZeroMemory(&bmi, sizeof(bmi));
557     bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
558     bmi.bmiHeader.biWidth = bm.bmWidth;
559     bmi.bmiHeader.biHeight = bm.bmHeight;
560     bmi.bmiHeader.biPlanes = 1;
561     bmi.bmiHeader.biBitCount = 24;
562 
563     DWORD widthbytes = WIDTHBYTES(24 * bm.bmWidth);
564     DWORD cbBits = widthbytes * bm.bmHeight;
565     LPBYTE pbBits = new BYTE[cbBits];
566 
567     HDC hdc = ::CreateCompatibleDC(NULL);
568     ::GetDIBits(hdc, hbm, 0, bm.bmHeight, pbBits, &bmi, DIB_RGB_COLORS);
569     ::DeleteDC(hdc);
570 
571     BOOL bBlackAndWhite = TRUE;
572     for (LONG y = 0; y < bm.bmHeight; ++y)
573     {
574         LPBYTE pbLine = &pbBits[widthbytes * y];
575         for (LONG x = 0; x < bm.bmWidth; ++x)
576         {
577             BYTE Blue = *pbLine++;
578             BYTE Green = *pbLine++;
579             BYTE Red = *pbLine++;
580             COLORREF rgbColor = RGB(Red, Green, Blue);
581             if (rgbColor != RGB(0, 0, 0) && rgbColor != RGB(255, 255, 255))
582             {
583                 bBlackAndWhite = FALSE;
584                 goto Finish;
585             }
586         }
587     }
588 
589 Finish:
590     delete[] pbBits;
591 
592     return bBlackAndWhite;
593 }
594 
595 HBITMAP ConvertToBlackAndWhite(HBITMAP hbm)
596 {
597     CWaitCursor waitCursor;
598 
599     BITMAP bm;
600     if (!::GetObject(hbm, sizeof(bm), &bm))
601         return NULL;
602 
603     BITMAPINFOEX bmi;
604     ZeroMemory(&bmi, sizeof(bmi));
605     bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
606     bmi.bmiHeader.biWidth = bm.bmWidth;
607     bmi.bmiHeader.biHeight = bm.bmHeight;
608     bmi.bmiHeader.biPlanes = 1;
609     bmi.bmiHeader.biBitCount = 1;
610     bmi.bmiColors[1].rgbBlue = 255;
611     bmi.bmiColors[1].rgbGreen = 255;
612     bmi.bmiColors[1].rgbRed = 255;
613     HDC hdc = ::CreateCompatibleDC(NULL);
614     LPVOID pvMonoBits;
615     HBITMAP hMonoBitmap = ::CreateDIBSection(hdc, &bmi, DIB_RGB_COLORS, &pvMonoBits, NULL, 0);
616     if (!hMonoBitmap)
617     {
618         ::DeleteDC(hdc);
619         return NULL;
620     }
621 
622     HBITMAP hNewBitmap = CreateDIBWithProperties(bm.bmWidth, bm.bmHeight);
623     if (hNewBitmap)
624     {
625         ::GetDIBits(hdc, hbm, 0, bm.bmHeight, pvMonoBits, &bmi, DIB_RGB_COLORS);
626         ::SetDIBits(hdc, hNewBitmap, 0, bm.bmHeight, pvMonoBits, &bmi, DIB_RGB_COLORS);
627     }
628     ::DeleteObject(hMonoBitmap);
629     ::DeleteDC(hdc);
630 
631     return hNewBitmap;
632 }
633