File indexing completed on 2024-12-22 04:12:33

0001 /*
0002  *  SPDX-FileCopyrightText: 2010 Sven Langkamp <sven.langkamp@gmail.com>
0003  *  SPDX-FileCopyrightText: 2011 Jan Hambrecht <jaham@gmx.net>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_shape_selection.h"
0009 
0010 
0011 #include <QPainter>
0012 #include <QTimer>
0013 #include <kundo2command.h>
0014 #include <QMimeData>
0015 #include <QApplication>
0016 #include <QThread>
0017 
0018 #include <KoShapeStroke.h>
0019 #include <KoPathShape.h>
0020 #include <KoShapeGroup.h>
0021 #include <KoCompositeOp.h>
0022 #include <KoShapeManager.h>
0023 #include <KisDocument.h>
0024 
0025 #include <KoXmlNS.h>
0026 #include <KoShapeRegistry.h>
0027 #include <KoShapeLoadingContext.h>
0028 #include <KoXmlWriter.h>
0029 #include <KoStore.h>
0030 #include <KoShapeController.h>
0031 #include <KoShapeSavingContext.h>
0032 #include <KoStoreDevice.h>
0033 #include <KoShapeTransformCommand.h>
0034 
0035 #include <kis_painter.h>
0036 #include <kis_paint_device.h>
0037 #include <kis_image.h>
0038 #include <kis_iterator_ng.h>
0039 #include <kis_selection.h>
0040 
0041 #include "kis_shape_selection_model.h"
0042 #include "kis_shape_selection_canvas.h"
0043 #include "kis_take_all_shapes_command.h"
0044 #include "kis_image_view_converter.h"
0045 #include "kis_shape_layer.h"
0046 #include "kis_lod_transform.h"
0047 
0048 #include <kis_debug.h>
0049 
0050 
0051 KisShapeSelection::KisShapeSelection(KoShapeControllerBase *shapeControllerBase, KisSelectionWSP selection)
0052     : KoShapeLayer(new KisShapeSelectionModel(selection->resolutionProxy(), selection, this)),
0053       m_model(static_cast<KisShapeSelectionModel*>(this->model())),
0054       m_resolutionProxy(m_model->resolutionProxy())
0055 {
0056     init(m_resolutionProxy, shapeControllerBase);
0057 }
0058 
0059 KisShapeSelection::KisShapeSelection(const KisShapeSelection& rhs, KisSelection* selection)
0060     : KoShapeLayer(new KisShapeSelectionModel(selection->resolutionProxy(), selection, this)),
0061       m_model(static_cast<KisShapeSelectionModel*>(this->model())),
0062       m_resolutionProxy(m_model->resolutionProxy())
0063 {
0064     init(m_resolutionProxy, rhs.m_shapeControllerBase);
0065 
0066     // TODO: refactor shape selection to pass signals
0067     //       via KoShapeManager, not via the model
0068     m_canvas->shapeManager()->setUpdatesBlocked(true);
0069     m_model->setUpdatesEnabled(false);
0070 
0071     m_canvas->shapeManager()->addShape(this);
0072     Q_FOREACH (KoShape *shape, rhs.shapes()) {
0073         KoShape *clonedShape = shape->cloneShape();
0074         KIS_SAFE_ASSERT_RECOVER(clonedShape) { continue; }
0075         this->addShape(clonedShape);
0076     }
0077 
0078     m_canvas->shapeManager()->setUpdatesBlocked(false);
0079     m_model->setUpdatesEnabled(true);
0080 }
0081 
0082 KisShapeSelection::~KisShapeSelection()
0083 {
0084     m_model->setShapeSelection(0);
0085     delete m_canvas;
0086     delete m_converter;
0087 }
0088 
0089 void KisShapeSelection::init(KisImageResolutionProxySP resolutionProxy, KoShapeControllerBase *shapeControllerBase)
0090 {
0091     KIS_SAFE_ASSERT_RECOVER_RETURN(resolutionProxy);
0092     KIS_SAFE_ASSERT_RECOVER_RETURN(shapeControllerBase);
0093 
0094     m_shapeControllerBase = shapeControllerBase;
0095 
0096     setShapeId("KisShapeSelection");
0097     setSelectable(false);
0098     m_converter = new KisImageViewConverter(resolutionProxy);
0099     m_canvas = new KisShapeSelectionCanvas(shapeControllerBase);
0100     m_canvas->shapeManager()->addShape(this);
0101 
0102     m_model->setObjectName("KisShapeSelectionModel");
0103     m_model->moveToThread(qApp->thread());
0104     m_canvas->setObjectName("KisShapeSelectionCanvas");
0105     m_canvas->moveToThread(qApp->thread());
0106 
0107     connect(this, SIGNAL(sigMoveShapes(QPointF)), SLOT(slotMoveShapes(QPointF)));
0108 }
0109 
0110 KisSelectionComponent* KisShapeSelection::clone(KisSelection* selection)
0111 {
0112     return new KisShapeSelection(*this, selection);
0113 }
0114 
0115 bool KisShapeSelection::saveSelection(KoStore * store, const QRect &imageRect) const
0116 {
0117     const QSizeF sizeInPx = imageRect.size();
0118     const QSizeF sizeInPt(sizeInPx.width() / m_resolutionProxy->xRes(), sizeInPx.height() / m_resolutionProxy->yRes());
0119 
0120     return KisShapeLayer::saveShapesToStore(store, this->shapes(), sizeInPt);
0121 }
0122 
0123 bool KisShapeSelection::loadSelection(KoStore* store, const QRect &imageRect)
0124 {
0125     QSizeF fragmentSize; // unused!
0126 
0127     // FIXME: we handle xRes() only!
0128     KIS_SAFE_ASSERT_RECOVER_NOOP(qFuzzyCompare(m_resolutionProxy->xRes(), m_resolutionProxy->yRes()));
0129     const qreal resolutionPPI = 72.0 * m_resolutionProxy->xRes();
0130 
0131     QList<KoShape*> shapes;
0132 
0133     if (store->open("content.svg")) {
0134         KoStoreDevice storeDev(store);
0135         storeDev.open(QIODevice::ReadOnly);
0136 
0137         shapes = KisShapeLayer::createShapesFromSvg(&storeDev,
0138                                                     "", imageRect,
0139                                                     resolutionPPI, m_canvas->shapeController()->resourceManager(),
0140                                                     true,
0141                                                     &fragmentSize);
0142 
0143         store->close();
0144 
0145         Q_FOREACH (KoShape *shape, shapes) {
0146             addShape(shape);
0147         }
0148 
0149         return true;
0150     }
0151 
0152     return false;
0153 }
0154 
0155 void KisShapeSelection::setUpdatesEnabled(bool enabled)
0156 {
0157     m_model->setUpdatesEnabled(enabled);
0158 }
0159 
0160 bool KisShapeSelection::updatesEnabled() const
0161 {
0162     return m_model->updatesEnabled();
0163 }
0164 
0165 KUndo2Command* KisShapeSelection::resetToEmpty()
0166 {
0167     return new KisTakeAllShapesCommand(this, true, false);
0168 }
0169 
0170 bool KisShapeSelection::isEmpty() const
0171 {
0172     return !m_model->count();
0173 }
0174 
0175 QPainterPath KisShapeSelection::outlineCache() const
0176 {
0177     return m_outline;
0178 }
0179 
0180 bool KisShapeSelection::outlineCacheValid() const
0181 {
0182     return true;
0183 }
0184 
0185 void KisShapeSelection::recalculateOutlineCache()
0186 {
0187     QTransform resolutionMatrix;
0188     resolutionMatrix.scale(m_resolutionProxy->xRes(), m_resolutionProxy->yRes());
0189 
0190     QList<KoShape*> shapesList = shapes();
0191 
0192     QPainterPath outline;
0193     Q_FOREACH (KoShape * shape, shapesList) {
0194         /**
0195          * WARNING: we should unite all the shapes in image coordinates,
0196          * not in points. Boolean operations inside the QPainterPath
0197          * linearize the curves into lines and they use absolute values
0198          * for thresholds.
0199          *
0200          * See KritaUtils::pathShapeBooleanSpaceWorkaround() for more info
0201          */
0202         QTransform shapeMatrix = shape->absoluteTransformation();
0203         outline = outline.united(resolutionMatrix.map(shapeMatrix.map(shape->outline())));
0204     }
0205 
0206     m_outline = outline;
0207 }
0208 
0209 void KisShapeSelection::paintComponent(QPainter& painter) const
0210 {
0211     Q_UNUSED(painter);
0212 }
0213 
0214 void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection)
0215 {
0216     Q_ASSERT(projection);
0217 
0218     QRectF boundingRect = outlineCache().boundingRect();
0219     renderSelection(projection, boundingRect.toAlignedRect());
0220 }
0221 
0222 void KisShapeSelection::renderToProjection(KisPaintDeviceSP projection, const QRect& r)
0223 {
0224     Q_ASSERT(projection);
0225     renderSelection(projection, r);
0226 }
0227 
0228 void KisShapeSelection::renderSelection(KisPaintDeviceSP projection, const QRect& requestedRect)
0229 {
0230     KIS_SAFE_ASSERT_RECOVER_RETURN(projection);
0231 
0232     const qint32 MASK_IMAGE_WIDTH = 256;
0233     const qint32 MASK_IMAGE_HEIGHT = 256;
0234 
0235     QPainterPath selectionOutline = outlineCache();
0236 
0237     if (projection->defaultBounds()->currentLevelOfDetail() > 0) {
0238         KisLodTransform t(projection);
0239         selectionOutline = t.map(selectionOutline);
0240     }
0241 
0242     if (*projection->defaultPixel().data() == OPACITY_TRANSPARENT_U8) {
0243         projection->clear(requestedRect);
0244     } else {
0245         KoColor transparentColor = KoColor::createTransparent(projection->colorSpace());
0246         projection->fill(requestedRect, transparentColor);
0247     }
0248     const QRect r = requestedRect & selectionOutline.boundingRect().toAlignedRect();
0249 
0250     QImage polygonMaskImage(MASK_IMAGE_WIDTH, MASK_IMAGE_HEIGHT, QImage::Format_ARGB32);
0251     QPainter maskPainter(&polygonMaskImage);
0252     maskPainter.setRenderHint(QPainter::Antialiasing, true);
0253 
0254     // Break the mask up into chunks so we don't have to allocate a potentially very large QImage.
0255     for (qint32 x = r.x(); x < r.x() + r.width(); x += MASK_IMAGE_WIDTH) {
0256         for (qint32 y = r.y(); y < r.y() + r.height(); y += MASK_IMAGE_HEIGHT) {
0257 
0258             maskPainter.fillRect(polygonMaskImage.rect(), Qt::black);
0259             maskPainter.translate(-x, -y);
0260             maskPainter.fillPath(selectionOutline, Qt::white);
0261             maskPainter.translate(x, y);
0262 
0263             qint32 rectWidth = qMin(r.x() + r.width() - x, MASK_IMAGE_WIDTH);
0264             qint32 rectHeight = qMin(r.y() + r.height() - y, MASK_IMAGE_HEIGHT);
0265 
0266             KisSequentialIterator it(projection, QRect(x, y, rectWidth, rectHeight));
0267             while (it.nextPixel()) {
0268                 (*it.rawData()) = qRed(polygonMaskImage.pixel(it.x() - x, it.y() - y));
0269             }
0270         }
0271     }
0272 }
0273 
0274 KoShapeManager* KisShapeSelection::shapeManager() const
0275 {
0276     return m_canvas->shapeManager();
0277 }
0278 
0279 KisShapeSelectionFactory::KisShapeSelectionFactory()
0280     : KoShapeFactoryBase("KisShapeSelection", "selection shape container")
0281 {
0282     setHidden(true);
0283 }
0284 
0285 void KisShapeSelection::moveX(qint32 x)
0286 {
0287     const QPointF diff(x / m_resolutionProxy->xRes(), 0);
0288     emit sigMoveShapes(diff);
0289 }
0290 
0291 void KisShapeSelection::moveY(qint32 y)
0292 {
0293     const QPointF diff(0, y / m_resolutionProxy->yRes());
0294     emit sigMoveShapes(diff);
0295 }
0296 
0297 void KisShapeSelection::slotMoveShapes(const QPointF &diff)
0298 {
0299     Q_FOREACH (KoShape* shape, shapeManager()->shapes()) {
0300         if (shape != this) {
0301             QPointF pos = shape->position();
0302             shape->setPosition(pos + diff);
0303         }
0304     }
0305 }
0306 
0307 // TODO same code as in vector layer, refactor!
0308 KUndo2Command* KisShapeSelection::transform(const QTransform &transform) {
0309     QList<KoShape*> shapes = m_canvas->shapeManager()->shapes();
0310     if(shapes.isEmpty()) return 0;
0311 
0312     QTransform realTransform = m_converter->documentToView() *
0313             transform * m_converter->viewToDocument();
0314 
0315     QList<QTransform> oldTransformations;
0316     QList<QTransform> newTransformations;
0317 
0318     // this code won't work if there are shapes, that inherit the transformation from the parent container.
0319     // the chart and tree shapes are examples for that, but they aren't used in krita and there are no other shapes like that.
0320     Q_FOREACH (const KoShape* shape, shapes) {
0321         QTransform oldTransform = shape->transformation();
0322         oldTransformations.append(oldTransform);
0323 
0324         // don't transform the container
0325         if (dynamic_cast<const KoShapeGroup *>(shape) || !shape->parent()) {
0326             newTransformations.append(oldTransform);
0327         } else {
0328             QTransform globalTransform = shape->absoluteTransformation();
0329             QTransform localTransform = globalTransform * realTransform * globalTransform.inverted();
0330             newTransformations.append(localTransform*oldTransform);
0331         }
0332     }
0333 
0334     return new KoShapeTransformCommand(shapes, oldTransformations, newTransformations);
0335 }
0336 
0337 void KisShapeSelection::setResolutionProxy(KisImageResolutionProxySP resolutionProxy)
0338 {
0339     m_resolutionProxy = resolutionProxy;
0340 
0341     // the model will automatically request update for the
0342     // canvas and recalculation of the outline
0343     m_model->setResolutionProxy(resolutionProxy);
0344 }