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