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_rgbBack(RGB(255, 255, 255)) 19 , m_bShow(FALSE) 20 , m_bContentChanged(FALSE) 21 { 22 m_rc.SetRectEmpty(); 23 m_rcOld.SetRectEmpty(); 24 m_ptHit = { -1, -1 }; 25 } 26 27 SelectionModel::~SelectionModel() 28 { 29 ClearColorImage(); 30 ClearMaskImage(); 31 } 32 33 void SelectionModel::DrawBackgroundPoly(HDC hDCImage, COLORREF crBg) 34 { 35 if (m_rcOld.IsRectEmpty()) 36 return; 37 38 HGDIOBJ hbrOld = ::SelectObject(hDCImage, ::GetStockObject(DC_BRUSH)); 39 ::SetDCBrushColor(hDCImage, crBg); 40 ::MaskBlt(hDCImage, m_rcOld.left, m_rcOld.top, m_rcOld.Width(), m_rcOld.Height(), 41 hDCImage, m_rcOld.left, m_rcOld.top, m_hbmMask, 0, 0, MAKEROP4(PATCOPY, SRCCOPY)); 42 ::SelectObject(hDCImage, hbrOld); 43 } 44 45 void SelectionModel::DrawBackgroundRect(HDC hDCImage, COLORREF crBg) 46 { 47 if (m_rcOld.IsRectEmpty()) 48 return; 49 50 Rect(hDCImage, m_rcOld.left, m_rcOld.top, m_rcOld.right, m_rcOld.bottom, crBg, crBg, 0, 1); 51 } 52 53 void SelectionModel::DrawBackground(HDC hDCImage, COLORREF crBg) 54 { 55 if (toolsModel.GetActiveTool() == TOOL_FREESEL) 56 DrawBackgroundPoly(hDCImage, crBg); 57 else 58 DrawBackgroundRect(hDCImage, crBg); 59 } 60 61 void 62 SelectionModel::DrawSelection(HDC hDCImage, COLORREF crBg, BOOL bBgTransparent, const CRect& rc, 63 HBITMAP hbm) 64 { 65 if (rc.IsRectEmpty()) 66 return; 67 68 BITMAP bm; 69 if (!GetObjectW(hbm, sizeof(BITMAP), &bm)) 70 return; 71 72 COLORREF keyColor = (bBgTransparent ? crBg : CLR_INVALID); 73 74 HDC hMemDC = CreateCompatibleDC(hDCImage); 75 HGDIOBJ hbmOld = SelectObject(hMemDC, hbm); 76 ColorKeyedMaskBlt(hDCImage, rc.left, rc.top, rc.Width(), rc.Height(), 77 hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, m_hbmMask, keyColor); 78 SelectObject(hMemDC, hbmOld); 79 DeleteDC(hMemDC); 80 } 81 82 void SelectionModel::setMask(const CRect& rc, HBITMAP hbmMask) 83 { 84 if (m_hbmMask) 85 ::DeleteObject(m_hbmMask); 86 87 m_hbmMask = hbmMask; 88 m_rc = m_rcOld = rc; 89 } 90 91 HBITMAP SelectionModel::GetSelectionContents() 92 { 93 HBITMAP hbmWhole = imageModel.LockBitmap(); 94 HBITMAP hbmPart = getSubImage(hbmWhole, (IsLanded() ? m_rc : m_rcOld)); 95 imageModel.UnlockBitmap(hbmWhole); 96 if (!hbmPart) 97 return NULL; 98 99 CRect rc = { 0, 0, m_rc.Width(), m_rc.Height() }; 100 101 HDC hdcMem = ::CreateCompatibleDC(NULL); 102 HBITMAP hbmNew = CreateColorDIB(rc.Width(), rc.Height(), paletteModel.GetBgColor()); 103 HGDIOBJ hbmOld = ::SelectObject(hdcMem, hbmNew); 104 selectionModel.DrawSelection(hdcMem, paletteModel.GetBgColor(), TRUE, rc, hbmPart); 105 ::SelectObject(hdcMem, hbmOld); 106 ::DeleteDC(hdcMem); 107 108 ::DeleteObject(hbmPart); 109 return hbmNew; 110 } 111 112 BOOL SelectionModel::IsLanded() const 113 { 114 return !m_hbmColor; 115 } 116 117 BOOL SelectionModel::TakeOff() 118 { 119 if (!IsLanded() || m_rc.IsRectEmpty()) 120 return FALSE; 121 122 // The background color is needed for transparency of selection 123 m_rgbBack = paletteModel.GetBgColor(); 124 125 // Get the contents of the selection area 126 ClearColorImage(); 127 m_hbmColor = GetSelectionContents(); 128 129 // RectSel doesn't need the mask image 130 if (toolsModel.GetActiveTool() == TOOL_RECTSEL) 131 ClearMaskImage(); 132 133 // Save the selection area 134 m_rcOld = m_rc; 135 136 imageModel.NotifyImageChanged(); 137 return TRUE; 138 } 139 140 void SelectionModel::Landing() 141 { 142 if (IsLanded() && !m_bShow) 143 { 144 imageModel.NotifyImageChanged(); 145 return; 146 } 147 148 if (m_bContentChanged || 149 (!m_rc.EqualRect(m_rcOld) && !m_rc.IsRectEmpty() && !m_rcOld.IsRectEmpty())) 150 { 151 CRect rc; 152 rc.UnionRect(m_rc, m_rcOld); 153 imageModel.PushImageForUndo(rc); 154 155 canvasWindow.m_drawing = FALSE; 156 toolsModel.OnDrawOverlayOnImage(imageModel.GetDC()); 157 } 158 159 HideSelection(); 160 } 161 162 void SelectionModel::InsertFromHBITMAP(HBITMAP hbmColor, INT x, INT y, HBITMAP hbmMask) 163 { 164 ::DeleteObject(m_hbmColor); 165 m_hbmColor = hbmColor; 166 167 m_rc.left = x; 168 m_rc.top = y; 169 m_rc.right = x + GetDIBWidth(hbmColor); 170 m_rc.bottom = y + GetDIBHeight(hbmColor); 171 172 if (hbmMask) 173 { 174 ::DeleteObject(m_hbmMask); 175 m_hbmMask = hbmMask; 176 } 177 else 178 { 179 ClearMaskImage(); 180 } 181 182 NotifyContentChanged(); 183 } 184 185 void SelectionModel::FlipHorizontally() 186 { 187 TakeOff(); 188 189 HDC hdcMem = ::CreateCompatibleDC(NULL); 190 if (m_hbmMask) 191 { 192 ::SelectObject(hdcMem, m_hbmMask); 193 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 194 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 195 } 196 if (m_hbmColor) 197 { 198 ::SelectObject(hdcMem, m_hbmColor); 199 ::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(), 200 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 201 } 202 ::DeleteDC(hdcMem); 203 204 NotifyContentChanged(); 205 } 206 207 void SelectionModel::FlipVertically() 208 { 209 TakeOff(); 210 211 HDC hdcMem = ::CreateCompatibleDC(NULL); 212 if (m_hbmMask) 213 { 214 ::SelectObject(hdcMem, m_hbmMask); 215 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 216 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 217 } 218 if (m_hbmColor) 219 { 220 ::SelectObject(hdcMem, m_hbmColor); 221 ::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(), 222 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 223 } 224 ::DeleteDC(hdcMem); 225 226 NotifyContentChanged(); 227 } 228 229 void SelectionModel::RotateNTimes90Degrees(int iN) 230 { 231 HBITMAP hbm; 232 HGDIOBJ hbmOld; 233 HDC hdcMem = ::CreateCompatibleDC(NULL); 234 235 switch (iN) 236 { 237 case 1: /* rotate 90 degrees */ 238 case 3: /* rotate 270 degrees */ 239 TakeOff(); 240 241 if (m_hbmColor) 242 { 243 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 244 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, FALSE); 245 ::SelectObject(hdcMem, hbmOld); 246 ::DeleteObject(m_hbmColor); 247 m_hbmColor = hbm; 248 } 249 if (m_hbmMask) 250 { 251 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 252 hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, TRUE); 253 ::SelectObject(hdcMem, hbmOld); 254 ::DeleteObject(m_hbmMask); 255 m_hbmMask = hbm; 256 } 257 258 SwapWidthAndHeight(); 259 break; 260 261 case 2: /* rotate 180 degrees */ 262 TakeOff(); 263 264 if (m_hbmColor) 265 { 266 hbmOld = ::SelectObject(hdcMem, m_hbmColor); 267 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 268 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 269 ::SelectObject(hdcMem, hbmOld); 270 } 271 if (m_hbmMask) 272 { 273 hbmOld = ::SelectObject(hdcMem, m_hbmMask); 274 ::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(), 275 hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY); 276 ::SelectObject(hdcMem, hbmOld); 277 } 278 break; 279 } 280 281 ::DeleteDC(hdcMem); 282 NotifyContentChanged(); 283 } 284 285 static void AttachHBITMAP(HBITMAP *phbm, HBITMAP hbmNew) 286 { 287 if (hbmNew == NULL) 288 return; 289 ::DeleteObject(*phbm); 290 *phbm = hbmNew; 291 } 292 293 void SelectionModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY) 294 { 295 if (nStretchPercentX == 100 && nStretchPercentY == 100 && nSkewDegX == 0 && nSkewDegY == 0) 296 return; 297 298 TakeOff(); 299 300 INT oldWidth = m_rc.Width(), oldHeight = m_rc.Height(); 301 INT newWidth = oldWidth * nStretchPercentX / 100; 302 INT newHeight = oldHeight * nStretchPercentY / 100; 303 304 HBITMAP hbmColor = m_hbmColor, hbmMask = m_hbmMask; 305 306 if (hbmMask == NULL) 307 hbmMask = CreateMonoBitmap(oldWidth, oldHeight, TRUE); 308 309 if (oldWidth != newWidth || oldHeight != newHeight) 310 { 311 AttachHBITMAP(&hbmColor, CopyDIBImage(hbmColor, newWidth, newHeight)); 312 AttachHBITMAP(&hbmMask, CopyMonoImage(hbmMask, newWidth, newHeight)); 313 } 314 315 HGDIOBJ hbmOld; 316 HDC hDC = ::CreateCompatibleDC(NULL); 317 318 if (nSkewDegX) 319 { 320 hbmOld = ::SelectObject(hDC, hbmColor); 321 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegX, FALSE)); 322 ::SelectObject(hDC, hbmMask); 323 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegX, FALSE, TRUE)); 324 ::SelectObject(hDC, hbmOld); 325 } 326 327 if (nSkewDegY) 328 { 329 hbmOld = ::SelectObject(hDC, hbmColor); 330 AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegY, TRUE)); 331 ::SelectObject(hDC, hbmMask); 332 AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegY, TRUE, TRUE)); 333 ::SelectObject(hDC, hbmOld); 334 } 335 336 ::DeleteDC(hDC); 337 338 InsertFromHBITMAP(hbmColor, m_rc.left, m_rc.top, hbmMask); 339 340 m_bShow = TRUE; 341 NotifyContentChanged(); 342 } 343 344 void SelectionModel::SetRectFromPoints(const POINT& ptFrom, const POINT& ptTo) 345 { 346 m_rc = CRect(ptFrom, ptTo); 347 m_rc.NormalizeRect(); 348 } 349 350 void SelectionModel::Dragging(HITTEST hit, POINT pt) 351 { 352 switch (hit) 353 { 354 case HIT_NONE: 355 break; 356 case HIT_UPPER_LEFT: 357 m_rc.left += pt.x - m_ptHit.x; 358 m_rc.top += pt.y - m_ptHit.y; 359 break; 360 case HIT_UPPER_CENTER: 361 m_rc.top += pt.y - m_ptHit.y; 362 break; 363 case HIT_UPPER_RIGHT: 364 m_rc.right += pt.x - m_ptHit.x; 365 m_rc.top += pt.y - m_ptHit.y; 366 break; 367 case HIT_MIDDLE_LEFT: 368 m_rc.left += pt.x - m_ptHit.x; 369 break; 370 case HIT_MIDDLE_RIGHT: 371 m_rc.right += pt.x - m_ptHit.x; 372 break; 373 case HIT_LOWER_LEFT: 374 m_rc.left += pt.x - m_ptHit.x; 375 m_rc.bottom += pt.y - m_ptHit.y; 376 break; 377 case HIT_LOWER_CENTER: 378 m_rc.bottom += pt.y - m_ptHit.y; 379 break; 380 case HIT_LOWER_RIGHT: 381 m_rc.right += pt.x - m_ptHit.x; 382 m_rc.bottom += pt.y - m_ptHit.y; 383 break; 384 case HIT_BORDER: 385 case HIT_INNER: 386 m_rc.OffsetRect(pt.x - m_ptHit.x, pt.y - m_ptHit.y); 387 break; 388 } 389 m_ptHit = pt; 390 } 391 392 void SelectionModel::ClearMaskImage() 393 { 394 if (m_hbmMask) 395 { 396 ::DeleteObject(m_hbmMask); 397 m_hbmMask = NULL; 398 } 399 } 400 401 void SelectionModel::ClearColorImage() 402 { 403 if (m_hbmColor) 404 { 405 ::DeleteObject(m_hbmColor); 406 m_hbmColor = NULL; 407 } 408 } 409 410 void SelectionModel::HideSelection() 411 { 412 m_bShow = m_bContentChanged = FALSE; 413 ClearColorImage(); 414 ClearMaskImage(); 415 m_rc.SetRectEmpty(); 416 m_rcOld.SetRectEmpty(); 417 imageModel.NotifyImageChanged(); 418 } 419 420 void SelectionModel::DeleteSelection() 421 { 422 if (!m_bShow) 423 return; 424 425 TakeOff(); 426 imageModel.PushImageForUndo(); 427 DrawBackground(imageModel.GetDC(), paletteModel.GetBgColor()); 428 429 HideSelection(); 430 } 431 432 void SelectionModel::InvertSelection() 433 { 434 TakeOff(); 435 436 BITMAP bm; 437 ::GetObjectW(m_hbmColor, sizeof(bm), &bm); 438 439 HDC hdc = ::CreateCompatibleDC(NULL); 440 HGDIOBJ hbmOld = ::SelectObject(hdc, m_hbmColor); 441 RECT rc = { 0, 0, bm.bmWidth, bm.bmHeight }; 442 ::InvertRect(hdc, &rc); 443 ::SelectObject(hdc, hbmOld); 444 ::DeleteDC(hdc); 445 446 NotifyContentChanged(); 447 } 448 449 void SelectionModel::NotifyContentChanged() 450 { 451 m_bContentChanged = TRUE; 452 imageModel.NotifyImageChanged(); 453 } 454 455 void SelectionModel::SwapWidthAndHeight() 456 { 457 INT cx = m_rc.Width(), cy = m_rc.Height(); 458 m_rc.right = m_rc.left + cy; 459 m_rc.bottom = m_rc.top + cx; 460 } 461 462 HITTEST SelectionModel::hitTest(POINT ptCanvas) 463 { 464 if (!m_bShow) 465 return HIT_NONE; 466 467 CRect rcSelection = m_rc; 468 canvasWindow.ImageToCanvas(rcSelection); 469 rcSelection.InflateRect(GRIP_SIZE, GRIP_SIZE); 470 return getSizeBoxHitTest(ptCanvas, &rcSelection); 471 } 472 473 void SelectionModel::drawFrameOnCanvas(HDC hCanvasDC) 474 { 475 if (!m_bShow) 476 return; 477 478 CRect rcSelection = m_rc; 479 canvasWindow.ImageToCanvas(rcSelection); 480 rcSelection.InflateRect(GRIP_SIZE, GRIP_SIZE); 481 drawSizeBoxes(hCanvasDC, &rcSelection, TRUE); 482 } 483 484 void SelectionModel::moveSelection(INT xDelta, INT yDelta) 485 { 486 if (!m_bShow) 487 return; 488 489 TakeOff(); 490 m_rc.OffsetRect(xDelta, yDelta); 491 canvasWindow.Invalidate(); 492 } 493 494 void SelectionModel::StretchSelection(BOOL bShrink) 495 { 496 if (!m_bShow) 497 return; 498 499 TakeOff(); 500 501 INT cx = m_rc.Width(), cy = m_rc.Height(); 502 503 if (bShrink) 504 m_rc.InflateRect(-cx / 4, -cy / 4); 505 else 506 m_rc.InflateRect(+cx / 2, +cy / 2); 507 508 // The selection area must exist there 509 if (m_rc.Width() <= 0) 510 m_rc.right = m_rc.left + 1; 511 if (m_rc.Height() <= 0) 512 m_rc.bottom = m_rc.top + 1; 513 514 imageModel.NotifyImageChanged(); 515 } 516