1 /*
2  * PROJECT:    PAINT for ReactOS
3  * LICENSE:    LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
4  * PURPOSE:    Undo and redo functionality
5  * COPYRIGHT:  Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
6  *             Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
7  */
8 
9 #include "precomp.h"
10 
11 ImageModel imageModel;
12 
13 /* FUNCTIONS ********************************************************/
14 
15 void IMAGE_PART::clear()
16 {
17     ::DeleteObject(m_hbmImage);
18     m_hbmImage = NULL;
19     m_rcPart.SetRectEmpty();
20     m_bPartial = FALSE;
21 }
22 
23 void ImageModel::NotifyImageChanged()
24 {
25     if (canvasWindow.IsWindow())
26     {
27         canvasWindow.updateScrollRange();
28         canvasWindow.Invalidate();
29     }
30 
31     if (miniature.IsWindow())
32         miniature.Invalidate();
33 }
34 
35 ImageModel::ImageModel()
36     : m_hDrawingDC(::CreateCompatibleDC(NULL))
37     , m_currInd(0)
38     , m_undoSteps(0)
39     , m_redoSteps(0)
40 {
41     ZeroMemory(m_historyItems, sizeof(m_historyItems));
42 
43     m_hbmMaster = CreateColorDIB(1, 1, RGB(255, 255, 255));
44     m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster);
45 
46     g_imageSaved = TRUE;
47 }
48 
49 ImageModel::~ImageModel()
50 {
51     ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
52     ::DeleteDC(m_hDrawingDC);
53     ::DeleteObject(m_hbmMaster);
54     ClearHistory();
55 }
56 
57 void ImageModel::SwapPart()
58 {
59     IMAGE_PART& part = m_historyItems[m_currInd];
60     if (!part.m_bPartial)
61     {
62         Swap(m_hbmMaster, part.m_hbmImage);
63         return;
64     }
65 
66     HBITMAP hbmMaster = LockBitmap();
67     HBITMAP hbmPart = getSubImage(hbmMaster, part.m_rcPart);
68     putSubImage(hbmMaster, part.m_rcPart, part.m_hbmImage);
69     ::DeleteObject(part.m_hbmImage);
70     part.m_hbmImage = hbmPart;
71     UnlockBitmap(hbmMaster);
72 }
73 
74 void ImageModel::Undo(BOOL bClearRedo)
75 {
76     ATLTRACE("%s: %d\n", __FUNCTION__, m_undoSteps);
77     if (!CanUndo())
78         return;
79 
80     selectionModel.HideSelection();
81 
82     m_currInd = (m_currInd + HISTORYSIZE - 1) % HISTORYSIZE; // Go previous
83     ATLASSERT(m_hbmMaster != NULL);
84     SwapPart();
85     ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
86 
87     m_undoSteps--;
88     if (bClearRedo)
89         m_redoSteps = 0;
90     else if (m_redoSteps < HISTORYSIZE - 1)
91         m_redoSteps++;
92 
93     NotifyImageChanged();
94 }
95 
96 void ImageModel::Redo()
97 {
98     ATLTRACE("%s: %d\n", __FUNCTION__, m_redoSteps);
99     if (!CanRedo())
100         return;
101 
102     selectionModel.HideSelection();
103 
104     ATLASSERT(m_hbmMaster != NULL);
105     SwapPart();
106     m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
107     ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
108 
109     m_redoSteps--;
110     if (m_undoSteps < HISTORYSIZE - 1)
111         m_undoSteps++;
112 
113     NotifyImageChanged();
114 }
115 
116 void ImageModel::ClearHistory()
117 {
118     for (int i = 0; i < HISTORYSIZE; ++i)
119     {
120         m_historyItems[i].clear();
121     }
122 
123     m_undoSteps = 0;
124     m_redoSteps = 0;
125 }
126 
127 void ImageModel::PushImageForUndo()
128 {
129     HBITMAP hbm = CopyBitmap();
130     if (hbm == NULL)
131     {
132         ShowOutOfMemory();
133         return;
134     }
135 
136     PushImageForUndo(hbm);
137 }
138 
139 void ImageModel::PushImageForUndo(HBITMAP hbm)
140 {
141     ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
142 
143     if (hbm == NULL)
144     {
145         ShowOutOfMemory();
146         return;
147     }
148 
149     IMAGE_PART& part = m_historyItems[m_currInd];
150     part.clear();
151     part.m_hbmImage = m_hbmMaster;
152     m_hbmMaster = hbm;
153     ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
154 
155     PushDone();
156 }
157 
158 void ImageModel::PushImageForUndo(const RECT& rcPartial)
159 {
160     ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
161 
162     IMAGE_PART& part = m_historyItems[m_currInd];
163     part.clear();
164     part.m_bPartial = TRUE;
165     part.m_rcPart = rcPartial;
166 
167     CRect rcImage = { 0, 0, GetWidth(), GetHeight() };
168     CRect& rc = part.m_rcPart;
169     if (!rc.IntersectRect(rc, rcImage))
170         rc.SetRect(-1, -1, 0, 0);
171 
172     HBITMAP hbmMaster = LockBitmap();
173     part.m_hbmImage = getSubImage(hbmMaster, rc);
174     UnlockBitmap(hbmMaster);
175 
176     PushDone();
177 }
178 
179 void ImageModel::PushDone()
180 {
181     m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
182 
183     if (m_undoSteps < HISTORYSIZE - 1)
184         m_undoSteps++;
185     m_redoSteps = 0;
186 
187     g_imageSaved = FALSE;
188     NotifyImageChanged();
189 }
190 
191 void ImageModel::Crop(int nWidth, int nHeight, int nOffsetX, int nOffsetY)
192 {
193     // We cannot create bitmaps of size zero
194     if (nWidth <= 0)
195         nWidth = 1;
196     if (nHeight <= 0)
197         nHeight = 1;
198 
199     // Create a white HBITMAP
200     HBITMAP hbmNew = CreateColorDIB(nWidth, nHeight, RGB(255, 255, 255));
201     if (!hbmNew)
202     {
203         ShowOutOfMemory();
204         return;
205     }
206 
207     // Put the master image as a sub-image
208     RECT rcPart = { -nOffsetX, -nOffsetY, GetWidth() - nOffsetX, GetHeight() - nOffsetY };
209     HBITMAP hbmOld = imageModel.LockBitmap();
210     putSubImage(hbmNew, rcPart, hbmOld);
211     imageModel.UnlockBitmap(hbmOld);
212 
213     // Push it
214     PushImageForUndo(hbmNew);
215 
216     NotifyImageChanged();
217 }
218 
219 void ImageModel::SaveImage(LPCWSTR lpFileName)
220 {
221     SaveDIBToFile(m_hbmMaster, lpFileName, TRUE);
222 }
223 
224 BOOL ImageModel::IsImageSaved() const
225 {
226     return g_imageSaved;
227 }
228 
229 void ImageModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
230 {
231     int oldWidth = GetWidth();
232     int oldHeight = GetHeight();
233     INT newWidth = oldWidth * nStretchPercentX / 100;
234     INT newHeight = oldHeight * nStretchPercentY / 100;
235     if (oldWidth != newWidth || oldHeight != newHeight)
236     {
237         HBITMAP hbm0 = CopyDIBImage(m_hbmMaster, newWidth, newHeight);
238         PushImageForUndo(hbm0);
239     }
240     if (nSkewDegX)
241     {
242         HBITMAP hbm1 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegX, FALSE);
243         PushImageForUndo(hbm1);
244     }
245     if (nSkewDegY)
246     {
247         HBITMAP hbm2 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegY, TRUE);
248         PushImageForUndo(hbm2);
249     }
250     NotifyImageChanged();
251 }
252 
253 int ImageModel::GetWidth() const
254 {
255     return GetDIBWidth(m_hbmMaster);
256 }
257 
258 int ImageModel::GetHeight() const
259 {
260     return GetDIBHeight(m_hbmMaster);
261 }
262 
263 void ImageModel::InvertColors()
264 {
265     RECT rect = {0, 0, GetWidth(), GetHeight()};
266     PushImageForUndo();
267     InvertRect(m_hDrawingDC, &rect);
268     NotifyImageChanged();
269 }
270 
271 HDC ImageModel::GetDC()
272 {
273     return m_hDrawingDC;
274 }
275 
276 void ImageModel::FlipHorizontally()
277 {
278     PushImageForUndo();
279     StretchBlt(m_hDrawingDC, GetWidth() - 1, 0, -GetWidth(), GetHeight(), GetDC(), 0, 0,
280                GetWidth(), GetHeight(), SRCCOPY);
281     NotifyImageChanged();
282 }
283 
284 void ImageModel::FlipVertically()
285 {
286     PushImageForUndo();
287     StretchBlt(m_hDrawingDC, 0, GetHeight() - 1, GetWidth(), -GetHeight(), GetDC(), 0, 0,
288                GetWidth(), GetHeight(), SRCCOPY);
289     NotifyImageChanged();
290 }
291 
292 void ImageModel::RotateNTimes90Degrees(int iN)
293 {
294     switch (iN)
295     {
296         case 1:
297         case 3:
298         {
299             HBITMAP hbm = Rotate90DegreeBlt(m_hDrawingDC, GetWidth(), GetHeight(), iN == 1, FALSE);
300             PushImageForUndo(hbm);
301             break;
302         }
303         case 2:
304         {
305             PushImageForUndo();
306             ::StretchBlt(m_hDrawingDC, GetWidth() - 1, GetHeight() - 1, -GetWidth(), -GetHeight(),
307                          m_hDrawingDC, 0, 0, GetWidth(), GetHeight(), SRCCOPY);
308             break;
309         }
310     }
311     NotifyImageChanged();
312 }
313 
314 void ImageModel::Clamp(POINT& pt) const
315 {
316     pt.x = max(0, min(pt.x, GetWidth()));
317     pt.y = max(0, min(pt.y, GetHeight()));
318 }
319 
320 HBITMAP ImageModel::CopyBitmap()
321 {
322     HBITMAP hBitmap = LockBitmap();
323     HBITMAP ret = CopyDIBImage(hBitmap);
324     UnlockBitmap(hBitmap);
325     return ret;
326 }
327 
328 BOOL ImageModel::IsBlackAndWhite()
329 {
330     HBITMAP hBitmap = LockBitmap();
331     BOOL bBlackAndWhite = IsBitmapBlackAndWhite(hBitmap);
332     UnlockBitmap(hBitmap);
333     return bBlackAndWhite;
334 }
335 
336 void ImageModel::PushBlackAndWhite()
337 {
338     HBITMAP hBitmap = LockBitmap();
339     HBITMAP hNewBitmap = ConvertToBlackAndWhite(hBitmap);
340     UnlockBitmap(hBitmap);
341 
342     PushImageForUndo(hNewBitmap);
343 }
344 
345 HBITMAP ImageModel::LockBitmap()
346 {
347     // NOTE: An app cannot select a bitmap into more than one device context at a time.
348     ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
349     HBITMAP hbmLocked = m_hbmMaster;
350     m_hbmMaster = NULL;
351     return hbmLocked;
352 }
353 
354 void ImageModel::UnlockBitmap(HBITMAP hbmLocked)
355 {
356     m_hbmMaster = hbmLocked;
357     m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
358 }
359