mirror of
https://github.com/reactos/reactos.git
synced 2024-12-28 18:15:11 +00:00
e928b42758
Follow-up to #6552. There was a bug that the cropped selection image is not the shape of selection. JIRA issue: CORE-19466 - Extend SelectionModel:: DrawSelection for drawing selection flexibly. - Improve SelectionModel:: GetSelectionContents method.
515 lines
13 KiB
C++
515 lines
13 KiB
C++
/*
|
|
* PROJECT: PAINT for ReactOS
|
|
* LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later)
|
|
* PURPOSE: Keep track of selection parameters, notify listeners
|
|
* COPYRIGHT: Copyright 2015 Benedikt Freisen <b.freisen@gmx.net>
|
|
* Copyright 2019 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com>
|
|
*/
|
|
|
|
#include "precomp.h"
|
|
|
|
SelectionModel selectionModel;
|
|
|
|
/* FUNCTIONS ********************************************************/
|
|
|
|
SelectionModel::SelectionModel()
|
|
: m_hbmColor(NULL)
|
|
, m_hbmMask(NULL)
|
|
, m_rgbBack(RGB(255, 255, 255))
|
|
, m_bShow(FALSE)
|
|
, m_bContentChanged(FALSE)
|
|
{
|
|
m_rc.SetRectEmpty();
|
|
m_rcOld.SetRectEmpty();
|
|
m_ptHit = { -1, -1 };
|
|
}
|
|
|
|
SelectionModel::~SelectionModel()
|
|
{
|
|
ClearColorImage();
|
|
ClearMaskImage();
|
|
}
|
|
|
|
void SelectionModel::DrawBackgroundPoly(HDC hDCImage, COLORREF crBg)
|
|
{
|
|
if (m_rcOld.IsRectEmpty())
|
|
return;
|
|
|
|
HGDIOBJ hbrOld = ::SelectObject(hDCImage, ::GetStockObject(DC_BRUSH));
|
|
::SetDCBrushColor(hDCImage, crBg);
|
|
::MaskBlt(hDCImage, m_rcOld.left, m_rcOld.top, m_rcOld.Width(), m_rcOld.Height(),
|
|
hDCImage, m_rcOld.left, m_rcOld.top, m_hbmMask, 0, 0, MAKEROP4(PATCOPY, SRCCOPY));
|
|
::SelectObject(hDCImage, hbrOld);
|
|
}
|
|
|
|
void SelectionModel::DrawBackgroundRect(HDC hDCImage, COLORREF crBg)
|
|
{
|
|
if (m_rcOld.IsRectEmpty())
|
|
return;
|
|
|
|
Rect(hDCImage, m_rcOld.left, m_rcOld.top, m_rcOld.right, m_rcOld.bottom, crBg, crBg, 0, 1);
|
|
}
|
|
|
|
void SelectionModel::DrawBackground(HDC hDCImage, COLORREF crBg)
|
|
{
|
|
if (toolsModel.GetActiveTool() == TOOL_FREESEL)
|
|
DrawBackgroundPoly(hDCImage, crBg);
|
|
else
|
|
DrawBackgroundRect(hDCImage, crBg);
|
|
}
|
|
|
|
void
|
|
SelectionModel::DrawSelection(HDC hDCImage, COLORREF crBg, BOOL bBgTransparent, const CRect& rc,
|
|
HBITMAP hbm)
|
|
{
|
|
if (rc.IsRectEmpty())
|
|
return;
|
|
|
|
BITMAP bm;
|
|
if (!GetObjectW(hbm, sizeof(BITMAP), &bm))
|
|
return;
|
|
|
|
COLORREF keyColor = (bBgTransparent ? crBg : CLR_INVALID);
|
|
|
|
HDC hMemDC = CreateCompatibleDC(hDCImage);
|
|
HGDIOBJ hbmOld = SelectObject(hMemDC, hbm);
|
|
ColorKeyedMaskBlt(hDCImage, rc.left, rc.top, rc.Width(), rc.Height(),
|
|
hMemDC, 0, 0, bm.bmWidth, bm.bmHeight, m_hbmMask, keyColor);
|
|
SelectObject(hMemDC, hbmOld);
|
|
DeleteDC(hMemDC);
|
|
}
|
|
|
|
void SelectionModel::setMask(const CRect& rc, HBITMAP hbmMask)
|
|
{
|
|
if (m_hbmMask)
|
|
::DeleteObject(m_hbmMask);
|
|
|
|
m_hbmMask = hbmMask;
|
|
m_rc = m_rcOld = rc;
|
|
}
|
|
|
|
HBITMAP SelectionModel::GetSelectionContents()
|
|
{
|
|
HBITMAP hbmWhole = imageModel.LockBitmap();
|
|
HBITMAP hbmPart = getSubImage(hbmWhole, (IsLanded() ? m_rc : m_rcOld));
|
|
imageModel.UnlockBitmap(hbmWhole);
|
|
if (!hbmPart)
|
|
return NULL;
|
|
|
|
CRect rc = { 0, 0, m_rc.Width(), m_rc.Height() };
|
|
|
|
HDC hdcMem = ::CreateCompatibleDC(NULL);
|
|
HBITMAP hbmNew = CreateColorDIB(rc.Width(), rc.Height(), paletteModel.GetBgColor());
|
|
HGDIOBJ hbmOld = ::SelectObject(hdcMem, hbmNew);
|
|
selectionModel.DrawSelection(hdcMem, paletteModel.GetBgColor(), TRUE, rc, hbmPart);
|
|
::SelectObject(hdcMem, hbmOld);
|
|
::DeleteDC(hdcMem);
|
|
|
|
::DeleteObject(hbmPart);
|
|
return hbmNew;
|
|
}
|
|
|
|
BOOL SelectionModel::IsLanded() const
|
|
{
|
|
return !m_hbmColor;
|
|
}
|
|
|
|
BOOL SelectionModel::TakeOff()
|
|
{
|
|
if (!IsLanded() || m_rc.IsRectEmpty())
|
|
return FALSE;
|
|
|
|
// The background color is needed for transparency of selection
|
|
m_rgbBack = paletteModel.GetBgColor();
|
|
|
|
// Get the contents of the selection area
|
|
ClearColorImage();
|
|
m_hbmColor = GetSelectionContents();
|
|
|
|
// RectSel doesn't need the mask image
|
|
if (toolsModel.GetActiveTool() == TOOL_RECTSEL)
|
|
ClearMaskImage();
|
|
|
|
// Save the selection area
|
|
m_rcOld = m_rc;
|
|
|
|
imageModel.NotifyImageChanged();
|
|
return TRUE;
|
|
}
|
|
|
|
void SelectionModel::Landing()
|
|
{
|
|
if (IsLanded() && !m_bShow)
|
|
{
|
|
imageModel.NotifyImageChanged();
|
|
return;
|
|
}
|
|
|
|
if (m_bContentChanged ||
|
|
(!m_rc.EqualRect(m_rcOld) && !m_rc.IsRectEmpty() && !m_rcOld.IsRectEmpty()))
|
|
{
|
|
CRect rc;
|
|
rc.UnionRect(m_rc, m_rcOld);
|
|
imageModel.PushImageForUndo(rc);
|
|
|
|
canvasWindow.m_drawing = FALSE;
|
|
toolsModel.OnDrawOverlayOnImage(imageModel.GetDC());
|
|
}
|
|
|
|
HideSelection();
|
|
}
|
|
|
|
void SelectionModel::InsertFromHBITMAP(HBITMAP hbmColor, INT x, INT y, HBITMAP hbmMask)
|
|
{
|
|
::DeleteObject(m_hbmColor);
|
|
m_hbmColor = hbmColor;
|
|
|
|
m_rc.left = x;
|
|
m_rc.top = y;
|
|
m_rc.right = x + GetDIBWidth(hbmColor);
|
|
m_rc.bottom = y + GetDIBHeight(hbmColor);
|
|
|
|
if (hbmMask)
|
|
{
|
|
::DeleteObject(m_hbmMask);
|
|
m_hbmMask = hbmMask;
|
|
}
|
|
else
|
|
{
|
|
ClearMaskImage();
|
|
}
|
|
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
void SelectionModel::FlipHorizontally()
|
|
{
|
|
TakeOff();
|
|
|
|
HDC hdcMem = ::CreateCompatibleDC(NULL);
|
|
if (m_hbmMask)
|
|
{
|
|
::SelectObject(hdcMem, m_hbmMask);
|
|
::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
}
|
|
if (m_hbmColor)
|
|
{
|
|
::SelectObject(hdcMem, m_hbmColor);
|
|
::StretchBlt(hdcMem, m_rc.Width() - 1, 0, -m_rc.Width(), m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
}
|
|
::DeleteDC(hdcMem);
|
|
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
void SelectionModel::FlipVertically()
|
|
{
|
|
TakeOff();
|
|
|
|
HDC hdcMem = ::CreateCompatibleDC(NULL);
|
|
if (m_hbmMask)
|
|
{
|
|
::SelectObject(hdcMem, m_hbmMask);
|
|
::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
}
|
|
if (m_hbmColor)
|
|
{
|
|
::SelectObject(hdcMem, m_hbmColor);
|
|
::StretchBlt(hdcMem, 0, m_rc.Height() - 1, m_rc.Width(), -m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
}
|
|
::DeleteDC(hdcMem);
|
|
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
void SelectionModel::RotateNTimes90Degrees(int iN)
|
|
{
|
|
HBITMAP hbm;
|
|
HGDIOBJ hbmOld;
|
|
HDC hdcMem = ::CreateCompatibleDC(NULL);
|
|
|
|
switch (iN)
|
|
{
|
|
case 1: /* rotate 90 degrees */
|
|
case 3: /* rotate 270 degrees */
|
|
TakeOff();
|
|
|
|
if (m_hbmColor)
|
|
{
|
|
hbmOld = ::SelectObject(hdcMem, m_hbmColor);
|
|
hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, FALSE);
|
|
::SelectObject(hdcMem, hbmOld);
|
|
::DeleteObject(m_hbmColor);
|
|
m_hbmColor = hbm;
|
|
}
|
|
if (m_hbmMask)
|
|
{
|
|
hbmOld = ::SelectObject(hdcMem, m_hbmMask);
|
|
hbm = Rotate90DegreeBlt(hdcMem, m_rc.Width(), m_rc.Height(), iN == 1, TRUE);
|
|
::SelectObject(hdcMem, hbmOld);
|
|
::DeleteObject(m_hbmMask);
|
|
m_hbmMask = hbm;
|
|
}
|
|
|
|
SwapWidthAndHeight();
|
|
break;
|
|
|
|
case 2: /* rotate 180 degrees */
|
|
TakeOff();
|
|
|
|
if (m_hbmColor)
|
|
{
|
|
hbmOld = ::SelectObject(hdcMem, m_hbmColor);
|
|
::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
::SelectObject(hdcMem, hbmOld);
|
|
}
|
|
if (m_hbmMask)
|
|
{
|
|
hbmOld = ::SelectObject(hdcMem, m_hbmMask);
|
|
::StretchBlt(hdcMem, m_rc.Width() - 1, m_rc.Height() - 1, -m_rc.Width(), -m_rc.Height(),
|
|
hdcMem, 0, 0, m_rc.Width(), m_rc.Height(), SRCCOPY);
|
|
::SelectObject(hdcMem, hbmOld);
|
|
}
|
|
break;
|
|
}
|
|
|
|
::DeleteDC(hdcMem);
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
static void AttachHBITMAP(HBITMAP *phbm, HBITMAP hbmNew)
|
|
{
|
|
if (hbmNew == NULL)
|
|
return;
|
|
::DeleteObject(*phbm);
|
|
*phbm = hbmNew;
|
|
}
|
|
|
|
void SelectionModel::StretchSkew(int nStretchPercentX, int nStretchPercentY, int nSkewDegX, int nSkewDegY)
|
|
{
|
|
if (nStretchPercentX == 100 && nStretchPercentY == 100 && nSkewDegX == 0 && nSkewDegY == 0)
|
|
return;
|
|
|
|
TakeOff();
|
|
|
|
INT oldWidth = m_rc.Width(), oldHeight = m_rc.Height();
|
|
INT newWidth = oldWidth * nStretchPercentX / 100;
|
|
INT newHeight = oldHeight * nStretchPercentY / 100;
|
|
|
|
HBITMAP hbmColor = m_hbmColor, hbmMask = m_hbmMask;
|
|
|
|
if (hbmMask == NULL)
|
|
hbmMask = CreateMonoBitmap(oldWidth, oldHeight, TRUE);
|
|
|
|
if (oldWidth != newWidth || oldHeight != newHeight)
|
|
{
|
|
AttachHBITMAP(&hbmColor, CopyDIBImage(hbmColor, newWidth, newHeight));
|
|
AttachHBITMAP(&hbmMask, CopyMonoImage(hbmMask, newWidth, newHeight));
|
|
}
|
|
|
|
HGDIOBJ hbmOld;
|
|
HDC hDC = ::CreateCompatibleDC(NULL);
|
|
|
|
if (nSkewDegX)
|
|
{
|
|
hbmOld = ::SelectObject(hDC, hbmColor);
|
|
AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegX, FALSE));
|
|
::SelectObject(hDC, hbmMask);
|
|
AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegX, FALSE, TRUE));
|
|
::SelectObject(hDC, hbmOld);
|
|
}
|
|
|
|
if (nSkewDegY)
|
|
{
|
|
hbmOld = ::SelectObject(hDC, hbmColor);
|
|
AttachHBITMAP(&hbmColor, SkewDIB(hDC, hbmColor, nSkewDegY, TRUE));
|
|
::SelectObject(hDC, hbmMask);
|
|
AttachHBITMAP(&hbmMask, SkewDIB(hDC, hbmMask, nSkewDegY, TRUE, TRUE));
|
|
::SelectObject(hDC, hbmOld);
|
|
}
|
|
|
|
::DeleteDC(hDC);
|
|
|
|
InsertFromHBITMAP(hbmColor, m_rc.left, m_rc.top, hbmMask);
|
|
|
|
m_bShow = TRUE;
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
void SelectionModel::SetRectFromPoints(const POINT& ptFrom, const POINT& ptTo)
|
|
{
|
|
m_rc = CRect(ptFrom, ptTo);
|
|
m_rc.NormalizeRect();
|
|
}
|
|
|
|
void SelectionModel::Dragging(HITTEST hit, POINT pt)
|
|
{
|
|
switch (hit)
|
|
{
|
|
case HIT_NONE:
|
|
break;
|
|
case HIT_UPPER_LEFT:
|
|
m_rc.left += pt.x - m_ptHit.x;
|
|
m_rc.top += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_UPPER_CENTER:
|
|
m_rc.top += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_UPPER_RIGHT:
|
|
m_rc.right += pt.x - m_ptHit.x;
|
|
m_rc.top += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_MIDDLE_LEFT:
|
|
m_rc.left += pt.x - m_ptHit.x;
|
|
break;
|
|
case HIT_MIDDLE_RIGHT:
|
|
m_rc.right += pt.x - m_ptHit.x;
|
|
break;
|
|
case HIT_LOWER_LEFT:
|
|
m_rc.left += pt.x - m_ptHit.x;
|
|
m_rc.bottom += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_LOWER_CENTER:
|
|
m_rc.bottom += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_LOWER_RIGHT:
|
|
m_rc.right += pt.x - m_ptHit.x;
|
|
m_rc.bottom += pt.y - m_ptHit.y;
|
|
break;
|
|
case HIT_BORDER:
|
|
case HIT_INNER:
|
|
m_rc.OffsetRect(pt.x - m_ptHit.x, pt.y - m_ptHit.y);
|
|
break;
|
|
}
|
|
m_ptHit = pt;
|
|
}
|
|
|
|
void SelectionModel::ClearMaskImage()
|
|
{
|
|
if (m_hbmMask)
|
|
{
|
|
::DeleteObject(m_hbmMask);
|
|
m_hbmMask = NULL;
|
|
}
|
|
}
|
|
|
|
void SelectionModel::ClearColorImage()
|
|
{
|
|
if (m_hbmColor)
|
|
{
|
|
::DeleteObject(m_hbmColor);
|
|
m_hbmColor = NULL;
|
|
}
|
|
}
|
|
|
|
void SelectionModel::HideSelection()
|
|
{
|
|
m_bShow = m_bContentChanged = FALSE;
|
|
ClearColorImage();
|
|
ClearMaskImage();
|
|
m_rc.SetRectEmpty();
|
|
m_rcOld.SetRectEmpty();
|
|
imageModel.NotifyImageChanged();
|
|
}
|
|
|
|
void SelectionModel::DeleteSelection()
|
|
{
|
|
if (!m_bShow)
|
|
return;
|
|
|
|
TakeOff();
|
|
imageModel.PushImageForUndo();
|
|
DrawBackground(imageModel.GetDC(), paletteModel.GetBgColor());
|
|
|
|
HideSelection();
|
|
}
|
|
|
|
void SelectionModel::InvertSelection()
|
|
{
|
|
TakeOff();
|
|
|
|
BITMAP bm;
|
|
::GetObjectW(m_hbmColor, sizeof(bm), &bm);
|
|
|
|
HDC hdc = ::CreateCompatibleDC(NULL);
|
|
HGDIOBJ hbmOld = ::SelectObject(hdc, m_hbmColor);
|
|
RECT rc = { 0, 0, bm.bmWidth, bm.bmHeight };
|
|
::InvertRect(hdc, &rc);
|
|
::SelectObject(hdc, hbmOld);
|
|
::DeleteDC(hdc);
|
|
|
|
NotifyContentChanged();
|
|
}
|
|
|
|
void SelectionModel::NotifyContentChanged()
|
|
{
|
|
m_bContentChanged = TRUE;
|
|
imageModel.NotifyImageChanged();
|
|
}
|
|
|
|
void SelectionModel::SwapWidthAndHeight()
|
|
{
|
|
INT cx = m_rc.Width(), cy = m_rc.Height();
|
|
m_rc.right = m_rc.left + cy;
|
|
m_rc.bottom = m_rc.top + cx;
|
|
}
|
|
|
|
HITTEST SelectionModel::hitTest(POINT ptCanvas)
|
|
{
|
|
if (!m_bShow)
|
|
return HIT_NONE;
|
|
|
|
CRect rcSelection = m_rc;
|
|
canvasWindow.ImageToCanvas(rcSelection);
|
|
rcSelection.InflateRect(GRIP_SIZE, GRIP_SIZE);
|
|
return getSizeBoxHitTest(ptCanvas, &rcSelection);
|
|
}
|
|
|
|
void SelectionModel::drawFrameOnCanvas(HDC hCanvasDC)
|
|
{
|
|
if (!m_bShow)
|
|
return;
|
|
|
|
CRect rcSelection = m_rc;
|
|
canvasWindow.ImageToCanvas(rcSelection);
|
|
rcSelection.InflateRect(GRIP_SIZE, GRIP_SIZE);
|
|
drawSizeBoxes(hCanvasDC, &rcSelection, TRUE);
|
|
}
|
|
|
|
void SelectionModel::moveSelection(INT xDelta, INT yDelta)
|
|
{
|
|
if (!m_bShow)
|
|
return;
|
|
|
|
TakeOff();
|
|
m_rc.OffsetRect(xDelta, yDelta);
|
|
canvasWindow.Invalidate();
|
|
}
|
|
|
|
void SelectionModel::StretchSelection(BOOL bShrink)
|
|
{
|
|
if (!m_bShow)
|
|
return;
|
|
|
|
TakeOff();
|
|
|
|
INT cx = m_rc.Width(), cy = m_rc.Height();
|
|
|
|
if (bShrink)
|
|
m_rc.InflateRect(-cx / 4, -cy / 4);
|
|
else
|
|
m_rc.InflateRect(+cx / 2, +cy / 2);
|
|
|
|
// The selection area must exist there
|
|
if (m_rc.Width() <= 0)
|
|
m_rc.right = m_rc.left + 1;
|
|
if (m_rc.Height() <= 0)
|
|
m_rc.bottom = m_rc.top + 1;
|
|
|
|
imageModel.NotifyImageChanged();
|
|
}
|