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