Code

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