Reactos
at master 361 lines 8.4 kB view raw
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}