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
clear()15 void IMAGE_PART::clear()
16 {
17 ::DeleteObject(m_hbmImage);
18 m_hbmImage = NULL;
19 m_rcPart.SetRectEmpty();
20 m_bPartial = FALSE;
21 }
22
NotifyImageChanged()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
ImageModel()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
~ImageModel()49 ImageModel::~ImageModel()
50 {
51 ::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
52 ::DeleteDC(m_hDrawingDC);
53 ::DeleteObject(m_hbmMaster);
54 ClearHistory();
55 }
56
SwapPart()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
Undo(BOOL bClearRedo)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
Redo()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
ClearHistory()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
PushImageForUndo()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
PushImageForUndo(HBITMAP hbm)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
PushImageForUndo(const RECT & rcPartial)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
PushDone()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
Crop(int nWidth,int nHeight,int nOffsetX,int nOffsetY)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
SaveImage(LPCWSTR lpFileName)219 void ImageModel::SaveImage(LPCWSTR lpFileName)
220 {
221 SaveDIBToFile(m_hbmMaster, lpFileName, TRUE);
222 }
223
IsImageSaved() const224 BOOL ImageModel::IsImageSaved() const
225 {
226 return g_imageSaved;
227 }
228
StretchSkew(int nStretchPercentX,int nStretchPercentY,int nSkewDegX,int nSkewDegY)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
GetWidth() const253 int ImageModel::GetWidth() const
254 {
255 return GetDIBWidth(m_hbmMaster);
256 }
257
GetHeight() const258 int ImageModel::GetHeight() const
259 {
260 return GetDIBHeight(m_hbmMaster);
261 }
262
InvertColors()263 void ImageModel::InvertColors()
264 {
265 RECT rect = {0, 0, GetWidth(), GetHeight()};
266 PushImageForUndo();
267 InvertRect(m_hDrawingDC, &rect);
268 NotifyImageChanged();
269 }
270
GetDC()271 HDC ImageModel::GetDC()
272 {
273 return m_hDrawingDC;
274 }
275
FlipHorizontally()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
FlipVertically()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
RotateNTimes90Degrees(int iN)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
Clamp(POINT & pt) const314 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
CopyBitmap()320 HBITMAP ImageModel::CopyBitmap()
321 {
322 HBITMAP hBitmap = LockBitmap();
323 HBITMAP ret = CopyDIBImage(hBitmap);
324 UnlockBitmap(hBitmap);
325 return ret;
326 }
327
IsBlackAndWhite()328 BOOL ImageModel::IsBlackAndWhite()
329 {
330 HBITMAP hBitmap = LockBitmap();
331 BOOL bBlackAndWhite = IsBitmapBlackAndWhite(hBitmap);
332 UnlockBitmap(hBitmap);
333 return bBlackAndWhite;
334 }
335
PushBlackAndWhite()336 void ImageModel::PushBlackAndWhite()
337 {
338 HBITMAP hBitmap = LockBitmap();
339 HBITMAP hNewBitmap = ConvertToBlackAndWhite(hBitmap);
340 UnlockBitmap(hBitmap);
341
342 PushImageForUndo(hNewBitmap);
343 }
344
LockBitmap()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
UnlockBitmap(HBITMAP hbmLocked)354 void ImageModel::UnlockBitmap(HBITMAP hbmLocked)
355 {
356 m_hbmMaster = hbmLocked;
357 m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
358 }
359