1 /* 2 * PROJECT: PAINT for ReactOS 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Keep track of selection parameters, notify listeners 5 * COPYRIGHT: Copyright 2015 Benedikt Freisen <b.freisen@gmx.net> 6 * Copyright 2019 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 7 */ 8 9 #include "precomp.h" 10 11 SelectionModel selectionModel; 12 13 /* FUNCTIONS ********************************************************/ 14 15 SelectionModel::SelectionModel() 16 : m_hbmColor(NULL) 17 , m_hbmMask(NULL) 18 , m_ptStack(NULL) 19 , m_iPtSP(0) 20 , m_rgbBack(RGB(255, 255, 255)) 21 , m_bShow(FALSE) 22 , m_bContentChanged(FALSE) 23 { 24 ::SetRectEmpty(&m_rc); 25 ::SetRectEmpty(&m_rcOld); 26 m_ptHit.x = m_ptHit.y = -1; 27 } 28 29 SelectionModel::~SelectionModel() 30 { 31 ClearColorImage(); 32 ClearMaskImage(); 33 ResetPtStack(); 34 } 35 36 void SelectionModel::ResetPtStack() 37 { 38 if (m_ptStack) 39 { 40 free(m_ptStack); 41 m_ptStack = NULL; 42 } 43 m_iPtSP = 0; 44 } 45 46 void SelectionModel::PushToPtStack(POINT pt) 47 { 48 #define GROW_COUNT 256 49 if (m_iPtSP % GROW_COUNT == 0) 50 { 51 INT nNewCount = m_iPtSP + GROW_COUNT; 52 LPPOINT pptNew = (LPPOINT)realloc(m_ptStack, sizeof(POINT) * nNewCount); 53 if (pptNew == NULL) 54 return; 55 m_ptStack = pptNew; 56 } 57 m_ptStack[m_iPtSP] = pt; 58 m_iPtSP++; 59 #undef GROW_COUNT 60 } 61 62 void SelectionModel::ShiftPtStack(INT dx, INT dy) 63 { 64 for (INT i = 0; i < m_iPtSP; ++i) 65 { 66 POINT& pt = m_ptStack[i]; 67 pt.x += dx; 68 pt.y += dy; 69 } 70 } 71 72 void SelectionModel::BuildMaskFromPtStack() 73 { 74 CRect rc = { MAXLONG, MAXLONG, 0, 0 }; 75 for (INT i = 0; i < m_iPtSP; ++i) 76 { 77 POINT& pt = m_ptStack[i]; 78 rc.left = min(pt.x, rc.left); 79 rc.top = min(pt.y, rc.top); 80 rc.right = max(pt.x, rc.right); 81 rc.bottom = max(pt.y, rc.bottom); 82 } 83 rc.right += 1; 84 rc.bottom += 1; 85 86 m_rc = m_rcOld = rc; 87 88 ClearMaskImage(); 89 90 ShiftPtStack(-m_rcOld.left, -m_rcOld.top); 91 92 HDC hdcMem = ::CreateCompatibleDC(NULL); 93 m_hbmMask = ::CreateBitmap(rc.Width(), rc.Height(), 1, 1, NULL); 94 HGDIOBJ hbmOld = ::SelectObject(hdcMem, m_hbmMask); 95 ::FillRect(hdcMem, &rc, (HBRUSH)::GetStockObject(BLACK_BRUSH)); 96 HGDIOBJ hPenOld = ::SelectObject(hdcMem, GetStockObject(NULL_PEN)); 97 HGDIOBJ hbrOld = ::SelectObject(hdcMem, GetStockObject(WHITE_BRUSH)); 98 ::Polygon(hdcMem, m_ptStack, m_iPtSP); 99 ::SelectObject(hdcMem, hbrOld); 100 ::SelectObject(hdcMem, hPenOld); 101 ::SelectObject(hdcMem, hbmOld); 102 ::DeleteDC(hdcMem); 103 104 ShiftPtStack(+m_rcOld.left, +m_rcOld.top); 105 } 106 107 void SelectionModel::DrawBackgroundPoly(HDC hDCImage, COLORREF crBg) 108 { 109 if (::IsRectEmpty(&m_rcOld)) 110 return; 111 112 HGDIOBJ hPenOld = ::SelectObject(hDCImage, ::GetStockObject(NULL_PEN)); 113 HGDIOBJ hbrOld = ::SelectObject(hDCImage, ::CreateSolidBrush(crBg)); 114 ::Polygon(hDCImage, m_ptStack, m_iPtSP); 115 ::DeleteObject(::SelectObject(hDCImage, hbrOld)); 116 ::SelectObject(hDCImage, hPenOld); 117 } 118 119 void SelectionModel::DrawBackgroundRect(HDC hDCImage, COLORREF crBg) 120 { 121 if (::IsRectEmpty(&m_rcOld)) 122 return; 123 124 Rect(hDCImage, m_rcOld.left, m_rcOld.top, m_rcOld.right, m_rcOld.bottom, crBg, crBg, 0, 1); 125 } 126 127 void SelectionModel::DrawBackground(HDC hDCImage) 128 { 129 if (toolsModel.GetActiveTool() == TOOL_FREESEL) 130 DrawBackgroundPoly(hDCImage, paletteModel.GetBgColor()); 131 else 132 DrawBackgroundRect(hDCImage, paletteModel.GetBgColor()); 133 } 134 135 void SelectionModel::DrawSelection(HDC hDCImage, COLORREF crBg, BOOL bBgTransparent) 136 { 137 CRect rc = m_rc; 138 if (::IsRectEmpty(&rc)) 139 return; 140 141 BITMAP bm; 142 if (!GetObject(m_hbmColor, sizeof(BITMAP), &bm)) 143 return; 144 145 COLORREF keyColor = (bBgTransparent ? crBg : CLR_INVALID); 146 147 HDC hMemDC = CreateCompatibleDC(hDCImage); 148 HGDIOBJ hbmOld = SelectObject(hMemDC, m_hbmColor); 149 ColorKeyedMaskBlt(hDCImage, rc.left, rc.top, rc.Width(), rc.Height(), 150 hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, m_hbmMask, keyColor); 151 SelectObject(hMemDC, hbmOld); 152 DeleteDC(hMemDC); 153 } 154 155 void SelectionModel::GetSelectionContents(HDC hDCImage) 156 { 157 ClearColorImage(); 158 159 HDC hMemDC = ::CreateCompatibleDC(NULL); 160 m_hbmColor = CreateColorDIB(m_rc.Width(), m_rc.Height(), RGB(255, 255, 255)); 161 HGDIOBJ hbmOld = ::SelectObject(hMemDC, m_hbmColor); 162 ::BitBlt(hMemDC, 0, 0, m_rc.Width(), m_rc.Height(), hDCImage, m_rc.left, m_rc.top, SRCCOPY); 163 ::SelectObject(hMemDC, hbmOld); 164 ::DeleteDC(hMemDC); 165 } 166 167 BOOL SelectionModel::IsLanded() const 168 { 169 return !m_hbmColor; 170 } 171 172 BOOL SelectionModel::TakeOff() 173 { 174 if (!IsLanded() || ::IsRectEmpty(&m_rc)) 175 return FALSE; 176 177 // The background color is needed for transparency of selection 178 m_rgbBack = paletteModel.GetBgColor(); 179 180 // Get the contents of the selection area 181 GetSelectionContents(imageModel.GetDC()); 182 183 // RectSel doesn't need the mask image 184 if (toolsModel.GetActiveTool() == TOOL_RECTSEL) 185 ClearMaskImage(); 186 187 // Save the selection area 188 m_rcOld = m_rc; 189 190 if (toolsModel.GetActiveTool() == TOOL_RECTSEL) 191 { 192 imageModel.PushImageForUndo(); 193 selectionModel.DrawBackgroundRect(imageModel.GetDC(), selectionModel.m_rgbBack); 194 } 195 else if (toolsModel.GetActiveTool() == TOOL_FREESEL) 196 { 197 imageModel.PushImageForUndo(); 198 selectionModel.DrawBackgroundPoly(imageModel.GetDC(), selectionModel.m_rgbBack); 199 } 200 201 imageModel.NotifyImageChanged(); 202 return TRUE; 203 } 204 205 void SelectionModel::Landing() 206 { 207 if (IsLanded() && !m_bShow) 208 { 209 imageModel.NotifyImageChanged(); 210 return; 211 } 212 213 m_bShow = FALSE; 214 215 if (m_bContentChanged || 216 (!::EqualRect(m_rc, m_rcOld) && !::IsRectEmpty(m_rc) && !::IsRectEmpty(m_rcOld))) 217 { 218 imageModel.PushImageForUndo(); 219 220 canvasWindow.m_drawing = FALSE; 221 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 222 } 223 224 HideSelection(); 225 } 226 227 void SelectionModel::InsertFromHBITMAP(HBITMAP hbmColor, INT x, INT y, HBITMAP hbmMask) 228 { 229 ::DeleteObject(m_hbmColor); 230 m_hbmColor = hbmColor; 231 232 m_rc.left = x; 233 m_rc.top = y; 234 m_rc.right = x + GetDIBWidth(hbmColor); 235 m_rc.bottom = y + GetDIBHeight(hbmColor); 236 237 if (hbmMask) 238 { 239 ::DeleteObject(m_hbmMask); 240 m_hbmMask = hbmMask; 241 } 242 else 243 { 244 ClearMaskImage(); 245 } 246 247 NotifyContentChanged(); 248 } 249 250 void SelectionModel::FlipHorizontally() 251 { 252 TakeOff(); 253 254 HDC hdcMem = ::CreateCompatibleDC(NULL); 255 if (m_hbmMask) 256 { 257 ::SelectObject(hdcMem, m_hbmMask); 258 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 259 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 260 } 261 if (m_hbmColor) 262 { 263 ::SelectObject(hdcMem, m_hbmColor); 264 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 265 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 266 } 267 ::DeleteDC(hdcMem); 268 269 NotifyContentChanged(); 270 } 271 272 void SelectionModel::FlipVertically() 273 { 274 TakeOff(); 275 276 HDC hdcMem = ::CreateCompatibleDC(NULL); 277 if (m_hbmMask) 278 { 279 ::SelectObject(hdcMem, m_hbmMask); 280 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 281 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 282 } 283 if (m_hbmColor) 284 { 285 ::SelectObject(hdcMem, m_hbmColor); 286 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 287 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 288 } 289 ::DeleteDC(hdcMem); 290 291 NotifyContentChanged(); 292 } 293 294 void SelectionModel::RotateNTimes90Degrees(int iN) 295 { 296 HBITMAP hbm; 297 HGDIOBJ hbmOld; 298 HDC hdcMem = ::CreateCompatibleDC(NULL); 299 300 switch (iN) 301 { 302 case 1: /* rotate 90 degrees */ 303 case 3: /* rotate 270 degrees */ 304 TakeOff(); 305 306 if (m_hbmColor) 307 { 308 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 309 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, FALSE); 310 ::SelectObject(hdcMem, hbmOld); 311 ::DeleteObject(m_hbmColor); 312 m_hbmColor = hbm; 313 } 314 if (m_hbmMask) 315 { 316 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 317 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, TRUE); 318 ::SelectObject(hdcMem, hbmOld); 319 ::DeleteObject(m_hbmMask); 320 m_hbmMask = hbm; 321 } 322 323 SwapWidthAndHeight(); 324 break; 325 326 case 2: /* rotate 180 degrees */ 327 TakeOff(); 328 329 if (m_hbmColor) 330 { 331 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 332 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 333 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 334 ::SelectObject(hdcMem, hbmOld); 335 } 336 if (m_hbmMask) 337 { 338 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 339 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 340 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 341 ::SelectObject(hdcMem, hbmOld); 342 } 343 break; 344 } 345 346 ::DeleteDC(hdcMem); 347 NotifyContentChanged(); 348 } 349 350 static void AttachHBITMAP(HBITMAP *phbm, HBITMAP hbmNew) 351 { 352 if (hbmNew == NULL) 353 return; 354 ::DeleteObject(*phbm); 355 *phbm = hbmNew; 356 } 357 358 void SelectionModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY) 359 { 360 if (nStretchPercentX == 100 && nStretchPercentY == 100 && nSkewDegX == 0 && nSkewDegY == 0) 361 return; 362 363 TakeOff(); 364 365 INT oldWidth = m_rc.Width(), oldHeight = m_rc.Height(); 366 INT newWidth = oldWidth * nStretchPercentX / 100; 367 INT newHeight = oldHeight * nStretchPercentY / 100; 368 369 HBITMAP hbmColor = m_hbmColor, hbmMask = m_hbmMask; 370 371 if (hbmMask == NULL) 372 hbmMask = CreateMonoBitmap(oldWidth, oldHeight, TRUE); 373 374 if (oldWidth != newWidth || oldHeight != newHeight) 375 { 376 AttachHBITMAP(&hbmColor, CopyDIBImage(hbmColor, newWidth, newHeight)); 377 AttachHBITMAP(&hbmMask, CopyMonoImage(hbmMask, newWidth, newHeight)); 378 } 379 380 HGDIOBJ hbmOld; 381 HDC hDC = ::CreateCompatibleDC(NULL); 382 383 if (nSkewDegX) 384 { 385 hbmOld = ::SelectObject(hDC, hbmColor); 386 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegX, FALSE)); 387 ::SelectObject(hDC, hbmMask); 388 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegX, FALSE, TRUE)); 389 ::SelectObject(hDC, hbmOld); 390 } 391 392 if (nSkewDegY) 393 { 394 hbmOld = ::SelectObject(hDC, hbmColor); 395 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegY, TRUE)); 396 ::SelectObject(hDC, hbmMask); 397 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegY, TRUE, TRUE)); 398 ::SelectObject(hDC, hbmOld); 399 } 400 401 ::DeleteDC(hDC); 402 403 InsertFromHBITMAP(hbmColor, m_rc.left, m_rc.top, hbmMask); 404 405 m_bShow = TRUE; 406 NotifyContentChanged(); 407 } 408 409 HBITMAP SelectionModel::CopyBitmap() 410 { 411 if (m_hbmColor == NULL) 412 GetSelectionContents(imageModel.GetDC()); 413 return CopyDIBImage(m_hbmColor); 414 } 415 416 int SelectionModel::PtStackSize() const 417 { 418 return m_iPtSP; 419 } 420 421 void SelectionModel::DrawFramePoly(HDC hDCImage) 422 { 423 /* draw the freehand selection inverted/xored */ 424 Poly(hDCImage, m_ptStack, m_iPtSP, 0, 0, 2, 0, FALSE, TRUE); 425 } 426 427 void SelectionModel::SetRectFromPoints(const POINT& ptFrom, const POINT& ptTo) 428 { 429 m_rc.left = min(ptFrom.x, ptTo.x); 430 m_rc.top = min(ptFrom.y, ptTo.y); 431 m_rc.right = max(ptFrom.x, ptTo.x); 432 m_rc.bottom = max(ptFrom.y, ptTo.y); 433 } 434 435 void SelectionModel::Dragging(HITTEST hit, POINT pt) 436 { 437 switch (hit) 438 { 439 case HIT_NONE: 440 break; 441 case HIT_UPPER_LEFT: 442 m_rc.left += pt.x - m_ptHit.x; 443 m_rc.top += pt.y - m_ptHit.y; 444 break; 445 case HIT_UPPER_CENTER: 446 m_rc.top += pt.y - m_ptHit.y; 447 break; 448 case HIT_UPPER_RIGHT: 449 m_rc.right += pt.x - m_ptHit.x; 450 m_rc.top += pt.y - m_ptHit.y; 451 break; 452 case HIT_MIDDLE_LEFT: 453 m_rc.left += pt.x - m_ptHit.x; 454 break; 455 case HIT_MIDDLE_RIGHT: 456 m_rc.right += pt.x - m_ptHit.x; 457 break; 458 case HIT_LOWER_LEFT: 459 m_rc.left += pt.x - m_ptHit.x; 460 m_rc.bottom += pt.y - m_ptHit.y; 461 break; 462 case HIT_LOWER_CENTER: 463 m_rc.bottom += pt.y - m_ptHit.y; 464 break; 465 case HIT_LOWER_RIGHT: 466 m_rc.right += pt.x - m_ptHit.x; 467 m_rc.bottom += pt.y - m_ptHit.y; 468 break; 469 case HIT_BORDER: 470 case HIT_INNER: 471 OffsetRect(&m_rc, pt.x - m_ptHit.x, pt.y - m_ptHit.y); 472 break; 473 } 474 m_ptHit = pt; 475 } 476 477 void SelectionModel::ClearMaskImage() 478 { 479 if (m_hbmMask) 480 { 481 ::DeleteObject(m_hbmMask); 482 m_hbmMask = NULL; 483 } 484 } 485 486 void SelectionModel::ClearColorImage() 487 { 488 if (m_hbmColor) 489 { 490 ::DeleteObject(m_hbmColor); 491 m_hbmColor = NULL; 492 } 493 } 494 495 void SelectionModel::HideSelection() 496 { 497 m_bShow = m_bContentChanged = FALSE; 498 ClearColorImage(); 499 ClearMaskImage(); 500 ::SetRectEmpty(&m_rc); 501 ::SetRectEmpty(&m_rcOld); 502 imageModel.NotifyImageChanged(); 503 } 504 505 void SelectionModel::DeleteSelection() 506 { 507 if (!m_bShow) 508 return; 509 510 TakeOff(); 511 imageModel.PushImageForUndo(); 512 DrawBackground(imageModel.GetDC()); 513 514 HideSelection(); 515 } 516 517 void SelectionModel::InvertSelection() 518 { 519 TakeOff(); 520 521 BITMAP bm; 522 ::GetObject(m_hbmColor, sizeof(bm), &bm); 523 524 HDC hdc = ::CreateCompatibleDC(NULL); 525 HGDIOBJ hbmOld = ::SelectObject(hdc, m_hbmColor); 526 RECT rc = { 0, 0, bm.bmWidth, bm.bmHeight }; 527 ::InvertRect(hdc, &rc); 528 ::SelectObject(hdc, hbmOld); 529 ::DeleteDC(hdc); 530 531 NotifyContentChanged(); 532 } 533 534 void SelectionModel::NotifyContentChanged() 535 { 536 m_bContentChanged = TRUE; 537 imageModel.NotifyImageChanged(); 538 } 539 540 void SelectionModel::SwapWidthAndHeight() 541 { 542 INT cx = m_rc.Width(); 543 INT cy = m_rc.Height(); 544 m_rc.right = m_rc.left + cy; 545 m_rc.bottom = m_rc.top + cx; 546 } 547 548 HBITMAP SelectionModel::LockBitmap() 549 { 550 HBITMAP hbm = m_hbmColor; 551 m_hbmColor = NULL; 552 return hbm; 553 } 554 555 void SelectionModel::UnlockBitmap(HBITMAP hbmLocked) 556 { 557 m_hbmColor = hbmLocked; 558 } 559 560 void SelectionModel::StretchSelection(BOOL bShrink) 561 { 562 if (!m_bShow) 563 return; 564 565 TakeOff(); 566 567 INT cx = m_rc.Width(), cy = m_rc.Height(); 568 569 if (bShrink) 570 m_rc.InflateRect(-cx / 4, -cy / 4); 571 else 572 m_rc.InflateRect(+cx / 2, +cy / 2); 573 574 // The selection area must exist there 575 if (m_rc.Width() <= 0) 576 m_rc.right = m_rc.left + 1; 577 if (m_rc.Height() <= 0) 578 m_rc.bottom = m_rc.top + 1; 579 580 imageModel.NotifyImageChanged(); 581 } 582