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