/* * 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 #include #include #include #include #include "Config.h" #include "ImageProcessor.h" #include "ImageLoader.h" #include "../layer/LayerItem.h" #include "../undo/AbstractCommand.h" #include "../undo/PaintStrokeCommand.h" #include "../undo/PerspectiveWarpCommand.h" #include "../undo/TransformLayerCommand.h" #include "../undo/DeleteUndoEntryCommand.h" #include "../undo/LassoCutCommand.h" #include "../undo/MirrorLayerCommand.h" #include "../undo/MoveLayerCommand.h" #include "../undo/CageWarpCommand.h" #include #include // ----------------------- Constructor ----------------------- ImageProcessor::ImageProcessor( const QImage& image ) : m_image(image) { qDebug() << "ImageProcessor::ImageProcessor(): Processing..."; { m_skipMainImage = true; m_undoStack = new QUndoStack(); buildMainImageLayer(); } } ImageProcessor::ImageProcessor() { m_undoStack = new QUndoStack(); } // ----------------------- Methods ----------------------- QString ImageProcessor::saveIntermediate( AbstractCommand *cmd, const QString &name, int step ) { if ( m_saveIntermediate && cmd != nullptr ) { QString outfilename = QString("%1/%2_%3.png").arg(m_intermediatePath).arg(m_basename).arg(1000+step); LayerItem *layer = cmd->layer(); if ( layer != nullptr ) { qInfo() << layer->pos() << " - " << layer->image().rect(); layer->image().save(outfilename); return QString("%1 %2 %3\n").arg(1000+step).arg(name).arg(outfilename); } } return ""; } void ImageProcessor::buildMainImageLayer() { if ( !m_image.isNull() ) { LayerItem* newLayer = new LayerItem(m_image); newLayer->setName("MainImage"); newLayer->setIndex(0); newLayer->setParent(nullptr); newLayer->setUndoStack(m_undoStack); m_layers << newLayer; } } void ImageProcessor::setIntermediatePath( const QString& path, const QString& outname ) { m_intermediatePath = path; if ( outname.length() > 0 ) { QFileInfo fileInfo(outname); m_basename = fileInfo.baseName(); } m_saveIntermediate = path.length() > 0 ? true : false; } bool ImageProcessor::process( const QString& filePath, bool forcedAlphaMasking ) { qDebug() << "ImageProcessor::process(): filePath='" << filePath << "', forcedAlphaMasking =" << forcedAlphaMasking; { QFile f(filePath); if ( !f.open(QIODevice::ReadOnly) ) { qDebug() << LogColor::Red << "ImageProcessor::process(): Cannot open '" << filePath << "'!" << LogColor::Reset; return false; } QJsonDocument doc = QJsonDocument::fromJson(f.readAll()); f.close(); if ( !doc.isObject() ) return false; QJsonObject root = doc.object(); // layers QJsonArray layerArray = root["layers"].toArray(); if ( !m_skipMainImage ) { for ( const QJsonValue& v : layerArray ) { QJsonObject layerObj = v.toObject(); QString name = layerObj["name"].toString(); int id = layerObj["id"].toInt(); if ( id == 0 ) { QString filename = layerObj["filename"].toString(); QString pathname = layerObj["pathname"].toString(); QString fullfilename = pathname+"/"+filename; ImageLoader loader; if ( loader.load(fullfilename,true) ) { m_image = loader.getImage(); Config::isWhiteBackgroundImage = loader.hasWhiteBackground(); buildMainImageLayer(); } else { qDebug() << LogColor::Red << "ImageProcessor::process(): Cannot find '" << fullfilename << "'!" << LogColor::Reset; return false; } } } } // loading sublayers int nCreatedLayers = m_layers.size(); for ( const QJsonValue& v : layerArray ) { QJsonObject layerObj = v.toObject(); int id = layerObj["id"].toInt(); if ( id != 0 ) { QString name = layerObj["name"].toString(); if ( layerObj.contains("data") ) { LayerItem* newLayer = nullptr; QString imgBase64 = layerObj["data"].toString(); QByteArray ba = QByteArray::fromBase64(imgBase64.toUtf8()); QImage mask; mask.loadFromData(ba,"PNG"); bool isBinaryMask = layerObj.value("binaryMask").toBool(false); int x = layerObj.value("x").toInt(-1); int y = layerObj.value("y").toInt(-1); if ( !( x > 0 && y > 0 ) ) { QJsonArray undoArray = root["undoStack"].toArray(); for ( const QJsonValue& v : undoArray ) { QJsonObject cmdObj = v.toObject(); QString type = cmdObj["type"].toString(); if ( type == "LassoCut" || type == "LassoCutCommand" ) { if( id == cmdObj["newLayerId"].toInt(-1) ) { QJsonObject r = cmdObj["rect"].toObject(); x = r["x"].toInt(); y = r["y"].toInt(); } } } } QRect rect = QRect(x,y,mask.width(), mask.height()); if ( isBinaryMask && x >= 0 && y >= 0 ) { QImage subImage = m_image.copy(x, y, mask.width(), mask.height()); subImage = subImage.convertToFormat(QImage::Format_ARGB32); int backgroundPixelColor = Config::isWhiteBackgroundImage ? 255 : 0; for ( int y = 0; y < subImage.height(); ++y ) { QRgb *rowData = reinterpret_cast(subImage.scanLine(y)); const uchar *maskData = mask.constScanLine(y); for ( int x = 0; x < subImage.width(); ++x ) { #if 0 if ( maskData[x] != 255 ) { rowData[x] = qRgba(qRed(rowData[x]), qGreen(rowData[x]), qBlue(rowData[x]), 0); } #else if ( maskData[x] == 255 ) { if( qRed(rowData[x]) == backgroundPixelColor ) { rowData[x] = qRgba(qRed(rowData[x]), qGreen(rowData[x]), qBlue(rowData[x]), 0); } } else { rowData[x] = qRgba(qRed(rowData[x]), qGreen(rowData[x]), qBlue(rowData[x]), 0); } #endif } } newLayer = new LayerItem(subImage); } else if ( forcedAlphaMasking ) { if ( mask.format() != QImage::Format_ARGB32 && mask.format() != QImage::Format_ARGB32_Premultiplied ) { mask = mask.convertToFormat(QImage::Format_ARGB32); } QImage subImage = m_image.copy(x, y, mask.width(), mask.height()); subImage = subImage.convertToFormat(QImage::Format_ARGB32); int backgroundPixelColor = Config::isWhiteBackgroundImage ? 255 : 0; for ( int y = 0; y < subImage.height(); ++y ) { auto *rowData = reinterpret_cast(subImage.scanLine(y)); const auto *maskRowData = reinterpret_cast(mask.constScanLine(y)); for ( int x = 0; x < subImage.width(); ++x ) { #if 0 if ( qRed(rowData[x]) == backgroundPixelColor || qAlpha(maskRowData[x]) != 255 ) { #else if ( qRed(rowData[x]) == backgroundPixelColor && qAlpha(maskRowData[x]) == 255 ) { #endif rowData[x] = 0; } } } newLayer = new LayerItem(subImage); } else { newLayer = new LayerItem(mask); } newLayer->setName(name); newLayer->setIndex(id); newLayer->setParent(nullptr); newLayer->setUndoStack(m_undoStack); m_layers << newLayer; nCreatedLayers += 1; } } } // --- Restore Undo/Redo Stack --- int nStep = 1; QString infoTextLines = ""; QJsonArray undoArray = root["undoStack"].toArray(); for ( const QJsonValue& v : undoArray ) { QJsonObject cmdObj = v.toObject(); QString type = cmdObj["type"].toString(); QString text = cmdObj["text"].toString(); qDebug() << "ImageProcessor::process(): Processing undo call: type=" << type << ", text=" << text; AbstractCommand* cmd = nullptr; if ( type == "PaintStroke" || type == "PaintStrokeCommand" ) { cmd = PaintStrokeCommand::fromJson(cmdObj, m_layers); } else if ( type == "LassoCut" || type == "LassoCutCommand" ) { cmd = LassoCutCommand::fromJson(cmdObj, m_layers); } else if ( type == "MoveLayer" || type == "MoveLayerCommand" ) { cmd = MoveLayerCommand::fromJson(cmdObj, m_layers); } else if ( type == "MirrorLayer" || type == "MirrorLayerCommand" ) { cmd = MirrorLayerCommand::fromJson(cmdObj, m_layers); } else if ( type == "CageWarp" || type == "CageWarpCommand" ) { cmd = CageWarpCommand::fromJson(cmdObj, m_layers); } else if ( type == "TransformLayer" || type == "TransformLayerCommand" ) { cmd = TransformLayerCommand::fromJson(cmdObj, m_layers); } else if ( type == "PerspectiveWarp" || type == "PerspectiveWarpCommand" ) { cmd = PerspectiveWarpCommand::fromJson(cmdObj, m_layers); } else if ( type == "DeleteUndoEntry" || type == "DeleteUndoEntryCommand" ) { cmd = DeleteUndoEntryCommand::fromJson(m_undoStack, cmdObj, m_layers); } else { qDebug() << LogColor::Red << "ImageProcessor::process(): Command " << type << " not yet processed." << LogColor::Reset; } // ggf. weitere Command-Typen hier hinzufügen if ( cmd ) { m_undoStack->push(cmd); infoTextLines += saveIntermediate(cmd,type,nStep); } else { qDebug() << LogColor::Red << "ImageProcessor::process(): Invalid command." << LogColor::Reset; } nStep += 1; } if ( m_saveIntermediate && infoTextLines != "" ) { QString outfilename = QString("%1/%2.info").arg(m_intermediatePath).arg(m_basename); QFile file(outfilename); if ( file.open(QIODevice::WriteOnly | QIODevice::Text) ) { QTextStream out(&file); out << infoTextLines.trimmed(); file.close(); } else { qWarning() << "Warning: Cannot save info file " << outfilename << ": " << file.errorString(); } } // --- combine layer images --- bool sizeLayerSorting = true; if ( setOutputImage(0) ) { qInfo() << "Creating output image..."; if ( sizeLayerSorting ) { auto sortedLayers = m_layers; std::sort(sortedLayers.begin(), sortedLayers.end(), [](QGraphicsItem* a, QGraphicsItem* b) { auto* layerA = dynamic_cast(a); auto* layerB = dynamic_cast(b); if ( !layerA || !layerB ) return false; auto rectA = layerA->image().size(); auto rectB = layerB->image().size(); long areaA = (long)rectA.width() * rectA.height(); long areaB = (long)rectB.width() * rectB.height(); return areaA > areaB; }); QPainter painter(&m_outImage); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); for ( auto* item : sortedLayers ) { auto* layer = dynamic_cast(item); if ( layer && layer->id() != 0 ) { QImage overlayImage = layer->image(); if ( !overlayImage.isNull() ) { int x = static_cast(layer->pos().x()); int y = static_cast(layer->pos().y()); qInfo() << " + drawing layer =" << layer->name() << ": size =" << overlayImage.width() << "x" << overlayImage.height(); painter.drawImage(x, y, overlayImage); } } } painter.end(); } else { QPainter painter(&m_outImage); painter.setCompositionMode(QPainter::CompositionMode_SourceOver); for ( auto* item : m_layers ) { auto* layer = dynamic_cast(item); if ( layer && layer->id() != 0 ) { QImage overlayImage = layer->image(); if ( !overlayImage.isNull() ) { int x = layer->pos().x(); int y = layer->pos().y(); qInfo() << " + drawing layer =" << layer->name() << ": id =" << layer->id( )<< ", pos =" << layer->pos() << ", rect =" << layer->boundingRect(); painter.drawImage(x, y, overlayImage); } } } painter.end(); } } else { qInfo() << "Warning: Malfunction in ImageProcessor::setOutputImage()."; return false; } return true; } } // ----------------------- Main image ----------------------- bool ImageProcessor::setOutputImage( int ident ) { qDebug() << "ImageProcessor::setOutputImage(): ident=" << ident; { for ( auto* item : m_layers ) { auto* layer = dynamic_cast(item); if ( layer && layer->id() == ident ) { m_outImage = layer->image(ident); return true; } } return false; } } // ----------------------- Misc ----------------------- void ImageProcessor::printself() { qDebug() << "ImageProcessor::printself(): Processing..."; { for ( auto* item : m_layers ) { auto* layer = dynamic_cast(item); if ( layer ) { layer->printself(); } } } }