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
SelectionModel()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
~SelectionModel()27 SelectionModel::~SelectionModel()
28 {
29 ClearColorImage();
30 ClearMaskImage();
31 }
32
DrawBackgroundPoly(HDC hDCImage,COLORREF crBg)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
DrawBackgroundRect(HDC hDCImage,COLORREF crBg)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
DrawBackground(HDC hDCImage,COLORREF crBg)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
DrawSelection(HDC hDCImage,COLORREF crBg,BOOL bBgTransparent,const CRect & rc,HBITMAP hbm)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
setMask(const CRect & rc,HBITMAP hbmMask)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
GetSelectionContents()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
IsLanded() const112 BOOL SelectionModel::IsLanded() const
113 {
114 return !m_hbmColor;
115 }
116
TakeOff()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
Landing()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
InsertFromHBITMAP(HBITMAP hbmColor,INT x,INT y,HBITMAP hbmMask)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
FlipHorizontally()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
FlipVertically()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
RotateNTimes90Degrees(int iN)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
AttachHBITMAP(HBITMAP * phbm,HBITMAP hbmNew)285 static void AttachHBITMAP(HBITMAP *phbm, HBITMAP hbmNew)
286 {
287 if (hbmNew == NULL)
288 return;
289 ::DeleteObject(*phbm);
290 *phbm = hbmNew;
291 }
292
StretchSkew(int nStretchPercentX,int nStretchPercentY,int nSkewDegX,int nSkewDegY)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
SetRectFromPoints(const POINT & ptFrom,const POINT & ptTo)344 void SelectionModel::SetRectFromPoints(const POINT& ptFrom, const POINT& ptTo)
345 {
346 m_rc = CRect(ptFrom, ptTo);
347 m_rc.NormalizeRect();
348 }
349
Dragging(HITTEST hit,POINT pt)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
ClearMaskImage()392 void SelectionModel::ClearMaskImage()
393 {
394 if (m_hbmMask)
395 {
396 ::DeleteObject(m_hbmMask);
397 m_hbmMask = NULL;
398 }
399 }
400
ClearColorImage()401 void SelectionModel::ClearColorImage()
402 {
403 if (m_hbmColor)
404 {
405 ::DeleteObject(m_hbmColor);
406 m_hbmColor = NULL;
407 }
408 }
409
HideSelection()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
DeleteSelection()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
InvertSelection()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
NotifyContentChanged()449 void SelectionModel::NotifyContentChanged()
450 {
451 m_bContentChanged = TRUE;
452 imageModel.NotifyImageChanged();
453 }
454
SwapWidthAndHeight()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
hitTest(POINT ptCanvas)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
drawFrameOnCanvas(HDC hCanvasDC)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
moveSelection(INT xDelta,INT yDelta)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
StretchSelection(BOOL bShrink)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