Code

fix bug in latex export, when svgwidth has not been defined at all
[inkscape.git] / src / sp-image.cpp
1 /*
2  * SVG <image> implementation
3  *
4  * Authors:
5  *   Lauris Kaplinski <lauris@kaplinski.com>
6  *   Edward Flick (EAF)
7  *
8  * Copyright (C) 1999-2005 Authors
9  * Copyright (C) 2000-2001 Ximian, Inc.
10  *
11  * Released under GNU GPL, read the file 'COPYING' for more information
12  */
14 #ifdef HAVE_CONFIG_H
15 # include "config.h"
16 #endif
18 // This has to be included prior to anything that includes setjmp.h, it croaks otherwise
19 #include <png.h>
21 #include <cstring>
22 #include <string>
23 #include <libnr/nr-matrix-fns.h>
24 #include <libnr/nr-matrix-ops.h>
25 #include <libnr/nr-translate-matrix-ops.h>
26 #include <libnr/nr-scale-translate-ops.h>
27 #include <libnr/nr-convert2geom.h>
28 #include <2geom/rect.h>
29 //#define GDK_PIXBUF_ENABLE_BACKEND 1
30 //#include <gdk-pixbuf/gdk-pixbuf-io.h>
31 #include "display/nr-arena-image.h"
32 #include <display/curve.h>
33 #include <glib/gstdio.h>
35 //Added for preserveAspectRatio support -- EAF
36 #include "enums.h"
37 #include "attributes.h"
39 #include "print.h"
40 #include "brokenimage.xpm"
41 #include "document.h"
42 #include "sp-image.h"
43 #include "sp-clippath.h"
44 #include <glibmm/i18n.h>
45 #include "xml/quote.h"
46 #include <xml/repr.h>
47 #include "snap-candidate.h"
48 #include "libnr/nr-matrix-fns.h"
50 #include "io/sys.h"
51 #if ENABLE_LCMS
52 #include "color-profile-fns.h"
53 #include "color-profile.h"
54 //#define DEBUG_LCMS
55 #ifdef DEBUG_LCMS
58 #define DEBUG_MESSAGE(key, ...)\
59 {\
60     g_message( __VA_ARGS__ );\
61 }
63 #include "preferences.h"
64 #include <gtk/gtkmessagedialog.h>
65 #endif // DEBUG_LCMS
66 #endif // ENABLE_LCMS
67 /*
68  * SPImage
69  */
71 // TODO: give these constants better names:
72 #define MAGIC_EPSILON 1e-9
73 #define MAGIC_EPSILON_TOO 1e-18
74 // TODO: also check if it is correct to be using two different epsilon values
76 static void sp_image_class_init (SPImageClass * klass);
77 static void sp_image_init (SPImage * image);
79 static void sp_image_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
80 static void sp_image_release (SPObject * object);
81 static void sp_image_set (SPObject *object, unsigned int key, const gchar *value);
82 static void sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags);
83 static void sp_image_modified (SPObject *object, unsigned int flags);
84 static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags);
86 static void sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const flags);
87 static void sp_image_print (SPItem * item, SPPrintContext *ctx);
88 static gchar * sp_image_description (SPItem * item);
89 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
90 static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags);
91 static Geom::Matrix sp_image_set_transform (SPItem *item, Geom::Matrix const &xform);
92 static void sp_image_set_curve(SPImage *image);
95 static GdkPixbuf *sp_image_repr_read_image( time_t& modTime, gchar*& pixPath, const gchar *href, const gchar *absref, const gchar *base );
96 static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf);
97 static void sp_image_update_canvas_image (SPImage *image);
98 static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data);
99 static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data);
101 static SPItemClass *parent_class;
104 extern "C"
106     void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length );
107     void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length );
108     void user_flush_data( png_structp png_ptr );
113 #ifdef DEBUG_LCMS
114 extern guint update_in_progress;
115 #define DEBUG_MESSAGE_SCISLAC(key, ...) \
116 {\
117     Inkscape::Preferences *prefs = Inkscape::Preferences::get();\
118     bool dump = prefs->getBool("/options/scislac/" #key);\
119     bool dumpD = prefs->getBool("/options/scislac/" #key "D");\
120     bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2");\
121     dumpD &&= ( (update_in_progress == 0) || dumpD2 );\
122     if ( dump )\
123     {\
124         g_message( __VA_ARGS__ );\
126     }\
127     if ( dumpD )\
128     {\
129         GtkWidget *dialog = gtk_message_dialog_new(NULL,\
130                                                    GTK_DIALOG_DESTROY_WITH_PARENT, \
131                                                    GTK_MESSAGE_INFO,    \
132                                                    GTK_BUTTONS_OK,      \
133                                                    __VA_ARGS__          \
134                                                    );\
135         g_signal_connect_swapped(dialog, "response",\
136                                  G_CALLBACK(gtk_widget_destroy),        \
137                                  dialog);                               \
138         gtk_widget_show_all( dialog );\
139     }\
141 #endif // DEBUG_LCMS
143 namespace Inkscape {
144 namespace IO {
146 class PushPull
148 public:
149     gboolean    first;
150     FILE*       fp;
151     guchar*     scratch;
152     gsize       size;
153     gsize       used;
154     gsize       offset;
155     GdkPixbufLoader *loader;
157     PushPull() : first(TRUE),
158                  fp(0),
159                  scratch(0),
160                  size(0),
161                  used(0),
162                  offset(0),
163                  loader(0) {};
165     gboolean readMore()
166     {
167         gboolean good = FALSE;
168         if ( offset )
169         {
170             g_memmove( scratch, scratch + offset, used - offset );
171             used -= offset;
172             offset = 0;
173         }
174         if ( used < size )
175         {
176             gsize space = size - used;
177             gsize got = fread( scratch + used, 1, space, fp );
178             if ( got )
179             {
180                 if ( loader )
181                 {
182                     GError *err = NULL;
183                     //g_message( " __read %d bytes", (int)got );
184                     if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) )
185                     {
186                         //g_message("_error writing pixbuf data");
187                     }
188                 }
190                 used += got;
191                 good = TRUE;
192             }
193             else
194             {
195                 good = FALSE;
196             }
197         }
198         return good;
199     }
201     gsize available() const
202     {
203         return (used - offset);
204     }
206     gsize readOut( gpointer data, gsize length )
207     {
208         gsize giving = available();
209         if ( length < giving )
210         {
211             giving = length;
212         }
213         g_memmove( data, scratch + offset, giving );
214         offset += giving;
215         if ( offset >= used )
216         {
217             offset = 0;
218             used = 0;
219         }
220         return giving;
221     }
223     void clear()
224     {
225         offset = 0;
226         used = 0;
227     }
229 private:
230     PushPull& operator = (const PushPull& other);
231     PushPull(const PushPull& other);
232 };
234 void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length )
236 //    g_message( "user_read_data(%d)", length );
238     PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr);
240     gsize filled = 0;
241     gboolean canRead = TRUE;
243     while ( filled < length && canRead )
244     {
245         gsize some = youme->readOut( data + filled, length - filled );
246         filled += some;
247         if ( filled < length )
248         {
249             canRead &= youme->readMore();
250         }
251     }
252 //    g_message("things out");
255 void user_write_data( png_structp /*png_ptr*/, png_bytep /*data*/, png_size_t /*length*/ )
257     //g_message( "user_write_data(%d)", length );
260 void user_flush_data( png_structp /*png_ptr*/ )
262     //g_message( "user_flush_data" );
265 static GdkPixbuf* pixbuf_new_from_file( const char *filename, time_t &modTime, gchar*& pixPath, GError **/*error*/ )
267     GdkPixbuf* buf = NULL;
268     PushPull youme;
269     gint dpiX = 0;
270     gint dpiY = 0;
271     modTime = 0;
272     if ( pixPath ) {
273         g_free(pixPath);
274         pixPath = 0;
275     }
277     //buf = gdk_pixbuf_new_from_file( filename, error );
278     dump_fopen_call( filename, "pixbuf_new_from_file" );
279     FILE* fp = fopen_utf8name( filename, "r" );
280     if ( fp )
281     {
282         {
283             struct stat st;
284             memset(&st, 0, sizeof(st));
285             int val = g_stat(filename, &st);
286             if ( !val ) {
287                 modTime = st.st_mtime;
288                 pixPath = g_strdup(filename);
289             }
290         }
292         GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
293         if ( loader )
294         {
295             GError *err = NULL;
297             // short buffer
298             guchar scratch[1024];
299             gboolean latter = FALSE;
300             gboolean isPng = FALSE;
301             png_structp pngPtr = NULL;
302             png_infop infoPtr = NULL;
303             //png_infop endPtr = NULL;
305             youme.fp = fp;
306             youme.scratch = scratch;
307             youme.size = sizeof(scratch);
308             youme.used = 0;
309             youme.offset = 0;
310             youme.loader = loader;
312             while ( !feof(fp) )
313             {
314                 if ( youme.readMore() )
315                 {
316                     if ( youme.first )
317                     {
318                         //g_message( "First data chunk" );
319                         youme.first = FALSE;
320                         isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() );
321                         //g_message( "  png? %s", (isPng ? "Yes":"No") );
322                         if ( isPng )
323                         {
324                             pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
325                                                              NULL,//(png_voidp)user_error_ptr,
326                                                              NULL,//user_error_fn,
327                                                              NULL//user_warning_fn
328                                 );
329                             if ( pngPtr )
330                             {
331                                 if ( setjmp(png_jmpbuf(pngPtr)) )
332                                 {
333                                     // libpng calls longjmp to return here if an error occurs.
334                                     png_destroy_read_struct( &pngPtr, &infoPtr, NULL );
335                                     fclose(fp);
336                                     gdk_pixbuf_loader_close(loader, NULL);
337                                     g_object_unref(loader);
338                                     return NULL;
339                                 }
341                                 infoPtr = png_create_info_struct( pngPtr );
342                                 //endPtr = png_create_info_struct( pngPtr );
344                                 png_set_read_fn( pngPtr, &youme, user_read_data );
345                                 //g_message( "In" );
347                                 //png_read_info( pngPtr, infoPtr );
348                                 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL );
350                                 //g_message("out");
352                                 //png_read_end(pngPtr, endPtr);
354                                 /*
355                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
356                                 {
357                                     g_message("pHYs chunk now valid" );
358                                 }
359                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
360                                 {
361                                     g_message("sCAL chunk now valid" );
362                                 }
363                                 */
365                                 png_uint_32 res_x = 0;
366                                 png_uint_32 res_y = 0;
367                                 int unit_type = 0;
368                                 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) )
369                                 {
370 //                                     g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
371 //                                                (unit_type == 1? "per meter" : "unknown")
372 //                                         );
374 //                                     g_message( "    dpi: (%d, %d)",
375 //                                                (int)(0.5 + ((double)res_x)/39.37),
376 //                                                (int)(0.5 + ((double)res_y)/39.37) );
377                                     if ( unit_type == PNG_RESOLUTION_METER )
378                                     {
379                                         // TODO come up with a more accurate DPI setting
380                                         dpiX = (int)(0.5 + ((double)res_x)/39.37);
381                                         dpiY = (int)(0.5 + ((double)res_y)/39.37);
382                                     }
383                                 }
384                                 else
385                                 {
386 //                                     g_message( "pHYs no" );
387                                 }
389 /*
390                                 double width = 0;
391                                 double height = 0;
392                                 int unit = 0;
393                                 if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
394                                 {
395                                     gchar* vals[] = {
396                                         "unknown", // PNG_SCALE_UNKNOWN
397                                         "meter", // PNG_SCALE_METER
398                                         "radian", // PNG_SCALE_RADIAN
399                                         "last", //
400                                         NULL
401                                     };
403                                     g_message( "sCAL: (%f, %f) %d (%s)",
404                                                width, height, unit,
405                                                ((unit >= 0 && unit < 3) ? vals[unit]:"???")
406                                         );
407                                 }
408 */
410 #if defined(PNG_sRGB_SUPPORTED)
411                                 {
412                                     int intent = 0;
413                                     if ( png_get_sRGB(pngPtr, infoPtr, &intent) ) {
414 //                                         g_message("Found an sRGB png chunk");
415                                     }
416                                 }
417 #endif // defined(PNG_sRGB_SUPPORTED)
419 #if defined(PNG_cHRM_SUPPORTED)
420                                 {
421                                     double white_x = 0;
422                                     double white_y = 0;
423                                     double red_x = 0;
424                                     double red_y = 0;
425                                     double green_x = 0;
426                                     double green_y = 0;
427                                     double blue_x = 0;
428                                     double blue_y = 0;
430                                     if ( png_get_cHRM(pngPtr, infoPtr,
431                                                       &white_x, &white_y,
432                                                       &red_x, &red_y,
433                                                       &green_x, &green_y,
434                                                       &blue_x, &blue_y) ) {
435 //                                         g_message("Found a cHRM png chunk");
436                                     }
437                                 }
438 #endif // defined(PNG_cHRM_SUPPORTED)
440 #if defined(PNG_gAMA_SUPPORTED)
441                                 {
442                                     double file_gamma = 0;
443                                     if ( png_get_gAMA(pngPtr, infoPtr, &file_gamma) ) {
444 //                                         g_message("Found a gAMA png chunk");
445                                     }
446                                 }
447 #endif // defined(PNG_gAMA_SUPPORTED)
449 #if defined(PNG_iCCP_SUPPORTED)
450                                 {
451                                     char* name = 0;
452                                     int compression_type = 0;
453                                     char* profile = 0;
454                                     png_uint_32 proflen = 0;
455                                     if ( png_get_iCCP(pngPtr, infoPtr, &name, &compression_type, &profile, &proflen) ) {
456 //                                         g_message("Found an iCCP chunk named [%s] with %d bytes and comp %d", name, proflen, compression_type);
457                                     }
458                                 }
459 #endif // defined(PNG_iCCP_SUPPORTED)
462                                 // now clean it up.
463                                 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr );
464                             }
465                             else
466                             {
467 //                                 g_message("Error when creating PNG read struct");
468                             }
469                         }
470                     }
471                     else if ( !latter )
472                     {
473                         latter = TRUE;
474                         //g_message("  READing latter");
475                     }
476                     // Now clear out the buffer so we can read more.
477                     // (dumping out unused)
478                     youme.clear();
479                 }
480             }
482             gboolean ok = gdk_pixbuf_loader_close(loader, &err);
483             if ( ok )
484             {
485                 buf = gdk_pixbuf_loader_get_pixbuf( loader );
486                 if ( buf )
487                 {
488                     g_object_ref(buf);
490                     if ( dpiX )
491                     {
492                         gchar *tmp = g_strdup_printf( "%d", dpiX );
493                         if ( tmp )
494                         {
495 //                             g_message("Need to set DpiX: %s", tmp);
496                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
497                             g_free( tmp );
498                         }
499                     }
500                     if ( dpiY )
501                     {
502                         gchar *tmp = g_strdup_printf( "%d", dpiY );
503                         if ( tmp )
504                         {
505 //                             g_message("Need to set DpiY: %s", tmp);
506                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
507                             g_free( tmp );
508                         }
509                     }
510                 }
511             }
512             else
513             {
514                 // do something
515                 g_message("error loading pixbuf at close");
516             }
518             g_object_unref(loader);
519         }
520         else
521         {
522             g_message("error when creating pixbuf loader");
523         }
524         fclose( fp );
525         fp = NULL;
526     }
527     else
528     {
529         g_warning ("Unable to open linked file: %s", filename);
530     }
532 /*
533     if ( buf )
534     {
535         const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
536         if ( bloop )
537         {
538             g_message("DPI X is [%s]", bloop);
539         }
540         bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
541         if ( bloop )
542         {
543             g_message("DPI Y is [%s]", bloop);
544         }
545     }
546 */
548     return buf;
551 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
553     time_t modTime = 0;
554     gchar* pixPath = 0;
555     GdkPixbuf* result = pixbuf_new_from_file( filename, modTime, pixPath, error );
556     if (pixPath) {
557         g_free(pixPath);
558     }
559     return result;
566 GType
567 sp_image_get_type (void)
569     static GType image_type = 0;
570     if (!image_type) {
571         GTypeInfo image_info = {
572             sizeof (SPImageClass),
573             NULL,       /* base_init */
574             NULL,       /* base_finalize */
575             (GClassInitFunc) sp_image_class_init,
576             NULL,       /* class_finalize */
577             NULL,       /* class_data */
578             sizeof (SPImage),
579             16, /* n_preallocs */
580             (GInstanceInitFunc) sp_image_init,
581             NULL,       /* value_table */
582         };
583         image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
584     }
585     return image_type;
588 static void
589 sp_image_class_init (SPImageClass * klass)
591     GObjectClass * gobject_class;
592     SPObjectClass * sp_object_class;
593     SPItemClass * item_class;
595     gobject_class = (GObjectClass *) klass;
596     sp_object_class = (SPObjectClass *) klass;
597     item_class = (SPItemClass *) klass;
599     parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
601     sp_object_class->build = sp_image_build;
602     sp_object_class->release = sp_image_release;
603     sp_object_class->set = sp_image_set;
604     sp_object_class->update = sp_image_update;
605     sp_object_class->modified = sp_image_modified;
606     sp_object_class->write = sp_image_write;
608     item_class->bbox = sp_image_bbox;
609     item_class->print = sp_image_print;
610     item_class->description = sp_image_description;
611     item_class->show = sp_image_show;
612     item_class->snappoints = sp_image_snappoints;
613     item_class->set_transform = sp_image_set_transform;
616 static void sp_image_init( SPImage *image )
618     image->x.unset();
619     image->y.unset();
620     image->width.unset();
621     image->height.unset();
622     image->aspect_align = SP_ASPECT_NONE;
624     image->trimx = 0;
625     image->trimy = 0;
626     image->trimwidth = 0;
627     image->trimheight = 0;
628     image->viewx = 0;
629     image->viewy = 0;
630     image->viewwidth = 0;
631     image->viewheight = 0;
633     image->curve = NULL;
635     image->href = 0;
636 #if ENABLE_LCMS
637     image->color_profile = 0;
638 #endif // ENABLE_LCMS
639     image->pixbuf = 0;
640     image->pixPath = 0;
641     image->lastMod = 0;
644 static void
645 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
647     if (((SPObjectClass *) parent_class)->build) {
648         ((SPObjectClass *) parent_class)->build (object, document, repr);
649     }
651     sp_object_read_attr (object, "xlink:href");
652     sp_object_read_attr (object, "x");
653     sp_object_read_attr (object, "y");
654     sp_object_read_attr (object, "width");
655     sp_object_read_attr (object, "height");
656     sp_object_read_attr (object, "preserveAspectRatio");
657     sp_object_read_attr (object, "color-profile");
659     /* Register */
660     sp_document_add_resource (document, "image", object);
663 static void
664 sp_image_release (SPObject *object)
666     SPImage *image = SP_IMAGE(object);
668     if (SP_OBJECT_DOCUMENT (object)) {
669         /* Unregister ourselves */
670         sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
671     }
673     if (image->href) {
674         g_free (image->href);
675         image->href = NULL;
676     }
678     if (image->pixbuf) {
679         gdk_pixbuf_unref (image->pixbuf);
680         image->pixbuf = NULL;
681     }
683 #if ENABLE_LCMS
684     if (image->color_profile) {
685         g_free (image->color_profile);
686         image->color_profile = NULL;
687     }
688 #endif // ENABLE_LCMS
690     if (image->pixPath) {
691         g_free(image->pixPath);
692         image->pixPath = 0;
693     }
695     if (image->curve) {
696         image->curve = image->curve->unref();
697     }
699     if (((SPObjectClass *) parent_class)->release) {
700         ((SPObjectClass *) parent_class)->release (object);
701     }
704 static void
705 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
707     SPImage *image = SP_IMAGE (object);
709     switch (key) {
710         case SP_ATTR_XLINK_HREF:
711             g_free (image->href);
712             image->href = (value) ? g_strdup (value) : NULL;
713             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
714             break;
715         case SP_ATTR_X:
716             if (!image->x.readAbsolute(value)) {
717                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
718                 image->x.unset();
719             }
720             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
721             break;
722         case SP_ATTR_Y:
723             if (!image->y.readAbsolute(value)) {
724                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
725                 image->y.unset();
726             }
727             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
728             break;
729         case SP_ATTR_WIDTH:
730             if (!image->width.readAbsolute(value)) {
731                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
732                 image->width.unset();
733             }
734             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
735             break;
736         case SP_ATTR_HEIGHT:
737             if (!image->height.readAbsolute(value)) {
738                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
739                 image->height.unset();
740             }
741             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
742             break;
743         case SP_ATTR_PRESERVEASPECTRATIO:
744             /* Do setup before, so we can use break to escape */
745             image->aspect_align = SP_ASPECT_NONE;
746             image->aspect_clip = SP_ASPECT_MEET;
747             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
748             if (value) {
749                 int len;
750                 gchar c[256];
751                 const gchar *p, *e;
752                 unsigned int align, clip;
753                 p = value;
754                 while (*p && *p == 32) p += 1;
755                 if (!*p) break;
756                 e = p;
757                 while (*e && *e != 32) e += 1;
758                 len = e - p;
759                 if (len > 8) break;
760                 memcpy (c, value, len);
761                 c[len] = 0;
762                 /* Now the actual part */
763                 if (!strcmp (c, "none")) {
764                     align = SP_ASPECT_NONE;
765                 } else if (!strcmp (c, "xMinYMin")) {
766                     align = SP_ASPECT_XMIN_YMIN;
767                 } else if (!strcmp (c, "xMidYMin")) {
768                     align = SP_ASPECT_XMID_YMIN;
769                 } else if (!strcmp (c, "xMaxYMin")) {
770                     align = SP_ASPECT_XMAX_YMIN;
771                 } else if (!strcmp (c, "xMinYMid")) {
772                     align = SP_ASPECT_XMIN_YMID;
773                 } else if (!strcmp (c, "xMidYMid")) {
774                     align = SP_ASPECT_XMID_YMID;
775                 } else if (!strcmp (c, "xMaxYMid")) {
776                     align = SP_ASPECT_XMAX_YMID;
777                 } else if (!strcmp (c, "xMinYMax")) {
778                     align = SP_ASPECT_XMIN_YMAX;
779                 } else if (!strcmp (c, "xMidYMax")) {
780                     align = SP_ASPECT_XMID_YMAX;
781                 } else if (!strcmp (c, "xMaxYMax")) {
782                     align = SP_ASPECT_XMAX_YMAX;
783                 } else {
784                     break;
785                 }
786                 clip = SP_ASPECT_MEET;
787                 while (*e && *e == 32) e += 1;
788                 if (*e) {
789                     if (!strcmp (e, "meet")) {
790                         clip = SP_ASPECT_MEET;
791                     } else if (!strcmp (e, "slice")) {
792                         clip = SP_ASPECT_SLICE;
793                     } else {
794                         break;
795                     }
796                 }
797                 image->aspect_align = align;
798                 image->aspect_clip = clip;
799             }
800             break;
801 #if ENABLE_LCMS
802         case SP_PROP_COLOR_PROFILE:
803             if ( image->color_profile ) {
804                 g_free (image->color_profile);
805             }
806             image->color_profile = (value) ? g_strdup (value) : NULL;
807 #ifdef DEBUG_LCMS
808             if ( value ) {
809                 DEBUG_MESSAGE( lcmsFour, "<image> color-profile set to '%s'", value );
810             } else {
811                 DEBUG_MESSAGE( lcmsFour, "<image> color-profile cleared" );
812             }
813 #endif // DEBUG_LCMS
814             // TODO check on this HREF_MODIFIED flag
815             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
816             break;
817 #endif // ENABLE_LCMS
818         default:
819             if (((SPObjectClass *) (parent_class))->set)
820                 ((SPObjectClass *) (parent_class))->set (object, key, value);
821             break;
822     }
824     sp_image_set_curve(image); //creates a curve at the image's boundary for snapping
827 static void
828 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
830     SPImage *image = SP_IMAGE(object);
831     SPDocument *doc = SP_OBJECT_DOCUMENT(object);
833     if (((SPObjectClass *) (parent_class))->update) {
834         ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
835     }
837     if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
838         if (image->pixbuf) {
839             gdk_pixbuf_unref (image->pixbuf);
840             image->pixbuf = NULL;
841         }
842         if ( image->pixPath ) {
843             g_free(image->pixPath);
844             image->pixPath = 0;
845         }
846         image->lastMod = 0;
847         if (image->href) {
848             GdkPixbuf *pixbuf;
849             pixbuf = sp_image_repr_read_image (
850                 image->lastMod,
851                 image->pixPath,
852                 object->repr->attribute("xlink:href"),
853                 object->repr->attribute("sodipodi:absref"),
854                 doc->base);
855             if (pixbuf) {
856                 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
857 // BLIP
858 #if ENABLE_LCMS
859                 if ( image->color_profile )
860                 {
861                     int imagewidth = gdk_pixbuf_get_width( pixbuf );
862                     int imageheight = gdk_pixbuf_get_height( pixbuf );
863                     int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
864                     guchar* px = gdk_pixbuf_get_pixels( pixbuf );
866                     if ( px ) {
867 #ifdef DEBUG_LCMS
868                         DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
869 #endif // DEBUG_LCMS
870                         guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
871                         cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
872                                                                               &profIntent,
873                                                                               image->color_profile );
874                         if ( prof ) {
875                             icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
876                             if ( profileClass != icSigNamedColorClass ) {
877                                 int intent = INTENT_PERCEPTUAL;
878                                 switch ( profIntent ) {
879                                     case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
880                                         intent = INTENT_RELATIVE_COLORIMETRIC;
881                                         break;
882                                     case Inkscape::RENDERING_INTENT_SATURATION:
883                                         intent = INTENT_SATURATION;
884                                         break;
885                                     case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
886                                         intent = INTENT_ABSOLUTE_COLORIMETRIC;
887                                         break;
888                                     case Inkscape::RENDERING_INTENT_PERCEPTUAL:
889                                     case Inkscape::RENDERING_INTENT_UNKNOWN:
890                                     case Inkscape::RENDERING_INTENT_AUTO:
891                                     default:
892                                         intent = INTENT_PERCEPTUAL;
893                                 }
894                                 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
895                                 cmsHTRANSFORM transf = cmsCreateTransform( prof,
896                                                                            TYPE_RGBA_8,
897                                                                            destProf,
898                                                                            TYPE_RGBA_8,
899                                                                            intent, 0 );
900                                 if ( transf ) {
901                                     guchar* currLine = px;
902                                     for ( int y = 0; y < imageheight; y++ ) {
903                                         // Since the types are the same size, we can do the transformation in-place
904                                         cmsDoTransform( transf, currLine, currLine, imagewidth );
905                                         currLine += rowstride;
906                                     }
908                                     cmsDeleteTransform( transf );
909                                 }
910 #ifdef DEBUG_LCMS
911                                 else
912                                 {
913                                     DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
914                                 }
915 #endif // DEBUG_LCMS
916                                 cmsCloseProfile( destProf );
917                             }
918 #ifdef DEBUG_LCMS
919                             else
920                             {
921                                 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
922                             }
923 #endif // DEBUG_LCMS
924                         }
925 #ifdef DEBUG_LCMS
926                         else
927                         {
928                             DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
929                         }
930 #endif // DEBUG_LCMS
931                     }
932                 }
933 #endif // ENABLE_LCMS
934                 image->pixbuf = pixbuf;
935             }
936         }
937     }
938     // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
939     if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
940         int imagewidth, imageheight;
941         double x,y;
943         imagewidth = gdk_pixbuf_get_width (image->pixbuf);
944         imageheight = gdk_pixbuf_get_height (image->pixbuf);
946         switch (image->aspect_align) {
947             case SP_ASPECT_XMIN_YMIN:
948                 x = 0.0;
949                 y = 0.0;
950                 break;
951             case SP_ASPECT_XMID_YMIN:
952                 x = 0.5;
953                 y = 0.0;
954                 break;
955             case SP_ASPECT_XMAX_YMIN:
956                 x = 1.0;
957                 y = 0.0;
958                 break;
959             case SP_ASPECT_XMIN_YMID:
960                 x = 0.0;
961                 y = 0.5;
962                 break;
963             case SP_ASPECT_XMID_YMID:
964                 x = 0.5;
965                 y = 0.5;
966                 break;
967             case SP_ASPECT_XMAX_YMID:
968                 x = 1.0;
969                 y = 0.5;
970                 break;
971             case SP_ASPECT_XMIN_YMAX:
972                 x = 0.0;
973                 y = 1.0;
974                 break;
975             case SP_ASPECT_XMID_YMAX:
976                 x = 0.5;
977                 y = 1.0;
978                 break;
979             case SP_ASPECT_XMAX_YMAX:
980                 x = 1.0;
981                 y = 1.0;
982                 break;
983             default:
984                 x = 0.0;
985                 y = 0.0;
986                 break;
987         }
989         if (image->aspect_clip == SP_ASPECT_SLICE) {
990             image->viewx = image->x.computed;
991             image->viewy = image->y.computed;
992             image->viewwidth = image->width.computed;
993             image->viewheight = image->height.computed;
994             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
995                 // Pixels aspect is wider than bounding box
996                 image->trimheight = imageheight;
997                 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
998                 image->trimy = 0;
999                 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
1000             } else {
1001                 // Pixels aspect is taller than bounding box
1002                 image->trimwidth = imagewidth;
1003                 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
1004                 image->trimx = 0;
1005                 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
1006             }
1007         } else {
1008             // Otherwise, assume SP_ASPECT_MEET
1009             image->trimx = 0;
1010             image->trimy = 0;
1011             image->trimwidth = imagewidth;
1012             image->trimheight = imageheight;
1013             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
1014                 // Pixels aspect is wider than bounding boz
1015                 image->viewwidth = image->width.computed;
1016                 image->viewheight = image->viewwidth * imageheight / imagewidth;
1017                 image->viewx=image->x.computed;
1018                 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
1019             } else {
1020                 // Pixels aspect is taller than bounding box
1021                 image->viewheight = image->height.computed;
1022                 image->viewwidth = image->viewheight * imagewidth / imageheight;
1023                 image->viewy=image->y.computed;
1024                 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
1025             }
1026         }
1027     }
1028     sp_image_update_canvas_image ((SPImage *) object);
1031 static void
1032 sp_image_modified (SPObject *object, unsigned int flags)
1034     SPImage *image = SP_IMAGE (object);
1036     if (((SPObjectClass *) (parent_class))->modified) {
1037       (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
1038     }
1040     if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
1041         for (SPItemView *v = SP_ITEM (image)->display; v != NULL; v = v->next) {
1042             nr_arena_image_set_style (NR_ARENA_IMAGE (v->arenaitem), object->style);
1043         }
1044     }
1047 static Inkscape::XML::Node *
1048 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1050     SPImage *image = SP_IMAGE (object);
1052     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1053         repr = xml_doc->createElement("svg:image");
1054     }
1056     repr->setAttribute("xlink:href", image->href);
1057     /* fixme: Reset attribute if needed (Lauris) */
1058     if (image->x._set) {
1059         sp_repr_set_svg_double(repr, "x", image->x.computed);
1060     }
1061     if (image->y._set) {
1062         sp_repr_set_svg_double(repr, "y", image->y.computed);
1063     }
1064     if (image->width._set) {
1065         sp_repr_set_svg_double(repr, "width", image->width.computed);
1066     }
1067     if (image->height._set) {
1068         sp_repr_set_svg_double(repr, "height", image->height.computed);
1069     }
1070     repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1071 #if ENABLE_LCMS
1072     if (image->color_profile) {
1073         repr->setAttribute("color-profile", image->color_profile);
1074     }
1075 #endif // ENABLE_LCMS
1077     if (((SPObjectClass *) (parent_class))->write) {
1078         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags);
1079     }
1081     return repr;
1084 static void
1085 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1087     SPImage const &image = *SP_IMAGE(item);
1089     if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1090         double const x0 = image.x.computed;
1091         double const y0 = image.y.computed;
1092         double const x1 = x0 + image.width.computed;
1093         double const y1 = y0 + image.height.computed;
1095         nr_rect_union_pt(bbox, Geom::Point(x0, y0) * transform);
1096         nr_rect_union_pt(bbox, Geom::Point(x1, y0) * transform);
1097         nr_rect_union_pt(bbox, Geom::Point(x1, y1) * transform);
1098         nr_rect_union_pt(bbox, Geom::Point(x0, y1) * transform);
1099     }
1102 static void
1103 sp_image_print (SPItem *item, SPPrintContext *ctx)
1105     SPImage *image = SP_IMAGE(item);
1107     if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) {
1108         guchar *px = gdk_pixbuf_get_pixels(image->pixbuf);
1109         int w = gdk_pixbuf_get_width(image->pixbuf);
1110         int h = gdk_pixbuf_get_height(image->pixbuf);
1111         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1112         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1114         Geom::Matrix t;
1115         if (image->aspect_align == SP_ASPECT_NONE) {
1116             /* fixme: (Lauris) */
1117             Geom::Translate tp(image->x.computed, image->y.computed);
1118             Geom::Scale s(image->width.computed, -image->height.computed);
1119             Geom::Translate ti(0.0, -1.0);
1120             t = s * tp;
1121             t = ti * t;
1122         } else { // preserveAspectRatio
1123             Geom::Translate tp(image->viewx, image->viewy);
1124             Geom::Scale s(image->viewwidth, -image->viewheight);
1125             Geom::Translate ti(0.0, -1.0);
1126             t = s * tp;
1127             t = ti * t;
1128         }
1130         if (image->aspect_align == SP_ASPECT_NONE) {
1131             sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1132         } else { // preserveAspectRatio
1133             sp_print_image_R8G8B8A8_N(ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE(item));
1134         }
1135     }
1138 static gchar *
1139 sp_image_description(SPItem *item)
1141     SPImage *image = SP_IMAGE(item);
1142     char *href_desc;
1143     if (image->href) {
1144         href_desc = (strncmp(image->href, "data:", 5) == 0)
1145             ? g_strdup(_("embedded"))
1146             : xml_quote_strdup(image->href);
1147     } else {
1148         g_warning("Attempting to call strncmp() with a null pointer.");
1149         href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1150     }
1152     char *ret = ( image->pixbuf == NULL
1153                   ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1154                   : g_strdup_printf(_("<b>Image</b> %d &#215; %d: %s"),
1155                                     gdk_pixbuf_get_width(image->pixbuf),
1156                                     gdk_pixbuf_get_height(image->pixbuf),
1157                                     href_desc) );
1158     g_free(href_desc);
1159     return ret;
1162 static NRArenaItem *
1163 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1165     SPImage * image = SP_IMAGE(item);
1166     NRArenaItem *ai = NRArenaImage::create(arena);
1168     if (image->pixbuf) {
1169         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1170         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1171         nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1172         if (image->aspect_align == SP_ASPECT_NONE) {
1173             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1174                                        gdk_pixbuf_get_pixels(image->pixbuf),
1175                                        gdk_pixbuf_get_width(image->pixbuf),
1176                                        gdk_pixbuf_get_height(image->pixbuf),
1177                                        rs);
1178         } else { // preserveAspectRatio
1179             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1180                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1181                                        image->trimwidth,
1182                                        image->trimheight,
1183                                        rs);
1184         }
1185     } else {
1186         nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai), NULL, 0, 0, 0);
1187     }
1188     if (image->aspect_align == SP_ASPECT_NONE) {
1189         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1190     } else { // preserveAspectRatio
1191         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1192     }
1194     return ai;
1197 /*
1198  * utility function to try loading image from href
1199  *
1200  * docbase/relative_src
1201  * absolute_src
1202  *
1203  */
1205 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1207     GdkPixbuf *pixbuf = 0;
1208     modTime = 0;
1209     if ( pixPath ) {
1210         g_free(pixPath);
1211         pixPath = 0;
1212     }
1214     const gchar *filename = href;
1215     if (filename != NULL) {
1216         if (strncmp (filename,"file:",5) == 0) {
1217             gchar *fullname = g_filename_from_uri(filename, NULL, NULL);
1218             if (fullname) {
1219                 // TODO check this. Was doing a UTF-8 to filename conversion here.
1220                 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1221                 if (pixbuf != NULL) {
1222                     return pixbuf;
1223                 }
1224             }
1225         } else if (strncmp (filename,"data:",5) == 0) {
1226             /* data URI - embedded image */
1227             filename += 5;
1228             pixbuf = sp_image_repr_read_dataURI (filename);
1229             if (pixbuf != NULL) {
1230                 return pixbuf;
1231             }
1232         } else {
1234             if (!g_path_is_absolute (filename)) {
1235                 /* try to load from relative pos combined with document base*/
1236                 const gchar *docbase = base;
1237                 if (!docbase) {
1238                     docbase = ".";
1239                 }
1240                 gchar *fullname = g_build_filename(docbase, filename, NULL);
1242                 // document base can be wrong (on the temporary doc when importing bitmap from a
1243                 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1244                 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1245                 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1246                     pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1247                     g_free (fullname);
1248                     if (pixbuf != NULL) {
1249                         return pixbuf;
1250                     }
1251                 }
1252             }
1254             /* try filename as absolute */
1255             if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1256                 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1257                 if (pixbuf != NULL) {
1258                     return pixbuf;
1259                 }
1260             }
1261         }
1262     }
1264     /* at last try to load from sp absolute path name */
1265     filename = absref;
1266     if (filename != NULL) {
1267         // using absref is outside of SVG rules, so we must at least warn the user
1268         if ( base != NULL && href != NULL ) {
1269             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);
1270         } else {
1271             g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1272         }
1274         pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1275         if (pixbuf != NULL) {
1276             return pixbuf;
1277         }
1278     }
1279     /* Nope: We do not find any valid pixmap file :-( */
1280     pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1282     /* It should be included xpm, so if it still does not does load, */
1283     /* our libraries are broken */
1284     g_assert (pixbuf != NULL);
1286     return pixbuf;
1289 static GdkPixbuf *
1290 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1292     GdkPixbuf* result;
1293     if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1294         result = pixbuf;
1295     } else {
1296         result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1297         gdk_pixbuf_unref(pixbuf);
1298     }
1299     return result;
1302 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1304 static void
1305 sp_image_update_canvas_image (SPImage *image)
1307     SPItem *item = SP_ITEM(image);
1309     if (image->pixbuf) {
1310         /* fixme: We are slightly violating spec here (Lauris) */
1311         if (!image->width._set) {
1312             image->width.computed = gdk_pixbuf_get_width(image->pixbuf);
1313         }
1314         if (!image->height._set) {
1315             image->height.computed = gdk_pixbuf_get_height(image->pixbuf);
1316         }
1317     }
1319     for (SPItemView *v = item->display; v != NULL; v = v->next) {
1320         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1321         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1322         nr_arena_image_set_style(NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1323         if (image->aspect_align == SP_ASPECT_NONE) {
1324             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1325                                        gdk_pixbuf_get_pixels(image->pixbuf),
1326                                        gdk_pixbuf_get_width(image->pixbuf),
1327                                        gdk_pixbuf_get_height(image->pixbuf),
1328                                        rs);
1329             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1330                                          image->x.computed, image->y.computed,
1331                                          image->width.computed, image->height.computed);
1332         } else { // preserveAspectRatio
1333             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1334                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1335                                        image->trimwidth,
1336                                        image->trimheight,
1337                                        rs);
1338             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1339                                          image->viewx, image->viewy,
1340                                          image->viewwidth, image->viewheight);
1341         }
1342     }
1345 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
1347     /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1348     to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1349     the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1350     */
1352     g_assert(item != NULL);
1353     g_assert(SP_IS_IMAGE(item));
1355     if (item->clip_ref->getObject()) {
1356         //We are looking at a clipped image: do not return any snappoints, as these might be
1357         //far far away from the visible part from the clipped image
1358         //TODO Do return snappoints, but only when within visual bounding box
1359     } else {
1360         // The image has not been clipped: return its corners, which might be rotated for example
1361         SPImage &image = *SP_IMAGE(item);
1362         double const x0 = image.x.computed;
1363         double const y0 = image.y.computed;
1364         double const x1 = x0 + image.width.computed;
1365         double const y1 = y0 + image.height.computed;
1366         Geom::Matrix const i2d (sp_item_i2d_affine (item));
1367         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1368         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1369         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1370         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1371     }
1374 /*
1375  * Initially we'll do:
1376  * Transform x, y, set x, y, clear translation
1377  */
1379 static Geom::Matrix
1380 sp_image_set_transform(SPItem *item, Geom::Matrix const &xform)
1382     SPImage *image = SP_IMAGE(item);
1384     /* Calculate position in parent coords. */
1385     Geom::Point pos( Geom::Point(image->x.computed, image->y.computed) * xform );
1387     /* This function takes care of translation and scaling, we return whatever parts we can't
1388        handle. */
1389     Geom::Matrix ret(Geom::Matrix(xform).without_translation());
1390     Geom::Point const scale(hypot(ret[0], ret[1]),
1391                             hypot(ret[2], ret[3]));
1392     if ( scale[Geom::X] > MAGIC_EPSILON ) {
1393         ret[0] /= scale[Geom::X];
1394         ret[1] /= scale[Geom::X];
1395     } else {
1396         ret[0] = 1.0;
1397         ret[1] = 0.0;
1398     }
1399     if ( scale[Geom::Y] > MAGIC_EPSILON ) {
1400         ret[2] /= scale[Geom::Y];
1401         ret[3] /= scale[Geom::Y];
1402     } else {
1403         ret[2] = 0.0;
1404         ret[3] = 1.0;
1405     }
1407     image->width = image->width.computed * scale[Geom::X];
1408     image->height = image->height.computed * scale[Geom::Y];
1410     /* Find position in item coords */
1411     pos = pos * ret.inverse();
1412     image->x = pos[Geom::X];
1413     image->y = pos[Geom::Y];
1415     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1417     return ret;
1420 static GdkPixbuf *
1421 sp_image_repr_read_dataURI (const gchar * uri_data)
1423     GdkPixbuf * pixbuf = NULL;
1425     gint data_is_image = 0;
1426     gint data_is_base64 = 0;
1428     const gchar * data = uri_data;
1430     while (*data) {
1431         if (strncmp(data,"base64",6) == 0) {
1432             /* base64-encoding */
1433             data_is_base64 = 1;
1434             data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1435             data += 6;
1436         }
1437         else if (strncmp(data,"image/png",9) == 0) {
1438             /* PNG image */
1439             data_is_image = 1;
1440             data += 9;
1441         }
1442         else if (strncmp(data,"image/jpg",9) == 0) {
1443             /* JPEG image */
1444             data_is_image = 1;
1445             data += 9;
1446         }
1447         else if (strncmp(data,"image/jpeg",10) == 0) {
1448             /* JPEG image */
1449             data_is_image = 1;
1450             data += 10;
1451         }
1452         else { /* unrecognized option; skip it */
1453             while (*data) {
1454                 if (((*data) == ';') || ((*data) == ',')) {
1455                     break;
1456                 }
1457                 data++;
1458             }
1459         }
1460         if ((*data) == ';') {
1461             data++;
1462             continue;
1463         }
1464         if ((*data) == ',') {
1465             data++;
1466             break;
1467         }
1468     }
1470     if ((*data) && data_is_image && data_is_base64) {
1471         pixbuf = sp_image_repr_read_b64(data);
1472     }
1474     return pixbuf;
1477 static GdkPixbuf *
1478 sp_image_repr_read_b64 (const gchar * uri_data)
1480     GdkPixbuf * pixbuf = NULL;
1482     static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1484     GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
1485     if (loader) {
1486         bool eos = false;
1487         bool failed = false;
1488         const gchar* btr = uri_data;
1489         gchar ud[4];
1490         guchar bd[57];
1492         while (!eos) {
1493             gint ell = 0;
1494             for (gint j = 0; j < 19; j++) {
1495                 gint len = 0;
1496                 for (gint k = 0; k < 4; k++) {
1497                     while (isspace ((int) (*btr))) {
1498                         if ((*btr) == '\0') break;
1499                         btr++;
1500                     }
1501                     if (eos) {
1502                         ud[k] = 0;
1503                         continue;
1504                     }
1505                     if (((*btr) == '\0') || ((*btr) == '=')) {
1506                         eos = true;
1507                         ud[k] = 0;
1508                         continue;
1509                     }
1510                     ud[k] = 64;
1511                     for (gint b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1512                         if (B64[b] == (*btr)) {
1513                             ud[k] = (gchar) b;
1514                             break;
1515                         }
1516                     }
1517                     if (ud[k] == 64) { /* data corruption ?? */
1518                         eos = true;
1519                         ud[k] = 0;
1520                         continue;
1521                     }
1522                     btr++;
1523                     len++;
1524                 }
1525                 guint32 bits = (guint32) ud[0];
1526                 bits = (bits << 6) | (guint32) ud[1];
1527                 bits = (bits << 6) | (guint32) ud[2];
1528                 bits = (bits << 6) | (guint32) ud[3];
1529                 bd[ell++] = (guchar) ((bits & 0xff0000) >> 16);
1530                 if (len > 2) {
1531                     bd[ell++] = (guchar) ((bits & 0xff00) >>  8);
1532                 }
1533                 if (len > 3) {
1534                     bd[ell++] = (guchar)  (bits & 0xff);
1535                 }
1536             }
1538             if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) ell, NULL)) {
1539                 failed = true;
1540                 break;
1541             }
1542         }
1544         gdk_pixbuf_loader_close (loader, NULL);
1546         if (!failed) {
1547             pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1548         }
1549     }
1551     return pixbuf;
1554 static void
1555 sp_image_set_curve(SPImage *image)
1557     //create a curve at the image's boundary for snapping
1558     if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) {
1559         if (image->curve) {
1560             image->curve = image->curve->unref();
1561         }
1562     } else {
1563         NRRect rect;
1564         sp_image_bbox(image, &rect, Geom::identity(), 0);
1565         Geom::Rect rect2 = to_2geom(*rect.upgrade());
1566         SPCurve *c = SPCurve::new_from_rect(rect2);
1568         if (image->curve) {
1569             image->curve = image->curve->unref();
1570         }
1572         if (c) {
1573             image->curve = c->ref();
1575             c->unref();
1576         }
1577     }
1580 /**
1581  * Return duplicate of curve (if any exists) or NULL if there is no curve
1582  */
1583 SPCurve *
1584 sp_image_get_curve (SPImage *image)
1586     SPCurve *result = 0;
1587     if (image->curve) {
1588         result = image->curve->copy();
1589     }
1590     return result;
1593 void sp_image_refresh_if_outdated( SPImage* image )
1595     if ( image->href && image->lastMod ) {
1596         // It *might* change
1598         struct stat st;
1599         memset(&st, 0, sizeof(st));
1600         int val = g_stat(image->pixPath, &st);
1601         if ( !val ) {
1602             // stat call worked. Check time now
1603             if ( st.st_mtime != image->lastMod ) {
1604                 SPCtx *ctx = 0;
1605                 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1606                 sp_image_update(image, ctx, flags);
1607             }
1608         }
1609     }
1612 /*
1613   Local Variables:
1614   mode:c++
1615   c-file-style:"stroustrup"
1616   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1617   indent-tabs-mode:nil
1618   fill-column:99
1619   End:
1620 */
1621 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :