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