1 // SPDX-License-Identifier: GPL-2.0-or-later
2 /** \file
3 * Inkscape::Extension::Internal::GimpGrad implementation
4 */
5
6 /*
7 * Authors:
8 * Ted Gould <ted@gould.cx>
9 * Abhishek Sharma
10 *
11 * Copyright (C) 2004-2005 Authors
12 *
13 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
14 */
15
16 #include <color-rgba.h>
17 #include "io/sys.h"
18 #include "extension/system.h"
19 #include "svg/css-ostringstream.h"
20 #include "svg/svg-color.h"
21
22 #include "gimpgrad.h"
23 #include "streq.h"
24 #include "strneq.h"
25 #include "document.h"
26 #include "extension/extension.h"
27
28 namespace Inkscape {
29 namespace Extension {
30 namespace Internal {
31
32 /**
33 \brief A function to allocate anything -- just an example here
34 \param module Unused
35 \return Whether the load was successful
36 */
load(Inkscape::Extension::Extension *)37 bool GimpGrad::load (Inkscape::Extension::Extension */*module*/)
38 {
39 // std::cout << "Hey, I'm loading!\n" << std::endl;
40 return TRUE;
41 }
42
43 /**
44 \brief A function to remove what was allocated
45 \param module Unused
46 \return None
47 */
unload(Inkscape::Extension::Extension *)48 void GimpGrad::unload (Inkscape::Extension::Extension */*module*/)
49 {
50 // std::cout << "Nooo! I'm being unloaded!" << std::endl;
51 return;
52 }
53
append_css_num(Glib::ustring & str,double const num)54 static void append_css_num(Glib::ustring &str, double const num)
55 {
56 CSSOStringStream stream;
57 stream << num;
58 str += stream.str();
59 }
60
61 /**
62 \brief A function to turn a color into a gradient stop
63 \param in_color The color for the stop
64 \param location Where the stop is placed in the gradient
65 \return The text that is the stop. Full SVG containing the element.
66
67 This function encapsulates all of the translation of the ColorRGBA
68 and the location into the gradient. It is really pretty simple except
69 that the ColorRGBA is in floats that are 0 to 1 and the SVG wants
70 hex values from 0 to 255 for color. Otherwise mostly this is just
71 turning the values into strings and returning it.
72 */
stop_svg(ColorRGBA const in_color,double const location)73 static Glib::ustring stop_svg(ColorRGBA const in_color, double const location)
74 {
75 Glib::ustring ret("<stop stop-color=\"");
76
77 char stop_color_css[16];
78 sp_svg_write_color(stop_color_css, sizeof(stop_color_css), in_color.getIntValue());
79 ret += stop_color_css;
80 ret += '"';
81
82 if (in_color[3] != 1) {
83 ret += " stop-opacity=\"";
84 append_css_num(ret, in_color[3]);
85 ret += '"';
86 }
87 ret += " offset=\"";
88 append_css_num(ret, location);
89 ret += "\"/>\n";
90 return ret;
91 }
92
93 /**
94 \brief Actually open the gradient and turn it into an SPDocument
95 \param module The input module being used
96 \param filename The filename of the gradient to be opened
97 \return A Document with the gradient in it.
98
99 GIMP gradients are pretty simple (atleast the newer format, this
100 function does not handle the old one yet). They start out with
101 the like "GIMP Gradient", then name it, and tell how many entries
102 there are. This function currently ignores the name and the number
103 of entries just reading until it fails.
104
105 The other small piece of trickery here is that GIMP gradients define
106 a left position, right position and middle position. SVG gradients
107 have no middle position in them. In order to handle this case the
108 left and right colors are averaged in a linear manner and the middle
109 position is used for that color.
110
111 That is another point, the GIMP gradients support many different types
112 of gradients -- linear being the most simple. This plugin assumes
113 that they are all linear. Most GIMP gradients are done this way,
114 but it is possible to encounter more complex ones -- which won't be
115 handled correctly.
116
117 The one optimization that this plugin makes that if the right side
118 of the previous segment is the same color as the left side of the
119 current segment, then the second one is dropped. This is often
120 done in GIMP gradients and they are not necissary in SVG.
121
122 What this function does is build up an SVG document with a single
123 linear gradient in it with all the stops of the colors in the GIMP
124 gradient that is passed in. This document is then turned into a
125 document using the \c sp_document_from_mem. That is then returned
126 to Inkscape.
127 */
128 SPDocument *
open(Inkscape::Extension::Input *,gchar const * filename)129 GimpGrad::open (Inkscape::Extension::Input */*module*/, gchar const *filename)
130 {
131 Inkscape::IO::dump_fopen_call(filename, "I");
132 FILE *gradient = Inkscape::IO::fopen_utf8name(filename, "r");
133 if (gradient == nullptr) {
134 return nullptr;
135 }
136
137 {
138 char tempstr[1024];
139 if (fgets(tempstr, 1024, gradient) == nullptr) {
140 // std::cout << "Seems that the read failed" << std::endl;
141 goto error;
142 }
143 if (!streq(tempstr, "GIMP Gradient\n")) {
144 // std::cout << "This doesn't appear to be a GIMP gradient" << std::endl;
145 goto error;
146 }
147
148 /* Name field. */
149 if (fgets(tempstr, 1024, gradient) == nullptr) {
150 // std::cout << "Seems that the second read failed" << std::endl;
151 goto error;
152 }
153 if (!strneq(tempstr, "Name: ", 6)) {
154 goto error;
155 }
156 /* Handle very long names. (And also handle nul bytes gracefully: don't use strlen.) */
157 while (memchr(tempstr, '\n', sizeof(tempstr) - 1) == nullptr) {
158 if (fgets(tempstr, sizeof(tempstr), gradient) == nullptr) {
159 goto error;
160 }
161 }
162
163 /* n. segments */
164 if (fgets(tempstr, 1024, gradient) == nullptr) {
165 // std::cout << "Seems that the third read failed" << std::endl;
166 goto error;
167 }
168 char *endptr = nullptr;
169 long const n_segs = strtol(tempstr, &endptr, 10);
170 if ((*endptr != '\n')
171 || n_segs < 1) {
172 /* SVG gradients are allowed to have zero stops (treated as `none'), but gimp 2.2
173 * requires at least one segment (i.e. at least two stops) (see gimp_gradient_load in
174 * gimpgradient-load.c). We try to use the same error handling as gimp, so that
175 * .ggr files that work in one program work in both programs. */
176 goto error;
177 }
178
179 ColorRGBA prev_color(-1.0, -1.0, -1.0, -1.0);
180 Glib::ustring outsvg("<svg><defs><linearGradient>\n");
181 long n_segs_found = 0;
182 double prev_right = 0.0;
183 while (fgets(tempstr, 1024, gradient) != nullptr) {
184 double dbls[3 + 4 + 4];
185 gchar *p = tempstr;
186 for (double & dbl : dbls) {
187 gchar *end = nullptr;
188 double const xi = g_ascii_strtod(p, &end);
189 if (!end || end == p || !g_ascii_isspace(*end)) {
190 goto error;
191 }
192 if (xi < 0 || 1 < xi) {
193 goto error;
194 }
195 dbl = xi;
196 p = end + 1;
197 }
198
199 double const left = dbls[0];
200 if (left != prev_right) {
201 goto error;
202 }
203 double const middle = dbls[1];
204 if (!(left <= middle)) {
205 goto error;
206 }
207 double const right = dbls[2];
208 if (!(middle <= right)) {
209 goto error;
210 }
211
212 ColorRGBA const leftcolor(dbls[3], dbls[4], dbls[5], dbls[6]);
213 ColorRGBA const rightcolor(dbls[7], dbls[8], dbls[9], dbls[10]);
214 g_assert(11 == G_N_ELEMENTS(dbls));
215
216 /* Interpolation enums: curve shape and colour space. */
217 {
218 /* TODO: Currently we silently ignore type & color, assuming linear interpolation in
219 * sRGB space (or whatever the default in SVG is). See gimp/app/core/gimpgradient.c
220 * for how gimp uses these. We could use gimp functions to sample at a few points, and
221 * add some intermediate stops to convert to the linear/sRGB interpolation */
222 int type; /* enum: linear, curved, sine, sphere increasing, sphere decreasing. */
223 int color_interpolation; /* enum: rgb, hsv anticlockwise, hsv clockwise. */
224 if (sscanf(p, "%8d %8d", &type, &color_interpolation) != 2) {
225 continue;
226 }
227 }
228
229 if (prev_color != leftcolor) {
230 outsvg += stop_svg(leftcolor, left);
231 }
232 if (fabs(middle - .5 * (left + right)) > 1e-4) {
233 outsvg += stop_svg(leftcolor.average(rightcolor), middle);
234 }
235 outsvg += stop_svg(rightcolor, right);
236
237 prev_color = rightcolor;
238 prev_right = right;
239 ++n_segs_found;
240 }
241 if (prev_right != 1.0) {
242 goto error;
243 }
244
245 if (n_segs_found != n_segs) {
246 goto error;
247 }
248
249 outsvg += "</linearGradient></defs></svg>";
250
251 // std::cout << "SVG Output: " << outsvg << std::endl;
252
253 fclose(gradient);
254
255 return SPDocument::createNewDocFromMem(outsvg.c_str(), outsvg.length(), TRUE);
256 }
257
258 error:
259 fclose(gradient);
260 return nullptr;
261 }
262
263 #include "clear-n_.h"
264
init()265 void GimpGrad::init ()
266 {
267 // clang-format off
268 Inkscape::Extension::build_from_mem(
269 "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\n"
270 "<name>" N_("GIMP Gradients") "</name>\n"
271 "<id>org.inkscape.input.gimpgrad</id>\n"
272 "<input>\n"
273 "<extension>.ggr</extension>\n"
274 "<mimetype>application/x-gimp-gradient</mimetype>\n"
275 "<filetypename>" N_("GIMP Gradient (*.ggr)") "</filetypename>\n"
276 "<filetypetooltip>" N_("Gradients used in GIMP") "</filetypetooltip>\n"
277 "</input>\n"
278 "</inkscape-extension>\n", new GimpGrad());
279 // clang-format on
280 return;
281 }
282
283 } } } /* namespace Internal; Extension; Inkscape */
284
285 /*
286 Local Variables:
287 mode:c++
288 c-file-style:"stroustrup"
289 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
290 indent-tabs-mode:nil
291 fill-column:99
292 End:
293 */
294 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :
295