Reactos
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
11ImageModel imageModel;
12
13/* FUNCTIONS ********************************************************/
14
15void IMAGE_PART::clear()
16{
17 ::DeleteObject(m_hbmImage);
18 m_hbmImage = NULL;
19 m_rcPart.SetRectEmpty();
20 m_bPartial = FALSE;
21}
22
23void 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
35ImageModel::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
49ImageModel::~ImageModel()
50{
51 ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
52 ::DeleteDC(m_hDrawingDC);
53 ::DeleteObject(m_hbmMaster);
54 ClearHistory();
55}
56
57void 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
74void 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
96void 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
116void 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
127void ImageModel::PushImageForUndo()
128{
129 HBITMAP hbm = CopyBitmap();
130 if (hbm == NULL)
131 {
132 ShowOutOfMemory();
133 ClearHistory();
134 return;
135 }
136
137 PushImageForUndo(hbm);
138}
139
140void ImageModel::PushImageForUndo(HBITMAP hbm)
141{
142 ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
143
144 if (hbm == NULL)
145 {
146 ShowOutOfMemory();
147 ClearHistory();
148 return;
149 }
150
151 IMAGE_PART& part = m_historyItems[m_currInd];
152 part.clear();
153 part.m_hbmImage = m_hbmMaster;
154 m_hbmMaster = hbm;
155 ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
156
157 PushDone();
158}
159
160void ImageModel::PushImageForUndo(const RECT& rcPartial)
161{
162 ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
163
164 IMAGE_PART& part = m_historyItems[m_currInd];
165 part.clear();
166 part.m_bPartial = TRUE;
167 part.m_rcPart = rcPartial;
168
169 CRect rcImage = { 0, 0, GetWidth(), GetHeight() };
170 CRect& rc = part.m_rcPart;
171 if (!rc.IntersectRect(rc, rcImage))
172 rc.SetRect(-1, -1, 0, 0);
173
174 HBITMAP hbmMaster = LockBitmap();
175 part.m_hbmImage = getSubImage(hbmMaster, rc);
176 UnlockBitmap(hbmMaster);
177
178 PushDone();
179}
180
181void ImageModel::PushDone()
182{
183 m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
184
185 if (m_undoSteps < HISTORYSIZE - 1)
186 m_undoSteps++;
187 m_redoSteps = 0;
188
189 g_imageSaved = FALSE;
190 NotifyImageChanged();
191}
192
193void ImageModel::Crop(int nWidth, int nHeight, int nOffsetX, int nOffsetY)
194{
195 // We cannot create bitmaps of size zero
196 if (nWidth <= 0)
197 nWidth = 1;
198 if (nHeight <= 0)
199 nHeight = 1;
200
201 // Create a white HBITMAP
202 HBITMAP hbmNew = CreateColorDIB(nWidth, nHeight, RGB(255, 255, 255));
203 if (!hbmNew)
204 {
205 ShowOutOfMemory();
206 ClearHistory();
207 return;
208 }
209
210 // Put the master image as a sub-image
211 RECT rcPart = { -nOffsetX, -nOffsetY, GetWidth() - nOffsetX, GetHeight() - nOffsetY };
212 HBITMAP hbmOld = imageModel.LockBitmap();
213 putSubImage(hbmNew, rcPart, hbmOld);
214 imageModel.UnlockBitmap(hbmOld);
215
216 // Push it
217 PushImageForUndo(hbmNew);
218
219 NotifyImageChanged();
220}
221
222void ImageModel::SaveImage(LPCWSTR lpFileName)
223{
224 SaveDIBToFile(m_hbmMaster, lpFileName, TRUE);
225}
226
227BOOL ImageModel::IsImageSaved() const
228{
229 return g_imageSaved;
230}
231
232void ImageModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
233{
234 int oldWidth = GetWidth();
235 int oldHeight = GetHeight();
236 INT newWidth = oldWidth * nStretchPercentX / 100;
237 INT newHeight = oldHeight * nStretchPercentY / 100;
238 if (oldWidth != newWidth || oldHeight != newHeight)
239 {
240 HBITMAP hbm0 = CopyDIBImage(m_hbmMaster, newWidth, newHeight);
241 PushImageForUndo(hbm0);
242 }
243 if (nSkewDegX)
244 {
245 HBITMAP hbm1 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegX, FALSE);
246 PushImageForUndo(hbm1);
247 }
248 if (nSkewDegY)
249 {
250 HBITMAP hbm2 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegY, TRUE);
251 PushImageForUndo(hbm2);
252 }
253 NotifyImageChanged();
254}
255
256int ImageModel::GetWidth() const
257{
258 return GetDIBWidth(m_hbmMaster);
259}
260
261int ImageModel::GetHeight() const
262{
263 return GetDIBHeight(m_hbmMaster);
264}
265
266void ImageModel::InvertColors()
267{
268 RECT rect = {0, 0, GetWidth(), GetHeight()};
269 PushImageForUndo();
270 InvertRect(m_hDrawingDC, &rect);
271 NotifyImageChanged();
272}
273
274HDC ImageModel::GetDC()
275{
276 return m_hDrawingDC;
277}
278
279void ImageModel::FlipHorizontally()
280{
281 PushImageForUndo();
282 StretchBlt(m_hDrawingDC, GetWidth() - 1, 0, -GetWidth(), GetHeight(), GetDC(), 0, 0,
283 GetWidth(), GetHeight(), SRCCOPY);
284 NotifyImageChanged();
285}
286
287void ImageModel::FlipVertically()
288{
289 PushImageForUndo();
290 StretchBlt(m_hDrawingDC, 0, GetHeight() - 1, GetWidth(), -GetHeight(), GetDC(), 0, 0,
291 GetWidth(), GetHeight(), SRCCOPY);
292 NotifyImageChanged();
293}
294
295void ImageModel::RotateNTimes90Degrees(int iN)
296{
297 switch (iN)
298 {
299 case 1:
300 case 3:
301 {
302 HBITMAP hbm = Rotate90DegreeBlt(m_hDrawingDC, GetWidth(), GetHeight(), iN == 1, FALSE);
303 PushImageForUndo(hbm);
304 break;
305 }
306 case 2:
307 {
308 PushImageForUndo();
309 ::StretchBlt(m_hDrawingDC, GetWidth() - 1, GetHeight() - 1, -GetWidth(), -GetHeight(),
310 m_hDrawingDC, 0, 0, GetWidth(), GetHeight(), SRCCOPY);
311 break;
312 }
313 }
314 NotifyImageChanged();
315}
316
317void ImageModel::Clamp(POINT& pt) const
318{
319 pt.x = max(0, min(pt.x, GetWidth()));
320 pt.y = max(0, min(pt.y, GetHeight()));
321}
322
323HBITMAP ImageModel::CopyBitmap()
324{
325 HBITMAP hBitmap = LockBitmap();
326 HBITMAP ret = CopyDIBImage(hBitmap);
327 UnlockBitmap(hBitmap);
328 return ret;
329}
330
331BOOL ImageModel::IsBlackAndWhite()
332{
333 HBITMAP hBitmap = LockBitmap();
334 BOOL bBlackAndWhite = IsBitmapBlackAndWhite(hBitmap);
335 UnlockBitmap(hBitmap);
336 return bBlackAndWhite;
337}
338
339void ImageModel::PushBlackAndWhite()
340{
341 HBITMAP hBitmap = LockBitmap();
342 HBITMAP hNewBitmap = ConvertToBlackAndWhite(hBitmap);
343 UnlockBitmap(hBitmap);
344
345 PushImageForUndo(hNewBitmap);
346}
347
348HBITMAP ImageModel::LockBitmap()
349{
350 // NOTE: An app cannot select a bitmap into more than one device context at a time.
351 ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
352 HBITMAP hbmLocked = m_hbmMaster;
353 m_hbmMaster = NULL;
354 return hbmLocked;
355}
356
357void ImageModel::UnlockBitmap(HBITMAP hbmLocked)
358{
359 m_hbmMaster = hbmLocked;
360 m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
361}