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