1 /*
2  *  Copyright (c) 2006-2007,2009 Cyrille Berger <cberger@cberger.net>
3  *
4  *  This program is free software; you can redistribute it and/or modify
5  *  it under the terms of the GNU General Public License as published by
6  *  the Free Software Foundation; either version 2 of the License, or
7  *  (at your option) any later version.
8  *
9  *  This program is distributed in the hope that it will be useful,
10  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  *  GNU General Public License for more details.
13  *
14  *  You should have received a copy of the GNU General Public License
15  *  along with this program; if not, write to the Free Software
16  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18 
19 #include "kis_open_raster_stack_save_visitor.h"
20 
21 #include <math.h>
22 
23 #include <QDomElement>
24 #include <QImage>
25 
26 #include <KoCompositeOpRegistry.h>
27 
28 #include "kis_adjustment_layer.h"
29 #include "filter/kis_filter.h"
30 #include "filter/kis_filter_configuration.h"
31 #include "kis_group_layer.h"
32 #include "kis_paint_layer.h"
33 #include <generator/kis_generator_layer.h>
34 #include "kis_open_raster_save_context.h"
35 #include <kis_clone_layer.h>
36 #include <kis_external_layer_iface.h>
37 
38 struct KisOpenRasterStackSaveVisitor::Private {
PrivateKisOpenRasterStackSaveVisitor::Private39     Private() {}
40     KisOpenRasterSaveContext* saveContext;
41     QDomDocument layerStack;
42     QDomElement currentElement;
43     vKisNodeSP activeNodes;
44 };
45 
KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext * saveContext,vKisNodeSP activeNodes)46 KisOpenRasterStackSaveVisitor::KisOpenRasterStackSaveVisitor(KisOpenRasterSaveContext* saveContext, vKisNodeSP activeNodes)
47     : d(new Private)
48 {
49     d->saveContext = saveContext;
50     d->activeNodes = activeNodes;
51 }
52 
~KisOpenRasterStackSaveVisitor()53 KisOpenRasterStackSaveVisitor::~KisOpenRasterStackSaveVisitor()
54 {
55     delete d;
56 }
57 
saveLayerInfo(QDomElement & elt,KisLayer * layer)58 void KisOpenRasterStackSaveVisitor::saveLayerInfo(QDomElement& elt, KisLayer* layer)
59 {
60     elt.setAttribute("name", layer->name());
61     elt.setAttribute("opacity", QString().setNum(layer->opacity() / 255.0));
62     elt.setAttribute("visibility", layer->visible() ? "visible" : "hidden");
63 
64     if (layer->inherits("KisGroupLayer")) {
65         // Workaround for the issue regarding ora specification.
66         // MyPaint treats layer's x and y relative to the group's x and y
67         //  while Gimp and Krita think those are absolute values.
68         // Hence we set x and y on group layers to always be 0.
69         elt.setAttribute("x", QString().setNum(0));
70         elt.setAttribute("y", QString().setNum(0));
71 
72     } else {
73         elt.setAttribute("x", QString().setNum(layer->exactBounds().x()));
74         elt.setAttribute("y", QString().setNum(layer->exactBounds().y()));
75     }
76 
77     if (layer->userLocked()) {
78         elt.setAttribute("edit-locked", "true");
79     }
80     if (d->activeNodes.contains(layer)) {
81         elt.setAttribute("selected", "true");
82     }
83     QString compop = layer->compositeOpId();
84     if (layer->compositeOpId() == COMPOSITE_CLEAR) compop = "svg:clear";
85     else if (layer->compositeOpId() == COMPOSITE_ERASE) compop = "svg:dst-out";
86     else if (layer->compositeOpId() == COMPOSITE_DESTINATION_ATOP) compop = "svg:dst-atop";
87     else if (layer->compositeOpId() == COMPOSITE_DESTINATION_IN) compop = "svg:dst-in";
88     else if (layer->compositeOpId() == COMPOSITE_ADD) compop = "svg:plus";
89     else if (layer->compositeOpId() == COMPOSITE_MULT) compop = "svg:multiply";
90     else if (layer->compositeOpId() == COMPOSITE_SCREEN) compop = "svg:screen";
91     else if (layer->compositeOpId() == COMPOSITE_OVERLAY) compop = "svg:overlay";
92     else if (layer->compositeOpId() == COMPOSITE_DARKEN) compop = "svg:darken";
93     else if (layer->compositeOpId() == COMPOSITE_LIGHTEN) compop = "svg:lighten";
94     else if (layer->compositeOpId() == COMPOSITE_DODGE) compop = "svg:color-dodge";
95     else if (layer->compositeOpId() == COMPOSITE_BURN) compop = "svg:color-burn";
96     else if (layer->compositeOpId() == COMPOSITE_HARD_LIGHT) compop = "svg:hard-light";
97     else if (layer->compositeOpId() == COMPOSITE_SOFT_LIGHT_SVG) compop = "svg:soft-light";
98     else if (layer->compositeOpId() == COMPOSITE_DIFF) compop = "svg:difference";
99     else if (layer->compositeOpId() == COMPOSITE_COLOR) compop = "svg:color";
100     else if (layer->compositeOpId() == COMPOSITE_LUMINIZE) compop = "svg:luminosity";
101     else if (layer->compositeOpId() == COMPOSITE_HUE) compop = "svg:hue";
102     else if (layer->compositeOpId() == COMPOSITE_SATURATION) compop = "svg:saturation";
103 
104     // it is important that the check for alphaChannelDisabled (and other non compositeOpId checks)
105     // come before the check for COMPOSITE_OVER, otherwise they will be logically ignored.
106     else if (layer->alphaChannelDisabled()) compop = "svg:src-atop";
107     else if (layer->compositeOpId() == COMPOSITE_OVER) compop = "svg:src-over";
108 
109     //else if (layer->compositeOpId() == COMPOSITE_EXCLUSION) compop = "svg:exclusion";
110     else compop = "krita:" + layer->compositeOpId();
111     elt.setAttribute("composite-op", compop);
112 }
113 
visit(KisPaintLayer * layer)114 bool KisOpenRasterStackSaveVisitor::visit(KisPaintLayer *layer)
115 {
116     return saveLayer(layer);
117 }
118 
visit(KisGeneratorLayer * layer)119 bool KisOpenRasterStackSaveVisitor::visit(KisGeneratorLayer* layer)
120 {
121     return saveLayer(layer);
122 }
123 
visit(KisGroupLayer * layer)124 bool KisOpenRasterStackSaveVisitor::visit(KisGroupLayer *layer)
125 {
126     QDomElement previousElt = d->currentElement;
127 
128     QDomElement elt = d->layerStack.createElement("stack");
129     d->currentElement = elt;
130     saveLayerInfo(elt, layer);
131     QString isolate = "isolate";
132     if (layer->passThroughMode()) {
133         isolate = "auto";
134     }
135     elt.setAttribute("isolation", isolate);
136     visitAll(layer);
137 
138     if (!previousElt.isNull()) {
139         previousElt.insertBefore(elt, QDomNode());
140         d->currentElement = previousElt;
141     } else {
142         QDomElement imageElt = d->layerStack.createElement("image");
143         int width = layer->image()->width();
144         int height = layer->image()->height();
145         int xRes = (int)(qRound(layer->image()->xRes() * 72));
146         int yRes = (int)(qRound(layer->image()->yRes() * 72));
147 
148         imageElt.setAttribute("version", "0.0.1");
149         imageElt.setAttribute("w", width);
150         imageElt.setAttribute("h", height);
151         imageElt.setAttribute("xres", xRes);
152         imageElt.setAttribute("yres", yRes);
153         imageElt.appendChild(elt);
154         d->layerStack.insertBefore(imageElt, QDomNode());
155         d->currentElement = QDomElement();
156         d->saveContext->saveStack(d->layerStack);
157     }
158 
159     return true;
160 }
161 
visit(KisAdjustmentLayer * layer)162 bool KisOpenRasterStackSaveVisitor::visit(KisAdjustmentLayer *layer)
163 {
164     QDomElement elt = d->layerStack.createElement("filter");
165     saveLayerInfo(elt, layer);
166     elt.setAttribute("type", "applications:krita:" + layer->filter()->name());
167     return true;
168 }
169 
visit(KisCloneLayer * layer)170 bool KisOpenRasterStackSaveVisitor::visit(KisCloneLayer *layer)
171 {
172     return saveLayer(layer);
173 }
174 
visit(KisExternalLayer * layer)175 bool KisOpenRasterStackSaveVisitor::visit(KisExternalLayer * layer)
176 {
177     return saveLayer(layer);
178 }
179 
saveLayer(KisLayer * layer)180 bool KisOpenRasterStackSaveVisitor::saveLayer(KisLayer *layer)
181 {
182     if (layer->isFakeNode()) {
183         // don't save grids, reference images layers etc.
184         return true;
185     }
186 
187     // here we adjust the bounds to encompass the entire area of the layer, including transforms
188     QRect adjustedBounds = layer->exactBounds();
189 
190     if (adjustedBounds.isEmpty()) {
191         // in case of an empty layer, artificially increase the size of the saved rectangle
192         // to just save an empty layer file
193         adjustedBounds.adjust(0, 0, 1, 1);
194     }
195 
196     QString filename = d->saveContext->saveDeviceData(layer->projection(), layer->metaData(), adjustedBounds, layer->image()->xRes(), layer->image()->yRes());
197 
198     QDomElement elt = d->layerStack.createElement("layer");
199     saveLayerInfo(elt, layer);
200     elt.setAttribute("src", filename);
201     d->currentElement.insertBefore(elt, QDomNode());
202 
203     return true;
204 }
205