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