Code

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