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