Code

A simple layout document as to what, why and how is cppification.
[inkscape.git] / src / extension / internal / gimpgrad.cpp
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)
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 SPDocument::createNewDocFromMem(outsvg.c_str(), outsvg.length(), TRUE);
260     }
262 error:
263     fclose(gradient);
264     return NULL;
267 #include "clear-n_.h"
269 void
270 GimpGrad::init (void)
272     Inkscape::Extension::build_from_mem(
273         "<inkscape-extension xmlns=\"" INKSCAPE_EXTENSION_URI "\">\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;
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 :