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