Code

Follow-on cleanup for bug #271401. Isolates setjmp(), removes early return, and allow...
[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" );
266 static bool readPngAndHeaders( PushPull &youme, gint & dpiX, gint & dpiY )
268     bool good = true;
270     gboolean isPng = !png_sig_cmp( youme.scratch + youme.offset, 0, youme.available() );
271     //g_message( "  png? %s", (isPng ? "Yes":"No") );
272     if ( isPng ) {
273         png_structp pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
274                                                      0, //(png_voidp)user_error_ptr,
275                                                      0, //user_error_fn,
276                                                      0 //user_warning_fn
277             );
278         png_infop infoPtr = pngPtr ? png_create_info_struct( pngPtr ) : 0;
280         if ( pngPtr && infoPtr ) {
281             if ( setjmp(png_jmpbuf(pngPtr)) ) {
282                 // libpng calls longjmp to return here if an error occurs.
283                 good = false;
284             }
286             if (good) {
287                 png_set_read_fn( pngPtr, &youme, user_read_data );
288                 //g_message( "In" );
290                 //png_read_info( pngPtr, infoPtr );
291                 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, 0 );
293                 //g_message("out");
295                 /*
296                   if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
297                   {
298                   g_message("pHYs chunk now valid" );
299                   }
300                   if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
301                   {
302                   g_message("sCAL chunk now valid" );
303                   }
304                 */
306                 png_uint_32 res_x = 0;
307                 png_uint_32 res_y = 0;
308                 int unit_type = 0;
309                 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) ) {
310 //                                     g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
311 //                                                (unit_type == 1? "per meter" : "unknown")
312 //                                         );
314 //                                     g_message( "    dpi: (%d, %d)",
315 //                                                (int)(0.5 + ((double)res_x)/39.37),
316 //                                                (int)(0.5 + ((double)res_y)/39.37) );
317                     if ( unit_type == PNG_RESOLUTION_METER )
318                     {
319                         // TODO come up with a more accurate DPI setting
320                         dpiX = (int)(0.5 + ((double)res_x)/39.37);
321                         dpiY = (int)(0.5 + ((double)res_y)/39.37);
322                     }
323                 } else {
324 //                                     g_message( "pHYs no" );
325                 }
327 /*
328   double width = 0;
329   double height = 0;
330   int unit = 0;
331   if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
332   {
333   gchar* vals[] = {
334   "unknown", // PNG_SCALE_UNKNOWN
335   "meter", // PNG_SCALE_METER
336   "radian", // PNG_SCALE_RADIAN
337   "last", //
338   NULL
339   };
341   g_message( "sCAL: (%f, %f) %d (%s)",
342   width, height, unit,
343   ((unit >= 0 && unit < 3) ? vals[unit]:"???")
344   );
345   }
346 */
348 #if defined(PNG_sRGB_SUPPORTED)
349                 {
350                     int intent = 0;
351                     if ( png_get_sRGB(pngPtr, infoPtr, &intent) ) {
352 //                                         g_message("Found an sRGB png chunk");
353                     }
354                 }
355 #endif // defined(PNG_sRGB_SUPPORTED)
357 #if defined(PNG_cHRM_SUPPORTED)
358                 {
359                     double white_x = 0;
360                     double white_y = 0;
361                     double red_x = 0;
362                     double red_y = 0;
363                     double green_x = 0;
364                     double green_y = 0;
365                     double blue_x = 0;
366                     double blue_y = 0;
368                     if ( png_get_cHRM(pngPtr, infoPtr,
369                                       &white_x, &white_y,
370                                       &red_x, &red_y,
371                                       &green_x, &green_y,
372                                       &blue_x, &blue_y) ) {
373 //                                         g_message("Found a cHRM png chunk");
374                     }
375                 }
376 #endif // defined(PNG_cHRM_SUPPORTED)
378 #if defined(PNG_gAMA_SUPPORTED)
379                 {
380                     double file_gamma = 0;
381                     if ( png_get_gAMA(pngPtr, infoPtr, &file_gamma) ) {
382 //                                         g_message("Found a gAMA png chunk");
383                     }
384                 }
385 #endif // defined(PNG_gAMA_SUPPORTED)
387 #if defined(PNG_iCCP_SUPPORTED)
388                 {
389                     char* name = 0;
390                     int compression_type = 0;
391                     char* profile = 0;
392                     png_uint_32 proflen = 0;
393                     if ( png_get_iCCP(pngPtr, infoPtr, &name, &compression_type, &profile, &proflen) ) {
394 //                                         g_message("Found an iCCP chunk named [%s] with %d bytes and comp %d", name, proflen, compression_type);
395                     }
396                 }
397 #endif // defined(PNG_iCCP_SUPPORTED)
399             }
400         } else {
401             g_message("Error when creating PNG read struct");
402         }
404         // now clean it up.
405         if (pngPtr && infoPtr) {
406             png_destroy_read_struct( &pngPtr, &infoPtr, 0 );
407             pngPtr = 0;
408             infoPtr = 0;
409         } else if (pngPtr) {
410             png_destroy_read_struct( &pngPtr, 0, 0 );
411             pngPtr = 0;
412         }
413     } else {
414         good = false; // Was not a png file
415     }
417     return good;
420 static GdkPixbuf* pixbuf_new_from_file( const char *filename, time_t &modTime, gchar*& pixPath, GError **/*error*/ )
422     GdkPixbuf* buf = NULL;
423     PushPull youme;
424     gint dpiX = 0;
425     gint dpiY = 0;
426     modTime = 0;
427     if ( pixPath ) {
428         g_free(pixPath);
429         pixPath = 0;
430     }
432     //buf = gdk_pixbuf_new_from_file( filename, error );
433     dump_fopen_call( filename, "pixbuf_new_from_file" );
434     FILE* fp = fopen_utf8name( filename, "r" );
435     if ( fp )
436     {
437         {
438             struct stat st;
439             memset(&st, 0, sizeof(st));
440             int val = g_stat(filename, &st);
441             if ( !val ) {
442                 modTime = st.st_mtime;
443                 pixPath = g_strdup(filename);
444             }
445         }
447         GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
448         if ( loader )
449         {
450             GError *err = NULL;
452             // short buffer
453             guchar scratch[1024];
454             gboolean latter = FALSE;
456             youme.fp = fp;
457             youme.scratch = scratch;
458             youme.size = sizeof(scratch);
459             youme.used = 0;
460             youme.offset = 0;
461             youme.loader = loader;
463             bool dropOut = false;
464             while ( !feof(fp) && !dropOut )
465             {
466                 if ( youme.readMore() ) {
467                     if ( youme.first ) {
468                         //g_message( "First data chunk" );
469                         youme.first = FALSE;
470                         if (!readPngAndHeaders(youme, dpiX, dpiY))
471                         {
472                             // We failed to read the PNG headers and info.
473                             // The GDK pixbuf loader may still get something.
474                             dropOut = true;
475                         }
476                     } else if ( !latter ) {
477                         latter = TRUE;
478                         //g_message("  READing latter");
479                     }
480                     // Now clear out the buffer so we can read more.
481                     // (dumping out unused)
482                     youme.clear();
483                 }
484             }
486             gboolean ok = gdk_pixbuf_loader_close(loader, &err);
487             if ( ok ) {
488                 buf = gdk_pixbuf_loader_get_pixbuf( loader );
489                 if ( buf ) {
490                     g_object_ref(buf);
492                     if ( dpiX ) {
493                         gchar *tmp = g_strdup_printf( "%d", dpiX );
494                         if ( tmp ) {
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                         gchar *tmp = g_strdup_printf( "%d", dpiY );
502                         if ( tmp ) {
503                             //g_message("Need to set DpiY: %s", tmp);
504                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
505                             g_free( tmp );
506                         }
507                     }
508                 }
509             } else {
510                 // do something
511                 g_message("error loading pixbuf at close");
512             }
514             g_object_unref(loader);
515         } else {
516             g_message("error when creating pixbuf loader");
517         }
518         fclose( fp );
519         fp = 0;
520     } else {
521         g_warning ("Unable to open linked file: %s", filename);
522     }
524 /*
525     if ( buf )
526     {
527         const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
528         if ( bloop )
529         {
530             g_message("DPI X is [%s]", bloop);
531         }
532         bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
533         if ( bloop )
534         {
535             g_message("DPI Y is [%s]", bloop);
536         }
537     }
538 */
540     return buf;
543 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
545     time_t modTime = 0;
546     gchar* pixPath = 0;
547     GdkPixbuf* result = pixbuf_new_from_file( filename, modTime, pixPath, error );
548     if (pixPath) {
549         g_free(pixPath);
550     }
551     return result;
558 GType
559 sp_image_get_type (void)
561     static GType image_type = 0;
562     if (!image_type) {
563         GTypeInfo image_info = {
564             sizeof (SPImageClass),
565             NULL,       /* base_init */
566             NULL,       /* base_finalize */
567             (GClassInitFunc) sp_image_class_init,
568             NULL,       /* class_finalize */
569             NULL,       /* class_data */
570             sizeof (SPImage),
571             16, /* n_preallocs */
572             (GInstanceInitFunc) sp_image_init,
573             NULL,       /* value_table */
574         };
575         image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
576     }
577     return image_type;
580 static void
581 sp_image_class_init (SPImageClass * klass)
583     GObjectClass * gobject_class;
584     SPObjectClass * sp_object_class;
585     SPItemClass * item_class;
587     gobject_class = (GObjectClass *) klass;
588     sp_object_class = (SPObjectClass *) klass;
589     item_class = (SPItemClass *) klass;
591     parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
593     sp_object_class->build = sp_image_build;
594     sp_object_class->release = sp_image_release;
595     sp_object_class->set = sp_image_set;
596     sp_object_class->update = sp_image_update;
597     sp_object_class->modified = sp_image_modified;
598     sp_object_class->write = sp_image_write;
600     item_class->bbox = sp_image_bbox;
601     item_class->print = sp_image_print;
602     item_class->description = sp_image_description;
603     item_class->show = sp_image_show;
604     item_class->snappoints = sp_image_snappoints;
605     item_class->set_transform = sp_image_set_transform;
608 static void sp_image_init( SPImage *image )
610     image->x.unset();
611     image->y.unset();
612     image->width.unset();
613     image->height.unset();
614     image->aspect_align = SP_ASPECT_NONE;
616     image->trimx = 0;
617     image->trimy = 0;
618     image->trimwidth = 0;
619     image->trimheight = 0;
620     image->viewx = 0;
621     image->viewy = 0;
622     image->viewwidth = 0;
623     image->viewheight = 0;
625     image->curve = NULL;
627     image->href = 0;
628 #if ENABLE_LCMS
629     image->color_profile = 0;
630 #endif // ENABLE_LCMS
631     image->pixbuf = 0;
632     image->pixPath = 0;
633     image->lastMod = 0;
636 static void
637 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
639     if (((SPObjectClass *) parent_class)->build) {
640         ((SPObjectClass *) parent_class)->build (object, document, repr);
641     }
643     sp_object_read_attr (object, "xlink:href");
644     sp_object_read_attr (object, "x");
645     sp_object_read_attr (object, "y");
646     sp_object_read_attr (object, "width");
647     sp_object_read_attr (object, "height");
648     sp_object_read_attr (object, "preserveAspectRatio");
649     sp_object_read_attr (object, "color-profile");
651     /* Register */
652     sp_document_add_resource (document, "image", object);
655 static void
656 sp_image_release (SPObject *object)
658     SPImage *image = SP_IMAGE(object);
660     if (SP_OBJECT_DOCUMENT (object)) {
661         /* Unregister ourselves */
662         sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
663     }
665     if (image->href) {
666         g_free (image->href);
667         image->href = NULL;
668     }
670     if (image->pixbuf) {
671         gdk_pixbuf_unref (image->pixbuf);
672         image->pixbuf = NULL;
673     }
675 #if ENABLE_LCMS
676     if (image->color_profile) {
677         g_free (image->color_profile);
678         image->color_profile = NULL;
679     }
680 #endif // ENABLE_LCMS
682     if (image->pixPath) {
683         g_free(image->pixPath);
684         image->pixPath = 0;
685     }
687     if (image->curve) {
688         image->curve = image->curve->unref();
689     }
691     if (((SPObjectClass *) parent_class)->release) {
692         ((SPObjectClass *) parent_class)->release (object);
693     }
696 static void
697 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
699     SPImage *image = SP_IMAGE (object);
701     switch (key) {
702         case SP_ATTR_XLINK_HREF:
703             g_free (image->href);
704             image->href = (value) ? g_strdup (value) : NULL;
705             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
706             break;
707         case SP_ATTR_X:
708             if (!image->x.readAbsolute(value)) {
709                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
710                 image->x.unset();
711             }
712             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
713             break;
714         case SP_ATTR_Y:
715             if (!image->y.readAbsolute(value)) {
716                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
717                 image->y.unset();
718             }
719             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
720             break;
721         case SP_ATTR_WIDTH:
722             if (!image->width.readAbsolute(value)) {
723                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
724                 image->width.unset();
725             }
726             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
727             break;
728         case SP_ATTR_HEIGHT:
729             if (!image->height.readAbsolute(value)) {
730                 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
731                 image->height.unset();
732             }
733             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
734             break;
735         case SP_ATTR_PRESERVEASPECTRATIO:
736             /* Do setup before, so we can use break to escape */
737             image->aspect_align = SP_ASPECT_NONE;
738             image->aspect_clip = SP_ASPECT_MEET;
739             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
740             if (value) {
741                 int len;
742                 gchar c[256];
743                 const gchar *p, *e;
744                 unsigned int align, clip;
745                 p = value;
746                 while (*p && *p == 32) p += 1;
747                 if (!*p) break;
748                 e = p;
749                 while (*e && *e != 32) e += 1;
750                 len = e - p;
751                 if (len > 8) break;
752                 memcpy (c, value, len);
753                 c[len] = 0;
754                 /* Now the actual part */
755                 if (!strcmp (c, "none")) {
756                     align = SP_ASPECT_NONE;
757                 } else if (!strcmp (c, "xMinYMin")) {
758                     align = SP_ASPECT_XMIN_YMIN;
759                 } else if (!strcmp (c, "xMidYMin")) {
760                     align = SP_ASPECT_XMID_YMIN;
761                 } else if (!strcmp (c, "xMaxYMin")) {
762                     align = SP_ASPECT_XMAX_YMIN;
763                 } else if (!strcmp (c, "xMinYMid")) {
764                     align = SP_ASPECT_XMIN_YMID;
765                 } else if (!strcmp (c, "xMidYMid")) {
766                     align = SP_ASPECT_XMID_YMID;
767                 } else if (!strcmp (c, "xMaxYMid")) {
768                     align = SP_ASPECT_XMAX_YMID;
769                 } else if (!strcmp (c, "xMinYMax")) {
770                     align = SP_ASPECT_XMIN_YMAX;
771                 } else if (!strcmp (c, "xMidYMax")) {
772                     align = SP_ASPECT_XMID_YMAX;
773                 } else if (!strcmp (c, "xMaxYMax")) {
774                     align = SP_ASPECT_XMAX_YMAX;
775                 } else {
776                     break;
777                 }
778                 clip = SP_ASPECT_MEET;
779                 while (*e && *e == 32) e += 1;
780                 if (*e) {
781                     if (!strcmp (e, "meet")) {
782                         clip = SP_ASPECT_MEET;
783                     } else if (!strcmp (e, "slice")) {
784                         clip = SP_ASPECT_SLICE;
785                     } else {
786                         break;
787                     }
788                 }
789                 image->aspect_align = align;
790                 image->aspect_clip = clip;
791             }
792             break;
793 #if ENABLE_LCMS
794         case SP_PROP_COLOR_PROFILE:
795             if ( image->color_profile ) {
796                 g_free (image->color_profile);
797             }
798             image->color_profile = (value) ? g_strdup (value) : NULL;
799 #ifdef DEBUG_LCMS
800             if ( value ) {
801                 DEBUG_MESSAGE( lcmsFour, "<image> color-profile set to '%s'", value );
802             } else {
803                 DEBUG_MESSAGE( lcmsFour, "<image> color-profile cleared" );
804             }
805 #endif // DEBUG_LCMS
806             // TODO check on this HREF_MODIFIED flag
807             object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
808             break;
809 #endif // ENABLE_LCMS
810         default:
811             if (((SPObjectClass *) (parent_class))->set)
812                 ((SPObjectClass *) (parent_class))->set (object, key, value);
813             break;
814     }
816     sp_image_set_curve(image); //creates a curve at the image's boundary for snapping
819 static void
820 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
822     SPImage *image = SP_IMAGE(object);
823     SPDocument *doc = SP_OBJECT_DOCUMENT(object);
825     if (((SPObjectClass *) (parent_class))->update) {
826         ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
827     }
829     if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
830         if (image->pixbuf) {
831             gdk_pixbuf_unref (image->pixbuf);
832             image->pixbuf = NULL;
833         }
834         if ( image->pixPath ) {
835             g_free(image->pixPath);
836             image->pixPath = 0;
837         }
838         image->lastMod = 0;
839         if (image->href) {
840             GdkPixbuf *pixbuf;
841             pixbuf = sp_image_repr_read_image (
842                 image->lastMod,
843                 image->pixPath,
844                 object->repr->attribute("xlink:href"),
845                 object->repr->attribute("sodipodi:absref"),
846                 doc->base);
847             if (pixbuf) {
848                 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
849 // BLIP
850 #if ENABLE_LCMS
851                 if ( image->color_profile )
852                 {
853                     int imagewidth = gdk_pixbuf_get_width( pixbuf );
854                     int imageheight = gdk_pixbuf_get_height( pixbuf );
855                     int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
856                     guchar* px = gdk_pixbuf_get_pixels( pixbuf );
858                     if ( px ) {
859 #ifdef DEBUG_LCMS
860                         DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
861 #endif // DEBUG_LCMS
862                         guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
863                         cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
864                                                                               &profIntent,
865                                                                               image->color_profile );
866                         if ( prof ) {
867                             icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
868                             if ( profileClass != icSigNamedColorClass ) {
869                                 int intent = INTENT_PERCEPTUAL;
870                                 switch ( profIntent ) {
871                                     case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
872                                         intent = INTENT_RELATIVE_COLORIMETRIC;
873                                         break;
874                                     case Inkscape::RENDERING_INTENT_SATURATION:
875                                         intent = INTENT_SATURATION;
876                                         break;
877                                     case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
878                                         intent = INTENT_ABSOLUTE_COLORIMETRIC;
879                                         break;
880                                     case Inkscape::RENDERING_INTENT_PERCEPTUAL:
881                                     case Inkscape::RENDERING_INTENT_UNKNOWN:
882                                     case Inkscape::RENDERING_INTENT_AUTO:
883                                     default:
884                                         intent = INTENT_PERCEPTUAL;
885                                 }
886                                 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
887                                 cmsHTRANSFORM transf = cmsCreateTransform( prof,
888                                                                            TYPE_RGBA_8,
889                                                                            destProf,
890                                                                            TYPE_RGBA_8,
891                                                                            intent, 0 );
892                                 if ( transf ) {
893                                     guchar* currLine = px;
894                                     for ( int y = 0; y < imageheight; y++ ) {
895                                         // Since the types are the same size, we can do the transformation in-place
896                                         cmsDoTransform( transf, currLine, currLine, imagewidth );
897                                         currLine += rowstride;
898                                     }
900                                     cmsDeleteTransform( transf );
901                                 }
902 #ifdef DEBUG_LCMS
903                                 else
904                                 {
905                                     DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
906                                 }
907 #endif // DEBUG_LCMS
908                                 cmsCloseProfile( destProf );
909                             }
910 #ifdef DEBUG_LCMS
911                             else
912                             {
913                                 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
914                             }
915 #endif // DEBUG_LCMS
916                         }
917 #ifdef DEBUG_LCMS
918                         else
919                         {
920                             DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
921                         }
922 #endif // DEBUG_LCMS
923                     }
924                 }
925 #endif // ENABLE_LCMS
926                 image->pixbuf = pixbuf;
927             }
928         }
929     }
930     // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
931     if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
932         int imagewidth, imageheight;
933         double x,y;
935         imagewidth = gdk_pixbuf_get_width (image->pixbuf);
936         imageheight = gdk_pixbuf_get_height (image->pixbuf);
938         switch (image->aspect_align) {
939             case SP_ASPECT_XMIN_YMIN:
940                 x = 0.0;
941                 y = 0.0;
942                 break;
943             case SP_ASPECT_XMID_YMIN:
944                 x = 0.5;
945                 y = 0.0;
946                 break;
947             case SP_ASPECT_XMAX_YMIN:
948                 x = 1.0;
949                 y = 0.0;
950                 break;
951             case SP_ASPECT_XMIN_YMID:
952                 x = 0.0;
953                 y = 0.5;
954                 break;
955             case SP_ASPECT_XMID_YMID:
956                 x = 0.5;
957                 y = 0.5;
958                 break;
959             case SP_ASPECT_XMAX_YMID:
960                 x = 1.0;
961                 y = 0.5;
962                 break;
963             case SP_ASPECT_XMIN_YMAX:
964                 x = 0.0;
965                 y = 1.0;
966                 break;
967             case SP_ASPECT_XMID_YMAX:
968                 x = 0.5;
969                 y = 1.0;
970                 break;
971             case SP_ASPECT_XMAX_YMAX:
972                 x = 1.0;
973                 y = 1.0;
974                 break;
975             default:
976                 x = 0.0;
977                 y = 0.0;
978                 break;
979         }
981         if (image->aspect_clip == SP_ASPECT_SLICE) {
982             image->viewx = image->x.computed;
983             image->viewy = image->y.computed;
984             image->viewwidth = image->width.computed;
985             image->viewheight = image->height.computed;
986             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
987                 // Pixels aspect is wider than bounding box
988                 image->trimheight = imageheight;
989                 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
990                 image->trimy = 0;
991                 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
992             } else {
993                 // Pixels aspect is taller than bounding box
994                 image->trimwidth = imagewidth;
995                 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
996                 image->trimx = 0;
997                 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
998             }
999         } else {
1000             // Otherwise, assume SP_ASPECT_MEET
1001             image->trimx = 0;
1002             image->trimy = 0;
1003             image->trimwidth = imagewidth;
1004             image->trimheight = imageheight;
1005             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
1006                 // Pixels aspect is wider than bounding boz
1007                 image->viewwidth = image->width.computed;
1008                 image->viewheight = image->viewwidth * imageheight / imagewidth;
1009                 image->viewx=image->x.computed;
1010                 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
1011             } else {
1012                 // Pixels aspect is taller than bounding box
1013                 image->viewheight = image->height.computed;
1014                 image->viewwidth = image->viewheight * imagewidth / imageheight;
1015                 image->viewy=image->y.computed;
1016                 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
1017             }
1018         }
1019     }
1020     sp_image_update_canvas_image ((SPImage *) object);
1023 static void
1024 sp_image_modified (SPObject *object, unsigned int flags)
1026     SPImage *image = SP_IMAGE (object);
1028     if (((SPObjectClass *) (parent_class))->modified) {
1029       (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
1030     }
1032     if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
1033         for (SPItemView *v = SP_ITEM (image)->display; v != NULL; v = v->next) {
1034             nr_arena_image_set_style (NR_ARENA_IMAGE (v->arenaitem), object->style);
1035         }
1036     }
1039 static Inkscape::XML::Node *
1040 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1042     SPImage *image = SP_IMAGE (object);
1044     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1045         repr = xml_doc->createElement("svg:image");
1046     }
1048     repr->setAttribute("xlink:href", image->href);
1049     /* fixme: Reset attribute if needed (Lauris) */
1050     if (image->x._set) {
1051         sp_repr_set_svg_double(repr, "x", image->x.computed);
1052     }
1053     if (image->y._set) {
1054         sp_repr_set_svg_double(repr, "y", image->y.computed);
1055     }
1056     if (image->width._set) {
1057         sp_repr_set_svg_double(repr, "width", image->width.computed);
1058     }
1059     if (image->height._set) {
1060         sp_repr_set_svg_double(repr, "height", image->height.computed);
1061     }
1062     repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1063 #if ENABLE_LCMS
1064     if (image->color_profile) {
1065         repr->setAttribute("color-profile", image->color_profile);
1066     }
1067 #endif // ENABLE_LCMS
1069     if (((SPObjectClass *) (parent_class))->write) {
1070         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags);
1071     }
1073     return repr;
1076 static void
1077 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1079     SPImage const &image = *SP_IMAGE(item);
1081     if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1082         double const x0 = image.x.computed;
1083         double const y0 = image.y.computed;
1084         double const x1 = x0 + image.width.computed;
1085         double const y1 = y0 + image.height.computed;
1087         nr_rect_union_pt(bbox, Geom::Point(x0, y0) * transform);
1088         nr_rect_union_pt(bbox, Geom::Point(x1, y0) * transform);
1089         nr_rect_union_pt(bbox, Geom::Point(x1, y1) * transform);
1090         nr_rect_union_pt(bbox, Geom::Point(x0, y1) * transform);
1091     }
1094 static void
1095 sp_image_print (SPItem *item, SPPrintContext *ctx)
1097     SPImage *image = SP_IMAGE(item);
1099     if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) {
1100         guchar *px = gdk_pixbuf_get_pixels(image->pixbuf);
1101         int w = gdk_pixbuf_get_width(image->pixbuf);
1102         int h = gdk_pixbuf_get_height(image->pixbuf);
1103         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1104         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1106         Geom::Matrix t;
1107         if (image->aspect_align == SP_ASPECT_NONE) {
1108             /* fixme: (Lauris) */
1109             Geom::Translate tp(image->x.computed, image->y.computed);
1110             Geom::Scale s(image->width.computed, -image->height.computed);
1111             Geom::Translate ti(0.0, -1.0);
1112             t = s * tp;
1113             t = ti * t;
1114         } else { // preserveAspectRatio
1115             Geom::Translate tp(image->viewx, image->viewy);
1116             Geom::Scale s(image->viewwidth, -image->viewheight);
1117             Geom::Translate ti(0.0, -1.0);
1118             t = s * tp;
1119             t = ti * t;
1120         }
1122         if (image->aspect_align == SP_ASPECT_NONE) {
1123             sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1124         } else { // preserveAspectRatio
1125             sp_print_image_R8G8B8A8_N(ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE(item));
1126         }
1127     }
1130 static gchar *
1131 sp_image_description(SPItem *item)
1133     SPImage *image = SP_IMAGE(item);
1134     char *href_desc;
1135     if (image->href) {
1136         href_desc = (strncmp(image->href, "data:", 5) == 0)
1137             ? g_strdup(_("embedded"))
1138             : xml_quote_strdup(image->href);
1139     } else {
1140         g_warning("Attempting to call strncmp() with a null pointer.");
1141         href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1142     }
1144     char *ret = ( image->pixbuf == NULL
1145                   ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1146                   : g_strdup_printf(_("<b>Image</b> %d &#215; %d: %s"),
1147                                     gdk_pixbuf_get_width(image->pixbuf),
1148                                     gdk_pixbuf_get_height(image->pixbuf),
1149                                     href_desc) );
1150     g_free(href_desc);
1151     return ret;
1154 static NRArenaItem *
1155 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1157     SPImage * image = SP_IMAGE(item);
1158     NRArenaItem *ai = NRArenaImage::create(arena);
1160     if (image->pixbuf) {
1161         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1162         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1163         nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1164         if (image->aspect_align == SP_ASPECT_NONE) {
1165             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1166                                        gdk_pixbuf_get_pixels(image->pixbuf),
1167                                        gdk_pixbuf_get_width(image->pixbuf),
1168                                        gdk_pixbuf_get_height(image->pixbuf),
1169                                        rs);
1170         } else { // preserveAspectRatio
1171             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1172                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1173                                        image->trimwidth,
1174                                        image->trimheight,
1175                                        rs);
1176         }
1177     } else {
1178         nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai), NULL, 0, 0, 0);
1179     }
1180     if (image->aspect_align == SP_ASPECT_NONE) {
1181         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1182     } else { // preserveAspectRatio
1183         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1184     }
1186     return ai;
1189 /*
1190  * utility function to try loading image from href
1191  *
1192  * docbase/relative_src
1193  * absolute_src
1194  *
1195  */
1197 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1199     GdkPixbuf *pixbuf = 0;
1200     modTime = 0;
1201     if ( pixPath ) {
1202         g_free(pixPath);
1203         pixPath = 0;
1204     }
1206     const gchar *filename = href;
1207     if (filename != NULL) {
1208         if (strncmp (filename,"file:",5) == 0) {
1209             gchar *fullname = g_filename_from_uri(filename, NULL, NULL);
1210             if (fullname) {
1211                 // TODO check this. Was doing a UTF-8 to filename conversion here.
1212                 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1213                 if (pixbuf != NULL) {
1214                     return pixbuf;
1215                 }
1216             }
1217         } else if (strncmp (filename,"data:",5) == 0) {
1218             /* data URI - embedded image */
1219             filename += 5;
1220             pixbuf = sp_image_repr_read_dataURI (filename);
1221             if (pixbuf != NULL) {
1222                 return pixbuf;
1223             }
1224         } else {
1226             if (!g_path_is_absolute (filename)) {
1227                 /* try to load from relative pos combined with document base*/
1228                 const gchar *docbase = base;
1229                 if (!docbase) {
1230                     docbase = ".";
1231                 }
1232                 gchar *fullname = g_build_filename(docbase, filename, NULL);
1234                 // document base can be wrong (on the temporary doc when importing bitmap from a
1235                 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1236                 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1237                 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1238                     pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1239                     g_free (fullname);
1240                     if (pixbuf != NULL) {
1241                         return pixbuf;
1242                     }
1243                 }
1244             }
1246             /* try filename as absolute */
1247             if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1248                 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1249                 if (pixbuf != NULL) {
1250                     return pixbuf;
1251                 }
1252             }
1253         }
1254     }
1256     /* at last try to load from sp absolute path name */
1257     filename = absref;
1258     if (filename != NULL) {
1259         // using absref is outside of SVG rules, so we must at least warn the user
1260         if ( base != NULL && href != NULL ) {
1261             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);
1262         } else {
1263             g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1264         }
1266         pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1267         if (pixbuf != NULL) {
1268             return pixbuf;
1269         }
1270     }
1271     /* Nope: We do not find any valid pixmap file :-( */
1272     pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1274     /* It should be included xpm, so if it still does not does load, */
1275     /* our libraries are broken */
1276     g_assert (pixbuf != NULL);
1278     return pixbuf;
1281 static GdkPixbuf *
1282 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1284     GdkPixbuf* result;
1285     if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1286         result = pixbuf;
1287     } else {
1288         result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1289         gdk_pixbuf_unref(pixbuf);
1290     }
1291     return result;
1294 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1296 static void
1297 sp_image_update_canvas_image (SPImage *image)
1299     SPItem *item = SP_ITEM(image);
1301     if (image->pixbuf) {
1302         /* fixme: We are slightly violating spec here (Lauris) */
1303         if (!image->width._set) {
1304             image->width.computed = gdk_pixbuf_get_width(image->pixbuf);
1305         }
1306         if (!image->height._set) {
1307             image->height.computed = gdk_pixbuf_get_height(image->pixbuf);
1308         }
1309     }
1311     for (SPItemView *v = item->display; v != NULL; v = v->next) {
1312         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1313         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1314         nr_arena_image_set_style(NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1315         if (image->aspect_align == SP_ASPECT_NONE) {
1316             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1317                                        gdk_pixbuf_get_pixels(image->pixbuf),
1318                                        gdk_pixbuf_get_width(image->pixbuf),
1319                                        gdk_pixbuf_get_height(image->pixbuf),
1320                                        rs);
1321             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1322                                          image->x.computed, image->y.computed,
1323                                          image->width.computed, image->height.computed);
1324         } else { // preserveAspectRatio
1325             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1326                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1327                                        image->trimwidth,
1328                                        image->trimheight,
1329                                        rs);
1330             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1331                                          image->viewx, image->viewy,
1332                                          image->viewwidth, image->viewheight);
1333         }
1334     }
1337 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
1339     /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1340     to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1341     the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1342     */
1344     g_assert(item != NULL);
1345     g_assert(SP_IS_IMAGE(item));
1347     if (item->clip_ref->getObject()) {
1348         //We are looking at a clipped image: do not return any snappoints, as these might be
1349         //far far away from the visible part from the clipped image
1350         //TODO Do return snappoints, but only when within visual bounding box
1351     } else {
1352         // The image has not been clipped: return its corners, which might be rotated for example
1353         SPImage &image = *SP_IMAGE(item);
1354         double const x0 = image.x.computed;
1355         double const y0 = image.y.computed;
1356         double const x1 = x0 + image.width.computed;
1357         double const y1 = y0 + image.height.computed;
1358         Geom::Matrix const i2d (sp_item_i2d_affine (item));
1359         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1360         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1361         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1362         p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1363     }
1366 /*
1367  * Initially we'll do:
1368  * Transform x, y, set x, y, clear translation
1369  */
1371 static Geom::Matrix
1372 sp_image_set_transform(SPItem *item, Geom::Matrix const &xform)
1374     SPImage *image = SP_IMAGE(item);
1376     /* Calculate position in parent coords. */
1377     Geom::Point pos( Geom::Point(image->x.computed, image->y.computed) * xform );
1379     /* This function takes care of translation and scaling, we return whatever parts we can't
1380        handle. */
1381     Geom::Matrix ret(Geom::Matrix(xform).without_translation());
1382     Geom::Point const scale(hypot(ret[0], ret[1]),
1383                             hypot(ret[2], ret[3]));
1384     if ( scale[Geom::X] > MAGIC_EPSILON ) {
1385         ret[0] /= scale[Geom::X];
1386         ret[1] /= scale[Geom::X];
1387     } else {
1388         ret[0] = 1.0;
1389         ret[1] = 0.0;
1390     }
1391     if ( scale[Geom::Y] > MAGIC_EPSILON ) {
1392         ret[2] /= scale[Geom::Y];
1393         ret[3] /= scale[Geom::Y];
1394     } else {
1395         ret[2] = 0.0;
1396         ret[3] = 1.0;
1397     }
1399     image->width = image->width.computed * scale[Geom::X];
1400     image->height = image->height.computed * scale[Geom::Y];
1402     /* Find position in item coords */
1403     pos = pos * ret.inverse();
1404     image->x = pos[Geom::X];
1405     image->y = pos[Geom::Y];
1407     item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1409     return ret;
1412 static GdkPixbuf *
1413 sp_image_repr_read_dataURI (const gchar * uri_data)
1415     GdkPixbuf * pixbuf = NULL;
1417     gint data_is_image = 0;
1418     gint data_is_base64 = 0;
1420     const gchar * data = uri_data;
1422     while (*data) {
1423         if (strncmp(data,"base64",6) == 0) {
1424             /* base64-encoding */
1425             data_is_base64 = 1;
1426             data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1427             data += 6;
1428         }
1429         else if (strncmp(data,"image/png",9) == 0) {
1430             /* PNG image */
1431             data_is_image = 1;
1432             data += 9;
1433         }
1434         else if (strncmp(data,"image/jpg",9) == 0) {
1435             /* JPEG image */
1436             data_is_image = 1;
1437             data += 9;
1438         }
1439         else if (strncmp(data,"image/jpeg",10) == 0) {
1440             /* JPEG image */
1441             data_is_image = 1;
1442             data += 10;
1443         }
1444         else { /* unrecognized option; skip it */
1445             while (*data) {
1446                 if (((*data) == ';') || ((*data) == ',')) {
1447                     break;
1448                 }
1449                 data++;
1450             }
1451         }
1452         if ((*data) == ';') {
1453             data++;
1454             continue;
1455         }
1456         if ((*data) == ',') {
1457             data++;
1458             break;
1459         }
1460     }
1462     if ((*data) && data_is_image && data_is_base64) {
1463         pixbuf = sp_image_repr_read_b64(data);
1464     }
1466     return pixbuf;
1469 static GdkPixbuf *
1470 sp_image_repr_read_b64 (const gchar * uri_data)
1472     GdkPixbuf * pixbuf = NULL;
1474     static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1476     GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
1477     if (loader) {
1478         bool eos = false;
1479         bool failed = false;
1480         const gchar* btr = uri_data;
1481         gchar ud[4];
1482         guchar bd[57];
1484         while (!eos) {
1485             gint ell = 0;
1486             for (gint j = 0; j < 19; j++) {
1487                 gint len = 0;
1488                 for (gint k = 0; k < 4; k++) {
1489                     while (isspace ((int) (*btr))) {
1490                         if ((*btr) == '\0') break;
1491                         btr++;
1492                     }
1493                     if (eos) {
1494                         ud[k] = 0;
1495                         continue;
1496                     }
1497                     if (((*btr) == '\0') || ((*btr) == '=')) {
1498                         eos = true;
1499                         ud[k] = 0;
1500                         continue;
1501                     }
1502                     ud[k] = 64;
1503                     for (gint b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1504                         if (B64[b] == (*btr)) {
1505                             ud[k] = (gchar) b;
1506                             break;
1507                         }
1508                     }
1509                     if (ud[k] == 64) { /* data corruption ?? */
1510                         eos = true;
1511                         ud[k] = 0;
1512                         continue;
1513                     }
1514                     btr++;
1515                     len++;
1516                 }
1517                 guint32 bits = (guint32) ud[0];
1518                 bits = (bits << 6) | (guint32) ud[1];
1519                 bits = (bits << 6) | (guint32) ud[2];
1520                 bits = (bits << 6) | (guint32) ud[3];
1521                 bd[ell++] = (guchar) ((bits & 0xff0000) >> 16);
1522                 if (len > 2) {
1523                     bd[ell++] = (guchar) ((bits & 0xff00) >>  8);
1524                 }
1525                 if (len > 3) {
1526                     bd[ell++] = (guchar)  (bits & 0xff);
1527                 }
1528             }
1530             if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) ell, NULL)) {
1531                 failed = true;
1532                 break;
1533             }
1534         }
1536         gdk_pixbuf_loader_close (loader, NULL);
1538         if (!failed) {
1539             pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1540         }
1541     }
1543     return pixbuf;
1546 static void
1547 sp_image_set_curve(SPImage *image)
1549     //create a curve at the image's boundary for snapping
1550     if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) {
1551         if (image->curve) {
1552             image->curve = image->curve->unref();
1553         }
1554     } else {
1555         NRRect rect;
1556         sp_image_bbox(image, &rect, Geom::identity(), 0);
1557         Geom::Rect rect2 = to_2geom(*rect.upgrade());
1558         SPCurve *c = SPCurve::new_from_rect(rect2);
1560         if (image->curve) {
1561             image->curve = image->curve->unref();
1562         }
1564         if (c) {
1565             image->curve = c->ref();
1567             c->unref();
1568         }
1569     }
1572 /**
1573  * Return duplicate of curve (if any exists) or NULL if there is no curve
1574  */
1575 SPCurve *
1576 sp_image_get_curve (SPImage *image)
1578     SPCurve *result = 0;
1579     if (image->curve) {
1580         result = image->curve->copy();
1581     }
1582     return result;
1585 void
1586 sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb, Glib::ustring const &mime_in)
1588     Glib::ustring format, mime;
1589     if (mime_in == "image/jpeg") {
1590         mime = mime_in;
1591         format = "jpeg";
1592     } else {
1593         mime = "image/png";
1594         format = "png";
1595     }
1597     gchar *data;
1598     gsize length;
1599     gdk_pixbuf_save_to_buffer(pb, &data, &length, format.data(), NULL, NULL);
1601     // Save base64 encoded data in image node
1602     // this formula taken from Glib docs
1603     guint needed_size = length * 4 / 3 + length * 4 / (3 * 72) + 7;
1604     needed_size += 5 + 8 + mime.size(); // 5 bytes for data:, 8 for ;base64,
1606     gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1607     buf_work += g_sprintf(buffer, "data:%s;base64,", mime.data());
1609     gint state = 0, save = 0;
1610     gsize written = 0;
1611     written += g_base64_encode_step((guchar*) data, length, TRUE, buf_work, &state, &save);
1612     written += g_base64_encode_close(TRUE, buf_work + written, &state, &save);
1613     buf_work[written] = 0; // null terminate
1615     image_node->setAttribute("xlink:href", buffer);
1616     g_free(buffer);
1619 void sp_image_refresh_if_outdated( SPImage* image )
1621     if ( image->href && image->lastMod ) {
1622         // It *might* change
1624         struct stat st;
1625         memset(&st, 0, sizeof(st));
1626         int val = g_stat(image->pixPath, &st);
1627         if ( !val ) {
1628             // stat call worked. Check time now
1629             if ( st.st_mtime != image->lastMod ) {
1630                 SPCtx *ctx = 0;
1631                 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1632                 sp_image_update(image, ctx, flags);
1633             }
1634         }
1635     }
1638 /*
1639   Local Variables:
1640   mode:c++
1641   c-file-style:"stroustrup"
1642   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1643   indent-tabs-mode:nil
1644   fill-column:99
1645   End:
1646 */
1647 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :