File indexing completed on 2024-12-22 04:15:49

0001 /*
0002  *  SPDX-FileCopyrightText: 2006-2007, 2009 Cyrille Berger <cberger@cberger.net>
0003  *  SPDX-FileCopyrightText: 2023 Carsten Hartenfels <carsten.hartenfels@pm.me>
0004  *
0005  *  SPDX-License-Identifier: GPL-2.0-or-later
0006  */
0007 
0008 #include "kis_open_raster_stack_save_visitor.h"
0009 
0010 #include <math.h>
0011 
0012 #include <QDomElement>
0013 #include <QImage>
0014 
0015 #include <KoCompositeOpRegistry.h>
0016 
0017 #include "kis_adjustment_layer.h"
0018 #include "filter/kis_filter.h"
0019 #include "filter/kis_filter_configuration.h"
0020 #include "kis_group_layer.h"
0021 #include "kis_paint_layer.h"
0022 #include <generator/kis_generator_layer.h>
0023 #include "kis_open_raster_save_context.h"
0024 #include <kis_clone_layer.h>
0025 #include <kis_external_layer_iface.h>
0026 
0027 struct KisOpenRasterStackSaveVisitor::Private {
0028     Private() {}
0029     KisOpenRasterSaveContext* saveContext {nullptr};
0030     QDomDocument layerStack;
0031     QDomElement currentElement;
0032     vKisNodeSP activeNodes;
0033 };
0034 
0035 KisOpenRasterStackSaveVisitor::KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext* saveContext, vKisNodeSP activeNodes)
0036     : d(new Private)
0037 {
0038     d->saveContext = saveContext;
0039     d->activeNodes = activeNodes;
0040 }
0041 
0042 KisOpenRasterStackSaveVisitor::~KisOpenRasterStackSaveVisitor()
0043 {
0044     delete d;
0045 }
0046 
0047 void KisOpenRasterStackSaveVisitor::saveLayerInfo(QDomElement& elt, KisLayer* layer)
0048 {
0049     elt.setAttribute("name", layer->name());
0050     elt.setAttribute("opacity", QString().setNum(layer->opacity() / 255.0));
0051     elt.setAttribute("visibility", layer->visible() ? "visible" : "hidden");
0052 
0053     if (layer->inherits("KisGroupLayer")) {
0054         // Workaround for the issue regarding ora specification.
0055         // MyPaint treats layer's x and y relative to the group's x and y
0056         //  while Gimp and Krita think those are absolute values.
0057         // Hence we set x and y on group layers to always be 0.
0058         elt.setAttribute("x", QString().setNum(0));
0059         elt.setAttribute("y", QString().setNum(0));
0060 
0061     } else {
0062         elt.setAttribute("x", QString().setNum(layer->exactBounds().x()));
0063         elt.setAttribute("y", QString().setNum(layer->exactBounds().y()));
0064     }
0065 
0066     if (layer->userLocked()) {
0067         elt.setAttribute("edit-locked", "true");
0068     }
0069     if (d->activeNodes.contains(layer)) {
0070         elt.setAttribute("selected", "true");
0071     }
0072     QString compop = layer->compositeOpId();
0073     if (layer->compositeOpId() == COMPOSITE_CLEAR) compop = "svg:clear";
0074     else if (layer->compositeOpId() == COMPOSITE_ERASE) compop = "svg:dst-out";
0075     else if (layer->compositeOpId() == COMPOSITE_DESTINATION_ATOP) compop = "svg:dst-atop";
0076     else if (layer->compositeOpId() == COMPOSITE_DESTINATION_IN) compop = "svg:dst-in";
0077     else if (layer->compositeOpId() == COMPOSITE_ADD) compop = "svg:plus";
0078     else if (layer->compositeOpId() == COMPOSITE_MULT) compop = "svg:multiply";
0079     else if (layer->compositeOpId() == COMPOSITE_SCREEN) compop = "svg:screen";
0080     else if (layer->compositeOpId() == COMPOSITE_OVERLAY) compop = "svg:overlay";
0081     else if (layer->compositeOpId() == COMPOSITE_DARKEN) compop = "svg:darken";
0082     else if (layer->compositeOpId() == COMPOSITE_LIGHTEN) compop = "svg:lighten";
0083     else if (layer->compositeOpId() == COMPOSITE_DODGE) compop = "svg:color-dodge";
0084     else if (layer->compositeOpId() == COMPOSITE_BURN) compop = "svg:color-burn";
0085     else if (layer->compositeOpId() == COMPOSITE_HARD_LIGHT) compop = "svg:hard-light";
0086     else if (layer->compositeOpId() == COMPOSITE_SOFT_LIGHT_SVG) compop = "svg:soft-light";
0087     else if (layer->compositeOpId() == COMPOSITE_DIFF) compop = "svg:difference";
0088     else if (layer->compositeOpId() == COMPOSITE_COLOR) compop = "svg:color";
0089     else if (layer->compositeOpId() == COMPOSITE_LUMINIZE) compop = "svg:luminosity";
0090     else if (layer->compositeOpId() == COMPOSITE_HUE) compop = "svg:hue";
0091     else if (layer->compositeOpId() == COMPOSITE_SATURATION) compop = "svg:saturation";
0092     else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over";
0093 
0094     //else if (layer->compositeOpId() == COMPOSITE_EXCLUSION) compop = "svg:exclusion";
0095     else compop = "krita:" + layer->compositeOpId();
0096 
0097     // Alpha preserve has a special case with the Normal blend mode, which is
0098     // stored as src-atop for compatibility with previous Krita versions that
0099     // don't understand the alpha-preserve property, as well as other programs
0100     // like MyPaint that don't support alpha preserve on layers in general.
0101     if(layer->alphaChannelDisabled()) {
0102         if (layer->compositeOpId() == COMPOSITE_OVER) {
0103             compop = "svg:src-atop";
0104         } else {
0105             elt.setAttribute("alpha-preserve", "true");
0106         }
0107     }
0108 
0109     elt.setAttribute("composite-op", compop);
0110 }
0111 
0112 bool KisOpenRasterStackSaveVisitor::visit(KisPaintLayer *layer)
0113 {
0114     return saveLayer(layer);
0115 }
0116 
0117 bool KisOpenRasterStackSaveVisitor::visit(KisGeneratorLayer* layer)
0118 {
0119     return saveLayer(layer);
0120 }
0121 
0122 bool KisOpenRasterStackSaveVisitor::visit(KisGroupLayer *layer)
0123 {
0124     QDomElement previousElt = d->currentElement;
0125 
0126     QDomElement elt = d->layerStack.createElement("stack");
0127     d->currentElement = elt;
0128     saveLayerInfo(elt, layer);
0129     QString isolate = "isolate";
0130     if (layer->passThroughMode()) {
0131         isolate = "auto";
0132     }
0133     elt.setAttribute("isolation", isolate);
0134     visitAll(layer);
0135 
0136     if (!previousElt.isNull()) {
0137         previousElt.insertBefore(elt, QDomNode());
0138         d->currentElement = previousElt;
0139     } else {
0140         QDomElement imageElt = d->layerStack.createElement("image");
0141         int width = layer->image()->width();
0142         int height = layer->image()->height();
0143         int xRes = (int)(qRound(layer->image()->xRes() * 72));
0144         int yRes = (int)(qRound(layer->image()->yRes() * 72));
0145 
0146         imageElt.setAttribute("version", "0.0.1");
0147         imageElt.setAttribute("w", width);
0148         imageElt.setAttribute("h", height);
0149         imageElt.setAttribute("xres", xRes);
0150         imageElt.setAttribute("yres", yRes);
0151         imageElt.appendChild(elt);
0152         d->layerStack.insertBefore(imageElt, QDomNode());
0153         d->currentElement = QDomElement();
0154         d->saveContext->saveStack(d->layerStack);
0155     }
0156 
0157     return true;
0158 }
0159 
0160 bool KisOpenRasterStackSaveVisitor::visit(KisAdjustmentLayer *layer)
0161 {
0162     QDomElement elt = d->layerStack.createElement("filter");
0163     saveLayerInfo(elt, layer);
0164     elt.setAttribute("type", "applications:krita:" + layer->filter()->name());
0165     return true;
0166 }
0167 
0168 bool KisOpenRasterStackSaveVisitor::visit(KisCloneLayer *layer)
0169 {
0170     return saveLayer(layer);
0171 }
0172 
0173 bool KisOpenRasterStackSaveVisitor::visit(KisExternalLayer * layer)
0174 {
0175     return saveLayer(layer);
0176 }
0177 
0178 bool KisOpenRasterStackSaveVisitor::saveLayer(KisLayer *layer)
0179 {
0180     if (layer->isFakeNode()) {
0181         // don't save grids, reference images layers etc.
0182         return true;
0183     }
0184 
0185     // here we adjust the bounds to encompass the entire area of the layer, including transforms
0186     QRect adjustedBounds = layer->exactBounds();
0187 
0188     if (adjustedBounds.isEmpty()) {
0189         // in case of an empty layer, artificially increase the size of the saved rectangle
0190         // to just save an empty layer file
0191         adjustedBounds.adjust(0, 0, 1, 1);
0192     }
0193 
0194     QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), adjustedBounds, layer->image()->xRes(), layer->image()->yRes());
0195 
0196     QDomElement elt = d->layerStack.createElement("layer");
0197     saveLayerInfo(elt, layer);
0198     elt.setAttribute("src", filename);
0199     d->currentElement.insertBefore(elt, QDomNode());
0200 
0201     return true;
0202 }