Code

No more NRMatrix or NRPoint.
[inkscape.git] / src / sp-image.cpp
1 #define __SP_IMAGE_C__
3 /*
4  * SVG <image> implementation
5  *
6  * Authors:
7  *   Lauris Kaplinski <lauris@kaplinski.com>
8  *   Edward Flick (EAF)
9  *
10  * Copyright (C) 1999-2005 Authors
11  * Copyright (C) 2000-2001 Ximian, Inc.
12  *
13  * Released under GNU GPL, read the file 'COPYING' for more information
14  */
16 #ifdef HAVE_CONFIG_H
17 # include "config.h"
18 #endif
20 #include <cstring>
21 #include <string>
22 #include <libnr/nr-matrix-fns.h>
23 #include <libnr/nr-matrix-ops.h>
24 #include <libnr/nr-translate-matrix-ops.h>
25 #include <libnr/nr-scale-translate-ops.h>
26 //#define GDK_PIXBUF_ENABLE_BACKEND 1
27 //#include <gdk-pixbuf/gdk-pixbuf-io.h>
28 #include "display/nr-arena-image.h"
29 #include <display/curve.h>
31 //Added for preserveAspectRatio support -- EAF
32 #include "enums.h"
33 #include "attributes.h"
35 #include "print.h"
36 #include "brokenimage.xpm"
37 #include "document.h"
38 #include "sp-image.h"
39 #include "sp-clippath.h"
40 #include <glibmm/i18n.h>
41 #include "xml/quote.h"
42 #include <xml/repr.h>
44 #include "libnr/nr-matrix-fns.h"
46 #include "io/sys.h"
47 #include <png.h>
48 #if ENABLE_LCMS
49 #include "color-profile-fns.h"
50 #include "color-profile.h"
51 //#define DEBUG_LCMS
52 #ifdef DEBUG_LCMS
53 #include "prefs-utils.h"
54 #include <gtk/gtkmessagedialog.h>
55 #endif // DEBUG_LCMS
56 #endif // ENABLE_LCMS
57 /*
58  * SPImage
59  */
62 static void sp_image_class_init (SPImageClass * klass);
63 static void sp_image_init (SPImage * image);
65 static void sp_image_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
66 static void sp_image_release (SPObject * object);
67 static void sp_image_set (SPObject *object, unsigned int key, const gchar *value);
68 static void sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags);
69 static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags);
71 static void sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
72 static void sp_image_print (SPItem * item, SPPrintContext *ctx);
73 static gchar * sp_image_description (SPItem * item);
74 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p);
75 static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags);
76 static NR::Matrix sp_image_set_transform (SPItem *item, NR::Matrix const &xform);
77 static void sp_image_set_curve(SPImage *image);
80 GdkPixbuf *sp_image_repr_read_image (const gchar *href, const gchar *absref, const gchar *base);
81 static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf);
82 static void sp_image_update_canvas_image (SPImage *image);
83 static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data);
84 static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data);
86 static SPItemClass *parent_class;
89 extern "C"
90 {
91     void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length );
92     void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length );
93     void user_flush_data( png_structp png_ptr );
95 }
98 #ifdef DEBUG_LCMS
99 extern guint update_in_progress;
100 #define DEBUG_MESSAGE(key, ...) \
101 {\
102     gint dump = prefs_get_int_attribute_limited("options.scislac", #key, 0, 0, 1);\
103     gint dumpD = prefs_get_int_attribute_limited("options.scislac", #key"D", 0, 0, 1);\
104     gint dumpD2 = prefs_get_int_attribute_limited("options.scislac", #key"D2", 0, 0, 1);\
105     dumpD &= ( (update_in_progress == 0) || dumpD2 );\
106     if ( dump )\
107     {\
108         g_message( __VA_ARGS__ );\
110     }\
111     if ( dumpD )\
112     {\
113         GtkWidget *dialog = gtk_message_dialog_new(NULL,\
114                                                    GTK_DIALOG_DESTROY_WITH_PARENT, \
115                                                    GTK_MESSAGE_INFO,    \
116                                                    GTK_BUTTONS_OK,      \
117                                                    __VA_ARGS__          \
118                                                    );\
119         g_signal_connect_swapped(dialog, "response",\
120                                  G_CALLBACK(gtk_widget_destroy),        \
121                                  dialog);                               \
122         gtk_widget_show_all( dialog );\
123     }\
125 #endif // DEBUG_LCMS
127 namespace Inkscape {
128 namespace IO {
130 class PushPull
132 public:
133     gboolean    first;
134     FILE*       fp;
135     guchar*     scratch;
136     gsize       size;
137     gsize       used;
138     gsize       offset;
139     GdkPixbufLoader *loader;
141     PushPull() : first(TRUE),
142                  fp(0),
143                  scratch(0),
144                  size(0),
145                  used(0),
146                  offset(0),
147                  loader(0) {};
149     gboolean readMore()
150     {
151         gboolean good = FALSE;
152         if ( offset )
153         {
154             g_memmove( scratch, scratch + offset, used - offset );
155             used -= offset;
156             offset = 0;
157         }
158         if ( used < size )
159         {
160             gsize space = size - used;
161             gsize got = fread( scratch + used, 1, space, fp );
162             if ( got )
163             {
164                 if ( loader )
165                 {
166                     GError *err = NULL;
167                     //g_message( " __read %d bytes", (int)got );
168                     if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) )
169                     {
170                         //g_message("_error writing pixbuf data");
171                     }
172                 }
174                 used += got;
175                 good = TRUE;
176             }
177             else
178             {
179                 good = FALSE;
180             }
181         }
182         return good;
183     }
185     gsize available() const
186     {
187         return (used - offset);
188     }
190     gsize readOut( gpointer data, gsize length )
191     {
192         gsize giving = available();
193         if ( length < giving )
194         {
195             giving = length;
196         }
197         g_memmove( data, scratch + offset, giving );
198         offset += giving;
199         if ( offset >= used )
200         {
201             offset = 0;
202             used = 0;
203         }
204         return giving;
205     }
207     void clear()
208     {
209         offset = 0;
210         used = 0;
211     }
213 private:
214     PushPull& operator = (const PushPull& other);
215     PushPull(const PushPull& other);
216 };
218 void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length )
220 //    g_message( "user_read_data(%d)", length );
222     PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr);
224     gsize filled = 0;
225     gboolean canRead = TRUE;
227     while ( filled < length && canRead )
228     {
229         gsize some = youme->readOut( data + filled, length - filled );
230         filled += some;
231         if ( filled < length )
232         {
233             canRead &= youme->readMore();
234         }
235     }
236 //    g_message("things out");
239 void user_write_data( png_structp /*png_ptr*/, png_bytep /*data*/, png_size_t /*length*/ )
241     //g_message( "user_write_data(%d)", length );
244 void user_flush_data( png_structp /*png_ptr*/ )
246     //g_message( "user_flush_data" );
249 GdkPixbuf*  pixbuf_new_from_file( const char *filename, GError **/*error*/ )
251     GdkPixbuf* buf = NULL;
252     PushPull youme;
253     gint dpiX = 0;
254     gint dpiY = 0;
256     //buf = gdk_pixbuf_new_from_file( filename, error );
257     dump_fopen_call( filename, "pixbuf_new_from_file" );
258     FILE* fp = fopen_utf8name( filename, "r" );
259     if ( fp )
260     {
261         GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
262         if ( loader )
263         {
264             GError *err = NULL;
266             // short buffer
267             guchar scratch[1024];
268             gboolean latter = FALSE;
269             gboolean isPng = FALSE;
270             png_structp pngPtr = NULL;
271             png_infop infoPtr = NULL;
272             //png_infop endPtr = NULL;
274             youme.fp = fp;
275             youme.scratch = scratch;
276             youme.size = sizeof(scratch);
277             youme.used = 0;
278             youme.offset = 0;
279             youme.loader = loader;
281             while ( !feof(fp) )
282             {
283                 if ( youme.readMore() )
284                 {
285                     if ( youme.first )
286                     {
287                         //g_message( "First data chunk" );
288                         youme.first = FALSE;
289                         isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() );
290                         //g_message( "  png? %s", (isPng ? "Yes":"No") );
291                         if ( isPng )
292                         {
293                             pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
294                                                              NULL,//(png_voidp)user_error_ptr,
295                                                              NULL,//user_error_fn,
296                                                              NULL//user_warning_fn
297                                 );
298                             if ( pngPtr )
299                             {
300                                 infoPtr = png_create_info_struct( pngPtr );
301                                 //endPtr = png_create_info_struct( pngPtr );
303                                 png_set_read_fn( pngPtr, &youme, user_read_data );
304                                 //g_message( "In" );
306                                 //png_read_info( pngPtr, infoPtr );
307                                 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL );
309                                 //g_message("out");
311                                 //png_read_end(pngPtr, endPtr);
313                                 /*
314                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
315                                 {
316                                     g_message("pHYs chunk now valid" );
317                                 }
318                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
319                                 {
320                                     g_message("sCAL chunk now valid" );
321                                 }
322                                 */
324                                 png_uint_32 res_x = 0;
325                                 png_uint_32 res_y = 0;
326                                 int unit_type = 0;
327                                 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) )
328                                 {
329 //                                     g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
330 //                                                (unit_type == 1? "per meter" : "unknown")
331 //                                         );
333 //                                     g_message( "    dpi: (%d, %d)",
334 //                                                (int)(0.5 + ((double)res_x)/39.37),
335 //                                                (int)(0.5 + ((double)res_y)/39.37) );
336                                     if ( unit_type == PNG_RESOLUTION_METER )
337                                     {
338                                         // TODO come up with a more accurate DPI setting
339                                         dpiX = (int)(0.5 + ((double)res_x)/39.37);
340                                         dpiY = (int)(0.5 + ((double)res_y)/39.37);
341                                     }
342                                 }
343                                 else
344                                 {
345 //                                     g_message( "pHYs no" );
346                                 }
348 /*
349                                 double width = 0;
350                                 double height = 0;
351                                 int unit = 0;
352                                 if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
353                                 {
354                                     gchar* vals[] = {
355                                         "unknown", // PNG_SCALE_UNKNOWN
356                                         "meter", // PNG_SCALE_METER
357                                         "radian", // PNG_SCALE_RADIAN
358                                         "last", //
359                                         NULL
360                                     };
362                                     g_message( "sCAL: (%f, %f) %d (%s)",
363                                                width, height, unit,
364                                                ((unit >= 0 && unit < 3) ? vals[unit]:"???")
365                                         );
366                                 }
367 */
369 #if defined(PNG_sRGB_SUPPORTED)
370                                 {
371                                     int intent = 0;
372                                     if ( png_get_sRGB(pngPtr, infoPtr, &intent) ) {
373 //                                         g_message("Found an sRGB png chunk");
374                                     }
375                                 }
376 #endif // defined(PNG_sRGB_SUPPORTED)
378 #if defined(PNG_cHRM_SUPPORTED)
379                                 {
380                                     double white_x = 0;
381                                     double white_y = 0;
382                                     double red_x = 0;
383                                     double red_y = 0;
384                                     double green_x = 0;
385                                     double green_y = 0;
386                                     double blue_x = 0;
387                                     double blue_y = 0;
389                                     if ( png_get_cHRM(pngPtr, infoPtr,
390                                                       &white_x, &white_y,
391                                                       &red_x, &red_y,
392                                                       &green_x, &green_y,
393                                                       &blue_x, &blue_y) ) {
394 //                                         g_message("Found a cHRM png chunk");
395                                     }
396                                 }
397 #endif // defined(PNG_cHRM_SUPPORTED)
399 #if defined(PNG_gAMA_SUPPORTED)
400                                 {
401                                     double file_gamma = 0;
402                                     if ( png_get_gAMA(pngPtr, infoPtr, &file_gamma) ) {
403 //                                         g_message("Found a gAMA png chunk");
404                                     }
405                                 }
406 #endif // defined(PNG_gAMA_SUPPORTED)
408 #if defined(PNG_iCCP_SUPPORTED)
409                                 {
410                                     char* name = 0;
411                                     int compression_type = 0;
412                                     char* profile = 0;
413                                     png_uint_32 proflen = 0;
414                                     if ( png_get_iCCP(pngPtr, infoPtr, &name, &compression_type, &profile, &proflen) ) {
415 //                                         g_message("Found an iCCP chunk named [%s] with %d bytes and comp %d", name, proflen, compression_type);
416                                     }
417                                 }
418 #endif // defined(PNG_iCCP_SUPPORTED)
421                                 // now clean it up.
422                                 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr );
423                             }
424                             else
425                             {
426 //                                 g_message("Error when creating PNG read struct");
427                             }
428                         }
429                     }
430                     else if ( !latter )
431                     {
432                         latter = TRUE;
433                         //g_message("  READing latter");
434                     }
435                     // Now clear out the buffer so we can read more.
436                     // (dumping out unused)
437                     youme.clear();
438                 }
439             }
441             gboolean ok = gdk_pixbuf_loader_close(loader, &err);
442             if ( ok )
443             {
444                 buf = gdk_pixbuf_loader_get_pixbuf( loader );
445                 if ( buf )
446                 {
447                     g_object_ref(buf);
449                     if ( dpiX )
450                     {
451                         gchar *tmp = g_strdup_printf( "%d", dpiX );
452                         if ( tmp )
453                         {
454 //                             g_message("Need to set DpiX: %s", tmp);
455                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
456                             g_free( tmp );
457                         }
458                     }
459                     if ( dpiY )
460                     {
461                         gchar *tmp = g_strdup_printf( "%d", dpiY );
462                         if ( tmp )
463                         {
464 //                             g_message("Need to set DpiY: %s", tmp);
465                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
466                             g_free( tmp );
467                         }
468                     }
469                 }
470             }
471             else
472             {
473                 // do something
474                 g_message("error loading pixbuf at close");
475             }
477             g_object_unref(loader);
478         }
479         else
480         {
481             g_message("error when creating pixbuf loader");
482         }
483         fclose( fp );
484         fp = NULL;
485     }
486     else
487     {
488         g_warning ("Unable to open linked file: %s", filename);
489     }
491 /*
492     if ( buf )
493     {
494         const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
495         if ( bloop )
496         {
497             g_message("DPI X is [%s]", bloop);
498         }
499         bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
500         if ( bloop )
501         {
502             g_message("DPI Y is [%s]", bloop);
503         }
504     }
505 */
507     return buf;
513 GType
514 sp_image_get_type (void)
516         static GType image_type = 0;
517         if (!image_type) {
518                 GTypeInfo image_info = {
519                         sizeof (SPImageClass),
520                         NULL,   /* base_init */
521                         NULL,   /* base_finalize */
522                         (GClassInitFunc) sp_image_class_init,
523                         NULL,   /* class_finalize */
524                         NULL,   /* class_data */
525                         sizeof (SPImage),
526                         16,     /* n_preallocs */
527                         (GInstanceInitFunc) sp_image_init,
528                         NULL,   /* value_table */
529                 };
530                 image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
531         }
532         return image_type;
535 static void
536 sp_image_class_init (SPImageClass * klass)
538         GObjectClass * gobject_class;
539         SPObjectClass * sp_object_class;
540         SPItemClass * item_class;
542         gobject_class = (GObjectClass *) klass;
543         sp_object_class = (SPObjectClass *) klass;
544         item_class = (SPItemClass *) klass;
546         parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
548         sp_object_class->build = sp_image_build;
549         sp_object_class->release = sp_image_release;
550         sp_object_class->set = sp_image_set;
551         sp_object_class->update = sp_image_update;
552         sp_object_class->write = sp_image_write;
554         item_class->bbox = sp_image_bbox;
555         item_class->print = sp_image_print;
556         item_class->description = sp_image_description;
557         item_class->show = sp_image_show;
558         item_class->snappoints = sp_image_snappoints;
559         item_class->set_transform = sp_image_set_transform;
562 static void
563 sp_image_init (SPImage *image)
565         image->x.unset();
566         image->y.unset();
567         image->width.unset();
568         image->height.unset();
569         image->aspect_align = SP_ASPECT_NONE;
570         image->curve = NULL;
573 static void
574 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
576         if (((SPObjectClass *) parent_class)->build)
577                 ((SPObjectClass *) parent_class)->build (object, document, repr);
579         sp_object_read_attr (object, "xlink:href");
580         sp_object_read_attr (object, "x");
581         sp_object_read_attr (object, "y");
582         sp_object_read_attr (object, "width");
583         sp_object_read_attr (object, "height");
584         sp_object_read_attr (object, "preserveAspectRatio");
585         sp_object_read_attr (object, "color-profile");
587         /* Register */
588         sp_document_add_resource (document, "image", object);
591 static void
592 sp_image_release (SPObject *object)
594         SPImage *image;
596         image = SP_IMAGE (object);
598         if (SP_OBJECT_DOCUMENT (object)) {
599                 /* Unregister ourselves */
600                 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
601         }
603         if (image->href) {
604                 g_free (image->href);
605                 image->href = NULL;
606         }
608         if (image->pixbuf) {
609                 gdk_pixbuf_unref (image->pixbuf);
610                 image->pixbuf = NULL;
611         }
613 #if ENABLE_LCMS
614         if (image->color_profile) {
615                 g_free (image->color_profile);
616                 image->color_profile = NULL;
617         }
618 #endif // ENABLE_LCMS
620     if (image->curve) {
621                 image->curve = sp_curve_unref (image->curve);
622         }
624         if (((SPObjectClass *) parent_class)->release)
625                 ((SPObjectClass *) parent_class)->release (object);
628 static void
629 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
631         SPImage *image;
633         image = SP_IMAGE (object);
635         switch (key) {
636         case SP_ATTR_XLINK_HREF:
637                 g_free (image->href);
638                 image->href = (value) ? g_strdup (value) : NULL;
639                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
640                 break;
641         case SP_ATTR_X:
642                 if (!image->x.readAbsolute(value)) {
643                     /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
644                         image->x.unset();
645                 }
646                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
647                 break;
648         case SP_ATTR_Y:
649                 if (!image->y.readAbsolute(value)) {
650                     /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
651                         image->y.unset();
652                 }
653                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
654                 break;
655         case SP_ATTR_WIDTH:
656                 if (!image->width.readAbsolute(value)) {
657                     /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
658                         image->width.unset();
659                 }
660                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
661                 break;
662         case SP_ATTR_HEIGHT:
663                 if (!image->height.readAbsolute(value)) {
664                     /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
665                         image->height.unset();
666                 }
667                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
668                 break;
669         case SP_ATTR_PRESERVEASPECTRATIO:
670                 /* Do setup before, so we can use break to escape */
671                 image->aspect_align = SP_ASPECT_NONE;
672                 image->aspect_clip = SP_ASPECT_MEET;
673                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
674                 if (value) {
675                         int len;
676                         gchar c[256];
677                         const gchar *p, *e;
678                         unsigned int align, clip;
679                         p = value;
680                         while (*p && *p == 32) p += 1;
681                         if (!*p) break;
682                         e = p;
683                         while (*e && *e != 32) e += 1;
684                         len = e - p;
685                         if (len > 8) break;
686                         memcpy (c, value, len);
687                         c[len] = 0;
688                         /* Now the actual part */
689                         if (!strcmp (c, "none")) {
690                                 align = SP_ASPECT_NONE;
691                         } else if (!strcmp (c, "xMinYMin")) {
692                                 align = SP_ASPECT_XMIN_YMIN;
693                         } else if (!strcmp (c, "xMidYMin")) {
694                                 align = SP_ASPECT_XMID_YMIN;
695                         } else if (!strcmp (c, "xMaxYMin")) {
696                                 align = SP_ASPECT_XMAX_YMIN;
697                         } else if (!strcmp (c, "xMinYMid")) {
698                                 align = SP_ASPECT_XMIN_YMID;
699                         } else if (!strcmp (c, "xMidYMid")) {
700                                 align = SP_ASPECT_XMID_YMID;
701                         } else if (!strcmp (c, "xMaxYMid")) {
702                                 align = SP_ASPECT_XMAX_YMID;
703                         } else if (!strcmp (c, "xMinYMax")) {
704                                 align = SP_ASPECT_XMIN_YMAX;
705                         } else if (!strcmp (c, "xMidYMax")) {
706                                 align = SP_ASPECT_XMID_YMAX;
707                         } else if (!strcmp (c, "xMaxYMax")) {
708                                 align = SP_ASPECT_XMAX_YMAX;
709                         } else {
710                                 break;
711                         }
712                         clip = SP_ASPECT_MEET;
713                         while (*e && *e == 32) e += 1;
714                         if (e) {
715                                 if (!strcmp (e, "meet")) {
716                                         clip = SP_ASPECT_MEET;
717                                 } else if (!strcmp (e, "slice")) {
718                                         clip = SP_ASPECT_SLICE;
719                                 } else {
720                                         break;
721                                 }
722                         }
723                         image->aspect_align = align;
724                         image->aspect_clip = clip;
725                 }
726                 break;
727 #if ENABLE_LCMS
728         case SP_PROP_COLOR_PROFILE:
729                 if ( image->color_profile ) {
730                     g_free (image->color_profile);
731                 }
732                 image->color_profile = (value) ? g_strdup (value) : NULL;
733 #ifdef DEBUG_LCMS
734                 if ( value ) {
735                     DEBUG_MESSAGE( lcmsFour, "<image> color-profile set to '%s'", value );
736                 } else {
737                     DEBUG_MESSAGE( lcmsFour, "<image> color-profile cleared" );
738                 }
739 #endif // DEBUG_LCMS
740                 // TODO check on this HREF_MODIFIED flag
741                 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
742                 break;
743 #endif // ENABLE_LCMS
744         default:
745                 if (((SPObjectClass *) (parent_class))->set)
746                         ((SPObjectClass *) (parent_class))->set (object, key, value);
747                 break;
748         }
749         
750         sp_image_set_curve(image); //creates a curve at the image's boundary for snapping
753 static void
754 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
756     SPImage *image;
758     image = (SPImage *) object;
759     SPDocument *doc = SP_OBJECT_DOCUMENT(object);
761         if (((SPObjectClass *) (parent_class))->update)
762                 ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
764         if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
765                 if (image->pixbuf) {
766                         gdk_pixbuf_unref (image->pixbuf);
767                         image->pixbuf = NULL;
768                 }
769                 if (image->href) {
770                         GdkPixbuf *pixbuf;
771                         pixbuf = sp_image_repr_read_image (
772                     object->repr->attribute("xlink:href"),
773                     object->repr->attribute("sodipodi:absref"),
774                     doc->base);
775                         if (pixbuf) {
776                                 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
777 // BLIP
778 #if ENABLE_LCMS
779                                 if ( image->color_profile )
780                                 {
781                                     int imagewidth = gdk_pixbuf_get_width( pixbuf );
782                                     int imageheight = gdk_pixbuf_get_height( pixbuf );
783                                     int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
784                                     guchar* px = gdk_pixbuf_get_pixels( pixbuf );
786                                     if ( px ) {
787 #ifdef DEBUG_LCMS
788                                         DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
789 #endif // DEBUG_LCMS
790                                         guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
791                                         cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
792                                                                                               &profIntent,
793                                                                                               image->color_profile );
794                                         if ( prof ) {
795                                             icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
796                                             if ( profileClass != icSigNamedColorClass ) {
797                                                 int intent = INTENT_PERCEPTUAL;
798                                                 switch ( profIntent ) {
799                                                     case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
800                                                         intent = INTENT_RELATIVE_COLORIMETRIC;
801                                                         break;
802                                                     case Inkscape::RENDERING_INTENT_SATURATION:
803                                                         intent = INTENT_SATURATION;
804                                                         break;
805                                                     case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
806                                                         intent = INTENT_ABSOLUTE_COLORIMETRIC;
807                                                         break;
808                                                     case Inkscape::RENDERING_INTENT_PERCEPTUAL:
809                                                     case Inkscape::RENDERING_INTENT_UNKNOWN:
810                                                     case Inkscape::RENDERING_INTENT_AUTO:
811                                                     default:
812                                                         intent = INTENT_PERCEPTUAL;
813                                                 }
814                                                 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
815                                                 cmsHTRANSFORM transf = cmsCreateTransform( prof,
816                                                                                            TYPE_RGBA_8,
817                                                                                            destProf,
818                                                                                            TYPE_RGBA_8,
819                                                                                            intent, 0 );
820                                                 if ( transf ) {
821                                                     guchar* currLine = px;
822                                                     for ( int y = 0; y < imageheight; y++ ) {
823                                                         // Since the types are the same size, we can do the transformation in-place
824                                                         cmsDoTransform( transf, currLine, currLine, imagewidth );
825                                                         currLine += rowstride;
826                                                     }
828                                                     cmsDeleteTransform( transf );
829                                                 }
830 #ifdef DEBUG_LCMS
831                                                 else
832                                                 {
833                                                     DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
834                                                 }
835 #endif // DEBUG_LCMS
836                                                 cmsCloseProfile( destProf );
837                                             }
838 #ifdef DEBUG_LCMS
839                                             else
840                                             {
841                                                 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
842                                             }
843 #endif // DEBUG_LCMS
844                                         }
845 #ifdef DEBUG_LCMS
846                                         else
847                                         {
848                                             DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
849                                         }
850 #endif // DEBUG_LCMS
851                                     }
852                                 }
853 #endif // ENABLE_LCMS
854                                 image->pixbuf = pixbuf;
855                         }
856                 }
857         }
858         // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
859         if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
860                         int imagewidth, imageheight;
861                         double x,y;
863                         imagewidth = gdk_pixbuf_get_width (image->pixbuf);
864                         imageheight = gdk_pixbuf_get_height (image->pixbuf);
866                         switch (image->aspect_align) {
867                         case SP_ASPECT_XMIN_YMIN:
868                                 x = 0.0;
869                                 y = 0.0;
870                                 break;
871                         case SP_ASPECT_XMID_YMIN:
872                                 x = 0.5;
873                                 y = 0.0;
874                                 break;
875                         case SP_ASPECT_XMAX_YMIN:
876                                 x = 1.0;
877                                 y = 0.0;
878                                 break;
879                         case SP_ASPECT_XMIN_YMID:
880                                 x = 0.0;
881                                 y = 0.5;
882                                 break;
883                         case SP_ASPECT_XMID_YMID:
884                                 x = 0.5;
885                                 y = 0.5;
886                                 break;
887                         case SP_ASPECT_XMAX_YMID:
888                                 x = 1.0;
889                                 y = 0.5;
890                                 break;
891                         case SP_ASPECT_XMIN_YMAX:
892                                 x = 0.0;
893                                 y = 1.0;
894                                 break;
895                         case SP_ASPECT_XMID_YMAX:
896                                 x = 0.5;
897                                 y = 1.0;
898                                 break;
899                         case SP_ASPECT_XMAX_YMAX:
900                                 x = 1.0;
901                                 y = 1.0;
902                                 break;
903                         default:
904                                 x = 0.0;
905                                 y = 0.0;
906                                 break;
907                         }
909                         if (image->aspect_clip == SP_ASPECT_SLICE) {
910                                 image->viewx = image->x.computed;
911                                 image->viewy = image->y.computed;
912                                 image->viewwidth = image->width.computed;
913                                 image->viewheight = image->height.computed;
914                                 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
915                                         // Pixels aspect is wider than bounding box
916                                         image->trimheight = imageheight;
917                                         image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
918                                         image->trimy = 0;
919                                         image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
920                                 } else {
921                                         // Pixels aspect is taller than bounding box
922                                         image->trimwidth = imagewidth;
923                                         image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
924                                         image->trimx = 0;
925                                         image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
926                                 }
927                         } else {
928                                 // Otherwise, assume SP_ASPECT_MEET
929                                 image->trimx = 0;
930                                 image->trimy = 0;
931                                 image->trimwidth = imagewidth;
932                                 image->trimheight = imageheight;
933                                 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
934                                         // Pixels aspect is wider than bounding boz
935                                         image->viewwidth = image->width.computed;
936                                         image->viewheight = image->viewwidth * imageheight / imagewidth;
937                                         image->viewx=image->x.computed;
938                                         image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
939                                 } else {
940                                         // Pixels aspect is taller than bounding box
941                                         image->viewheight = image->height.computed;
942                                         image->viewwidth = image->viewheight * imagewidth / imageheight;
943                                         image->viewy=image->y.computed;
944                                         image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
945                                 }
946                         }
947         }
948         sp_image_update_canvas_image ((SPImage *) object);
951 static Inkscape::XML::Node *
952 sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
954         SPImage *image;
956         image = SP_IMAGE (object);
958         if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
959                 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
960                 repr = xml_doc->createElement("svg:image");
961         }
963         repr->setAttribute("xlink:href", image->href);
964         /* fixme: Reset attribute if needed (Lauris) */
965         if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed);
966         if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed);
967         if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed);
968         if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed);
969         repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
970 #if ENABLE_LCMS
971         if (image->color_profile) repr->setAttribute("color-profile", image->color_profile);
972 #endif // ENABLE_LCMS
974         if (((SPObjectClass *) (parent_class))->write)
975                 ((SPObjectClass *) (parent_class))->write (object, repr, flags);
977         return repr;
980 static void
981 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const /*flags*/)
983         SPImage const &image = *SP_IMAGE(item);
985         if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
986                 double const x0 = image.x.computed;
987                 double const y0 = image.y.computed;
988                 double const x1 = x0 + image.width.computed;
989                 double const y1 = y0 + image.height.computed;
991                 nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform);
992                 nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform);
993                 nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform);
994                 nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform);
995         }
998 static void
999 sp_image_print (SPItem *item, SPPrintContext *ctx)
1001         SPImage *image;
1002         guchar *px;
1003         int w, h, rs, pixskip;
1005         image = SP_IMAGE (item);
1007         if (!image->pixbuf) return;
1008         if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return;
1010         px = gdk_pixbuf_get_pixels (image->pixbuf);
1011         w = gdk_pixbuf_get_width (image->pixbuf);
1012         h = gdk_pixbuf_get_height (image->pixbuf);
1013         rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1014         pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1016     NR::Matrix t;
1017         if (image->aspect_align == SP_ASPECT_NONE) {
1018                 /* fixme: (Lauris) */
1019         NR::translate tp = NR::translate(image->x.computed, image->y.computed);
1020         NR::scale s = NR::scale(image->width.computed, -image->height.computed);
1021         NR::translate ti = NR::translate(0.0, -1.0);
1022             t = s * tp;
1023             t = ti * t;
1024         } else { // preserveAspectRatio
1025         NR::translate tp = NR::translate(image->viewx, image->viewy);
1026         NR::scale s = NR::scale(image->viewwidth, -image->viewheight);
1027         NR::translate ti = NR::translate(0.0, -1.0);
1028             t = s * tp;
1029             t = ti * t;
1030         }
1032         if (image->aspect_align == SP_ASPECT_NONE)
1033                 sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1034         else // preserveAspectRatio
1035                 sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item));
1038 static gchar *
1039 sp_image_description(SPItem *item)
1041         SPImage *image = SP_IMAGE(item);
1042         char *href_desc;
1043         if (image->href) {
1044             href_desc = (strncmp(image->href, "data:", 5) == 0)
1045                 ? g_strdup(_("embedded"))
1046                 : xml_quote_strdup(image->href);
1047         } else {
1048             g_warning("Attempting to call strncmp() with a null pointer.");
1049             href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1050         }
1052         char *ret = ( image->pixbuf == NULL
1053                       ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1054                       : g_strdup_printf(_("<b>Image</b> %d &#215; %d: %s"),
1055                                         gdk_pixbuf_get_width(image->pixbuf),
1056                                         gdk_pixbuf_get_height(image->pixbuf),
1057                                         href_desc) );
1058         g_free(href_desc);
1059         return ret;
1062 static NRArenaItem *
1063 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1065         int pixskip, rs;
1066         SPImage * image;
1067         NRArenaItem *ai;
1069         image = (SPImage *) item;
1071         ai = NRArenaImage::create(arena);
1073         if (image->pixbuf) {
1074                 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1075                 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1076                 nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1077                 if (image->aspect_align == SP_ASPECT_NONE)
1078                         nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1079                                            gdk_pixbuf_get_pixels (image->pixbuf),
1080                                            gdk_pixbuf_get_width (image->pixbuf),
1081                                            gdk_pixbuf_get_height (image->pixbuf),
1082                                            rs);
1083                 else // preserveAspectRatio
1084                         nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1085                                            gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1086                                            image->trimwidth,
1087                                            image->trimheight,
1088                                            rs);
1089         } else {
1090                 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0);
1091         }
1092         if (image->aspect_align == SP_ASPECT_NONE)
1093                 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1094         else // preserveAspectRatio
1095                 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1097         return ai;
1100 /*
1101  * utility function to try loading image from href
1102  *
1103  * docbase/relative_src
1104  * absolute_src
1105  *
1106  */
1108 GdkPixbuf *
1109 sp_image_repr_read_image (const gchar *href, const gchar *absref, const gchar *base)
1111     const gchar *filename, *docbase;
1112     gchar *fullname;
1113     GdkPixbuf *pixbuf;
1115     filename = href;
1116     if (filename != NULL) {
1117         if (strncmp (filename,"file:",5) == 0) {
1118             fullname = g_filename_from_uri(filename, NULL, NULL);
1119             if (fullname) {
1120                 // TODO check this. Was doing a UTF-8 to filename conversion here.
1121                 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, NULL);
1122                 if (pixbuf != NULL) return pixbuf;
1123             }
1124         } else if (strncmp (filename,"data:",5) == 0) {
1125             /* data URI - embedded image */
1126             filename += 5;
1127             pixbuf = sp_image_repr_read_dataURI (filename);
1128             if (pixbuf != NULL) return pixbuf;
1129         } else {
1131             if (!g_path_is_absolute (filename)) {
1132                 /* try to load from relative pos combined with document base*/
1133                 docbase = base;
1134                 if (!docbase) docbase = ".";
1135                 fullname = g_build_filename(docbase, filename, NULL);
1137                 // document base can be wrong (on the temporary doc when importing bitmap from a
1138                 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1139                 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1140                 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1141                     pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, NULL );
1142                     g_free (fullname);
1143                     if (pixbuf != NULL) return pixbuf;
1144                 }
1145             }
1147             /* try filename as absolute */
1148             if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1149                 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1150                 if (pixbuf != NULL) return pixbuf;
1151             }
1152         }
1153     }
1155     /* at last try to load from sp absolute path name */
1156     filename = absref;
1157     if (filename != NULL) {
1158         // using absref is outside of SVG rules, so we must at least warn the user
1159         if ( base != NULL && href != NULL )
1160                 g_warning ("<image xlink:href=\"%s\"> did not resolve to a valid image file (base dir is %s), now trying sodipodi:absref=\"%s\"", href, base, absref);
1161                 else
1162                     g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1164         pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1165         if (pixbuf != NULL) return pixbuf;
1166     }
1167     /* Nope: We do not find any valid pixmap file :-( */
1168     pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1170     /* It should be included xpm, so if it still does not does load, */
1171     /* our libraries are broken */
1172     g_assert (pixbuf != NULL);
1174     return pixbuf;
1177 static GdkPixbuf *
1178 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1180         GdkPixbuf * newbuf;
1182         if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf;
1184         newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
1185         gdk_pixbuf_unref (pixbuf);
1187         return newbuf;
1190 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1192 static void
1193 sp_image_update_canvas_image (SPImage *image)
1195         int rs, pixskip;
1196         SPItem *item;
1197         SPItemView *v;
1199         item = SP_ITEM (image);
1201         if (image->pixbuf) {
1202                 /* fixme: We are slightly violating spec here (Lauris) */
1203                 if (!image->width._set) {
1204                         image->width.computed = gdk_pixbuf_get_width (image->pixbuf);
1205                 }
1206                 if (!image->height._set) {
1207                         image->height.computed = gdk_pixbuf_get_height (image->pixbuf);
1208                 }
1209         }
1211         for (v = item->display; v != NULL; v = v->next) {
1212                 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1213                 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1214                 nr_arena_image_set_style (NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1215                 if (image->aspect_align == SP_ASPECT_NONE) {
1216                         nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1217                                            gdk_pixbuf_get_pixels (image->pixbuf),
1218                                            gdk_pixbuf_get_width (image->pixbuf),
1219                                            gdk_pixbuf_get_height (image->pixbuf),
1220                                            rs);
1221                         nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1222                                              image->x.computed, image->y.computed,
1223                                              image->width.computed, image->height.computed);
1224                 } else { // preserveAspectRatio
1225                         nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1226                                            gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1227                                            image->trimwidth,
1228                                            image->trimheight,
1229                                            rs);
1230                         nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1231                                              image->viewx, image->viewy,
1232                                              image->viewwidth, image->viewheight);
1233                 }
1234         }
1237 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1239     /* An image doesn't have any nodes to snap, but still we want to be able snap one image 
1240     to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1241     the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1242     */
1243      
1244     g_assert(item != NULL);
1245     g_assert(SP_IS_IMAGE(item));
1247     if (item->clip_ref->getObject()) {
1248         //We are looking at a clipped image: do not return any snappoints, as these might be
1249         //far far away from the visible part from the clipped image
1250     } else {
1251         // The image has not been clipped: return its corners, which might be rotated for example
1252         SPImage &image = *SP_IMAGE(item);
1253         double const x0 = image.x.computed;
1254                 double const y0 = image.y.computed;
1255                 double const x1 = x0 + image.width.computed;
1256                 double const y1 = y0 + image.height.computed;
1257                 NR::Matrix const i2d (sp_item_i2d_affine (item));
1258                 *p = NR::Point(x0, y0) * i2d;
1259         *p = NR::Point(x0, y1) * i2d;
1260         *p = NR::Point(x1, y1) * i2d;
1261         *p = NR::Point(x1, y0) * i2d;
1262     }
1265 /*
1266  * Initially we'll do:
1267  * Transform x, y, set x, y, clear translation
1268  */
1270 static NR::Matrix
1271 sp_image_set_transform(SPItem *item, NR::Matrix const &xform)
1273         SPImage *image = SP_IMAGE(item);
1275         /* Calculate position in parent coords. */
1276         NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform );
1278         /* This function takes care of translation and scaling, we return whatever parts we can't
1279            handle. */
1280         NR::Matrix ret(NR::transform(xform));
1281         NR::Point const scale(hypot(ret[0], ret[1]),
1282                               hypot(ret[2], ret[3]));
1283         if ( scale[NR::X] > 1e-9 ) {
1284                 ret[0] /= scale[NR::X];
1285                 ret[1] /= scale[NR::X];
1286         } else {
1287                 ret[0] = 1.0;
1288                 ret[1] = 0.0;
1289         }
1290         if ( scale[NR::Y] > 1e-9 ) {
1291                 ret[2] /= scale[NR::Y];
1292                 ret[3] /= scale[NR::Y];
1293         } else {
1294                 ret[2] = 0.0;
1295                 ret[3] = 1.0;
1296         }
1298         image->width = image->width.computed * scale[NR::X];
1299         image->height = image->height.computed * scale[NR::Y];
1301         /* Find position in item coords */
1302         pos = pos * ret.inverse();
1303         image->x = pos[NR::X];
1304         image->y = pos[NR::Y];
1306         item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1308         return ret;
1311 static GdkPixbuf *
1312 sp_image_repr_read_dataURI (const gchar * uri_data)
1313 {       GdkPixbuf * pixbuf = NULL;
1315         gint data_is_image = 0;
1316         gint data_is_base64 = 0;
1318         const gchar * data = uri_data;
1320         while (*data) {
1321                 if (strncmp (data,"base64",6) == 0) {
1322                         /* base64-encoding */
1323                         data_is_base64 = 1;
1324                         data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1325                         data += 6;
1326                 }
1327                 else if (strncmp (data,"image/png",9) == 0) {
1328                         /* PNG image */
1329                         data_is_image = 1;
1330                         data += 9;
1331                 }
1332                 else if (strncmp (data,"image/jpg",9) == 0) {
1333                         /* JPEG image */
1334                         data_is_image = 1;
1335                         data += 9;
1336                 }
1337                 else if (strncmp (data,"image/jpeg",10) == 0) {
1338                         /* JPEG image */
1339                         data_is_image = 1;
1340                         data += 10;
1341                 }
1342                 else { /* unrecognized option; skip it */
1343                         while (*data) {
1344                                 if (((*data) == ';') || ((*data) == ',')) break;
1345                                 data++;
1346                         }
1347                 }
1348                 if ((*data) == ';') {
1349                         data++;
1350                         continue;
1351                 }
1352                 if ((*data) == ',') {
1353                         data++;
1354                         break;
1355                 }
1356         }
1358         if ((*data) && data_is_image && data_is_base64) {
1359                 pixbuf = sp_image_repr_read_b64 (data);
1360         }
1362         return pixbuf;
1365 static GdkPixbuf *
1366 sp_image_repr_read_b64 (const gchar * uri_data)
1367 {       GdkPixbuf * pixbuf = NULL;
1368         GdkPixbufLoader * loader = NULL;
1370         gint j;
1371         gint k;
1372         gint l;
1373         gint b;
1374         gint len;
1375         gint eos = 0;
1376         gint failed = 0;
1378         guint32 bits;
1380         static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1382         const gchar* btr = uri_data;
1384         gchar ud[4];
1386         guchar bd[57];
1388         loader = gdk_pixbuf_loader_new ();
1390         if (loader == NULL) return NULL;
1392         while (eos == 0) {
1393                 l = 0;
1394                 for (j = 0; j < 19; j++) {
1395                         len = 0;
1396                         for (k = 0; k < 4; k++) {
1397                                 while (isspace ((int) (*btr))) {
1398                                         if ((*btr) == '\0') break;
1399                                         btr++;
1400                                 }
1401                                 if (eos) {
1402                                         ud[k] = 0;
1403                                         continue;
1404                                 }
1405                                 if (((*btr) == '\0') || ((*btr) == '=')) {
1406                                         eos = 1;
1407                                         ud[k] = 0;
1408                                         continue;
1409                                 }
1410                                 ud[k] = 64;
1411                                 for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1412                                         if (B64[b] == (*btr)) {
1413                                                 ud[k] = (gchar) b;
1414                                                 break;
1415                                         }
1416                                 }
1417                                 if (ud[k] == 64) { /* data corruption ?? */
1418                                         eos = 1;
1419                                         ud[k] = 0;
1420                                         continue;
1421                                 }
1422                                 btr++;
1423                                 len++;
1424                         }
1425                         bits = (guint32) ud[0];
1426                         bits = (bits << 6) | (guint32) ud[1];
1427                         bits = (bits << 6) | (guint32) ud[2];
1428                         bits = (bits << 6) | (guint32) ud[3];
1429                         bd[l++] = (guchar) ((bits & 0xff0000) >> 16);
1430                         if (len > 2) {
1431                                 bd[l++] = (guchar) ((bits & 0xff00) >>  8);
1432                         }
1433                         if (len > 3) {
1434                                 bd[l++] = (guchar)  (bits & 0xff);
1435                         }
1436                 }
1438                 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) {
1439                         failed = 1;
1440                         break;
1441                 }
1442         }
1444         gdk_pixbuf_loader_close (loader, NULL);
1446         if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1448         return pixbuf;
1451 static void
1452 sp_image_set_curve(SPImage *image) 
1454     //create a curve at the image's boundary for snapping
1455     if ((image->height.computed < 1e-18) || (image->width.computed < 1e-18) || (image->clip_ref->getObject())) {
1456         if (image->curve) {
1457             image->curve = sp_curve_unref(image->curve);
1458         }
1459         return;
1460     }
1461     
1462     NRRect rect;
1463         sp_image_bbox(image, &rect, NR::identity(), 0);
1464         NR::Maybe<NR::Rect> rect2 = rect.upgrade();
1465         SPCurve *c = sp_curve_new_from_rect(rect2);
1466         
1467     if (image->curve) {
1468         image->curve = sp_curve_unref(image->curve);
1469     }
1470     
1471     if (c) {
1472         image->curve = sp_curve_ref(c);
1473     }
1474     
1475     sp_curve_unref(c);    
1478 /**
1479  * Return duplicate of curve (if any exists) or NULL if there is no curve
1480  */
1481 SPCurve *
1482 sp_image_get_curve (SPImage *image)
1484         if (image->curve) {
1485                 return sp_curve_copy(image->curve);
1486         }
1487         return NULL;
1490 /*
1491   Local Variables:
1492   mode:c++
1493   c-file-style:"stroustrup"
1494   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1495   indent-tabs-mode:nil
1496   fill-column:99
1497   End:
1498 */
1499 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :