2015-05-08 16:02:36 +00:00
|
|
|
/*
|
2023-06-23 11:04:32 +00:00
|
|
|
* PROJECT: PAINT for ReactOS
|
|
|
|
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
|
|
|
|
* PURPOSE: Undo and redo functionality
|
2023-06-27 18:22:21 +00:00
|
|
|
* COPYRIGHT: Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
|
2023-06-23 11:04:32 +00:00
|
|
|
* Copyright 2023 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
|
2015-05-08 16:02:36 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "precomp.h"
|
|
|
|
|
2023-03-28 13:31:26 +00:00
|
|
|
ImageModel imageModel;
|
|
|
|
|
2015-05-08 16:02:36 +00:00
|
|
|
/* FUNCTIONS ********************************************************/
|
|
|
|
|
2023-11-19 03:59:39 +00:00
|
|
|
void IMAGE_PART::clear()
|
|
|
|
{
|
|
|
|
::DeleteObject(m_hbmImage);
|
|
|
|
m_hbmImage = NULL;
|
|
|
|
m_rcPart.SetRectEmpty();
|
|
|
|
m_bPartial = FALSE;
|
|
|
|
}
|
|
|
|
|
2015-07-07 11:56:37 +00:00
|
|
|
void ImageModel::NotifyImageChanged()
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-04-08 13:25:27 +00:00
|
|
|
if (canvasWindow.IsWindow())
|
2023-10-05 11:01:41 +00:00
|
|
|
{
|
2023-10-16 22:25:50 +00:00
|
|
|
canvasWindow.updateScrollRange();
|
2023-10-05 11:01:41 +00:00
|
|
|
canvasWindow.Invalidate();
|
|
|
|
}
|
|
|
|
|
2023-06-14 09:51:40 +00:00
|
|
|
if (miniature.IsWindow())
|
2023-10-05 11:01:41 +00:00
|
|
|
miniature.Invalidate();
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2015-07-23 07:48:32 +00:00
|
|
|
ImageModel::ImageModel()
|
2023-06-17 12:15:35 +00:00
|
|
|
: m_hDrawingDC(::CreateCompatibleDC(NULL))
|
|
|
|
, m_currInd(0)
|
|
|
|
, m_undoSteps(0)
|
|
|
|
, m_redoSteps(0)
|
2015-07-23 07:48:32 +00:00
|
|
|
{
|
2023-11-19 03:59:39 +00:00
|
|
|
ZeroMemory(m_historyItems, sizeof(m_historyItems));
|
2015-07-23 13:11:54 +00:00
|
|
|
|
2023-11-18 02:19:38 +00:00
|
|
|
m_hbmMaster = CreateColorDIB(1, 1, RGB(255, 255, 255));
|
|
|
|
m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster);
|
2015-07-23 13:11:54 +00:00
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
g_imageSaved = TRUE;
|
2015-07-23 07:48:32 +00:00
|
|
|
}
|
|
|
|
|
2023-06-13 14:23:48 +00:00
|
|
|
ImageModel::~ImageModel()
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
|
2023-06-17 12:15:35 +00:00
|
|
|
::DeleteDC(m_hDrawingDC);
|
2023-11-18 02:19:38 +00:00
|
|
|
::DeleteObject(m_hbmMaster);
|
|
|
|
ClearHistory();
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2023-11-19 03:59:39 +00:00
|
|
|
void ImageModel::SwapPart()
|
|
|
|
{
|
|
|
|
IMAGE_PART& part = m_historyItems[m_currInd];
|
|
|
|
if (!part.m_bPartial)
|
|
|
|
{
|
|
|
|
Swap(m_hbmMaster, part.m_hbmImage);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
HBITMAP hbmMaster = LockBitmap();
|
|
|
|
HBITMAP hbmPart = getSubImage(hbmMaster, part.m_rcPart);
|
|
|
|
putSubImage(hbmMaster, part.m_rcPart, part.m_hbmImage);
|
|
|
|
::DeleteObject(part.m_hbmImage);
|
|
|
|
part.m_hbmImage = hbmPart;
|
|
|
|
UnlockBitmap(hbmMaster);
|
|
|
|
}
|
|
|
|
|
2022-01-30 03:05:23 +00:00
|
|
|
void ImageModel::Undo(BOOL bClearRedo)
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
ATLTRACE("%s: %d\n", __FUNCTION__, m_undoSteps);
|
2023-06-13 14:23:48 +00:00
|
|
|
if (!CanUndo())
|
|
|
|
return;
|
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
selectionModel.HideSelection();
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-11-18 02:19:38 +00:00
|
|
|
m_currInd = (m_currInd + HISTORYSIZE - 1) % HISTORYSIZE; // Go previous
|
|
|
|
ATLASSERT(m_hbmMaster != NULL);
|
2023-11-19 03:59:39 +00:00
|
|
|
SwapPart();
|
2023-11-18 02:19:38 +00:00
|
|
|
::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
m_undoSteps--;
|
2023-06-13 14:23:48 +00:00
|
|
|
if (bClearRedo)
|
2023-06-17 12:15:35 +00:00
|
|
|
m_redoSteps = 0;
|
|
|
|
else if (m_redoSteps < HISTORYSIZE - 1)
|
|
|
|
m_redoSteps++;
|
2023-06-13 14:23:48 +00:00
|
|
|
|
|
|
|
NotifyImageChanged();
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 11:56:37 +00:00
|
|
|
void ImageModel::Redo()
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
ATLTRACE("%s: %d\n", __FUNCTION__, m_redoSteps);
|
2023-06-13 14:23:48 +00:00
|
|
|
if (!CanRedo())
|
|
|
|
return;
|
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
selectionModel.HideSelection();
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-11-18 02:19:38 +00:00
|
|
|
ATLASSERT(m_hbmMaster != NULL);
|
2023-11-19 03:59:39 +00:00
|
|
|
SwapPart();
|
2023-11-18 02:19:38 +00:00
|
|
|
m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
|
|
|
|
::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
m_redoSteps--;
|
|
|
|
if (m_undoSteps < HISTORYSIZE - 1)
|
|
|
|
m_undoSteps++;
|
2023-06-13 14:23:48 +00:00
|
|
|
|
|
|
|
NotifyImageChanged();
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 11:56:37 +00:00
|
|
|
void ImageModel::ClearHistory()
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-10-05 11:01:41 +00:00
|
|
|
for (int i = 0; i < HISTORYSIZE; ++i)
|
|
|
|
{
|
2023-11-19 03:59:39 +00:00
|
|
|
m_historyItems[i].clear();
|
2023-10-05 11:01:41 +00:00
|
|
|
}
|
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
m_undoSteps = 0;
|
|
|
|
m_redoSteps = 0;
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2023-10-24 17:35:49 +00:00
|
|
|
void ImageModel::PushImageForUndo()
|
|
|
|
{
|
|
|
|
HBITMAP hbm = CopyBitmap();
|
2023-10-29 22:41:57 +00:00
|
|
|
if (hbm == NULL)
|
2023-10-24 17:35:49 +00:00
|
|
|
{
|
|
|
|
ShowOutOfMemory();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
PushImageForUndo(hbm);
|
|
|
|
}
|
|
|
|
|
2023-06-13 14:23:48 +00:00
|
|
|
void ImageModel::PushImageForUndo(HBITMAP hbm)
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-10-24 17:35:49 +00:00
|
|
|
if (hbm == NULL)
|
|
|
|
{
|
|
|
|
ShowOutOfMemory();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-11-19 03:59:39 +00:00
|
|
|
IMAGE_PART& part = m_historyItems[m_currInd];
|
|
|
|
part.clear();
|
|
|
|
part.m_hbmImage = m_hbmMaster;
|
2023-11-18 02:19:38 +00:00
|
|
|
m_hbmMaster = hbm;
|
|
|
|
::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-11-19 03:59:39 +00:00
|
|
|
PushDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::PushImageForUndo(const RECT& rcPartial)
|
|
|
|
{
|
|
|
|
ATLTRACE("%s: %d\n", __FUNCTION__, m_currInd);
|
|
|
|
|
|
|
|
IMAGE_PART& part = m_historyItems[m_currInd];
|
|
|
|
part.clear();
|
|
|
|
part.m_bPartial = TRUE;
|
|
|
|
part.m_rcPart = rcPartial;
|
|
|
|
|
2023-11-25 04:44:31 +00:00
|
|
|
CRect rcImage = { 0, 0, GetWidth(), GetHeight() };
|
|
|
|
CRect& rc = part.m_rcPart;
|
|
|
|
if (!rc.IntersectRect(rc, rcImage))
|
|
|
|
rc.SetRect(-1, -1, 0, 0);
|
|
|
|
|
2023-11-19 03:59:39 +00:00
|
|
|
HBITMAP hbmMaster = LockBitmap();
|
2023-11-25 04:44:31 +00:00
|
|
|
part.m_hbmImage = getSubImage(hbmMaster, rc);
|
2023-11-19 03:59:39 +00:00
|
|
|
UnlockBitmap(hbmMaster);
|
|
|
|
|
|
|
|
PushDone();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::PushDone()
|
|
|
|
{
|
|
|
|
m_currInd = (m_currInd + 1) % HISTORYSIZE; // Go next
|
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
if (m_undoSteps < HISTORYSIZE - 1)
|
|
|
|
m_undoSteps++;
|
|
|
|
m_redoSteps = 0;
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-06-17 12:15:35 +00:00
|
|
|
g_imageSaved = FALSE;
|
2015-07-07 11:56:37 +00:00
|
|
|
NotifyImageChanged();
|
2015-05-08 16:02:36 +00:00
|
|
|
}
|
|
|
|
|
2015-07-07 11:56:37 +00:00
|
|
|
void ImageModel::Crop(int nWidth, int nHeight, int nOffsetX, int nOffsetY)
|
2015-05-08 16:02:36 +00:00
|
|
|
{
|
2023-06-13 14:23:48 +00:00
|
|
|
// We cannot create bitmaps of size zero
|
2017-06-07 10:00:04 +00:00
|
|
|
if (nWidth <= 0)
|
|
|
|
nWidth = 1;
|
|
|
|
if (nHeight <= 0)
|
|
|
|
nHeight = 1;
|
|
|
|
|
2023-11-18 05:25:19 +00:00
|
|
|
// Create a white HBITMAP
|
|
|
|
HBITMAP hbmNew = CreateColorDIB(nWidth, nHeight, RGB(255, 255, 255));
|
|
|
|
if (!hbmNew)
|
2023-10-24 17:35:49 +00:00
|
|
|
{
|
|
|
|
ShowOutOfMemory();
|
2023-06-13 14:23:48 +00:00
|
|
|
return;
|
2023-10-24 17:35:49 +00:00
|
|
|
}
|
2023-06-13 14:23:48 +00:00
|
|
|
|
2023-11-18 05:25:19 +00:00
|
|
|
// Put the master image as a sub-image
|
|
|
|
RECT rcPart = { -nOffsetX, -nOffsetY, GetWidth() - nOffsetX, GetHeight() - nOffsetY };
|
|
|
|
HBITMAP hbmOld = imageModel.LockBitmap();
|
|
|
|
putSubImage(hbmNew, rcPart, hbmOld);
|
|
|
|
imageModel.UnlockBitmap(hbmOld);
|
2023-06-13 14:23:48 +00:00
|
|
|
|
|
|
|
// Push it
|
2023-11-18 05:25:19 +00:00
|
|
|
PushImageForUndo(hbmNew);
|
2015-05-08 16:02:36 +00:00
|
|
|
|
2015-07-07 11:56:37 +00:00
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
|
|
|
|
2023-11-04 10:25:45 +00:00
|
|
|
void ImageModel::SaveImage(LPCWSTR lpFileName)
|
2015-07-07 11:56:37 +00:00
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
SaveDIBToFile(m_hbmMaster, lpFileName, TRUE);
|
2015-07-07 11:56:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-28 00:49:36 +00:00
|
|
|
BOOL ImageModel::IsImageSaved() const
|
2015-07-07 11:56:37 +00:00
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
return g_imageSaved;
|
2015-07-07 11:56:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
|
|
|
|
{
|
|
|
|
int oldWidth = GetWidth();
|
|
|
|
int oldHeight = GetHeight();
|
2022-01-09 23:50:37 +00:00
|
|
|
INT newWidth = oldWidth * nStretchPercentX / 100;
|
|
|
|
INT newHeight = oldHeight * nStretchPercentY / 100;
|
2022-02-14 07:18:18 +00:00
|
|
|
if (oldWidth != newWidth || oldHeight != newHeight)
|
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
HBITMAP hbm0 = CopyDIBImage(m_hbmMaster, newWidth, newHeight);
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo(hbm0);
|
2022-02-14 07:18:18 +00:00
|
|
|
}
|
|
|
|
if (nSkewDegX)
|
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
HBITMAP hbm1 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegX, FALSE);
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo(hbm1);
|
2022-02-14 07:18:18 +00:00
|
|
|
}
|
|
|
|
if (nSkewDegY)
|
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
HBITMAP hbm2 = SkewDIB(m_hDrawingDC, m_hbmMaster, nSkewDegY, TRUE);
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo(hbm2);
|
2022-02-14 07:18:18 +00:00
|
|
|
}
|
2015-07-07 11:56:37 +00:00
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
|
|
|
|
2021-12-28 00:49:36 +00:00
|
|
|
int ImageModel::GetWidth() const
|
2015-07-07 11:56:37 +00:00
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
return GetDIBWidth(m_hbmMaster);
|
2015-07-07 11:56:37 +00:00
|
|
|
}
|
|
|
|
|
2021-12-28 00:49:36 +00:00
|
|
|
int ImageModel::GetHeight() const
|
2015-07-07 11:56:37 +00:00
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
return GetDIBHeight(m_hbmMaster);
|
2015-07-07 11:56:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::InvertColors()
|
|
|
|
{
|
|
|
|
RECT rect = {0, 0, GetWidth(), GetHeight()};
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo();
|
2023-06-17 12:15:35 +00:00
|
|
|
InvertRect(m_hDrawingDC, &rect);
|
2015-07-07 11:56:37 +00:00
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
|
|
|
|
2015-07-23 13:52:02 +00:00
|
|
|
HDC ImageModel::GetDC()
|
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
return m_hDrawingDC;
|
2015-07-23 13:52:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::FlipHorizontally()
|
|
|
|
{
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo();
|
2023-06-17 12:15:35 +00:00
|
|
|
StretchBlt(m_hDrawingDC, GetWidth() - 1, 0, -GetWidth(), GetHeight(), GetDC(), 0, 0,
|
2015-07-23 13:52:02 +00:00
|
|
|
GetWidth(), GetHeight(), SRCCOPY);
|
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::FlipVertically()
|
|
|
|
{
|
2023-06-13 14:23:48 +00:00
|
|
|
PushImageForUndo();
|
2023-06-17 12:15:35 +00:00
|
|
|
StretchBlt(m_hDrawingDC, 0, GetHeight() - 1, GetWidth(), -GetHeight(), GetDC(), 0, 0,
|
2015-07-23 13:52:02 +00:00
|
|
|
GetWidth(), GetHeight(), SRCCOPY);
|
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::RotateNTimes90Degrees(int iN)
|
|
|
|
{
|
2022-02-14 03:08:34 +00:00
|
|
|
switch (iN)
|
2015-07-23 13:52:02 +00:00
|
|
|
{
|
2023-06-13 14:23:48 +00:00
|
|
|
case 1:
|
|
|
|
case 3:
|
|
|
|
{
|
2023-06-17 12:15:35 +00:00
|
|
|
HBITMAP hbm = Rotate90DegreeBlt(m_hDrawingDC, GetWidth(), GetHeight(), iN == 1, FALSE);
|
2023-10-24 17:35:49 +00:00
|
|
|
PushImageForUndo(hbm);
|
2023-06-13 14:23:48 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
case 2:
|
|
|
|
{
|
|
|
|
PushImageForUndo();
|
2023-11-04 10:25:45 +00:00
|
|
|
::StretchBlt(m_hDrawingDC, GetWidth() - 1, GetHeight() - 1, -GetWidth(), -GetHeight(),
|
|
|
|
m_hDrawingDC, 0, 0, GetWidth(), GetHeight(), SRCCOPY);
|
2023-06-13 14:23:48 +00:00
|
|
|
break;
|
|
|
|
}
|
2015-07-23 13:52:02 +00:00
|
|
|
}
|
|
|
|
NotifyImageChanged();
|
|
|
|
}
|
2022-01-30 03:05:23 +00:00
|
|
|
|
2023-06-21 22:57:36 +00:00
|
|
|
void ImageModel::Clamp(POINT& pt) const
|
2022-01-30 03:05:23 +00:00
|
|
|
{
|
|
|
|
pt.x = max(0, min(pt.x, GetWidth()));
|
|
|
|
pt.y = max(0, min(pt.y, GetHeight()));
|
|
|
|
}
|
2023-06-17 12:15:35 +00:00
|
|
|
|
|
|
|
HBITMAP ImageModel::CopyBitmap()
|
|
|
|
{
|
2023-09-22 01:48:13 +00:00
|
|
|
HBITMAP hBitmap = LockBitmap();
|
|
|
|
HBITMAP ret = CopyDIBImage(hBitmap);
|
|
|
|
UnlockBitmap(hBitmap);
|
2023-06-17 12:15:35 +00:00
|
|
|
return ret;
|
|
|
|
}
|
2023-08-11 11:27:12 +00:00
|
|
|
|
|
|
|
BOOL ImageModel::IsBlackAndWhite()
|
|
|
|
{
|
2023-09-22 01:48:13 +00:00
|
|
|
HBITMAP hBitmap = LockBitmap();
|
|
|
|
BOOL bBlackAndWhite = IsBitmapBlackAndWhite(hBitmap);
|
|
|
|
UnlockBitmap(hBitmap);
|
2023-08-20 07:46:18 +00:00
|
|
|
return bBlackAndWhite;
|
2023-08-11 11:27:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::PushBlackAndWhite()
|
|
|
|
{
|
2023-09-22 01:48:13 +00:00
|
|
|
HBITMAP hBitmap = LockBitmap();
|
|
|
|
HBITMAP hNewBitmap = ConvertToBlackAndWhite(hBitmap);
|
|
|
|
UnlockBitmap(hBitmap);
|
2023-08-11 11:27:12 +00:00
|
|
|
|
2023-10-24 17:35:49 +00:00
|
|
|
PushImageForUndo(hNewBitmap);
|
2023-08-11 11:27:12 +00:00
|
|
|
}
|
2023-09-22 01:48:13 +00:00
|
|
|
|
|
|
|
HBITMAP ImageModel::LockBitmap()
|
|
|
|
{
|
|
|
|
// NOTE: An app cannot select a bitmap into more than one device context at a time.
|
|
|
|
::SelectObject(m_hDrawingDC, m_hbmOld); // De-select
|
2023-11-18 02:19:38 +00:00
|
|
|
HBITMAP hbmLocked = m_hbmMaster;
|
|
|
|
m_hbmMaster = NULL;
|
2023-09-22 01:48:13 +00:00
|
|
|
return hbmLocked;
|
|
|
|
}
|
|
|
|
|
|
|
|
void ImageModel::UnlockBitmap(HBITMAP hbmLocked)
|
|
|
|
{
|
2023-11-18 02:19:38 +00:00
|
|
|
m_hbmMaster = hbmLocked;
|
|
|
|
m_hbmOld = ::SelectObject(m_hDrawingDC, m_hbmMaster); // Re-select
|
2023-09-22 01:48:13 +00:00
|
|
|
}
|