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 (!GetObjectW(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 HBITMAP SelectionModel::GetSelectionContents() 156 { 157 if (m_hbmColor) 158 return CopyDIBImage(m_hbmColor, m_rc.Width(), m_rc.Height()); 159 160 HDC hMemDC = ::CreateCompatibleDC(NULL); 161 HBITMAP hBitmap = CreateColorDIB(m_rc.Width(), m_rc.Height(), RGB(255, 255, 255)); 162 HGDIOBJ hbmOld = ::SelectObject(hMemDC, hBitmap); 163 ::BitBlt(hMemDC, 0, 0, m_rc.Width(), m_rc.Height(), imageModel.GetDC(), m_rc.left, m_rc.top, SRCCOPY); 164 ::SelectObject(hMemDC, hbmOld); 165 ::DeleteDC(hMemDC); 166 167 return hBitmap; 168 } 169 170 BOOL SelectionModel::IsLanded() const 171 { 172 return !m_hbmColor; 173 } 174 175 BOOL SelectionModel::TakeOff() 176 { 177 if (!IsLanded() || ::IsRectEmpty(&m_rc)) 178 return FALSE; 179 180 // The background color is needed for transparency of selection 181 m_rgbBack = paletteModel.GetBgColor(); 182 183 // Get the contents of the selection area 184 ClearColorImage(); 185 m_hbmColor = GetSelectionContents(); 186 187 // RectSel doesn't need the mask image 188 if (toolsModel.GetActiveTool() == TOOL_RECTSEL) 189 ClearMaskImage(); 190 191 // Save the selection area 192 m_rcOld = m_rc; 193 194 if (toolsModel.GetActiveTool() == TOOL_RECTSEL) 195 { 196 imageModel.PushImageForUndo(); 197 selectionModel.DrawBackgroundRect(imageModel.GetDC(), selectionModel.m_rgbBack); 198 } 199 else if (toolsModel.GetActiveTool() == TOOL_FREESEL) 200 { 201 imageModel.PushImageForUndo(); 202 selectionModel.DrawBackgroundPoly(imageModel.GetDC(), selectionModel.m_rgbBack); 203 } 204 205 imageModel.NotifyImageChanged(); 206 return TRUE; 207 } 208 209 void SelectionModel::Landing() 210 { 211 if (IsLanded() && !m_bShow) 212 { 213 imageModel.NotifyImageChanged(); 214 return; 215 } 216 217 m_bShow = FALSE; 218 219 if (m_bContentChanged || 220 (!::EqualRect(m_rc, m_rcOld) && !::IsRectEmpty(m_rc) && !::IsRectEmpty(m_rcOld))) 221 { 222 imageModel.PushImageForUndo(); 223 224 canvasWindow.m_drawing = FALSE; 225 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 226 } 227 228 HideSelection(); 229 } 230 231 void SelectionModel::InsertFromHBITMAP(HBITMAP hbmColor, INT x, INT y, HBITMAP hbmMask) 232 { 233 ::DeleteObject(m_hbmColor); 234 m_hbmColor = hbmColor; 235 236 m_rc.left = x; 237 m_rc.top = y; 238 m_rc.right = x + GetDIBWidth(hbmColor); 239 m_rc.bottom = y + GetDIBHeight(hbmColor); 240 241 if (hbmMask) 242 { 243 ::DeleteObject(m_hbmMask); 244 m_hbmMask = hbmMask; 245 } 246 else 247 { 248 ClearMaskImage(); 249 } 250 251 NotifyContentChanged(); 252 } 253 254 void SelectionModel::FlipHorizontally() 255 { 256 TakeOff(); 257 258 HDC hdcMem = ::CreateCompatibleDC(NULL); 259 if (m_hbmMask) 260 { 261 ::SelectObject(hdcMem, m_hbmMask); 262 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 263 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 264 } 265 if (m_hbmColor) 266 { 267 ::SelectObject(hdcMem, m_hbmColor); 268 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 269 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 270 } 271 ::DeleteDC(hdcMem); 272 273 NotifyContentChanged(); 274 } 275 276 void SelectionModel::FlipVertically() 277 { 278 TakeOff(); 279 280 HDC hdcMem = ::CreateCompatibleDC(NULL); 281 if (m_hbmMask) 282 { 283 ::SelectObject(hdcMem, m_hbmMask); 284 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 285 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 286 } 287 if (m_hbmColor) 288 { 289 ::SelectObject(hdcMem, m_hbmColor); 290 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 291 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 292 } 293 ::DeleteDC(hdcMem); 294 295 NotifyContentChanged(); 296 } 297 298 void SelectionModel::RotateNTimes90Degrees(int iN) 299 { 300 HBITMAP hbm; 301 HGDIOBJ hbmOld; 302 HDC hdcMem = ::CreateCompatibleDC(NULL); 303 304 switch (iN) 305 { 306 case 1: /* rotate 90 degrees */ 307 case 3: /* rotate 270 degrees */ 308 TakeOff(); 309 310 if (m_hbmColor) 311 { 312 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 313 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, FALSE); 314 ::SelectObject(hdcMem, hbmOld); 315 ::DeleteObject(m_hbmColor); 316 m_hbmColor = hbm; 317 } 318 if (m_hbmMask) 319 { 320 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 321 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, TRUE); 322 ::SelectObject(hdcMem, hbmOld); 323 ::DeleteObject(m_hbmMask); 324 m_hbmMask = hbm; 325 } 326 327 SwapWidthAndHeight(); 328 break; 329 330 case 2: /* rotate 180 degrees */ 331 TakeOff(); 332 333 if (m_hbmColor) 334 { 335 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 336 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 337 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 338 ::SelectObject(hdcMem, hbmOld); 339 } 340 if (m_hbmMask) 341 { 342 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 343 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 344 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 345 ::SelectObject(hdcMem, hbmOld); 346 } 347 break; 348 } 349 350 ::DeleteDC(hdcMem); 351 NotifyContentChanged(); 352 } 353 354 static void AttachHBITMAP(HBITMAP *phbm, HBITMAP hbmNew) 355 { 356 if (hbmNew == NULL) 357 return; 358 ::DeleteObject(*phbm); 359 *phbm = hbmNew; 360 } 361 362 void SelectionModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY) 363 { 364 if (nStretchPercentX == 100 && nStretchPercentY == 100 && nSkewDegX == 0 && nSkewDegY == 0) 365 return; 366 367 TakeOff(); 368 369 INT oldWidth = m_rc.Width(), oldHeight = m_rc.Height(); 370 INT newWidth = oldWidth * nStretchPercentX / 100; 371 INT newHeight = oldHeight * nStretchPercentY / 100; 372 373 HBITMAP hbmColor = m_hbmColor, hbmMask = m_hbmMask; 374 375 if (hbmMask == NULL) 376 hbmMask = CreateMonoBitmap(oldWidth, oldHeight, TRUE); 377 378 if (oldWidth != newWidth || oldHeight != newHeight) 379 { 380 AttachHBITMAP(&hbmColor, CopyDIBImage(hbmColor, newWidth, newHeight)); 381 AttachHBITMAP(&hbmMask, CopyMonoImage(hbmMask, newWidth, newHeight)); 382 } 383 384 HGDIOBJ hbmOld; 385 HDC hDC = ::CreateCompatibleDC(NULL); 386 387 if (nSkewDegX) 388 { 389 hbmOld = ::SelectObject(hDC, hbmColor); 390 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegX, FALSE)); 391 ::SelectObject(hDC, hbmMask); 392 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegX, FALSE, TRUE)); 393 ::SelectObject(hDC, hbmOld); 394 } 395 396 if (nSkewDegY) 397 { 398 hbmOld = ::SelectObject(hDC, hbmColor); 399 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegY, TRUE)); 400 ::SelectObject(hDC, hbmMask); 401 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegY, TRUE, TRUE)); 402 ::SelectObject(hDC, hbmOld); 403 } 404 405 ::DeleteDC(hDC); 406 407 InsertFromHBITMAP(hbmColor, m_rc.left, m_rc.top, hbmMask); 408 409 m_bShow = TRUE; 410 NotifyContentChanged(); 411 } 412 413 int SelectionModel::PtStackSize() const 414 { 415 return m_iPtSP; 416 } 417 418 void SelectionModel::DrawFramePoly(HDC hDCImage) 419 { 420 /* draw the freehand selection inverted/xored */ 421 Poly(hDCImage, m_ptStack, m_iPtSP, 0, 0, 2, 0, FALSE, TRUE); 422 } 423 424 void SelectionModel::SetRectFromPoints(const POINT& ptFrom, const POINT& ptTo) 425 { 426 m_rc.left = min(ptFrom.x, ptTo.x); 427 m_rc.top = min(ptFrom.y, ptTo.y); 428 m_rc.right = max(ptFrom.x, ptTo.x); 429 m_rc.bottom = max(ptFrom.y, ptTo.y); 430 } 431 432 void SelectionModel::Dragging(HITTEST hit, POINT pt) 433 { 434 switch (hit) 435 { 436 case HIT_NONE: 437 break; 438 case HIT_UPPER_LEFT: 439 m_rc.left += pt.x - m_ptHit.x; 440 m_rc.top += pt.y - m_ptHit.y; 441 break; 442 case HIT_UPPER_CENTER: 443 m_rc.top += pt.y - m_ptHit.y; 444 break; 445 case HIT_UPPER_RIGHT: 446 m_rc.right += pt.x - m_ptHit.x; 447 m_rc.top += pt.y - m_ptHit.y; 448 break; 449 case HIT_MIDDLE_LEFT: 450 m_rc.left += pt.x - m_ptHit.x; 451 break; 452 case HIT_MIDDLE_RIGHT: 453 m_rc.right += pt.x - m_ptHit.x; 454 break; 455 case HIT_LOWER_LEFT: 456 m_rc.left += pt.x - m_ptHit.x; 457 m_rc.bottom += pt.y - m_ptHit.y; 458 break; 459 case HIT_LOWER_CENTER: 460 m_rc.bottom += pt.y - m_ptHit.y; 461 break; 462 case HIT_LOWER_RIGHT: 463 m_rc.right += pt.x - m_ptHit.x; 464 m_rc.bottom += pt.y - m_ptHit.y; 465 break; 466 case HIT_BORDER: 467 case HIT_INNER: 468 OffsetRect(&m_rc, pt.x - m_ptHit.x, pt.y - m_ptHit.y); 469 break; 470 } 471 m_ptHit = pt; 472 } 473 474 void SelectionModel::ClearMaskImage() 475 { 476 if (m_hbmMask) 477 { 478 ::DeleteObject(m_hbmMask); 479 m_hbmMask = NULL; 480 } 481 } 482 483 void SelectionModel::ClearColorImage() 484 { 485 if (m_hbmColor) 486 { 487 ::DeleteObject(m_hbmColor); 488 m_hbmColor = NULL; 489 } 490 } 491 492 void SelectionModel::HideSelection() 493 { 494 m_bShow = m_bContentChanged = FALSE; 495 ClearColorImage(); 496 ClearMaskImage(); 497 ::SetRectEmpty(&m_rc); 498 ::SetRectEmpty(&m_rcOld); 499 imageModel.NotifyImageChanged(); 500 } 501 502 void SelectionModel::DeleteSelection() 503 { 504 if (!m_bShow) 505 return; 506 507 TakeOff(); 508 imageModel.PushImageForUndo(); 509 DrawBackground(imageModel.GetDC()); 510 511 HideSelection(); 512 } 513 514 void SelectionModel::InvertSelection() 515 { 516 TakeOff(); 517 518 BITMAP bm; 519 ::GetObjectW(m_hbmColor, sizeof(bm), &bm); 520 521 HDC hdc = ::CreateCompatibleDC(NULL); 522 HGDIOBJ hbmOld = ::SelectObject(hdc, m_hbmColor); 523 RECT rc = { 0, 0, bm.bmWidth, bm.bmHeight }; 524 ::InvertRect(hdc, &rc); 525 ::SelectObject(hdc, hbmOld); 526 ::DeleteDC(hdc); 527 528 NotifyContentChanged(); 529 } 530 531 void SelectionModel::NotifyContentChanged() 532 { 533 m_bContentChanged = TRUE; 534 imageModel.NotifyImageChanged(); 535 } 536 537 void SelectionModel::SwapWidthAndHeight() 538 { 539 INT cx = m_rc.Width(); 540 INT cy = m_rc.Height(); 541 m_rc.right = m_rc.left + cy; 542 m_rc.bottom = m_rc.top + cx; 543 } 544 545 void SelectionModel::StretchSelection(BOOL bShrink) 546 { 547 if (!m_bShow) 548 return; 549 550 TakeOff(); 551 552 INT cx = m_rc.Width(), cy = m_rc.Height(); 553 554 if (bShrink) 555 m_rc.InflateRect(-cx / 4, -cy / 4); 556 else 557 m_rc.InflateRect(+cx / 2, +cy / 2); 558 559 // The selection area must exist there 560 if (m_rc.Width() <= 0) 561 m_rc.right = m_rc.left + 1; 562 if (m_rc.Height() <= 0) 563 m_rc.bottom = m_rc.top + 1; 564 565 imageModel.NotifyImageChanged(); 566 } 567