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