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     HBITMAP hbmMaster = LockBitmap();
168     part.m_hbmImage = getSubImage(hbmMaster, rcPartial);
169     UnlockBitmap(hbmMaster);
170 
171     PushDone();
172 }
173 
174 void ImageModel::PushDone()
175 {
176     m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
177 
178     if (m_undoSteps < HISTORYSIZE - 1)
179         m_undoSteps++;
180     m_redoSteps = 0;
181 
182     g_imageSaved = FALSE;
183     NotifyImageChanged();
184 }
185 
186 void ImageModel::Crop(int nWidth, int nHeight, int nOffsetX, int nOffsetY)
187 {
188     // We cannot create bitmaps of size zero
189     if (nWidth <= 0)
190         nWidth = 1;
191     if (nHeight <= 0)
192         nHeight = 1;
193 
194     // Create a white HBITMAP
195     HBITMAP hbmNew = CreateColorDIB(nWidth, nHeight, RGB(255, 255, 255));
196     if (!hbmNew)
197     {
198         ShowOutOfMemory();
199         return;
200     }
201 
202     // Put the master image as a sub-image
203     RECT rcPart = { -nOffsetX, -nOffsetY, GetWidth() - nOffsetX, GetHeight() - nOffsetY };
204     HBITMAP hbmOld = imageModel.LockBitmap();
205     putSubImage(hbmNew, rcPart, hbmOld);
206     imageModel.UnlockBitmap(hbmOld);
207 
208     // Push it
209     PushImageForUndo(hbmNew);
210 
211     NotifyImageChanged();
212 }
213 
214 void ImageModel::SaveImage(LPCWSTR lpFileName)
215 {
216     SaveDIBToFile(m_hbmMaster, lpFileName, TRUE);
217 }
218 
219 BOOL ImageModel::IsImageSaved() const
220 {
221     return g_imageSaved;
222 }
223 
224 void ImageModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
225 {
226     int oldWidth = GetWidth();
227     int oldHeight = GetHeight();
228     INT newWidth = oldWidth * nStretchPercentX / 100;
229     INT newHeight = oldHeight * nStretchPercentY / 100;
230     if (oldWidth != newWidth || oldHeight != newHeight)
231     {
232         HBITMAP hbm0 = CopyDIBImage(m_hbmMaster, newWidth, newHeight);
233         PushImageForUndo(hbm0);
234     }
235     if (nSkewDegX)
236     {
237         HBITMAP hbm1 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegX, FALSE);
238         PushImageForUndo(hbm1);
239     }
240     if (nSkewDegY)
241     {
242         HBITMAP hbm2 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegY, TRUE);
243         PushImageForUndo(hbm2);
244     }
245     NotifyImageChanged();
246 }
247 
248 int ImageModel::GetWidth() const
249 {
250     return GetDIBWidth(m_hbmMaster);
251 }
252 
253 int ImageModel::GetHeight() const
254 {
255     return GetDIBHeight(m_hbmMaster);
256 }
257 
258 void ImageModel::InvertColors()
259 {
260     RECT rect = {0, 0, GetWidth(), GetHeight()};
261     PushImageForUndo();
262     InvertRect(m_hDrawingDC, &rect);
263     NotifyImageChanged();
264 }
265 
266 HDC ImageModel::GetDC()
267 {
268     return m_hDrawingDC;
269 }
270 
271 void ImageModel::FlipHorizontally()
272 {
273     PushImageForUndo();
274     StretchBlt(m_hDrawingDC, GetWidth() - 1, 0, -GetWidth(), GetHeight(), GetDC(), 0, 0,
275                GetWidth(), GetHeight(), SRCCOPY);
276     NotifyImageChanged();
277 }
278 
279 void ImageModel::FlipVertically()
280 {
281     PushImageForUndo();
282     StretchBlt(m_hDrawingDC, 0, GetHeight() - 1, GetWidth(), -GetHeight(), GetDC(), 0, 0,
283                GetWidth(), GetHeight(), SRCCOPY);
284     NotifyImageChanged();
285 }
286 
287 void ImageModel::RotateNTimes90Degrees(int iN)
288 {
289     switch (iN)
290     {
291         case 1:
292         case 3:
293         {
294             HBITMAP hbm = Rotate90DegreeBlt(m_hDrawingDC, GetWidth(), GetHeight(), iN == 1, FALSE);
295             PushImageForUndo(hbm);
296             break;
297         }
298         case 2:
299         {
300             PushImageForUndo();
301             ::StretchBlt(m_hDrawingDC, GetWidth() - 1, GetHeight() - 1, -GetWidth(), -GetHeight(),
302                          m_hDrawingDC, 0, 0, GetWidth(), GetHeight(), SRCCOPY);
303             break;
304         }
305     }
306     NotifyImageChanged();
307 }
308 
309 void ImageModel::Clamp(POINT& pt) const
310 {
311     pt.x = max(0, min(pt.x, GetWidth()));
312     pt.y = max(0, min(pt.y, GetHeight()));
313 }
314 
315 HBITMAP ImageModel::CopyBitmap()
316 {
317     HBITMAP hBitmap = LockBitmap();
318     HBITMAP ret = CopyDIBImage(hBitmap);
319     UnlockBitmap(hBitmap);
320     return ret;
321 }
322 
323 BOOL ImageModel::IsBlackAndWhite()
324 {
325     HBITMAP hBitmap = LockBitmap();
326     BOOL bBlackAndWhite = IsBitmapBlackAndWhite(hBitmap);
327     UnlockBitmap(hBitmap);
328     return bBlackAndWhite;
329 }
330 
331 void ImageModel::PushBlackAndWhite()
332 {
333     HBITMAP hBitmap = LockBitmap();
334     HBITMAP hNewBitmap = ConvertToBlackAndWhite(hBitmap);
335     UnlockBitmap(hBitmap);
336 
337     PushImageForUndo(hNewBitmap);
338 }
339 
340 HBITMAP ImageModel::LockBitmap()
341 {
342     // NOTE: An app cannot select a bitmap into more than one device context at a time.
343     ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
344     HBITMAP hbmLocked = m_hbmMaster;
345     m_hbmMaster = NULL;
346     return hbmLocked;
347 }
348 
349 void ImageModel::UnlockBitmap(HBITMAP hbmLocked)
350 {
351     m_hbmMaster = hbmLocked;
352     m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
353 }
354 
355 void ImageModel::SelectionClone(BOOL bUndoable)
356 {
357     if (!selectionModel.m_bShow || ::IsRectEmpty(&selectionModel.m_rc))
358         return;
359 
360     if (bUndoable)
361         PushImageForUndo();
362 
363     selectionModel.DrawSelection(m_hDrawingDC, paletteModel.GetBgColor(),
364                                  toolsModel.IsBackgroundTransparent());
365     NotifyImageChanged();
366 }
367