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