/* * Copyright 2026 Forschungszentrum Jülich * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ #include "LayerItem.h" #include "Layer.h" #include "TransformHandleItem.h" #include "CageControlPointItem.h" #include "CageOverlayItem.h" #include "CageMesh.h" #include "../core/IMainSystem.h" #include "../gui/MainWindow.h" #include "../gui/ImageView.h" #include "../undo/TransformLayerCommand.h" #include "../undo/MirrorLayerCommand.h" #include "../undo/MoveLayerCommand.h" #include "../undo/CageWarpCommand.h" #include "../util/Interpolation.h" #include "../util/GeometryUtils.h" #include "../util/TriangleWarp.h" #include #include #include #include #include #include #include #include #include // ------------------------ LayerItem ------------------------ LayerItem::LayerItem( const QPixmap& pixmap, QGraphicsItem* parent ) : QGraphicsPixmapItem(pixmap,parent) { qCDebug(logEditor) << "LayerItem::LayerItem(): Pixmap processing..."; { m_image = pixmap.toImage(); m_originalImage = pixmap.toImage(); init(); } } LayerItem::LayerItem( const QImage& image, QGraphicsItem* parent ) : QGraphicsPixmapItem(parent) , m_originalImage(image) , m_image(image) { qCDebug(logEditor) << "LayerItem::LayerItem(): Image processing..."; { if ( qobject_cast(qApp) ) { setPixmap(QPixmap::fromImage(m_image)); } else { m_nogui = true; } init(); } } // >>> void LayerItem::applyPerspective() { QImage warped = m_perspective.apply(m_originalImage); setPixmap(QPixmap::fromImage(warped)); } // returns unified rect of the pixmap AND the m_cageMesh QRectF LayerItem::boundingRect() const { QRectF pixmapRect(offset(), pixmap().size()); if ( m_cageMesh.isActive() ) { QRectF cageRect = QPolygonF(m_cageMesh.points()).boundingRect(); return pixmapRect.united(cageRect); } return pixmapRect; } // per default moveable layer item // NOTE: QGraphicsItem::ItemIsSelectable expands the size of the QGraphicsPixmapItem by +1+1 ! void LayerItem::init() { setFlags(QGraphicsItem::ItemIsSelectable | QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemSendsGeometryChanges | QGraphicsItem::ItemIsFocusable); setTransformationMode(EditorStyle::instance().transformationMode()); setAcceptedMouseButtons(Qt::LeftButton); setShapeMode(QGraphicsPixmapItem::BoundingRectShape); // setShapeMode(QGraphicsPixmapItem::MaskShape); // the better choice but complex setZValue(2); m_showBoundingBox = true; // Lasso pen m_lassoPen = QPen(Qt::green); m_lassoPen.setWidth(0); m_lassoPen.setStyle(Qt::SolidLine); // Selection pen m_selectedPen = QPen(Qt::red); m_selectedPen.setWidth(0); m_selectedPen.setStyle(Qt::SolidLine); } // ------------------------ Self info ------------------------ void LayerItem::printself( bool debugSave ) { qInfo() << " LayerItem::printself(): name =" << name() << ", id =" << m_index << ", visible =" << isVisible() << ", zValue =" << zValue() << ", selected=" << isSelected() << ", enabled =" << isEnabled(); QRectF rect = m_nogui ? m_image.rect() : boundingRect(); QString geometry = QString("%1x%2+%3+%4").arg(rect.width()).arg(rect.height()).arg(pos().x()).arg(pos().y()); qInfo() << " + position =" << pos(); qInfo() << " + geometry =" << geometry; qInfo() << " + cage: enabled =" << m_cageEnabled << ", edited=" << m_cageEditing; qInfo() << " + cage overlay =" << (m_cageOverlay != nullptr ? "ok" : "null"); qInfo() << " + operation mode =" << m_operationMode; qInfo() << " + bounding box =" << m_showBoundingBox; qInfo() << " + parent =" << ( m_parent != nullptr ? "null" : "ok" ); } // ------------------------ Getter / Setter ------------------------ QImage& LayerItem::image( int id ) { return id == 1 ? m_originalImage : m_image; } void LayerItem::setInActive( bool isInActive ) { if ( !m_layer ) return; m_layer->m_active = !isInActive; } void LayerItem::setImage( const QImage &image ) { m_image = image; updatePixmap(); } void LayerItem::setLayer( Layer *layer ) { m_layer = layer; } void LayerItem::setType( LayerType layerType ) { m_type = layerType; if ( m_type == LayerType::MainImage ) { setFlag(QGraphicsItem::ItemIsMovable, false); setAcceptedMouseButtons(Qt::NoButton); } else { setFlag(QGraphicsItem::ItemIsMovable, true); setAcceptedMouseButtons(Qt::LeftButton); setAcceptedMouseButtons(Qt::RightButton); } } void LayerItem::setFileInfo( const QString &filePath ) { // set filename m_filename = filePath; // compute checksum QFile file(filePath); if ( file.open(QIODevice::ReadOnly) ) { QCryptographicHash hash(QCryptographicHash::Md5); while ( !file.atEnd() ) { hash.addData(file.read(8192)); } m_checksum = hash.result().toHex(); } } void LayerItem::setImageRect( const QRectF& rect ) { prepareGeometryChange(); } void LayerItem::setOriginalImage( const QImage& originalImage ) { m_originalImage = originalImage; } const QImage& LayerItem::originalImage() { if ( m_originalImage.format() != QImage::Format_ARGB32 ) m_originalImage = m_originalImage.convertToFormat(QImage::Format_ARGB32); return m_originalImage; } QString LayerItem::name() const { if ( m_index == 0 ) return "MainImage"; if ( m_layer ) return m_layer->name(); return m_name; } // ------------------------ Update ------------------------ void LayerItem::updatePixmap() { if ( qobject_cast(qApp) ) { setPixmap(QPixmap::fromImage(m_image)); } } void LayerItem::resetPixmap() { if ( qobject_cast(qApp) ) { setPixmap(QPixmap::fromImage(m_originalImage)); } } void LayerItem::updateImageRegion( const QRect& rect ) { if ( rect.isEmpty() ) return; QPixmap pm = pixmap(); if ( pm.isNull() ) return; QPainter painter(&pm); painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(rect.topLeft(),m_image,rect); painter.end(); setPixmap(pm); update(rect); } void LayerItem::updateOriginalImage() { m_originalImage = m_image; } // ------------------------ Selected ------------------------ void LayerItem::setIsSelected( bool isSelected ) { qCDebug(logEditor) << "LayerItem::setIsSelected(): name =" << name() << ", selected =" << isSelected << ", enabled =" << isEnabled(); QGraphicsItem::setSelected(isSelected); } // ------------------------ Mirror ------------------------ void LayerItem::setMirror( int mirrorPlane ) { qCDebug(logEditor) << "LayerItem::setMirror(): mirrorPlane=" << mirrorPlane; { if ( mirrorPlane > 0 ) { QTransform transform; transform.scale(mirrorPlane == 1 ? +1 : -1,mirrorPlane == 1 ? -1 : +1); setImageTransform(transform); } } } // ------------------------ Transform ------------------------ void LayerItem::resetTotalTransform() { qCDebug(logEditor) << "LayerItem::resetTotalTransform(): Processing..."; { m_totalTransform = QTransform(); setImageTransform(m_totalTransform); } } void LayerItem::setImageTransform( const QTransform& transform, bool combine ) { qCDebug(logEditor) << "LayerItem::setImageTransform(): combine =" << combine; { // *** DO NOT USE INTERNAL TRANSFORMATIONS *** if ( 1 == 2 && qobject_cast(qApp) ) { qDebug() << " + internal pixmap processing: transform_origin =" << QGraphicsPixmapItem::transformOriginPoint() << ", pos =" << QGraphicsPixmapItem::pos(); QGraphicsPixmapItem::setTransform(transform,combine); return; } // *** hard QImage transformation *** qCDebug(logEditor) << "LayerItem::setImageTransform(): ALTERNATIVE PROCESS FOR CASE WHERE NO PIXMAP IS AVAILABLE"; // transform image prepareGeometryChange(); QPointF sceneCenter = mapToScene(QRectF(pixmap().rect()).center()); QPointF imageCenter = QRectF(m_originalImage.rect()).center(); m_totalTransform.translate(imageCenter.x(), imageCenter.y()); m_totalTransform *= transform; // This is my actual rotation m_totalTransform.translate(-imageCenter.x(), -imageCenter.y()); m_image = m_originalImage.transformed(m_totalTransform, Qt::SmoothTransformation); QPointF newImageCenter(m_image.width() / 2.0, m_image.height() / 2.0); setPos(sceneCenter - newImageCenter); // reset transform setTransform(QTransform()); updatePixmap(); } } // ------------------------ Paint ------------------------ void LayerItem::paint( QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget ) { #if 0 qCDebug(logEditor) << "LayerItem::paint(): Processing " << (m_layer?m_layer->name():"unknown") << ", selected = " << isSelected(); #endif { QGraphicsPixmapItem::paint(painter,option,widget); if ( isSelected() ) { painter->setPen(m_selectedPen); painter->setBrush(Qt::NoBrush); painter->drawRect(boundingRect()); } else if ( m_showBoundingBox ) { painter->setPen(m_lassoPen); painter->setBrush(Qt::NoBrush); painter->drawRect(boundingRect()); } // render cage if ( m_cageEnabled ) { painter->setPen(QPen(Qt::red,0)); painter->setBrush(Qt::NoBrush); painter->drawPolygon(m_cage.data(), m_cage.size()); } } } void LayerItem::paintStrokeSegment( const QPoint& p0, const QPoint& p1, const QColor &color, int radius, float hardness ) { qCDebug(logEditor) << "LayerItem::paintStrokeSegment(): Processing..."; if ( radius < 0.0 ) return; { const float spacing = std::max(1.0f, radius * 0.35f); const float dx = p1.x() - p0.x(); const float dy = p1.y() - p0.y(); const float dist = std::sqrt(dx*dx + dy*dy); if ( dist <= 0.0f ) { Interpolation::dab(m_image, p0, color, radius, hardness); return; } const int steps = std::ceil(dist / spacing); for ( int i = 0; i <= steps; ++i ) { float t = float(i) / float(steps); QPoint p( int(p0.x() + t * dx), int(p0.y() + t * dy) ); Interpolation::dab(m_image, p, color, radius, hardness); } // --- QRect dirtyRect = QRect(p0, QSize(1,1)); dirtyRect |= QRect(p1, QSize(1,1)); int pad = radius + 2; dirtyRect.adjust(-pad, -pad, pad, pad); dirtyRect &= m_image.rect(); updateImageRegion(dirtyRect); // --- // setPixmap(QPixmap::fromImage(m_image)); } } // ------------------------ Cage ------------------------ QImage LayerItem::applyTriangleWarp() { qCDebug(logEditor) << "LayerItem::applyTriangleWarp(): meshActive =" << m_cageMesh.isActive() << ", cageEnabled =" << m_cageEnabled << ", cageEditing =" << m_cageEditing << ", intialized =" << m_cageMesh.isInitialized(); { #if 0 // removed by CLAUDE if ( !m_cageEditing ) { return m_cageMesh.image(); // muss das original image sein vor jedem warp !!! } #endif if ( !m_cageMesh.isInitialized(true) ) m_cageMesh.setImage(m_image); TriangleWarp::WarpResult warped = TriangleWarp::warp(m_cageMesh.image(),m_cageMesh); // TriangleWarp::WarpResult warped = TriangleWarp::warp(m_originalImage,m_cageMesh); if ( !warped.image.isNull() ) { setPixmap(QPixmap::fromImage(warped.image)); m_image = warped.image; QGraphicsPixmapItem::setPos(QGraphicsPixmapItem::pos() + m_cageMesh.getOffset()); return m_image.copy(); } else { qDebug() << "LayerItem::applyTriangleWarp(): WARNING: Image isNull..."; } return QImage(); } } void LayerItem::applyCageWarp() { qDebug() << "DEPRECTAED **** LayerItem::applyCageWarp(): None!"; } void LayerItem::enableCage( int cols, int rows ) { qCDebug(logEditor) << "LayerItem::enableCage(): cols =" << cols << ", rows =" << rows << ", enabled =" << m_cageEnabled; { m_cageMesh.create(boundingRect(), cols, rows); if ( !m_cageMesh.isInitialized() ) m_cageMesh.setImage(m_image); m_cageEnabled = true; if ( !scene() ) return; if ( !m_cageOverlay ) { m_cageOverlay = new CageOverlayItem(this); m_cageOverlay->setParentItem(this); } m_cageOverlay->setVisible(true); // Handles qDeleteAll(m_handles); m_handles.clear(); for ( int i = 0; i < m_cageMesh.pointCount(); ++i ) { auto* h = new CageControlPointItem(this, i); h->setParentItem(this); h->setPos(m_cageMesh.point(i)); int col = i % m_cageMesh.cols(); int row = i / m_cageMesh.cols(); int dx = ( col == 0 ) ? 0 : ( ( col == m_cageMesh.cols()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); int dy = ( row == 0 ) ? 0 : ( ( row == m_cageMesh.rows()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); h->setRect(dx, dy, 2*m_cageMesh.getRadius(), 2*m_cageMesh.getRadius() ); m_handles << h; } } } // --- what about the grid ??? --- void LayerItem::initCage( const QVector& pts, const QRectF &rect, int nrows, int ncolumns ) { qCDebug(logEditor) << "LayerItem::initCage(): cageOverlay =" << (m_cageOverlay!=nullptr?"ok":"null") << ", rect =" << rect << ", rows =" << nrows << ", ncolumns =" << ncolumns; { m_cageMesh.setIsInitialized(); m_cageMesh.create(rect,nrows,ncolumns); // m_cageMesh.setImage(m_image); // ADDED by CLAUDE // 04.01.2026: REMOVED by Hartmut: // Multiple undo/redo operations result in cumulative distortions m_cageMesh.setPoints(pts); if ( m_cageOverlay == nullptr ) { m_cageOverlay = new CageOverlayItem(this); m_cageOverlay->setParentItem(this); } } } void LayerItem::setCageVisible( bool isVisible ) { qCDebug(logEditor) << "LayerItem::setCageVisible(" << isVisible << "): name = " << name() << ", cageOverlay =" << (m_cageOverlay != nullptr?"ok":"none") << ", cageEditing=" << m_cageEditing << "|" << m_cageEnabled << " nControlPoints=" << m_cageMesh.pointCount(); { if ( m_cageOverlay != nullptr ) { if ( isVisible ) { m_cageMesh.setActive(true); m_cageEnabled = true; m_cageEditing = true; m_cageOverlay->setVisible(true); } else { #if 0 // CLAUDE says: This is potentially wrong. This will reset // m_selectedCageLayer to null in MainWindow, which is not // necessarily what we want to do when making other inactive // cages invisible. Let the parent MainWindow decide about this. if ( m_parent != nullptr ) { MainWindow* parent = dynamic_cast(m_parent); if ( parent && parent->getViewer() != nullptr ) { parent->getViewer()->setActiveCageLayer(nullptr); } } #endif m_cageMesh.setActive(false); m_cageEnabled = false; m_cageEditing = false; for ( int i = 0; i < m_handles.size(); ++i ) { m_handles[i]->setVisible(false); } m_cageOverlay->setVisible(false); update(); } } } } void LayerItem::setCageVisible( LayerItem::OperationMode mode, bool isVisible, bool pushBackImage ) { qCDebug(logEditor) << "LayerItem::setCageVisible(): mode =" << mode << ", isVisible =" << isVisible << ", pushBackImage =" << pushBackImage; { switch ( mode ) { case LayerItem::OperationMode::CageWarp: if ( m_cageOverlay != nullptr ) { if( m_handles.size() == 0 ) { // create handles if they have not been created (when read from .json) // CLAUDE -- this should be moved to a more convenient place. qDeleteAll(m_handles); m_handles.clear(); for ( int i = 0; i < m_cageMesh.pointCount(); ++i ) { auto* h = new CageControlPointItem(this, i); h->setParentItem(this); h->setPos(m_cageMesh.point(i)); int col = i % m_cageMesh.cols(); int row = i / m_cageMesh.cols(); int dx = ( col == 0 ) ? 0 : ( ( col == m_cageMesh.cols()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); int dy = ( row == 0 ) ? 0 : ( ( row == m_cageMesh.rows()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); h->setRect(dx, dy, 2*m_cageMesh.getRadius(), 2*m_cageMesh.getRadius() ); m_handles << h; } } for ( int i = 0; i < m_handles.size(); ++i ) { m_handles[i]->setVisible(isVisible); } m_cageOverlay->setVisible(isVisible); } else { qCDebug(logEditor) << "WARNING: m_cageOverlay is null."; } break; case LayerItem::OperationMode::Scale: case LayerItem::OperationMode::Perspective: if ( m_parent != nullptr ) { MainWindow* parent = dynamic_cast(m_parent); if ( parent && parent->getViewer() != nullptr ) { parent->getViewer()->setCageVisible(this,mode,isVisible); } } break; default: qDebug() << " + unprocessed mode " << mode; } if ( isVisible == false && pushBackImage == true ) { setOriginalImage(m_cageMesh.image()); } } } int LayerItem::changeNumberOfActiveCagePoints( int step ) { qCDebug(logEditor) << "LayerItem::changeNumberOfActiveCagePoints(): cage = " << m_cageMesh.cols() << ", step =" << step; { int expo = std::log2(m_cageMesh.cols()-1); expo -= step > 0 ? 0 : 1; int ds = pow(2,expo); int columns = m_cageMesh.cols(); columns += step*ds; int rows = m_cageMesh.rows(); rows += step*ds; if ( rows >= 3 && columns >= 3 ) { // update cage m_cageMesh.needUpdate(); m_cageMesh.update(boundingRect(),std::max(3,rows),std::max(3,columns)); // update handles qDeleteAll(m_handles); m_handles.clear(); for ( int i = 0; i < m_cageMesh.pointCount(); ++i ) { auto* h = new CageControlPointItem(this, i); h->setParentItem(this); h->setPos(m_cageMesh.point(i)); int col = i % m_cageMesh.cols(); int row = i / m_cageMesh.cols(); int dx = ( col == 0 ) ? 0 : ( ( col == m_cageMesh.cols()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); int dy = ( row == 0 ) ? 0 : ( ( row == m_cageMesh.rows()-1 ) ? -2*m_cageMesh.getRadius() : -m_cageMesh.getRadius() ); h->setRect(dx, dy, 2*m_cageMesh.getRadius(), 2*m_cageMesh.getRadius() ); m_handles << h; } return rows; } return 0; } } void LayerItem::setNumberOfActiveCagePoints( int nControlPoints ) { enableCage(nControlPoints,nControlPoints); } void LayerItem::setCageWarpProperty( int type, double value ) { if ( type == 1 ) { m_cageMesh.setNumberOfRelaxationsSteps(int(value)); } else if ( type == 2 ) { m_cageMesh.setStiffness(value); } else if ( type == 3 ) { m_cageMesh.setFixedBoundaries(value>0?true:false); } else { return; } m_cageMesh.needUpdate(); } void LayerItem::setCagePoints( const QVector& pts ) { m_cageMesh.setPoints(pts); update(); } QVector LayerItem::cagePoints() const { return m_cageMesh.points(); } void LayerItem::updateCagePoint( TransformHandleItem* handle, const QPointF& localPos ) { qCDebug(logEditor) << "LayerItem::updateCagePoint(): Processing..."; { int idx = m_handles.indexOf(handle); if ( idx < 0 ) return; m_cage[idx] = localPos; update(); // neu zeichnen } } void LayerItem::resetCageToPixmap() { qCDebug(logEditor) << "LayerItem::resetCageToPixmap(): size =" << m_handles.size(); { prepareGeometryChange(); qreal w = pixmap().width(); qreal h = pixmap().height(); setOffset(0, 0); QList resetPoints; int nrc = qSqrt(m_handles.size()); qreal dx = w/(nrc-1); qreal dy = h/(nrc-1); for ( int i=0 ; isetPos(resetPoints[i]); } } applyTriangleWarp(); } } /** BEST */ void LayerItem::setCagePoint( int idx, const QPointF& pos ) { qCDebug(logEditor) << "LayerItem::setCagePoint(): index =" << idx << ", point =" << pos; { QPointF localPos = mapFromScene(pos); m_cageMesh.setPoint(idx,localPos); QRectF newBounds = QPolygonF(m_cageMesh.points()).boundingRect(); if ( newBounds.x() != 0 || newBounds.y() != 0 ) { qreal dx = newBounds.x(); qreal dy = newBounds.y(); prepareGeometryChange(); setPos(mapToScene(QPointF(dx, dy))); m_cageMesh.addOffset(dx,dy); // setOffset(offset() - QPointF(dx, dy)); QList pts = m_cageMesh.points(); for ( int i = 0; i < pts.size(); ++i ) { pts[i] -= QPointF(dx, dy); } m_cageMesh.setPoints(pts); } for ( int i = 0; i < m_handles.size(); ++i ) { m_handles[i]->setPos(m_cageMesh.point(i)); } m_cageMesh.relax(); update(); } } /** */ /* OLD - VERSION void LayerItem::setCagePoint( int idx, const QPointF& pos ) { qDebug() << "LayerItem::setCagePoint(): index =" << idx << ", pos =(" << pos.x() << ":" << pos.y() << ")..."; { QRectF bounds_before = QPolygonF(m_cageMesh.points()).boundingRect(); QPointF local = mapFromScene(pos); m_cageMesh.setPoint(idx, local); QRectF bounds_after = QPolygonF(m_cageMesh.points()).boundingRect(); qreal dx = bounds_after.x() - bounds_before.x(); qreal dy = bounds_after.y() - bounds_before.y(); m_cageMesh.addOffset(dx,dy); for ( int i = 0; i < m_handles.size(); ++i ) m_handles[i]->setPos(m_cageMesh.point(i)); m_cageMesh.relax(); update(); } } */ void LayerItem::commitCageTransform( const QVector &cage ) { qCDebug(logEditor) << "LayerItem::commitCageTransform(): Processing..."; { if ( m_cage.size() < 4 || m_originalCage.size() < 4 ) return; QTransform t; if ( QTransform::quadToQuad(m_originalCage.mid(0, 4),cage.mid(0, 4),t) ) { setTransform(t, false); } // Neue Basis für weitere Transformationen m_cage = cage; m_originalCage = m_cage; update(); } } void LayerItem::beginCageEdit() { qCDebug(logEditor) << "LayerItem::beginCageEdit(): Processing..."; { m_cageEditing = true; m_startPos = pos(); m_startTransform = transform(); } } void LayerItem::endCageEdit( int idx, const QPointF& startPos ) { qCDebug(logEditor) << "LayerItem::endCageEdit(): Processing..."; { QVector cage; for ( int i=0 ; ipush(new TransformLayerCommand(this, m_startPos, pos(), m_startTransform, transform())); } } LayerItem::OperationMode LayerItem::getPolygonOperationMode() { if ( m_parent != nullptr ) { MainWindow* parent = dynamic_cast(m_parent); if ( parent != nullptr ) { return parent->getViewer()->getPolygonOperationMode(); } } return LayerItem::OperationMode::None; } void LayerItem::setPolygonOperationMode( OperationMode mode ) { if ( m_polygonOperationMode == mode ) return; m_polygonOperationMode = mode; } void LayerItem::setOperationMode( OperationMode mode ) { qCDebug(logEditor) << "LayerItem::setOperationMode(): index=" << m_index << ", mode=" << m_operationMode << "|" << mode; { if ( m_operationMode == mode ) return; if ( m_operationMode == OperationMode::CageWarp ) { setCageVisible(false); } m_operationMode = mode; } } void LayerItem::setRotationAngle( double angleDelta ) { qCDebug(logEditor) << "LayerItem::setRotationAngle(): rotationAngle =" << angleDelta; { QString name = "Rotate Layer"; TransformLayerCommand::LayerTransformType trafoType = TransformLayerCommand::LayerTransformType::Rotate; name += QString(" %1").arg(m_index); QPointF c = boundingRect().center(); QTransform t; t.translate(c.x(), c.y()); t.rotate(angleDelta); t.translate(-c.x(), -c.y()); m_undoStack->push( new TransformLayerCommand(this, m_startPos, pos(), m_currentRotation, m_startTransform, t, name, trafoType) ); } } // ------------------------ Mouse events ------------------------ bool LayerItem::isValidMouseEventOperation() { if ( m_parent != nullptr ) { MainWindow* parent = dynamic_cast(m_parent); MainWindow::MainOperationMode opMode = parent != nullptr ? parent->getOperationMode(): MainWindow::MainOperationMode::None; return opMode == MainWindow::MainOperationMode::ImageLayer ? true : false; } return false; } void LayerItem::mouseDoubleClickEvent( QGraphicsSceneMouseEvent* event ) { qCDebug(logEditor) << "LayerItem::mouseDoubleClickEvent(): index=" << m_index << ", mode=" << m_operationMode; { if ( isValidMouseEventOperation() ) { if ( m_operationMode == OperationMode::CageWarp ) { if ( m_cageEnabled == false ) { if ( m_parent != nullptr ) { MainWindow* parent = dynamic_cast(m_parent); if ( parent && parent->getViewer() != nullptr ) { parent->getViewer()->setActiveCageLayer(this); int nCagePoints = m_cageMesh.rows(); enableCage(nCagePoints,nCagePoints); return; } } // enableCage(3,3); // CLAUDE -- why???? } else { setCageVisible(false); } } else if ( m_operationMode == OperationMode::Perspective ) { qCDebug(logEditor) << " + perpective call +"; } else if ( m_operationMode == OperationMode::Flip ) { if ( IMainSystem::instance()->getLayerOperationParameter(LayerItem::OperationMode::Flip) > 0 ) { m_undoStack->push(new MirrorLayerCommand(this, m_index, 1)); } else { m_undoStack->push(new MirrorLayerCommand(this, m_index, 2)); } } } } } void LayerItem::mousePressEvent( QGraphicsSceneMouseEvent* event ) { qCDebug(logEditor) << "LayerItem::mousePressEvent(): layer =" << name() << ", zValue = " << zValue() << ", operationMode =" << m_operationMode << ", active=" << m_mouseOperationActive; { // qDebug() << " selectedLayerItemName =" << IMainSystem::instance()->getSelectedLayerItemName(); // if ( IMainSystem::instance()->getSelectedLayerItemName() != name() ) { // event->ignore(); // return; // } if ( event->button() != Qt::LeftButton ) { QGraphicsPixmapItem::mousePressEvent(event); return; } MainWindow* parent = m_parent != nullptr ? dynamic_cast(m_parent) : nullptr; if ( isValidMouseEventOperation() && parent != nullptr ) { if ( m_operationMode == OperationMode::Translate ) { m_startPos = pos(); m_mouseOperationActive = true; m_startTransform = transform(); QGraphicsPixmapItem::mousePressEvent(event); } else if ( m_operationMode == OperationMode::Rotate ) { m_startLayerRotation = m_currentRotation; m_mouseOperationActive = true; m_startPos = pos(); QGraphicsPixmapItem::mousePressEvent(event); } else { // m_operationMode = OperationMode::None; m_mouseOperationActive = false; } } } } void LayerItem::mouseMoveEvent( QGraphicsSceneMouseEvent* event ) { qCDebug(logEditor) << "LayerItem::mouseMoveEvent(): mode =" << m_operationMode; { if ( m_operationMode == OperationMode::Translate ) { QGraphicsPixmapItem::mouseMoveEvent(event); return; } MainWindow* parent = m_parent != nullptr ? dynamic_cast(m_parent) : nullptr; if ( m_operationMode == OperationMode::Rotate ) { QPointF delta = event->scenePos() - event->buttonDownScenePos(Qt::LeftButton); double angleDelta = m_startLayerRotation + delta.x()/20.0 - m_currentRotation; m_currentRotation += angleDelta; setRotationAngle(angleDelta); parent->updateLayerOperationParameter(LayerItem::OperationMode::Rotate,m_currentRotation); event->accept(); } } } void LayerItem::mouseReleaseEvent( QGraphicsSceneMouseEvent* event ) { qCDebug(logEditor) << "LayerItem::mouseReleaseEvent(): index =" << m_index << ", name =" << name(); { if ( !m_undoStack ) { QGraphicsPixmapItem::mouseReleaseEvent(event); return; } if ( m_operationMode == OperationMode::Translate ) { if ( pos() != m_startPos ) { m_undoStack->push( new MoveLayerCommand(this, m_startPos, pos(), m_index) ); } } else if ( m_operationMode == OperationMode::Rotate ) { /* if ( transform() != m_startTransform ) { QString name = "Rotate Layer"; TransformLayerCommand::LayerTransformType trafoType = TransformLayerCommand::LayerTransformType::Rotate; name += QString(" %1").arg(m_index); TransformLayerCommand *layerCommand = new TransformLayerCommand(this, m_startPos, pos(), m_startTransform, transform(), name, trafoType); m_undoStack->push(layerCommand); } */ } // m_operationMode = None; QGraphicsPixmapItem::mouseReleaseEvent(event); } } // ------------------------ Handles ------------------------ void LayerItem::updateHandles() { qCDebug(logEditor) << "LayerItem::updateHandles(): Processing..."; { qDeleteAll(m_handles); m_handles.clear(); if ( !isSelected() ) return; QRectF r = boundingRect(); auto addScale = [&](QPointF p) { auto* h = new TransformHandleItem(this, TransformHandleItem::Scale); h->setParentItem(this); h->setPos(p); m_handles << h; }; addScale(r.topLeft()); addScale(r.topRight()); addScale(r.bottomLeft()); addScale(r.bottomRight()); auto* rot = new TransformHandleItem(this, TransformHandleItem::Rotate); rot->setParentItem(this); rot->setPos(QPointF(r.center().x(), r.top() - 30)); m_handles << rot; } } // ----- void LayerItem::applyPerspectiveQuad( const QVector& quad ) { qCDebug(logEditor) << "LayerItem::applyPerspectiveQuad(): Processing..."; { QRectF r = boundingRect(); QVector src = { r.topLeft(), r.topRight(), r.bottomRight(), r.bottomLeft() }; QTransform warp; if ( QTransform::quadToQuad(src, quad, warp) ) { this->setTransform(warp); } } }