/* * 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 "ImageView.h" #include "MainWindow.h" #include "../core/Config.h" #include "../layer/LayerItem.h" #include "../layer/MaskLayerItem.h" #include "../layer/EditablePolygon.h" #include "../layer/EditablePolygonItem.h" #include "../layer/TransformOverlay.h" #include "../undo/EditablePolygonCommand.h" #include "../undo/PaintCommand.h" #include "../undo/CageWarpCommand.h" #include "../undo/MaskStrokeCommand.h" #include "../undo/PaintStrokeCommand.h" #include "../undo/InvertLayerCommand.h" #include "../undo/DeleteLayerCommand.h" #include "../undo/LassoCutCommand.h" #include "../util/GeometryUtils.h" #include "../util/QUndoSortDialog.h" #include "../util/QImageUtils.h" #include "../util/MaskUtils.h" #include #include #include #include #include #include #include #include #include #include /* ============================================================ * Helper * ============================================================ */ static qreal pointToSegmentDist( const QPointF& p, const QPointF& a, const QPointF& b ) { const QPointF ab = b - a; const qreal t = std::clamp( QPointF::dotProduct(p - a, ab) / QPointF::dotProduct(ab, ab), 0.0, 1.0 ); const QPointF proj = a + t * ab; return QLineF(p, proj).length(); } static qreal distanceToPolygon( const QPointF& p, const QPolygonF& poly ) { qreal minDist = std::numeric_limits::max(); for ( int i = 0; i < poly.size(); ++i ) { QPointF a = poly[i]; QPointF b = poly[(i + 1) % poly.size()]; minDist = std::min(minDist, pointToSegmentDist(p, a, b)); } return minDist; } /* ============================================================ * Constructor * ============================================================ */ ImageView::ImageView( QWidget* parent ) : QGraphicsView(parent), m_parent(parent) { qCDebug(logEditor) << "ImageView::ImageView(): Processing..."; { m_crosshairVisible = EditorStyle::instance().crosshair(); m_selectedLayer = nullptr; m_selectedCageLayer = nullptr; m_scene = new QGraphicsScene(this); m_scene->setItemIndexMethod(QGraphicsScene::NoIndex); // Hack to prevent crash // setup setMouseTracking(true); setRenderHint(QPainter::Antialiasing); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); setDragMode(QGraphicsView::NoDrag); m_undoStack = new QUndoStack(this); connect(m_undoStack, &QUndoStack::cleanChanged, this, [this](bool isClean){ setWindowModified(!isClean); setWindowTitle(QString("Mein Editor[*]")); }); // --- history control (only stable for step-by-step processing) --- connect(m_undoStack, &QUndoStack::indexChanged, this, [this](int currentIndex) { // *** have to go to all commands between last and current index *** qCDebug(logEditor) << "ImageView::ImageView(): lastIndex =" << m_lastIndex << ", currentIndex =" << currentIndex; bool perspectiveCommandWasUndone = false; if ( currentIndex < m_lastIndex ) { for ( int i = currentIndex; i < m_lastIndex && i < m_undoStack->count(); ++i ) { const QUndoCommand* undoneCommand = m_undoStack->command(i); if ( undoneCommand != nullptr && undoneCommand->text().startsWith("Perspective Warp") ) { perspectiveCommandWasUndone = true; break; } } } if ( perspectiveCommandWasUndone ) { disablePerspectiveWarp(); if ( MainWindow *mainWindow = dynamic_cast(m_parent) ) { mainWindow->setLayerOperationMode(LayerItem::OperationMode::Translate); } setLayerOperationMode(LayerItem::OperationMode::Translate); } if ( currentIndex == 0 ) { if ( auto *ms = IMainSystem::instance() ) { IMainSystem::instance()->showMessage(QString("Initial state")); } } else if ( currentIndex > 0 ) { const QUndoCommand* justFinishedCommand = m_undoStack->command(currentIndex - 1); if ( justFinishedCommand != nullptr ) { qCDebug(logEditor) << "Just finished command :" << justFinishedCommand->text() << ", currentIndex =" << currentIndex; if ( currentIndex > 1 ) { // qDebug() << " + lastIndex =" << m_lastIndex << " -> " << (currentIndex-1); int ks = m_lastIndex == 0 ? m_lastIndex : m_lastIndex-1; int ke = m_lastIndex == 0 ? currentIndex - 1 : currentIndex - 2; for ( int k = ks ; k <= ke ; k++ ) { const QUndoCommand* previousActiveCommand = m_undoStack->command(k); if ( previousActiveCommand != nullptr ) { if ( previousActiveCommand->text().startsWith("Scale") ) { // qDebug() << " *** cleaning scale operation here ***"; if ( m_transformOverlay != nullptr ) { m_transformOverlay->setVisible(false); // cleanupCommand(previousActiveCommand); } } else if ( previousActiveCommand->text().startsWith("Cage Warp") ) { // CLAUDE says: This is very tricky here. This attempts, here, // to end the Cage Warp on the previous command on the Stack to // hide the cage. However, there is confusion on how to select // this previous layer if the new command is itself a Cage. for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if( layer ) { // make invisible if there is a cage but no warp (not active) if( !layer->isCageWarp() && layer->hasActiveCage() ) { layer->setCageVisible(5,false); } } } // LayerItem *layer = getSelectedItem(true); // if ( layer != nullptr ) layer->setCageVisible(false); } else if ( previousActiveCommand->text().startsWith("Perspective Warp") ) { if ( !justFinishedCommand->text().startsWith("Perspective") ) { disablePerspectiveWarp(); } } else if ( previousActiveCommand->text().startsWith("Editable Polygon") ) { EditablePolygonCommand *polyCommand = const_cast( dynamic_cast(previousActiveCommand)); if ( polyCommand != nullptr ) { polyCommand->setVisible(false); if ( polyCommand->layer() != nullptr ) { polyCommand->layer()->update(); } } } } } } // >>> MainWindow *mainWindow = dynamic_cast(m_parent); int layerId = -1; QRegularExpression re("(\\d+)"); QRegularExpressionMatch match = re.match(justFinishedCommand->text()); if ( match.hasMatch() && mainWindow != nullptr ) { if ( !justFinishedCommand->text().startsWith("Editable Polygon") ) { layerId = match.captured(1).toInt(); setSelectedLayer(2,QString("Layer %1").arg(layerId)); } } if ( justFinishedCommand->text().startsWith("Scale") ) { // qDebug() << " *** handle scale transform operation ***"; mainWindow->setLayerOperationMode(LayerItem::OperationMode::Scale); if ( m_transformOverlay != nullptr ) { m_transformOverlay->setVisible(true); } } else if ( justFinishedCommand->text().startsWith("Delete Layer") ) { if ( mainWindow != nullptr ) { mainWindow->updateLayerList(); } } else if ( justFinishedCommand->text().startsWith("Editable Polygon") ) { qDebug() << " *** handle editable polygon operation ***"; mainWindow->setMainOperationMode(MainWindow::MainOperationMode::Polygon); EditablePolygonCommand *polyCommand = const_cast( dynamic_cast(justFinishedCommand)); if ( polyCommand != nullptr ) { polyCommand->setVisible(true); if ( polyCommand->layer() != nullptr ) { mainWindow->activePolygon(polyCommand->name()); polyCommand->layer()->update(); } } } else if ( justFinishedCommand->text().startsWith("Polygon") && justFinishedCommand->text().endsWith("Cut")) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); } else if ( justFinishedCommand->text().startsWith("Move Layer") ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::Translate); } else if ( justFinishedCommand->text().startsWith("Rotate Layer") ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::Rotate); } else if ( justFinishedCommand->text().startsWith("Mirror Vertical") ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::Flip); } else if ( justFinishedCommand->text().startsWith("Mirror Horizontal") ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::Flop); } else if ( justFinishedCommand->text().startsWith("Perspective") ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::Perspective); setLayerOperationMode(LayerItem::OperationMode::Perspective); setOverlayVisibility(2,true); } else if ( justFinishedCommand->text().startsWith("Cage") ) { // qDebug() << "Just finished Cage command: " << justFinishedCommand->text(); mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); mainWindow->setLayerOperationMode(LayerItem::OperationMode::CageWarp); setOnlyCageVisible(layerId); } // Going-back in undo stack actions const QUndoCommand* previousCommand = m_undoStack->command(m_lastIndex-1); if ( previousCommand != nullptr ) { if ( previousCommand->text().startsWith("Delete Layer") ) { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { mainWindow->updateLayerList(); } } } } } m_lastIndex = currentIndex; }); /** setDragMode(QGraphicsView::NoDrag); setMouseTracking(true); setScene(new QGraphicsScene(this)); setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); */ } } // ------------------------ Self info ------------------------------------- void ImageView::printself() { qInfo() << " ImageView::printself():"; qInfo() << " + Pixmap items in scene:" << getScene()->items().count(); qInfo() << " + nEditablePolygons:" << m_editablePolygons.size(); qInfo() << " + undoStackSize:" << m_undoStack->count(); auto items = getScene()->items(); for ( QGraphicsItem* item : items ) { if ( auto pixmapItem = qgraphicsitem_cast(item) ) { qInfo() << " + Pixmap found: Size =" << pixmapItem->pixmap().size() << ", position =" << item->pos() << ", visible =" << item->isVisible(); } else if ( auto polyItem = qgraphicsitem_cast(item) ) { qInfo() << " + Polygon found with " << polyItem->polygon().size() << "points, " << ", visible =" << item->isVisible(); } else if ( auto lineItem = qgraphicsitem_cast(item) ) { qInfo() << " + Line found"; } else if ( auto ellipseItem = qgraphicsitem_cast(item) ) { QGraphicsEllipseItem* ellipse = qgraphicsitem_cast(item); if ( ellipse ) { qInfo() << " + Ellipse found: rect =" << ellipse->rect() << ", position =" << item->pos() << ", visible =" << item->isVisible(); } } else if ( auto pathItem = qgraphicsitem_cast(item) ) { qInfo() << " + Path found"; } else if ( auto rectItem = qgraphicsitem_cast(item) ) { qInfo() << " + Rect found"; } else if ( auto textItem = qgraphicsitem_cast(item) ) { qInfo() << " + Text found"; } else if ( item->type() == 65536 ) { QGraphicsObject* obj = item->toGraphicsObject(); if ( obj ) { QString name = obj->objectName().isEmpty() ? "none" : obj->objectName(); const char* className = typeid(*obj).name(); qInfo() << " + QGraphicsObject (65536):" << " name =" << name << ", class (mangled) = " << className << ", position =" << obj->pos() << ", visible =" << item->isVisible(); } } else { qInfo() << " + Unknown item found: " << item->type(); } } } // ------------------------ Update ------------------------------------- void ImageView::forcedUpdate() { qCDebug(logEditor) << "ImageView::forcedUpdate(): Processing..."; { if ( m_selectedLayer != nullptr ) { m_selectedLayer->setCageVisible(6,false); } else { qInfo() << " - no selected layer found"; } } } // ------------------------ Setter ------------------------------------- void ImageView::setCrosshairVisible( bool visible ) { m_crosshairVisible = visible; viewport()->update(); } void ImageView::setLassoEnabled( bool enabled ) { m_lassoEnabled = enabled; if ( !enabled && m_lassoPreview ) { m_scene->removeItem(m_lassoPreview); delete m_lassoPreview; m_lassoPreview = nullptr; m_lassoPolygon.clear(); } viewport()->update(); } void ImageView::setImage( const QImage& img ) { m_image = img.convertToFormat(QImage::Format_ARGB32); QVector lut(256); for ( int i=0 ; i<256 ; i++ ) lut[i] = qRgb(i,i,i); setColorTable(lut); } // ------------------------ UndoStack ------------------------------------- void ImageView::rebuildUndoStack() { if ( !m_undoStack || m_layers.size() == 0 ) return; qCDebug(logEditor) << "ImageView::rebuildUndoStack(): undoStack =" << m_undoStack->count(); { QList possible_ids; for ( int i=0 ; iid()); } bool ok; auto settings = QUndoSortDialog::getUserOptions(possible_ids, &ok, this); if ( !ok ) return; // preparing struct CommandEntry { const QUndoCommand* cmd; int id; int originalIndex; }; QList sortedCommands; QList sortedLayerIdents; std::vector entries; for ( int i=0; i < m_undoStack->count(); ++i ) { const QUndoCommand* base = m_undoStack->command(i); auto* cmd = dynamic_cast(base); if ( cmd != nullptr ) { const EditablePolygonCommand *editablePolygon = dynamic_cast(cmd); if ( editablePolygon != nullptr ) { int id = editablePolygon->childLayerId(); entries.push_back({base, id, i}); } else { if ( cmd->layer() ) { entries.push_back({base, cmd->layer()->id(), i}); } else { qCDebug(logEditor) << " + found a none abstract command"; } } } } // output for ( int i=0; i < entries.size(); ++i ) { qDebug() << " + name = " << entries[i].cmd->text() << ": id =" << entries[i].id << ", originalIndex =" << entries[i].originalIndex; } // sort method if ( settings.sortMode == QUndoSortDialog::SortMode::Ascending ) { // ascending sorting order std::stable_sort(entries.begin(), entries.end(), [](const CommandEntry& a, const CommandEntry& b) { if ( a.id != b.id ) { return a.id < b.id; // Kleinerer Wert kommt nach vorne } return false; }); } else if ( settings.sortMode == QUndoSortDialog::SortMode::Descending ) { // descending sorting order std::stable_sort(entries.begin(), entries.end(), [](const CommandEntry& a, const CommandEntry& b) { if ( a.id != b.id ) { return a.id > b.id; // Kleinerer Wert kommt nach vorne } return false; }); } else if ( settings.sortMode == QUndoSortDialog::SortMode::IndexAtEnd ) { // put a given index (=1) to the end int targetIdToEnd = settings.targetIndex; std::stable_sort(entries.begin(), entries.end(), [targetIdToEnd](const CommandEntry& a, const CommandEntry& b) { if (a.id == targetIdToEnd && b.id != targetIdToEnd) return false; if (a.id != targetIdToEnd && b.id == targetIdToEnd) return true; return a.id < b.id; }); } else { return; } // finalize for ( const auto& entry : entries ) { sortedLayerIdents.append(entry.originalIndex); } for ( int i = 0; i < sortedLayerIdents.count(); ++i ) { const QUndoCommand* base = m_undoStack->command(sortedLayerIdents[i]); auto* cmd = dynamic_cast(base); if ( cmd && cmd->layer() ) { qDebug() << " cloning " << cmd->text(); sortedCommands.append(cmd->clone()); } } m_undoStack->clear(); for ( auto* base : sortedCommands ) { auto* cmd = dynamic_cast(base); if ( cmd ) { // cmd->setSilent(true); m_undoStack->push(cmd); // cmd->setSilent(false); } } // re-run the complete undo stack m_undoStack->setIndex(0); m_undoStack->setIndex(m_undoStack->count()); } } // ------------------------ --- ------------------------------------- // need a re-build of the undostack stack if only the commands of layer N are involved // ------------------------ --- ------------------------------------- void ImageView::removeOperationsByIndexUndoStack(const QString& name, int index) { qDebug() << "ImageView::removeOperationsByIndexUndoStack(): name =" << name << ", index =" << index; { if ( !m_undoStack ) { return; } if ( index < 0 || index > m_undoStack->count() ) { return; } std::vector> commandsToReplay; commandsToReplay.reserve(index); for ( int i = 0; i < index; ++i ) { const auto* cmd = dynamic_cast(m_undoStack->command(i)); if (!cmd) { qWarning() << "ImageView::removeOperationsByIdUndoStack(): Cannot rebuild undo stack: command is not AbstractCommand at index" << i; return; } auto* clone = cmd->clone(); if ( !clone ) { qWarning() << "ImageView::removeOperationsByIdUndoStack(): Cannot rebuild undo stack: clone failed at index" << i; return; } commandsToReplay.emplace_back(clone); } // Roll document state back to the base state through QUndoStack. m_undoStack->setIndex(0); m_undoStack->clear(); // Reconstruct document state and history up to index. // push() calls redo(); that is intended here because we start from index 0. for ( auto& command : commandsToReplay ) { m_undoStack->push(command.release()); } } } // ------------------------ Revoke delete layer ------------------------------------- void ImageView::removeOperationsByIdUndoStack( int layerId ) { qDebug() << "ImageView::removeOperationsByIdUndoStack(): layerId =" << layerId; { if ( !m_undoStack ) { return; } const int oldIndex = m_undoStack->index(); std::vector> commandsToReplay; commandsToReplay.reserve(oldIndex); // Clone only commands that are currently applied. // Commands after oldIndex are redo history and are intentionally discarded. for ( int i = 0; i < oldIndex; ++i ) { const auto* cmd = dynamic_cast(m_undoStack->command(i)); if ( !cmd ) { qWarning() << "ImageView::removeOperationsByIdUndoStack(): Cannot rebuild undo stack: command is not AbstractCommand at index" << i; return; } const auto* layer = cmd->layer(); if ( layer && layer->id() == layerId ) { continue; } auto* cloned = cmd->clone(); if ( !cloned ) { qWarning() << "ImageView::removeOperationsByIdUndoStack(): Cannot rebuild undo stack: clone failed at index" << i; return; } qDebug() << cloned->type(); commandsToReplay.emplace_back(cloned); } // Undo through QUndoStack, not by calling QUndoCommand::undo() directly. // This keeps the stack index and Qt's internal state consistent. m_undoStack->setIndex(0); // Now it is safe to delete the old command objects. m_undoStack->clear(); // Mark layer as deleted before replaying remaining commands. for ( auto* layer : m_layers ) { if ( layer && layer->id() == layerId ) { layer->m_deleted = true; break; } } // Rebuild the stack. push() calls redo(), which is intended here: // we are reconstructing the current document state from the base state. for ( auto& command : commandsToReplay ) { m_undoStack->push(command.release()); } } } // ------------------------ Mask layer ------------------------------------- void ImageView::createMaskLayer( const QSize& size ) { qCDebug(logEditor) << "ImageView::createMaskLayer(): size =" << size; { // delete old mask if ( m_maskItem ) { QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "Please confirm operation", "A label mask already exists. Are you sure you want to create a new one? The old data will be completely deleted?", QMessageBox::Yes | QMessageBox::No); if ( reply == QMessageBox::Yes ) { scene()->removeItem(m_maskItem); delete m_maskItem; m_maskItem = nullptr; } else { return; } } delete m_maskLayer; m_maskLayer = nullptr; // --- Mask data --- m_maskLayer = new MaskLayer(size); // --- Mask overlay --- m_maskItem = new MaskLayerItem(m_maskLayer); m_maskItem->setZValue(1000); m_maskItem->setOpacityFactor(0.4); m_maskItem->setLabelColors(defaultMaskColors()); scene()->addItem(m_maskItem); } } void ImageView::saveMaskImage( const QString& filename ) { qCDebug(logEditor) << "ImageView::saveMaskImage(): filename =" << filename; { if ( m_maskLayer != nullptr ) { // --- save index class file --- QImage indexedImage = m_maskLayer->image().convertToFormat(QImage::Format_Indexed8); QList colorTable; colorTable.reserve(256); QVector maskColors = defaultMaskColors(); for ( const QColor &c : maskColors ) { colorTable.append(c.rgb()); } for ( int i=maskColors.size() ; i < 256 ; ++i ) { colorTable.append(qRgb(i, 255 - i, 0)); } indexedImage.setColorTable(colorTable); indexedImage.save(filename,"PNG"); // --- save index file --- QFileInfo info(filename); QString ext = info.suffix().toLower(); if ( ext == "png" || ext == "mnc" ) { QString infoFileName = info.path() + "/" + info.completeBaseName() + ".json"; QJsonObject mainObject; QHashIterator i(m_maskLabelTypeNames); while ( i.hasNext() ) { i.next(); mainObject.insert(i.key(),static_cast(i.value())); } QJsonDocument doc(mainObject); QFile file(infoFileName); if ( file.open(QIODevice::WriteOnly) ) { file.write(doc.toJson(QJsonDocument::Indented)); file.close(); } } } } } void ImageView::loadMaskImage( const QString& filename ) { qCDebug(logEditor)<< "ImageView::loadMaskImage(): maskfile =" << filename; { // --- load mask image --- QImage img(filename); if ( img.isNull() ) { qInfo() << "Error: Could not load image!"; return; } QSize size = baseLayer()->image().size(); if ( img.size() != size ) { qInfo() << "Error: Size mismatch could not load file!"; return; } if ( m_maskLayer != nullptr ) { QMessageBox::StandardButton reply; reply = QMessageBox::question(this, "Please confirm operation", "A label mask already exists. Are you sure you want to load a new one? The old data will be lost?", QMessageBox::Yes | QMessageBox::No); if ( reply == QMessageBox::No ) { return; } } else { m_maskLayer = new MaskLayer(size); m_maskItem = new MaskLayerItem(m_maskLayer); m_maskItem->setZValue(1000); m_maskItem->setOpacityFactor(0.4); scene()->addItem(m_maskItem); } if ( img.format() != QImage::Format_Indexed8 ) { m_maskLayer->setImage(img.convertToFormat(QImage::Format_Grayscale8)); } else { QImage grayImg(img.size(),QImage::Format_Grayscale8); for ( int y = 0; y < img.height(); ++y ) { const uchar* srcLine = img.scanLine(y); uchar* dstLine = grayImg.scanLine(y); memcpy(dstLine, srcLine, img.width()); } m_maskLayer->setImage(grayImg); } // --- load json file --- QFileInfo info(filename); QString infoFileName = info.path() + "/" + info.completeBaseName() + ".json"; QFile file(infoFileName); if ( file.open(QIODevice::ReadOnly) ) { QByteArray data = file.readAll(); file.close(); QJsonDocument doc = QJsonDocument::fromJson(data); if ( !doc.isObject() ) { qWarning() << "Error: Invalid JSON format!"; return; } QJsonObject mainObject = doc.object(); m_maskLabelTypeNames.clear(); for ( auto it = mainObject.begin(); it != mainObject.end(); ++it ) { QString key = it.key(); MaskCutTool toolValue = static_cast(it.value().toInt()); m_maskLabelTypeNames.insert(key, toolValue); } } // --- update --- viewport()->update(); } } void ImageView::setMaskTool( MaskTool t ) { m_maskTool = t == m_maskTool ? MaskTool::None : t; if ( m_maskTool != MaskTool::None && m_maskLayer == nullptr ) { createMaskLayer(baseLayer()->image().size()); } } void ImageView::setMaskCutTool( const QString &maskLabelName, MaskCutTool t ) { m_maskCutTool = t == m_maskCutTool ? MaskCutTool::Ignore : t; m_maskLabelTypeNames[maskLabelName] = t; } int ImageView::getMaskCutToolType( const QString& name ) { qCDebug(logEditor) << "ImageView::getMaskCutToolType(): name =" << name; { if ( m_maskLabelTypeNames.contains(name) ) { return m_maskLabelTypeNames[name]; } m_maskLabelTypeNames[name] = MaskCutTool::Ignore; return 0; } } // ------------------------ Layer tools ------------------------------------- void ImageView::centerOnLayer( Layer* layer ) { if ( !layer || !layer->m_item ) return; centerOn(layer->m_item); // QGraphicsView::centerOn } LayerItem* ImageView::currentLayer() const { auto items = m_scene->items(Qt::DescendingOrder); for ( QGraphicsItem* item : items ) { if ( auto* layer = dynamic_cast(item) ) return layer; } return nullptr; } void ImageView::deleteLayer( Layer* layer ) { qDebug() << "ImageView::deleteLayer(): Processing..."; { if ( layer == nullptr || layer->m_item == nullptr ) return; LayerItem *imageLayer = dynamic_cast(layer->m_item); m_undoStack->push(new DeleteLayerCommand(imageLayer,imageLayer->pos(),layer->id())); } } void ImageView::setSelectedLayer( int caller, LayerItem* layer ) { m_selectedLayer = layer; } void ImageView::setSelectedLayer( int caller, const QString &name ) { qCDebug(logEditor) << "ImageView::setSelectedLayer(" << caller << "): name =" << name; { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow == nullptr ) return; mainWindow->setSelectedLayer(3,name); for ( auto* item : m_scene->items() ) { auto* layer = dynamic_cast(item); if ( layer && !layer->name().startsWith("Main") ) { bool isSelected = layer->name().endsWith(name) ? true : false; layer->setIsSelected(1,isSelected); } } } } void ImageView::setActiveLayer( const QString &name, bool initialize ) { qCDebug(logEditor) << "ImageView::setActiveLayer(): name =" << name << ", initialize =" << initialize; { for ( auto* item : m_scene->items() ) { auto* layer = dynamic_cast(item); if ( layer && !layer->name().startsWith("Main") ) { bool isSelected = name == layer->name() ? true : false; layer->setIsSelected(2,isSelected); if ( isSelected ) { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { mainWindow->setSelectedLayer(6,QString("Layer %1").arg(layer->id()),initialize); mainWindow->updateLayerOperationParameter(LayerItem::OperationMode::Rotate,layer->getRotationAngle()); if ( initialize ) { mainWindow->setLayerOperationMode(LayerItem::OperationMode::Translate); } } } } } } } // ------------------------ Colortable tools ------------------------------------- void ImageView::setColorTable( const QVector &lut ) { LayerItem* layer = currentLayer(); if ( !layer ) return; m_undoStack->push(new InvertLayerCommand(layer,lut)); } void ImageView::enablePipette( bool enabled ) { m_pipette = enabled; setCursor(enabled ? Qt::CrossCursor : Qt::ArrowCursor); } // ------------------------ ------------------------ ------------------------ // ------------------------ Key Event tools ------------------------ // ------------------------ ------------------------ ------------------------ void ImageView::keyPressEvent( QKeyEvent* event ) { qCDebug(logEditor) << "ImageView::keyPressEvent(): key =" << event->key(); { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { MainWindow::MainOperationMode opMode = mainWindow->getOperationMode(); // general if ( event->key() == Qt::Key_Tab ) { int isSelected = -1; int i = 0; QVector layers; for ( auto* item : m_scene->items() ) { auto* layer = dynamic_cast(item); if ( layer ) { if ( !layer->name().startsWith("Main") && layer->isVisible() ) { layers.push_back(layer); if ( layer->isSelected() ) { isSelected = i; } i += 1; } } } if ( layers.length() != 0 ) { m_oldVisibleLayerItemNum = -1; int index = 0; if ( layers.length() > 1 ) { index = isSelected == -1 ? 0 : (isSelected+1)%layers.length(); } QRegularExpression re("\\d+$"); LayerItem *selectedLayer = layers[index]; selectedLayer->setIsSelected(3,true); setOnlyCageVisible(selectedLayer->id()); QRegularExpressionMatch match = re.match(selectedLayer->name()); if ( match.hasMatch() ) { QString numberStr = match.captured(0); mainWindow->setSelectedLayer(4,QString("Layer %1").arg(numberStr.toInt())); QString geometryString = GeometryUtils::getGeometryString(selectedLayer); IMainSystem::instance()->showMessage(QString("Selected layer %1 with geometry %2").arg(selectedLayer->name()).arg(geometryString)); } } return; } else if ( event->key() == Qt::Key_Plus ) { scaleScene(+1); } else if ( event->key() == Qt::Key_Minus ) { scaleScene(-1); } // operation specific if ( opMode == MainWindow::MainOperationMode::Polygon ) { if ( m_polygonEnabled && ( event->key() == Qt::Key_Return || event->key() == Qt::Key_Escape ) ) { setPolygonEnabled(false); return; } else if ( event->modifiers() & Qt::ControlModifier ) { LayerItem::OperationMode polygonTransformMode = LayerItem::OperationMode::None; if ( event->key() == Qt::Key_A ) { polygonTransformMode = LayerItem::OperationMode::AddPoint; } else if ( event->key() == Qt::Key_D ) { polygonTransformMode = LayerItem::OperationMode::DeletePoint; } else if ( event->key() == Qt::Key_M ) { polygonTransformMode = LayerItem::OperationMode::MovePoint; } else if ( event->key() == Qt::Key_R ) { polygonTransformMode = LayerItem::OperationMode::ReducePolygon; } else if ( event->key() == Qt::Key_S ) { polygonTransformMode = LayerItem::OperationMode::SmoothPolygon; } else if ( event->key() == Qt::Key_T ) { polygonTransformMode = LayerItem::OperationMode::TranslatePolygon; } else { QGraphicsView::keyPressEvent(event); return; } mainWindow->setPolygonOperationMode(polygonTransformMode); setPolygonOperationMode(polygonTransformMode); } } else if ( opMode == MainWindow::MainOperationMode::ImageLayer ) { if ( event->modifiers() & Qt::AltModifier ) { if ( event->key() == Qt::Key_V ) { setLayerVisible(1); } else if ( event->key() == Qt::Key_I ) { setLayerVisible(2); } } else if ( event->modifiers() & Qt::ControlModifier ) { if ( event->key() == Qt::Key_V ) { setLayerVisible(1); } else if ( event->key() == Qt::Key_I ) { setLayerVisible(2); } else { LayerItem::OperationMode transformMode = LayerItem::OperationMode::None; if ( event->key() == Qt::Key_T ) { transformMode = LayerItem::OperationMode::Translate; } else if ( event->key() == Qt::Key_S ) { transformMode = LayerItem::OperationMode::Scale; } else if ( event->key() == Qt::Key_R ) { transformMode = LayerItem::OperationMode::Rotate; } else if ( event->key() == Qt::Key_M ) { transformMode = LayerItem::OperationMode::Flip; } else if ( event->key() == Qt::Key_W ) { transformMode = LayerItem::OperationMode::CageWarp; } else if ( event->key() == Qt::Key_P ) { transformMode = LayerItem::OperationMode::Perspective; } else if ( event->key() == Qt::Key_V ) { setLayerVisible(1); } else if ( event->key() == Qt::Key_I ) { setLayerVisible(2); } else { QGraphicsView::keyPressEvent(event); return; } mainWindow->setLayerOperationMode(transformMode); setLayerOperationMode(transformMode); } } else if ( m_layerOperationMode == LayerItem::OperationMode::Scale && m_transformOverlay != nullptr ) { if ( event->key() == Qt::Key_Q ) { disableTransformMode(); } else if ( event->key() == Qt::Key_R ) { m_transformOverlay->reset(); } } else if ( m_layerOperationMode == LayerItem::OperationMode::CageWarp ) { if ( event->key() == Qt::Key_Q ) { qDebug() << "ImageView::keyPressEvent(): CageWarp quit..."; } else if ( event->key() == Qt::Key_R ) { qDebug() << "ImageView::keyPressEvent(): CageWarp reset..."; } } else if ( m_layerOperationMode == LayerItem::OperationMode::Perspective ) { if ( event->key() == Qt::Key_Q ) { qDebug() << "ImageView::keyPressEvent(): Perspective quit..."; } else if ( event->key() == Qt::Key_R ) { qDebug() << "ImageView::keyPressEvent(): Perspective reset..."; } } } } QGraphicsView::keyPressEvent(event); } } // ------------------------ ------------------------ ------------------------ // ------------------------ Mouse Event tools ------------------------ // ------------------------ ------------------------ ------------------------ void ImageView::mousePressEvent( QMouseEvent* event ) { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow == nullptr ) return; qCDebug(logEditor) << "ImageView::mousePressEvent(): operationMode =" << mainWindow->getOperationMode() << ", polygonEnabled =" << m_polygonEnabled; { if ( !scene() ) return; if ( event->button() != Qt::LeftButton && event->button() != Qt::RightButton ) { QGraphicsView::mousePressEvent(event); return; } QPointF scenePos = mapToScene(event->pos()); m_cursorPos = scenePos; // --- Shift-Pan: Bild verschieben --- if ( event->modifiers() & Qt::ShiftModifier ) { m_panning = true; m_lastMousePos = event->pos(); setCursor(Qt::ClosedHandCursor); return; // kein weiteres Event behandeln } // --- Main window processing --- if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::ImageLayer && event->button() == Qt::LeftButton ) { const QList itemsUnderCursor = scene()->items(scenePos); LayerItem* clickedLayer = itemsUnderCursor.isEmpty() ? nullptr : dynamic_cast(itemsUnderCursor.first()); if ( clickedLayer && clickedLayer->getType() == LayerItem::MainImage ) { // qDebug() << "ImageView::mousePressEvent(): Processing MainImage click..."; // THIS WILL DESELECT ANY LAYER AND CAUSED A COUPLE OF PROBLEMS: // mainWindow->showMessage("Deselected layer"); // mainWindow->setSelectedLayer(5,""); event->accept(); return; } } // --- Pipette --- if ( m_pipette ) { qCDebug(logEditor) << "ImageView::mousePressEvent(): Pipette processing..."; auto itemsUnderCursor = scene()->items(scenePos); for ( auto* item : itemsUnderCursor ) { auto* layer = dynamic_cast(item); if ( !layer ) continue; QPoint localPos = layer->mapFromScene(scenePos).toPoint(); if ( !layer->image().rect().contains(localPos) ) continue; QColor color = layer->image().pixelColor(localPos); setBrushColor(color); emit pickedColorChanged(color); return; } } // --- Painting --- if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::Paint ) { qCDebug(logEditor) << "ImageView::mousePressEvent(): Paint processing..."; if ( m_paintToolEnabled ) { auto itemsUnderCursor = scene()->items(scenePos); for ( auto* item : itemsUnderCursor ) { auto* layer = dynamic_cast(item); if ( !layer ) continue; QPoint localPos = layer->mapFromScene(scenePos).toPoint(); if ( !layer->image().rect().contains(localPos) ) continue; m_painting = true; m_paintLayer = layer; m_currentStroke.clear(); m_currentStroke << localPos; //ALT: m_undoStack->push(new PaintStrokeCommand(layer, localPos, m_brushColor, m_brushRadius, m_brushHardness)); layer->updateOriginalImage(); layer->paintStrokeSegment(localPos,localPos,m_brushColor,m_brushRadius,m_brushHardness); viewport()->update(); break; } } } // --- Mask Painting --- if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::Mask ) { qCDebug(logEditor) << "ImageView::mousePressEvent(): Mask processing..."; if ( m_maskTool != MaskTool::None && (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) ) { // NEW: m_maskStrokeActive = true; // NEW: m_currentMaskStroke.clear(); m_maskPainting = true; m_maskStrokePoints.clear(); m_maskStrokePoints << mapToScene(event->pos()).toPoint(); return; } } // --- Free selection event --- if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::FreeSelection ) { qCDebug(logEditor) << "ImageView::mousePressEvent(): FreeSelection processing..."; if ( m_lassoEnabled ) { m_lassoPolygon.clear(); m_lassoPolygon << scenePos.toPoint(); if ( !m_lassoPreview ) { m_lassoPreview = scene()->addPolygon(QPolygonF(m_lassoPolygon)); m_lassoPreview->setZValue(1000); m_lassoPreview->setPen(QPen(EditorStyle::instance().lassoColor(), EditorStyle::instance().lassoWidth())); } else { m_lassoPreview->setPolygon(QPolygonF(m_lassoPolygon)); } QRectF br = QPolygonF(m_lassoPolygon).boundingRect(); if ( !m_lassoBoundingBox ) { QPen pen(Qt::green); pen.setWidth(0); // kosmetisch, 1px auf Bildschirm pen.setStyle(Qt::SolidLine); m_lassoBoundingBox = m_scene->addRect(br, pen); m_lassoBoundingBox->setZValue(1000); } else { m_lassoBoundingBox->setRect(br); } return; } } // --- polygon events --- if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::Polygon ) { qCDebug(logEditor) << "ImageView::mousePressEvent(): Polygon processing..."; // --- Add a new point to polygon --- if ( m_polygonEnabled && event->button() == Qt::LeftButton ) { if ( m_activePolygon != nullptr ) { m_activePolygon->addPoint(scenePos); return; } } } QGraphicsView::mousePressEvent(event); } } void ImageView::mouseDoubleClickEvent( QMouseEvent* event ) { qCDebug(logEditor) << "ImageView::mouseDoubleClickEvent(): Processing..."; { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::Polygon && ( m_polygonOperationMode == LayerItem::OperationMode::Select || event->modifiers() & Qt::ControlModifier ) ) { // check whether a polygon has been double clicked QPointF scenePos = mapToScene(event->pos()); EditablePolygonItem* clickedItem = nullptr; auto itemsUnderCursor = m_scene->items(scenePos); for ( auto* item : itemsUnderCursor ) { auto* editablePolygon = dynamic_cast(item); // EditablePolygonItem if ( editablePolygon != nullptr && editablePolygon->polygon() != nullptr ) { if ( editablePolygon->hitTestPolygon(scenePos) > 0 ) { editablePolygon->polygon()->setSelected(!editablePolygon->polygon()->isSelected()); if ( editablePolygon->polygon()->isSelected() ) { int index = mainWindow->activePolygon(editablePolygon->polygon()->name()); if ( index >= 0 ) { QVector colors = defaultMaskColors(); editablePolygon->setColor(colors[(index+1)%colors.length()]); } } else { editablePolygon->setColor(QColor(128,128,128)); } clickedItem = editablePolygon; break; } } } if ( clickedItem != nullptr ) { for ( auto* item : m_scene->items() ) { auto* editablePolygon = dynamic_cast(item); if ( editablePolygon && editablePolygon != clickedItem ) { editablePolygon->polygon()->setSelected(false); editablePolygon->setColor(QColor(128,128,128)); // best color for white or black background images ? } } } } else if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::ImageLayer && m_layerOperationMode == LayerItem::OperationMode::Scale ) { setEnableTransformMode(m_selectedLayer); } else if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::ImageLayer && m_layerOperationMode == LayerItem::OperationMode::Perspective ) { setEnablePerspectiveWarp(m_selectedLayer); } else if ( mainWindow->getOperationMode() == MainWindow::MainOperationMode::ImageLayer && m_layerOperationMode == LayerItem::OperationMode::CageWarp ) { m_selectedCageLayer = nullptr; } } QGraphicsView::mouseDoubleClickEvent(event); } } void ImageView::mouseMoveEvent( QMouseEvent* event ) { #if 0 // this statement prints too often during debugging and hides other statements. qCDebug(logEditor) << "ImageView::mouseMoveEvent(): m_painting =" << m_maskPainting << ", m_paintToolEnabled =" << m_paintToolEnabled; #endif { if ( !scene() ) return; QPointF scenePos = mapToScene(event->pos()); m_cursorPos = scenePos; emit cursorPositionChanged(int(scenePos.x()), int(scenePos.y())); emit scaleChanged(transform().m11()); // --- Cursor-Farbe unter Maus --- bool colorFound = false; auto itemsUnderCursor = scene()->items(scenePos); for ( auto* item : itemsUnderCursor ) { auto* layer = dynamic_cast(item); if ( !layer ) continue; QPoint layerPos = layer->mapFromScene(scenePos).toPoint(); if ( layer->image().rect().contains(layerPos) ) { QColor c = layer->image().pixelColor(layerPos); emit cursorColorChanged(c); colorFound = true; break; } } if ( !colorFound ) emit cursorColorChanged(Qt::transparent); // --- Shift-Pan: nur wenn Shift gedr?ckt & linke Maustaste --- // std::cout << "shift-pan: processing: shift=" << (event->modifiers() & Qt::ShiftModifier) << "..." << std::endl; if ( (event->modifiers() & Qt::ShiftModifier) && (event->buttons() & Qt::LeftButton) ) { QPoint delta = event->pos() - m_lastMousePos; horizontalScrollBar()->setValue(horizontalScrollBar()->value() - delta.x()); verticalScrollBar()->setValue(verticalScrollBar()->value() - delta.y()); m_lastMousePos = event->pos(); return; } // --- Painting --- if ( m_painting && m_paintToolEnabled ) { auto itemsUnderCursor = m_scene->items(scenePos); for ( auto* item : itemsUnderCursor ) { auto* layer = dynamic_cast(item); if ( !layer || !layer->isVisible() ) continue; QPoint localPos = layer->mapFromScene(scenePos).toPoint(); if ( !layer->image().rect().contains(localPos) ) continue; // PaintCommand jetzt auf Layer if ( m_currentStroke.isEmpty() || m_currentStroke.last() != localPos ) { m_currentStroke << localPos; layer->paintStrokeSegment(m_currentStroke[m_currentStroke.size()-2],localPos,m_brushColor,m_brushRadius,m_brushHardness); viewport()->update(); } //ALT: m_undoStack->push(new PaintStrokeCommand(layer, localPos, m_brushColor, m_brushRadius, m_brushHardness)); //ALT: viewport()->update(); break; // nur oberstes Layer malen } return; } // --- Mask Painting --- if ( m_maskPainting && (event->buttons() & Qt::LeftButton || event->buttons() & Qt::RightButton) ) { if ( m_maskLayer ) { // NEW: && m_maskStrokeActive // m_maskStrokePoints << mapToScene(event->pos()).toPoint(); // m_maskItem->update(); bool isRightButton = event->buttons() & Qt::RightButton ? true : false; int x = int(scenePos.x()); int y = int(scenePos.y()); if( !(x < 0 || y < 0 || x >= m_maskLayer->width() || y >= m_maskLayer->height()) ) { int r = m_maskBrushRadius; int rr = r*r; uchar newValue = isRightButton ? (m_maskTool == MaskTool::MaskErase ? m_currentMaskLabel : 0) : (m_maskTool == MaskTool::MaskErase ? 0 : m_currentMaskLabel); for( int dy = -r; dy <= r; ++dy ) { for( int dx = -r; dx <= r; ++dx ) { int px = x+dx; int py = y+dy; if( dx*dx + dy*dy > rr ) continue; // Kreis // NEW: uchar oldValue = m_maskLayer->pixel(x,y); // NEW: if ( oldValue == newValue ) continue; // NEW: m_currentMaskStroke.push_back({x,y,oldValue,newValue}); m_maskLayer->setPixel(px, py, newValue); } } m_maskItem->maskUpdated(); return; } } } // --- Lasso-Zeichnung --- if ( m_lassoEnabled && (event->buttons() & Qt::LeftButton) ) { m_lassoPolygon << scenePos.toPoint(); if ( !m_lassoPreview ) { m_lassoPreview = scene()->addPolygon(QPolygonF(m_lassoPolygon)); } else { m_lassoPreview->setPolygon(QPolygonF(m_lassoPolygon)); } QRectF br = QPolygonF(m_lassoPolygon).boundingRect(); if ( !m_lassoBoundingBox ) { QPen pen(Qt::green); pen.setWidth(0); // kosmetisch pen.setStyle(Qt::SolidLine); m_lassoBoundingBox = m_scene->addRect(br, pen); m_lassoBoundingBox->setZValue(1000); } else { m_lassoBoundingBox->setRect(br); } return; } // --- Freie Auswahl (Path) --- if ( m_selecting ) { m_selectionPath.lineTo(scenePos); viewport()->update(); return; } // --- Letzte Mausposition merken --- m_lastMousePos = event->pos(); viewport()->update(); // >>> QGraphicsView::mouseMoveEvent(event); } } void ImageView::mouseReleaseEvent( QMouseEvent* event ) { qCDebug(logEditor) << "ImageView::mouseReleaseEvent(): panning =" << m_panning << ", selectedCageLayer =" << ( m_selectedCageLayer == nullptr ? "null" : "ok" ); { if ( !scene() ) return; if ( m_panning == false ) { // --- Cage warp beenden --- LayerItem *selectedCageLayer = m_selectedCageLayer != nullptr ? m_selectedCageLayer : getSelectedItem(true); if ( event->button() == Qt::LeftButton && selectedCageLayer != nullptr ) { QVector cageAfter = selectedCageLayer->cageMesh().points(); QVector cageBefore = selectedCageLayer->cageMesh().originalPoints(); qCDebug(logEditor) << "ImageView::mouseReleaseEvent(): layer =" << selectedCageLayer->name() << ": cageAfter =" << cageAfter.size() << ", cageBefore =" << m_cageBefore.size(); if ( cageAfter != m_cageBefore ) { if ( selectedCageLayer->getCageWarpCommand() == nullptr ) { qCDebug(logEditor) << "ImageView::mouseReleaseEvent(): Creating new cage layer undo/redo instance..."; int rows = selectedCageLayer->cageMesh().rows(); int columns = selectedCageLayer->cageMesh().cols(); m_undoStack->push( new CageWarpCommand(selectedCageLayer, cageBefore, cageAfter, selectedCageLayer->sceneBoundingRect(), selectedCageLayer->pos(), rows, columns ) ); } else { selectedCageLayer->getCageWarpCommand()->pushNewWarpStep( selectedCageLayer->pos(), cageAfter ); } if( selectedCageLayer ) { selectedCageLayer->applyCageWarp("ImageView::1"); } } m_cageBefore.clear(); } // --- Lasso beenden --- if ( m_lassoEnabled && event->button() == Qt::LeftButton ) { if ( m_lassoPolygon.size() > 2 ) { createLassoLayer(); } // Polygon entfernen if ( m_lassoPreview ) { scene()->removeItem(m_lassoPreview); delete m_lassoPreview; m_lassoPreview = nullptr; } // Bounding Box entfernen if ( m_lassoBoundingBox ) { scene()->removeItem(m_lassoBoundingBox); delete m_lassoBoundingBox; m_lassoBoundingBox = nullptr; } m_lassoPolygon.clear(); return; } // --- Painting beenden --- if ( m_painting && event->button() == Qt::LeftButton ) { if ( m_currentStroke.size() > 1 ) { m_undoStack->push(new PaintStrokeCommand(m_paintLayer,m_currentStroke,m_brushColor, m_brushRadius,m_brushHardness)); } m_paintLayer = nullptr; m_currentStroke.clear(); m_painting = false; } } // --- Misc --- if ( event->button() == Qt::LeftButton ) { m_painting = false; // Shift-Pan beenden if ( m_panning ) { m_panning = false; setCursor(Qt::ArrowCursor); } // Freie Auswahl beenden if ( m_selecting ) { m_selecting = false; m_selectionPath.closeSubpath(); viewport()->update(); } } // --- Mask Painting beenden --- if ( m_maskPainting && (event->button() == Qt::LeftButton || event->button() == Qt::RightButton) ) { m_maskStrokeActive = false; m_maskPainting = false; // NEW: if ( !m_currentMaskStroke.isEmpty() ) { m_undoStack->push( new MaskPaintCommand( m_maskLayer, std::move(m_currentMaskStroke) ) ); } /* **** crash **** quint8 label = m_maskEraser ? 0 : m_currentMaskLabel; m_undoStack->push( new MaskStrokeCommand( m_maskLayer, m_maskStrokePoints, label, m_brushRadius ) ); */ return; } QGraphicsView::mouseReleaseEvent(event); } } // IMPORTANT: Security-Check: Prevents extrem small zoom values (potential crash source!) // If newScale will be too small (e.g. < 0.001), the BSP-Tree crashed. void ImageView::wheelEvent( QWheelEvent* event ) { if ( ImageView::scaleScene(event->angleDelta().y()>0?+1:-1) ) { event->accept(); return; } event->ignore(); } bool ImageView::scaleScene( int direction ) { if ( scene() && !scene()->items().isEmpty() ) { constexpr qreal zoomFactor = 1.15; qreal factor = direction > 0 ? zoomFactor : 1.0 / zoomFactor; qreal currentScale = transform().m11(); qreal newScale = currentScale * factor; if ( newScale < 0.01 || newScale > 100.0 ) { return false; } emit scaleChanged(transform().m11()); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); scale(factor, factor); return true; } return false; } // --------------------------------- drawing --------------------------------- void ImageView::drawForeground( QPainter* painter, const QRectF& ) { if ( !scene() ) return; painter->save(); // ---------------------------- // LASSO // ---------------------------- if ( m_selecting ) { QPen pen(Qt::white, 0, Qt::DashLine); painter->setPen(pen); painter->setBrush(Qt::NoBrush); painter->drawPath(m_selectionPath); } // ---------------------------- // CROSSHAIR // ---------------------------- if ( m_crosshairVisible ) { QPen pen(Qt::red, 0); // 0 = kosmetisch painter->setPen(pen); QRectF r = scene()->sceneRect(); painter->drawLine(QPointF(m_cursorPos.x(), r.top()), QPointF(m_cursorPos.x(), r.bottom())); painter->drawLine(QPointF(r.left(), m_cursorPos.y()), QPointF(r.right(), m_cursorPos.y())); } // ---------------------------- // BRUSH-VORSCHAU // ---------------------------- if ( m_showBrushPreview && m_paintToolEnabled ) { QPen outerPen(Qt::green, 0); outerPen.setCosmetic(true); painter->setPen(outerPen); painter->setBrush(Qt::NoBrush); painter->drawEllipse(m_cursorPos, m_brushRadius, m_brushRadius); // Hardness-Ring if ( m_brushHardness > 0.0 ) { qreal innerRadius = m_brushRadius * m_brushHardness; QPen innerPen(Qt::green, 0, Qt::DashLine); innerPen.setCosmetic(true); painter->setPen(innerPen); painter->drawEllipse(m_cursorPos, innerRadius, innerRadius); } } painter->restore(); } // ---------------------------- Selectior ----------------------------- void ImageView::clearSelection() { m_selectionPath = QPainterPath(); viewport()->update(); } // ---------------------------- Layer methods ----------------------------- void ImageView::setLayerVisible( int layerVisibleOp ) { qCDebug(logEditor) << "ImageView::setLayerVisible(): layerVisibleOp =" << layerVisibleOp << ", m_oldVisibleLayerItemNum =" << m_oldVisibleLayerItemNum; { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { if ( layerVisibleOp == 1 ) { // make all layers visible m_oldVisibleLayerItemNum = -1; for ( int i=0 ; im_item ) { m_layers[i]->m_visible = true; m_layers[i]->m_item->setVisible(true); } } mainWindow->updateLayerList(); IMainSystem::instance()->showMessage(QString("Made all %1 layers visible").arg(m_layers.size())); } else if ( layerVisibleOp == 2 ) { // hide selected layer for ( int i=0 ; im_item ) { if ( m_layers[i]->m_item->isSelected() ) { m_oldVisibleLayerItemNum = i; bool isVisible = m_layers[i]->m_visible; m_layers[i]->m_visible = !isVisible; m_layers[i]->m_item->setVisible(!isVisible); mainWindow->updateLayerList(); IMainSystem::instance()->showMessage(QString("%1 layer %2").arg(isVisible?"Hide":"Show").arg(m_layers[i]->name())); return; } else if ( i == m_oldVisibleLayerItemNum ) { LayerItem *layerItem = dynamic_cast(m_layers[i]->m_item); if ( layerItem != nullptr ) { m_oldVisibleLayerItemNum = -1; m_layers[i]->m_visible = true; layerItem->setVisible(true); layerItem->setIsSelected(9,true); mainWindow->updateLayerList(); layerItem->setCageVisible(9,true); IMainSystem::instance()->showMessage(QString("Made layer %1 visible again").arg(i)); } } } } } IMainSystem::instance()->showMessage("No layer selected"); } } } LayerItem* ImageView::getLayerItem( const QString& name ) { qCDebug(logEditor) << "ImageView::getLayerItem(): name =" << name; { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer && layer->name().endsWith(name,Qt::CaseInsensitive) ) return layer; } return nullptr; } } LayerItem* ImageView::getLayerItem( int id ) { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer && layer->id() == id ) return layer; } return nullptr; } LayerItem* ImageView::getSelectedItem( bool isActiveCageItem ) { qCDebug(logEditor) << "ImageView::getSelectedItem(): isActiveCageItem =" << isActiveCageItem; { if ( isActiveCageItem ) { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer && layer->getType() != LayerItem::MainImage ) { if ( layer->hasActiveCage() && layer->isCageWarp() && layer->cageEnabled() ) { return layer; } } } } else { for ( auto* item : m_scene->items() ) { auto* layer = dynamic_cast(item); if ( layer && layer->isSelected() ) { return layer; } } } return nullptr; } } void ImageView::setPolygonOperationMode( LayerItem::OperationMode mode ) { qCDebug(logEditor) << "ImageView::setPolygonOperationMode(): mode =" << mode << ", m_polygonEnabled =" << m_polygonEnabled; { m_polygonOperationMode = mode; QString todoText; switch ( mode ) { case LayerItem::OperationMode::MovePoint: todoText = "Select polygon point and move point"; break; case LayerItem::OperationMode::AddPoint: todoText = "Double click near a polygon line to add a new polygon point"; break; case LayerItem::OperationMode::DeletePoint: todoText = "Double click on polygon point to delete this point"; break; case LayerItem::OperationMode::TranslatePolygon: todoText = "Click inside the polygon and move it by holding down the left mouse button"; break; case LayerItem::OperationMode::SmoothPolygon: todoText = "Double click inside the polygon to enable Laplace smoothing"; break; case LayerItem::OperationMode::ReducePolygon: todoText = "Double click inside the polygon to reduce number of polygon points"; break; case LayerItem::OperationMode::DeletePolygon: todoText = "Double click inside the polygon to delete it"; break; default: todoText = ""; break; } IMainSystem::instance()->showMessage(QString(todoText)); } } void ImageView::initCageWarpForLayer( LayerItem *layerItem ) { qCDebug(logEditor) << "ImageView::initCageWarpForLayer(): layerItem =" << (layerItem?"ok":"null"); { if ( !layerItem || layerItem->cageMesh().needUpdate() == false ) return; if ( layerItem->cageMesh().isInitialized() ) return; // --- initialize --- m_selectedLayer = layerItem; layerItem->enableCage(); // --- setup --- if ( layerItem->hasActiveCage() ) { m_selectedCageLayer = layerItem; m_selectedCageLayer->setCageEditing( true ); m_selectedCageLayer->setCageVisible(9,true); // this one actually makes the control points visible. m_selectedCageLayer->setCageVisible( LayerItem::OperationMode::CageWarp, true ); m_cageBefore = layerItem->cageMesh().points(); } else { m_selectedCageLayer = nullptr; m_cageBefore.clear(); } // --- create undo stack entry --- if ( m_selectedCageLayer != nullptr ) { qCDebug(logEditor) << "ImageView::initCageWarpForLayer(): Processing CageWarpCommand..."; QVector cageAfter = m_selectedCageLayer->cageMesh().points(); QVector cageBefore = m_selectedCageLayer->cageMesh().originalPoints(); if ( m_selectedCageLayer->getCageWarpCommand() == nullptr ) { int rows = m_selectedCageLayer->cageMesh().rows(); int columns = m_selectedCageLayer->cageMesh().cols(); m_undoStack->push( new CageWarpCommand(m_selectedCageLayer, cageBefore, cageAfter,m_selectedCageLayer->sceneBoundingRect(), m_selectedCageLayer->pos(), rows, columns ) ); } else { m_selectedCageLayer->getCageWarpCommand()->pushNewWarpStep( m_selectedCageLayer->pos(), cageAfter ); } if ( m_selectedCageLayer ) { m_selectedCageLayer->applyCageWarp("ImageView::2"); } m_cageBefore.clear(); } else { qCritical() << "ImageView::initCageWarpForLayer(): No selectedCageLayer"; } } } void ImageView::setLayerOperationMode( LayerItem::OperationMode mode ) { qCDebug(logEditor) << "ImageView::setLayerOperationMode(): mode =" << mode << ", m_polygonEnabled =" << m_polygonEnabled; { const bool modeChanged = ( m_layerOperationMode != mode ); // --- handle old mode --- if ( modeChanged ) { if ( m_layerOperationMode == LayerItem::OperationMode::Scale ) { disableTransformMode(); } else if ( m_layerOperationMode == LayerItem::OperationMode::Perspective ) { disablePerspectiveWarp(); } else if ( m_layerOperationMode == LayerItem::OperationMode::CageWarp ) { m_selectedCageLayer = nullptr; } } // --- handle new mode --- m_layerOperationMode = mode; if ( m_polygonEnabled ) { setPolygonEnabled(false); } // update message info if ( m_layerOperationMode == LayerItem::OperationMode::Translate ) { IMainSystem::instance()->showMessage("Enabled translate. Press and move left mouse button on active layer to translate layer"); } else if ( m_layerOperationMode == LayerItem::OperationMode::Perspective ) { IMainSystem::instance()->showMessage("Enabled perspective warp. Double click on active layer to activate control grid"); } else if ( m_layerOperationMode == LayerItem::OperationMode::Scale ) { IMainSystem::instance()->showMessage("Enabled scale. Double click on active layer to activate control grid"); } else if ( m_layerOperationMode == LayerItem::OperationMode::Flip || m_layerOperationMode == LayerItem::OperationMode::Flop ) { IMainSystem::instance()->showMessage("Enabled mirror. Double click on active layer to mirror layer"); } else if ( m_layerOperationMode == LayerItem::OperationMode::Rotate ) { IMainSystem::instance()->showMessage("Enabled rotation. Press and move left mouse button on active layer to rotate layer"); } else if ( m_layerOperationMode == LayerItem::OperationMode::CageWarp ) { IMainSystem::instance()->showMessage("Enabled cage warp. Select and move a control grid point to warp layer"); } // handle layer LayerItem *layer = getSelectedItem(); if ( layer != nullptr ) { if ( m_layerOperationMode == LayerItem::OperationMode::CageWarp ) { if ( modeChanged ) { initCageWarpForLayer(layer); } } else if ( m_layerOperationMode == LayerItem::OperationMode::Scale ) { if ( modeChanged || !m_transformOverlay ) { setEnableTransformMode(layer); } } else if ( m_layerOperationMode == LayerItem::OperationMode::Perspective ) { if ( modeChanged || !m_perspectiveOverlay ) { setEnablePerspectiveWarp(layer); } } layer->setOperationMode(mode); } else { layer = baseLayer(); if ( layer != nullptr ) { layer->setOperationMode(mode); } } } } LayerItem* ImageView::baseLayer() { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer && layer->getType() == LayerItem::MainImage ) { return layer; } } return nullptr; } void ImageView::clearLayers() { qDeleteAll(m_layers); m_layers.clear(); } void ImageView::createLassoLayer() { createNewLayer(m_lassoPolygon,"Lasso Layer"); emit lassoLayerAdded(); } LassoCutCommand* ImageView::createNewLayer( const QPolygonF& polygon, const QString &name ) { qDebug() << "ImageView::createNewLayer(): name =" << name << ", polygon_size =" << polygon.size() << ", operationMode =" << m_layerOperationMode; { // --- switch to layer operation mode --- MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); } // --- get main image layer --- LayerItem* base = baseLayer(); if ( !base || polygon.size() < 3 ) return nullptr; QImage& src = base->image(); // --- prepare --- QColor backgroundColor = Config::isWhiteBackgroundImage ? Qt::white : Qt::black; QPolygonF polyF = QPolygonF(polygon.begin(), polygon.end()); QRectF boundsF = polyF.boundingRect(); QRect bounds = boundsF.toAlignedRect(); QImage backup = src.copy(bounds); // --- create mask --- QImage mask(bounds.size(), QImage::Format_Alpha8); mask.fill(0); QPainter pm(&mask); pm.setRenderHint(QPainter::Antialiasing); pm.setBrush(backgroundColor); // Qt::white); pm.setPen(Qt::NoPen); // Polygon relativ zur Bounding-Box verschieben QPolygonF relativePoly = polyF; for ( int i=0; i 0 ) { mask = QImageUtils::blurAlphaMask(mask,m_lassoFeatherRadius); } // --- cut layer --- QImage cut(bounds.size(), QImage::Format_ARGB32_Premultiplied); cut.fill(Qt::transparent); if ( m_maskCutTool == MaskCutTool::Mask && m_maskLayer != nullptr ) { for ( int y=0; y 0 && m_maskLayer->pixel(xpos,ypos) == 0 ) { c.setAlpha(m[x]); cut.setPixelColor(x,y,c); } } } } else if ( m_maskCutTool == MaskCutTool::OnlyMask && m_maskLayer != nullptr ) { for ( int y=0; y 0 && m_maskLayer->pixel(xpos,ypos) != 0 ) { c.setAlpha(m[x]); cut.setPixelColor(x,y,c); } } } } else if ( m_maskCutTool == MaskCutTool::Copy && m_maskLayer != nullptr ) { // --- testing --- // src = mainImage // mask = m = maskImage -> area under polygon // m_maskLayer = class data // cut = the cut layer itself for ( int y=0; y 0 ) { if ( m_maskLayer->pixel(xpos,ypos) != 0 ) { int index = m_maskLayer->pixel(xpos,ypos); int alpha = index == 1 ? 0 : ( index > 2 ? 128 : 255 ); // 1 := cut, >2 := copy c.setAlpha(alpha); cut.setPixelColor(x,y,c); } else { c.setAlpha(m[x]); cut.setPixelColor(x,y,c); } } } } // --- --- --- --- --- } else { for ( int y=0; y 0 ) { // results in border artefacts: c.setAlpha(m[x]); cut.setPixelColor(x,y,c); cut.setPixelColor(x,y,c); } } } } // --- Neues LayerItem --- int nidx = 0; for ( int i=0 ; iid() > nidx ? m_layers[i]->id() : nidx; } nidx += 1; Layer* layer = new Layer(nidx,cut); layer->m_name = QString("%1 %2").arg(name).arg(nidx); layer->m_creator = name; layer->m_bounds = bounds; LayerItem* newLayer = new LayerItem(cut); newLayer->setParent(m_parent); newLayer->setIndex(nidx); newLayer->setLayer(layer); newLayer->setUndoStack(m_undoStack); LassoCutCommand* cmd = new LassoCutCommand(base, newLayer, bounds, cut, nidx, name); m_undoStack->push(cmd); newLayer->setPos(base->mapToScene(bounds.topLeft())); newLayer->setZValue(base->zValue()+1); // newLayer->setShowGreenBorder(true); // falls vorhanden // ITEM ALREADY IN SCENE: m_scene->addItem(newLayer); base->updatePixmap(); layer->m_item = newLayer; m_layers.push_back(layer); // --- Set new active layer --- setActiveLayer(layer->m_name); // --- Ready --- return cmd; } } // ---------------------------- --------------- ----------------------------- // ---------------------------- CageWarp methods ---------------------------- // ---------------------------- --------------- ----------------------------- // ToDo: // + increase/decrease needs an udate // + reset not yet implemented // ---------------------------- --------------- ----------------------------- void ImageView::enableCageLayer( LayerItem *layer, bool isEnabled ) { if ( !layer ) return; m_selectedCageLayer = isEnabled ? layer : nullptr; } void ImageView::setCageWarpReset() { qCDebug(logEditor) << "ImageView::setCageWarpReset(): Processing..."; { if ( !m_selectedLayer || !m_selectedLayer->hasActiveCage() ) return; m_selectedLayer->resetCageToPixmap(); } } void ImageView::setPerspectiveWarpReset() { qCDebug(logEditor) << "ImageView::setPerspectiveWarpReset(): Processing..."; { if ( !m_perspectiveOverlay ) return; m_perspectiveOverlay->resetWarp(); } } void ImageView::setIncreaseNumberOfCageControlPoints() { qCDebug(logEditor) << "ImageView::setIncreaseNumberOfCageControlPoints(): layer =" << (m_selectedLayer ? true : false); { if ( !m_selectedLayer ) return; { if ( !m_selectedLayer->hasActiveCage() ) { qWarning() << "ImageView::setIncreaseNumberOfCageControlPoints(): No active Cage available."; return; } auto* command = m_selectedLayer->getCageWarpCommand(); if ( command ) { int n = m_selectedLayer->changeNumberOfActiveCagePoints(+1); m_selectedLayer->getCageWarpCommand()->setNumberOfRowsAndColumns(n); } else { qCritical() << "ImageView::setIncreaseNumberOfCageControlPoints(): No CageWarpCommand available!"; } } } } void ImageView::setDecreaseNumberOfCageControlPoints() { if ( !m_selectedLayer || !m_selectedLayer->hasActiveCage() ) return; auto* command = m_selectedLayer->getCageWarpCommand(); if ( command ) { int n = m_selectedLayer->changeNumberOfActiveCagePoints(-1); m_selectedLayer->getCageWarpCommand()->setNumberOfRowsAndColumns(n); if ( n != 0 ) m_selectedLayer->applyCageWarp("ImageView::3"); } } void ImageView::setNumberOfCageControlPoints( int nControlPoints ) { qCDebug(logEditor) << "ImageView::setNumberOfCageControlPoints(): nControlPoints=" << nControlPoints; { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer && layer->getType() != LayerItem::MainImage ) { if ( layer->hasActiveCage() ) { layer->setNumberOfActiveCagePoints(nControlPoints); return; } } } } } void ImageView::setCageWarpRelaxationSteps( int nRelaxationSteps ) { qCDebug(logEditor) << "ImageView::setCageWarpRelaxationSteps(): nRelaxationSteps=" << nRelaxationSteps; { LayerItem *layer = getSelectedItem(true); if ( layer != nullptr ) { layer->setCageWarpProperty(1,nRelaxationSteps); } } } void ImageView::setCageWarpStiffness( double stiffness ) { LayerItem *layer = getSelectedItem(true); if ( layer != nullptr ) { layer->setCageWarpProperty(2,stiffness); } } void ImageView::setCageWarpFixBoundary( bool isChecked ) { LayerItem *layer = getSelectedItem(true); if ( layer != nullptr ) { layer->setCageWarpProperty(3,isChecked); } } // ---------------------------- --------------- ----------------------------- // ---------------------------- Polygon methods ----------------------------- // ---------------------------- --------------- ----------------------------- void ImageView::setPolygonIndex( quint8 index, bool doUpdate ) { qDebug() << "ImageView::setPolygonIndex(): index =" << index << ", update =" << doUpdate; { m_polygonIndex = index; if ( !doUpdate ) return; // >>> int visIndex = -1; bool foundAnyPolygon = false; QString polyName = QString("Polygon %1").arg(index); for ( EditablePolygon* polygon : m_editablePolygons ) { if ( !polygon ) continue; try { if ( polygon->name() == polyName ) { polygon->setVisible(true); IMainSystem* system = IMainSystem::instance(); if ( system ) { system->showMessage(QString("Selected %1").arg(polyName)); } visIndex = -2; foundAnyPolygon = true; } else { polygon->setVisible(false); } } catch (...) { qDebug() << "FATAL ERROR: Polygon-Object is invalid!"; } } MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow ) { if ( foundAnyPolygon == false ) { mainWindow->showMessage(QString("No selectable polygon %1 found").arg(index)); } mainWindow->setPolygonOperationMode(visIndex); } // find polygon for ( auto* item : m_scene->items() ) { auto* editablePolygon = dynamic_cast(item); if ( editablePolygon && !editablePolygon->polygon()->layer() ) { // qDebug() << " + polygon_name: " << editablePolygon->name() << ", hasLayer =" << editablePolygon->polygon()->layer(); if ( editablePolygon->name() == polyName == true ) { m_activePolygonItem = editablePolygon; editablePolygon->visibilityChangedTo(true); } else { editablePolygon->visibilityChangedTo(false); } } } // end test } } EditablePolygonCommand* ImageView::getPolygonUndoCommand( const QString& name, bool isSelected ) { qDebug() << "ImageView::getPolygonUndoCommand(): name =" << name << ", isSelected =" << isSelected; { if ( name == "" ) { if ( isSelected == true ) { for ( int i = 0; i < m_undoStack->count(); ++i ) { const QUndoCommand* baseCmd = m_undoStack->command(i); auto polyCmd = dynamic_cast(baseCmd); if ( polyCmd && polyCmd->isSelected() ) { return const_cast(polyCmd); } } return nullptr; } const QUndoCommand* cmd = m_undoStack->command(m_undoStack->index() - 1); EditablePolygonCommand* polyCmd = const_cast( dynamic_cast(cmd) ); return polyCmd; } for ( int i = m_undoStack->count() - 1; i >= 0; --i ) { const QUndoCommand* cmd = m_undoStack->command(i); qDebug() << cmd->text(); if ( cmd->text() == name ) { return const_cast( dynamic_cast(cmd) ); } } return nullptr; } } void ImageView::undoPolygonOperation() { qDebug() << "ImageView::undoPolygonOperation(): polygonIndex =" << m_polygonIndex; { EditablePolygonCommand* polyCmd = ImageView::getPolygonUndoCommand(QString("Editable Polygon %1").arg(m_polygonIndex)); if ( polyCmd == nullptr ) return; EditablePolygon *polygon = polyCmd->model(); if ( polygon != nullptr ) { polygon->undoStack()->undo(); } } } void ImageView::redoPolygonOperation() { qDebug() << "ImageView::redoPolygonOperation(): polygonIndex =" << m_polygonIndex; { EditablePolygonCommand* polyCmd = ImageView::getPolygonUndoCommand(QString("Editable Polygon %1").arg(m_polygonIndex)); if ( polyCmd == nullptr ) return; EditablePolygon *polygon = polyCmd->model(); if ( polygon != nullptr ) { polygon->undoStack()->redo(); } } } void ImageView::createPolygonLayer() { qDebug() << "ImageView::createPolygonLayer(): polygonIndex =" << m_polygonIndex; { // check whether a layer has been already created for ( int i=0 ; iindex() == m_polygonIndex ) { if ( m_editablePolygons[i]->layer() ) { IMainSystem::instance()->showMessage(QString("Cannot create a new layer from %1. Layer already created.").arg(m_editablePolygons[i]->name()),1); return; } m_editablePolygons[i]->setLayer(); } } // >>> if ( m_activePolygon != nullptr ) { LayerItem *layer = baseLayer(); if ( layer != nullptr ) { finishPolygonDrawing(layer); } } MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { mainWindow->setMainOperationMode(MainWindow::MainOperationMode::ImageLayer); } // get active polygon EditablePolygonCommand* polyCmd = ImageView::getPolygonUndoCommand(QString("Editable Polygon %1").arg(m_polygonIndex),true); if ( polyCmd == nullptr ) { qWarning() << "ImageView::createPolygonLayer(): No polygon " << m_polygonIndex << " found"; return; } // process polygon qDebug() << "ImageView::createPolygonLayer(): polygon name =" << polyCmd->name(); EditablePolygon *editablePolygon = polyCmd->model(); if ( editablePolygon != nullptr ) { int index = m_layers.size()+1; LassoCutCommand *layerCut = createNewLayer(editablePolygon->polygon(),QString("%1 Layer").arg(polyCmd->name())); if ( layerCut != nullptr ) { layerCut->setController(polyCmd); editablePolygon->setVisible(false); polyCmd->setChildLayerId(index); } emit lassoLayerAdded(); } } } // finalize polygon drawing void ImageView::finishPolygonDrawing( LayerItem* layer ) { qDebug() << "ImageView::finishPolygonDrawing(): polygonIndex =" << m_polygonIndex << ", nEditablePolygons =" << m_editablePolygons.size(); { if ( !m_activePolygon || m_activePolygon->pointCount() < 3 ) return; QColor color = QColor(255,0,0); MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow != nullptr ) { mainWindow->setPolygonOperationMode(-2); mainWindow->setMainOperationMode(MainWindow::MainOperationMode::CreatePolygon); int index = mainWindow->activePolygon(m_activePolygon->name()); if ( index >= 0 ) { QVector colors = defaultMaskColors(); color = colors[(index+1)%colors.length()]; } } setOnlySelectedPolygon(m_activePolygon->name()); QPolygonF poly = m_activePolygon->polygon(); scene()->removeItem(m_activePolygonItem); m_activePolygonItem->deleteLater(); // delete m_activePolygon; m_activePolygon = nullptr; m_activePolygonItem = nullptr; EditablePolygonCommand *polyCmd = new EditablePolygonCommand(layer,scene(),poly,QString("Polygon %1").arg(m_polygonIndex)); // m_editablePolygons.size())); polyCmd->setColor(color); m_undoStack->push(polyCmd); } } void ImageView::setOnlySelectedPolygon( const QString& name ) { qCDebug(logEditor) << "ImageView::setOnlySelectedPolygon(): name =" << name; { for ( int i = m_undoStack->count() - 1; i >= 0; --i ) { const QUndoCommand* cmd = m_undoStack->command(i); EditablePolygonCommand *polyCmd = const_cast(dynamic_cast(cmd)); if ( polyCmd != nullptr ) { polyCmd->setSelected(cmd->text() == name ? true : false); } } } } void ImageView::setPolygonEnabled( bool enabled ) { qDebug() << "ImageView::setPolygonEnabled(): npolygons =" << m_editablePolygons.size() << ", enabled =" << enabled; { LayerItem *layer = baseLayer(); if ( layer != nullptr ) { m_polygonEnabled = enabled; if ( m_polygonEnabled ) { MainWindow *mainWindow = dynamic_cast(m_parent); if ( mainWindow == nullptr ) return; int polygonIndex = mainWindow->activePolygon(""); // check whether index is already in use for ( EditablePolygon* polygon : m_editablePolygons ) { if ( polygon && polygon->index() == polygonIndex ) { mainWindow->showMessage(QString("Polygon %1 already used.").arg(polygonIndex),1); return; } } setActiveLayer(QString("None")); QString name = QString("Polygon %1").arg(polygonIndex); // 1+m_editablePolygons.size()); m_polygonIndex = polygonIndex; m_activePolygon = new EditablePolygon("ImageView::setPolygonEnabled()",name); m_editablePolygons.push_back(m_activePolygon); m_activePolygonItem = new EditablePolygonItem(m_activePolygon,layer); m_activePolygonItem->setColor(QColor(255,0,0)); m_activePolygonItem->setName(name); } else { finishPolygonDrawing(layer); } } } } int ImageView::pushEditablePolygon( EditablePolygon* editablePolygon ) { if ( !editablePolygon ) return 0; qCDebug(logEditor) << "ImageView::pushEditablePolygon): name =" << editablePolygon->name(); for ( int i=0 ; iname() == editablePolygon->name() ) { return -m_editablePolygons.size(); } } m_editablePolygons.push_back(editablePolygon); return m_editablePolygons.size(); } int ImageView::getNextFreePolygonIndex( void ) { QSet indices; for ( EditablePolygon* polygon : m_editablePolygons ) { indices.insert(polygon->index()); } QSet indexSet(indices.begin(), indices.end()); for ( int i=1 ; i<=50 ; ++i ) { if ( !indexSet.contains(i) ) { return i; } } return 0; } // ---------------------------- --------------- ----------------------------- // ---------------------------- Perspective free scale ---------------------- // ---------------------------- --------------- ----------------------------- void ImageView::setCageVisible( LayerItem* layer, LayerItem::OperationMode mode, bool isVisible ) { qCDebug(logEditor) << "ImageView::setCageVisible(): mode =" << mode << ", visible =" << isVisible; { if ( mode == LayerItem::OperationMode::Scale ) { if ( m_transformOverlay == nullptr && isVisible == true ) { m_transformOverlay = new TransformOverlay(layer,m_undoStack); scene()->addItem(m_transformOverlay); } if ( m_transformOverlay != nullptr ) { m_transformOverlay->updateOverlay(); m_transformOverlay->setVisible(isVisible); } } else if ( mode == LayerItem::OperationMode::Perspective ) { if ( m_perspectiveOverlay != nullptr ) { m_perspectiveOverlay->setVisible(isVisible); } } } } void ImageView::setOnlyCageVisible( int ident ) { qCDebug(logEditor) << "ImageView::setOnlyCageVisible(): ident =" << ident; { for ( auto* item : m_scene->items(Qt::DescendingOrder) ) { auto* layer = dynamic_cast(item); if ( layer ) { bool isVisible = ( layer->id() == ident && layer->operationMode() == LayerItem::OperationMode::CageWarp ) ? true : false ; layer->setCageVisible( 10, isVisible); } } } } // ---------------------------- --------------- ----------------------------- // ---------------------------- Perspective free scale ---------------------- // ---------------------------- --------------- ----------------------------- void ImageView::setEnableTransformMode( LayerItem* layer ) { qCDebug(logEditor) << "ImageView::setEnableTransformMode(): layer =" << (layer != nullptr ? layer->name() : "NULL"); { if ( !scene() || !layer ) return; if ( m_transformOverlay ) { scene()->removeItem(m_transformOverlay); delete m_transformOverlay; m_transformOverlay = nullptr; } m_transformOverlay = new TransformOverlay(layer,m_undoStack); scene()->addItem(m_transformOverlay); m_transformOverlay->updateOverlay(); } } void ImageView::disableTransformMode() { qCDebug(logEditor) << "ImageView::disableTransformMode(): Processing..."; { if ( !m_transformOverlay ) return; scene()->removeItem(m_transformOverlay); delete m_transformOverlay; m_transformOverlay = nullptr; } } // ---------------------------- --------------- ----------------------------- // ---------------------------- Perspective warp ------------------------- // ---------------------------- --------------- ----------------------------- void ImageView::setEnablePerspectiveWarp( LayerItem* layer ) { qCDebug(logEditor) << "ImageView::setEnablePerspectiveWarp(): layer =" << (layer != nullptr ? layer->name() : "NULL"); { if ( !scene() || !layer ) return; if ( m_perspectiveOverlay ) { scene()->removeItem(m_perspectiveOverlay); delete m_perspectiveOverlay; m_perspectiveOverlay = nullptr; } m_perspectiveOverlay = new PerspectiveOverlay(layer, m_undoStack); scene()->addItem(m_perspectiveOverlay); // NOT REQUIRED: m_perspectiveOverlay->updateOverlay(); } } void ImageView::disablePerspectiveWarp() { if ( !m_perspectiveOverlay ) return; scene()->removeItem(m_perspectiveOverlay); delete m_perspectiveOverlay; m_perspectiveOverlay = nullptr; } // ---------------------------- --------------- ----------------------------- // ---------------------------- --------------- ----------------------------- void ImageView::setOverlayVisibility( int overlayType, bool isVisible ) { qCDebug(logEditor) << "ImageView::setOverlayVisibility(): type =" << overlayType << ", visible =" << isVisible; { if ( overlayType == 1 && m_transformOverlay != nullptr ) { m_transformOverlay->setVisible(isVisible); } else if ( overlayType == 2 && m_perspectiveOverlay != nullptr ) { m_perspectiveOverlay->setVisible(isVisible); } } }