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 }