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 }