Code

Updating to current trunk
[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>
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, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const *snapprefs);
90 static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags);
91 static Geom::Matrix sp_image_set_transform (SPItem *item, Geom::Matrix const &xform);
92 static void sp_image_set_curve(SPImage *image);
95 static GdkPixbuf *sp_image_repr_read_image( time_t& modTime, gchar*& pixPath, const gchar *href, const gchar *absref, const gchar *base );
96 static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf);
97 static void sp_image_update_canvas_image (SPImage *image);
98 static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data);
99 static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data);
101 static SPItemClass *parent_class;
104 extern "C"
106     void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length );
107     void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length );
108     void user_flush_data( png_structp png_ptr );
113 #ifdef DEBUG_LCMS
114 extern guint update_in_progress;
115 #define DEBUG_MESSAGE_SCISLAC(key, ...) \
116 {\
117     Inkscape::Preferences *prefs = Inkscape::Preferences::get();\
118     bool dump = prefs->getBool("/options/scislac/" #key);\
119     bool dumpD = prefs->getBool("/options/scislac/" #key "D");\
120     bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2");\
121     dumpD &&= ( (update_in_progress == 0) || dumpD2 );\
122     if ( dump )\
123     {\
124         g_message( __VA_ARGS__ );\
126     }\
127     if ( dumpD )\
128     {\
129         GtkWidget *dialog = gtk_message_dialog_new(NULL,\
130                                                    GTK_DIALOG_DESTROY_WITH_PARENT, \
131                                                    GTK_MESSAGE_INFO,    \
132                                                    GTK_BUTTONS_OK,      \
133                                                    __VA_ARGS__          \
134                                                    );\
135         g_signal_connect_swapped(dialog, "response",\
136                                  G_CALLBACK(gtk_widget_destroy),        \
137                                  dialog);                               \
138         gtk_widget_show_all( dialog );\
139     }\
141 #endif // DEBUG_LCMS
143 namespace Inkscape {
144 namespace IO {
146 class PushPull
148 public:
149     gboolean    first;
150     FILE*       fp;
151     guchar*     scratch;
152     gsize       size;
153     gsize       used;
154     gsize       offset;
155     GdkPixbufLoader *loader;
157     PushPull() : first(TRUE),
158                  fp(0),
159                  scratch(0),
160                  size(0),
161                  used(0),
162                  offset(0),
163                  loader(0) {};
165     gboolean readMore()
166     {
167         gboolean good = FALSE;
168         if ( offset )
169         {
170             g_memmove( scratch, scratch + offset, used - offset );
171             used -= offset;
172             offset = 0;
173         }
174         if ( used < size )
175         {
176             gsize space = size - used;
177             gsize got = fread( scratch + used, 1, space, fp );
178             if ( got )
179             {
180                 if ( loader )
181                 {
182                     GError *err = NULL;
183                     //g_message( " __read %d bytes", (int)got );
184                     if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) )
185                     {
186                         //g_message("_error writing pixbuf data");
187                     }
188                 }
190                 used += got;
191                 good = TRUE;
192             }
193             else
194             {
195                 good = FALSE;
196             }
197         }
198         return good;
199     }
201     gsize available() const
202     {
203         return (used - offset);
204     }
206     gsize readOut( gpointer data, gsize length )
207     {
208         gsize giving = available();
209         if ( length < giving )
210         {
211             giving = length;
212         }
213         g_memmove( data, scratch + offset, giving );
214         offset += giving;
215         if ( offset >= used )
216         {
217             offset = 0;
218             used = 0;
219         }
220         return giving;
221     }
223     void clear()
224     {
225         offset = 0;
226         used = 0;
227     }
229 private:
230     PushPull& operator = (const PushPull& other);
231     PushPull(const PushPull& other);
232 };
234 void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length )
236 //    g_message( "user_read_data(%d)", length );
238     PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr);
240     gsize filled = 0;
241     gboolean canRead = TRUE;
243     while ( filled < length && canRead )
244     {
245         gsize some = youme->readOut( data + filled, length - filled );
246         filled += some;
247         if ( filled < length )
248         {
249             canRead &= youme->readMore();
250         }
251     }
252 //    g_message("things out");
255 void user_write_data( png_structp /*png_ptr*/, png_bytep /*data*/, png_size_t /*length*/ )
257     //g_message( "user_write_data(%d)", length );
260 void user_flush_data( png_structp /*png_ptr*/ )
262     //g_message( "user_flush_data" );
265 static GdkPixbuf* pixbuf_new_from_file( const char *filename, time_t &modTime, gchar*& pixPath, GError **/*error*/ )
267     GdkPixbuf* buf = NULL;
268     PushPull youme;
269     gint dpiX = 0;
270     gint dpiY = 0;
271     modTime = 0;
272     if ( pixPath ) {
273         g_free(pixPath);
274         pixPath = 0;
275     }
277     //buf = gdk_pixbuf_new_from_file( filename, error );
278     dump_fopen_call( filename, "pixbuf_new_from_file" );
279     FILE* fp = fopen_utf8name( filename, "r" );
280     if ( fp )
281     {
282         {
283             struct stat st;
284             memset(&st, 0, sizeof(st));
285             int val = g_stat(filename, &st);
286             if ( !val ) {
287                 modTime = st.st_mtime;
288                 pixPath = g_strdup(filename);
289             }
290         }
292         GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
293         if ( loader )
294         {
295             GError *err = NULL;
297             // short buffer
298             guchar scratch[1024];
299             gboolean latter = FALSE;
300             gboolean isPng = FALSE;
301             png_structp pngPtr = NULL;
302             png_infop infoPtr = NULL;
303             //png_infop endPtr = NULL;
305             youme.fp = fp;
306             youme.scratch = scratch;
307             youme.size = sizeof(scratch);
308             youme.used = 0;
309             youme.offset = 0;
310             youme.loader = loader;
312             while ( !feof(fp) )
313             {
314                 if ( youme.readMore() )
315                 {
316                     if ( youme.first )
317                     {
318                         //g_message( "First data chunk" );
319                         youme.first = FALSE;
320                         isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() );
321                         //g_message( "  png? %s", (isPng ? "Yes":"No") );
322                         if ( isPng )
323                         {
324                             pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
325                                                              NULL,//(png_voidp)user_error_ptr,
326                                                              NULL,//user_error_fn,
327                                                              NULL//user_warning_fn
328                                 );
329                             if ( pngPtr )
330                             {
331                                 infoPtr = png_create_info_struct( pngPtr );
332                                 //endPtr = png_create_info_struct( pngPtr );
334                                 png_set_read_fn( pngPtr, &youme, user_read_data );
335                                 //g_message( "In" );
337                                 //png_read_info( pngPtr, infoPtr );
338                                 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL );
340                                 //g_message("out");
342                                 //png_read_end(pngPtr, endPtr);
344                                 /*
345                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
346                                 {
347                                     g_message("pHYs chunk now valid" );
348                                 }
349                                 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
350                                 {
351                                     g_message("sCAL chunk now valid" );
352                                 }
353                                 */
355                                 png_uint_32 res_x = 0;
356                                 png_uint_32 res_y = 0;
357                                 int unit_type = 0;
358                                 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) )
359                                 {
360 //                                     g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
361 //                                                (unit_type == 1? "per meter" : "unknown")
362 //                                         );
364 //                                     g_message( "    dpi: (%d, %d)",
365 //                                                (int)(0.5 + ((double)res_x)/39.37),
366 //                                                (int)(0.5 + ((double)res_y)/39.37) );
367                                     if ( unit_type == PNG_RESOLUTION_METER )
368                                     {
369                                         // TODO come up with a more accurate DPI setting
370                                         dpiX = (int)(0.5 + ((double)res_x)/39.37);
371                                         dpiY = (int)(0.5 + ((double)res_y)/39.37);
372                                     }
373                                 }
374                                 else
375                                 {
376 //                                     g_message( "pHYs no" );
377                                 }
379 /*
380                                 double width = 0;
381                                 double height = 0;
382                                 int unit = 0;
383                                 if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
384                                 {
385                                     gchar* vals[] = {
386                                         "unknown", // PNG_SCALE_UNKNOWN
387                                         "meter", // PNG_SCALE_METER
388                                         "radian", // PNG_SCALE_RADIAN
389                                         "last", //
390                                         NULL
391                                     };
393                                     g_message( "sCAL: (%f, %f) %d (%s)",
394                                                width, height, unit,
395                                                ((unit >= 0 && unit < 3) ? vals[unit]:"???")
396                                         );
397                                 }
398 */
400 #if defined(PNG_sRGB_SUPPORTED)
401                                 {
402                                     int intent = 0;
403                                     if ( png_get_sRGB(pngPtr, infoPtr, &intent) ) {
404 //                                         g_message("Found an sRGB png chunk");
405                                     }
406                                 }
407 #endif // defined(PNG_sRGB_SUPPORTED)
409 #if defined(PNG_cHRM_SUPPORTED)
410                                 {
411                                     double white_x = 0;
412                                     double white_y = 0;
413                                     double red_x = 0;
414                                     double red_y = 0;
415                                     double green_x = 0;
416                                     double green_y = 0;
417                                     double blue_x = 0;
418                                     double blue_y = 0;
420                                     if ( png_get_cHRM(pngPtr, infoPtr,
421                                                       &white_x, &white_y,
422                                                       &red_x, &red_y,
423                                                       &green_x, &green_y,
424                                                       &blue_x, &blue_y) ) {
425 //                                         g_message("Found a cHRM png chunk");
426                                     }
427                                 }
428 #endif // defined(PNG_cHRM_SUPPORTED)
430 #if defined(PNG_gAMA_SUPPORTED)
431                                 {
432                                     double file_gamma = 0;
433                                     if ( png_get_gAMA(pngPtr, infoPtr, &file_gamma) ) {
434 //                                         g_message("Found a gAMA png chunk");
435                                     }
436                                 }
437 #endif // defined(PNG_gAMA_SUPPORTED)
439 #if defined(PNG_iCCP_SUPPORTED)
440                                 {
441                                     char* name = 0;
442                                     int compression_type = 0;
443                                     char* profile = 0;
444                                     png_uint_32 proflen = 0;
445                                     if ( png_get_iCCP(pngPtr, infoPtr, &name, &compression_type, &profile, &proflen) ) {
446 //                                         g_message("Found an iCCP chunk named [%s] with %d bytes and comp %d", name, proflen, compression_type);
447                                     }
448                                 }
449 #endif // defined(PNG_iCCP_SUPPORTED)
452                                 // now clean it up.
453                                 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr );
454                             }
455                             else
456                             {
457 //                                 g_message("Error when creating PNG read struct");
458                             }
459                         }
460                     }
461                     else if ( !latter )
462                     {
463                         latter = TRUE;
464                         //g_message("  READing latter");
465                     }
466                     // Now clear out the buffer so we can read more.
467                     // (dumping out unused)
468                     youme.clear();
469                 }
470             }
472             gboolean ok = gdk_pixbuf_loader_close(loader, &err);
473             if ( ok )
474             {
475                 buf = gdk_pixbuf_loader_get_pixbuf( loader );
476                 if ( buf )
477                 {
478                     g_object_ref(buf);
480                     if ( dpiX )
481                     {
482                         gchar *tmp = g_strdup_printf( "%d", dpiX );
483                         if ( tmp )
484                         {
485 //                             g_message("Need to set DpiX: %s", tmp);
486                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
487                             g_free( tmp );
488                         }
489                     }
490                     if ( dpiY )
491                     {
492                         gchar *tmp = g_strdup_printf( "%d", dpiY );
493                         if ( tmp )
494                         {
495 //                             g_message("Need to set DpiY: %s", tmp);
496                             //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
497                             g_free( tmp );
498                         }
499                     }
500                 }
501             }
502             else
503             {
504                 // do something
505                 g_message("error loading pixbuf at close");
506             }
508             g_object_unref(loader);
509         }
510         else
511         {
512             g_message("error when creating pixbuf loader");
513         }
514         fclose( fp );
515         fp = NULL;
516     }
517     else
518     {
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 (sp_item_get_type (), "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 (sp_item_get_type ());
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     sp_object_read_attr (object, "xlink:href");
642     sp_object_read_attr (object, "x");
643     sp_object_read_attr (object, "y");
644     sp_object_read_attr (object, "width");
645     sp_object_read_attr (object, "height");
646     sp_object_read_attr (object, "preserveAspectRatio");
647     sp_object_read_attr (object, "color-profile");
649     /* Register */
650     sp_document_add_resource (document, "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_document_remove_resource (SP_OBJECT_DOCUMENT (object), "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,
842                 object->repr->attribute("xlink:href"),
843                 object->repr->attribute("sodipodi:absref"),
844                 doc->base);
845             if (pixbuf) {
846                 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
847 // BLIP
848 #if ENABLE_LCMS
849                 if ( image->color_profile )
850                 {
851                     int imagewidth = gdk_pixbuf_get_width( pixbuf );
852                     int imageheight = gdk_pixbuf_get_height( pixbuf );
853                     int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
854                     guchar* px = gdk_pixbuf_get_pixels( pixbuf );
856                     if ( px ) {
857 #ifdef DEBUG_LCMS
858                         DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
859 #endif // DEBUG_LCMS
860                         guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
861                         cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
862                                                                               &profIntent,
863                                                                               image->color_profile );
864                         if ( prof ) {
865                             icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
866                             if ( profileClass != icSigNamedColorClass ) {
867                                 int intent = INTENT_PERCEPTUAL;
868                                 switch ( profIntent ) {
869                                     case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
870                                         intent = INTENT_RELATIVE_COLORIMETRIC;
871                                         break;
872                                     case Inkscape::RENDERING_INTENT_SATURATION:
873                                         intent = INTENT_SATURATION;
874                                         break;
875                                     case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
876                                         intent = INTENT_ABSOLUTE_COLORIMETRIC;
877                                         break;
878                                     case Inkscape::RENDERING_INTENT_PERCEPTUAL:
879                                     case Inkscape::RENDERING_INTENT_UNKNOWN:
880                                     case Inkscape::RENDERING_INTENT_AUTO:
881                                     default:
882                                         intent = INTENT_PERCEPTUAL;
883                                 }
884                                 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
885                                 cmsHTRANSFORM transf = cmsCreateTransform( prof,
886                                                                            TYPE_RGBA_8,
887                                                                            destProf,
888                                                                            TYPE_RGBA_8,
889                                                                            intent, 0 );
890                                 if ( transf ) {
891                                     guchar* currLine = px;
892                                     for ( int y = 0; y < imageheight; y++ ) {
893                                         // Since the types are the same size, we can do the transformation in-place
894                                         cmsDoTransform( transf, currLine, currLine, imagewidth );
895                                         currLine += rowstride;
896                                     }
898                                     cmsDeleteTransform( transf );
899                                 }
900 #ifdef DEBUG_LCMS
901                                 else
902                                 {
903                                     DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
904                                 }
905 #endif // DEBUG_LCMS
906                                 cmsCloseProfile( destProf );
907                             }
908 #ifdef DEBUG_LCMS
909                             else
910                             {
911                                 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
912                             }
913 #endif // DEBUG_LCMS
914                         }
915 #ifdef DEBUG_LCMS
916                         else
917                         {
918                             DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
919                         }
920 #endif // DEBUG_LCMS
921                     }
922                 }
923 #endif // ENABLE_LCMS
924                 image->pixbuf = pixbuf;
925             }
926         }
927     }
928     // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
929     if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
930         int imagewidth, imageheight;
931         double x,y;
933         imagewidth = gdk_pixbuf_get_width (image->pixbuf);
934         imageheight = gdk_pixbuf_get_height (image->pixbuf);
936         switch (image->aspect_align) {
937             case SP_ASPECT_XMIN_YMIN:
938                 x = 0.0;
939                 y = 0.0;
940                 break;
941             case SP_ASPECT_XMID_YMIN:
942                 x = 0.5;
943                 y = 0.0;
944                 break;
945             case SP_ASPECT_XMAX_YMIN:
946                 x = 1.0;
947                 y = 0.0;
948                 break;
949             case SP_ASPECT_XMIN_YMID:
950                 x = 0.0;
951                 y = 0.5;
952                 break;
953             case SP_ASPECT_XMID_YMID:
954                 x = 0.5;
955                 y = 0.5;
956                 break;
957             case SP_ASPECT_XMAX_YMID:
958                 x = 1.0;
959                 y = 0.5;
960                 break;
961             case SP_ASPECT_XMIN_YMAX:
962                 x = 0.0;
963                 y = 1.0;
964                 break;
965             case SP_ASPECT_XMID_YMAX:
966                 x = 0.5;
967                 y = 1.0;
968                 break;
969             case SP_ASPECT_XMAX_YMAX:
970                 x = 1.0;
971                 y = 1.0;
972                 break;
973             default:
974                 x = 0.0;
975                 y = 0.0;
976                 break;
977         }
979         if (image->aspect_clip == SP_ASPECT_SLICE) {
980             image->viewx = image->x.computed;
981             image->viewy = image->y.computed;
982             image->viewwidth = image->width.computed;
983             image->viewheight = image->height.computed;
984             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
985                 // Pixels aspect is wider than bounding box
986                 image->trimheight = imageheight;
987                 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
988                 image->trimy = 0;
989                 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
990             } else {
991                 // Pixels aspect is taller than bounding box
992                 image->trimwidth = imagewidth;
993                 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
994                 image->trimx = 0;
995                 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
996             }
997         } else {
998             // Otherwise, assume SP_ASPECT_MEET
999             image->trimx = 0;
1000             image->trimy = 0;
1001             image->trimwidth = imagewidth;
1002             image->trimheight = imageheight;
1003             if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
1004                 // Pixels aspect is wider than bounding boz
1005                 image->viewwidth = image->width.computed;
1006                 image->viewheight = image->viewwidth * imageheight / imagewidth;
1007                 image->viewx=image->x.computed;
1008                 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
1009             } else {
1010                 // Pixels aspect is taller than bounding box
1011                 image->viewheight = image->height.computed;
1012                 image->viewwidth = image->viewheight * imagewidth / imageheight;
1013                 image->viewy=image->y.computed;
1014                 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
1015             }
1016         }
1017     }
1018     sp_image_update_canvas_image ((SPImage *) object);
1021 static void
1022 sp_image_modified (SPObject *object, unsigned int flags)
1024     SPImage *image = SP_IMAGE (object);
1026     if (((SPObjectClass *) (parent_class))->modified) {
1027       (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
1028     }
1030     if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
1031         for (SPItemView *v = SP_ITEM (image)->display; v != NULL; v = v->next) {
1032             nr_arena_image_set_style (NR_ARENA_IMAGE (v->arenaitem), object->style);
1033         }
1034     }
1037 static Inkscape::XML::Node *
1038 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1040     SPImage *image = SP_IMAGE (object);
1042     if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1043         repr = xml_doc->createElement("svg:image");
1044     }
1046     repr->setAttribute("xlink:href", image->href);
1047     /* fixme: Reset attribute if needed (Lauris) */
1048     if (image->x._set) {
1049         sp_repr_set_svg_double(repr, "x", image->x.computed);
1050     }
1051     if (image->y._set) {
1052         sp_repr_set_svg_double(repr, "y", image->y.computed);
1053     }
1054     if (image->width._set) {
1055         sp_repr_set_svg_double(repr, "width", image->width.computed);
1056     }
1057     if (image->height._set) {
1058         sp_repr_set_svg_double(repr, "height", image->height.computed);
1059     }
1060     repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1061 #if ENABLE_LCMS
1062     if (image->color_profile) {
1063         repr->setAttribute("color-profile", image->color_profile);
1064     }
1065 #endif // ENABLE_LCMS
1067     if (((SPObjectClass *) (parent_class))->write) {
1068         ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags);
1069     }
1071     return repr;
1074 static void
1075 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1077     SPImage const &image = *SP_IMAGE(item);
1079     if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1080         double const x0 = image.x.computed;
1081         double const y0 = image.y.computed;
1082         double const x1 = x0 + image.width.computed;
1083         double const y1 = y0 + image.height.computed;
1085         nr_rect_union_pt(bbox, Geom::Point(x0, y0) * transform);
1086         nr_rect_union_pt(bbox, Geom::Point(x1, y0) * transform);
1087         nr_rect_union_pt(bbox, Geom::Point(x1, y1) * transform);
1088         nr_rect_union_pt(bbox, Geom::Point(x0, y1) * transform);
1089     }
1092 static void
1093 sp_image_print (SPItem *item, SPPrintContext *ctx)
1095     SPImage *image = SP_IMAGE(item);
1097     if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) {
1098         guchar *px = gdk_pixbuf_get_pixels(image->pixbuf);
1099         int w = gdk_pixbuf_get_width(image->pixbuf);
1100         int h = gdk_pixbuf_get_height(image->pixbuf);
1101         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1102         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1104         Geom::Matrix t;
1105         if (image->aspect_align == SP_ASPECT_NONE) {
1106             /* fixme: (Lauris) */
1107             Geom::Translate tp(image->x.computed, image->y.computed);
1108             Geom::Scale s(image->width.computed, -image->height.computed);
1109             Geom::Translate ti(0.0, -1.0);
1110             t = s * tp;
1111             t = ti * t;
1112         } else { // preserveAspectRatio
1113             Geom::Translate tp(image->viewx, image->viewy);
1114             Geom::Scale s(image->viewwidth, -image->viewheight);
1115             Geom::Translate ti(0.0, -1.0);
1116             t = s * tp;
1117             t = ti * t;
1118         }
1120         if (image->aspect_align == SP_ASPECT_NONE) {
1121             sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1122         } else { // preserveAspectRatio
1123             sp_print_image_R8G8B8A8_N(ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE(item));
1124         }
1125     }
1128 static gchar *
1129 sp_image_description(SPItem *item)
1131     SPImage *image = SP_IMAGE(item);
1132     char *href_desc;
1133     if (image->href) {
1134         href_desc = (strncmp(image->href, "data:", 5) == 0)
1135             ? g_strdup(_("embedded"))
1136             : xml_quote_strdup(image->href);
1137     } else {
1138         g_warning("Attempting to call strncmp() with a null pointer.");
1139         href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1140     }
1142     char *ret = ( image->pixbuf == NULL
1143                   ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1144                   : g_strdup_printf(_("<b>Image</b> %d &#215; %d: %s"),
1145                                     gdk_pixbuf_get_width(image->pixbuf),
1146                                     gdk_pixbuf_get_height(image->pixbuf),
1147                                     href_desc) );
1148     g_free(href_desc);
1149     return ret;
1152 static NRArenaItem *
1153 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1155     SPImage * image = SP_IMAGE(item);
1156     NRArenaItem *ai = NRArenaImage::create(arena);
1158     if (image->pixbuf) {
1159         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1160         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1161         nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1162         if (image->aspect_align == SP_ASPECT_NONE) {
1163             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1164                                        gdk_pixbuf_get_pixels(image->pixbuf),
1165                                        gdk_pixbuf_get_width(image->pixbuf),
1166                                        gdk_pixbuf_get_height(image->pixbuf),
1167                                        rs);
1168         } else { // preserveAspectRatio
1169             nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1170                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1171                                        image->trimwidth,
1172                                        image->trimheight,
1173                                        rs);
1174         }
1175     } else {
1176         nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai), NULL, 0, 0, 0);
1177     }
1178     if (image->aspect_align == SP_ASPECT_NONE) {
1179         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1180     } else { // preserveAspectRatio
1181         nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1182     }
1184     return ai;
1187 /*
1188  * utility function to try loading image from href
1189  *
1190  * docbase/relative_src
1191  * absolute_src
1192  *
1193  */
1195 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1197     GdkPixbuf *pixbuf = 0;
1198     modTime = 0;
1199     if ( pixPath ) {
1200         g_free(pixPath);
1201         pixPath = 0;
1202     }
1204     const gchar *filename = href;
1205     if (filename != NULL) {
1206         if (strncmp (filename,"file:",5) == 0) {
1207             gchar *fullname = g_filename_from_uri(filename, NULL, NULL);
1208             if (fullname) {
1209                 // TODO check this. Was doing a UTF-8 to filename conversion here.
1210                 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1211                 if (pixbuf != NULL) {
1212                     return pixbuf;
1213                 }
1214             }
1215         } else if (strncmp (filename,"data:",5) == 0) {
1216             /* data URI - embedded image */
1217             filename += 5;
1218             pixbuf = sp_image_repr_read_dataURI (filename);
1219             if (pixbuf != NULL) {
1220                 return pixbuf;
1221             }
1222         } else {
1224             if (!g_path_is_absolute (filename)) {
1225                 /* try to load from relative pos combined with document base*/
1226                 const gchar *docbase = base;
1227                 if (!docbase) {
1228                     docbase = ".";
1229                 }
1230                 gchar *fullname = g_build_filename(docbase, filename, NULL);
1232                 // document base can be wrong (on the temporary doc when importing bitmap from a
1233                 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1234                 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1235                 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1236                     pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1237                     g_free (fullname);
1238                     if (pixbuf != NULL) {
1239                         return pixbuf;
1240                     }
1241                 }
1242             }
1244             /* try filename as absolute */
1245             if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1246                 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1247                 if (pixbuf != NULL) {
1248                     return pixbuf;
1249                 }
1250             }
1251         }
1252     }
1254     /* at last try to load from sp absolute path name */
1255     filename = absref;
1256     if (filename != NULL) {
1257         // using absref is outside of SVG rules, so we must at least warn the user
1258         if ( base != NULL && href != NULL ) {
1259             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);
1260         } else {
1261             g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1262         }
1264         pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1265         if (pixbuf != NULL) {
1266             return pixbuf;
1267         }
1268     }
1269     /* Nope: We do not find any valid pixmap file :-( */
1270     pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1272     /* It should be included xpm, so if it still does not does load, */
1273     /* our libraries are broken */
1274     g_assert (pixbuf != NULL);
1276     return pixbuf;
1279 static GdkPixbuf *
1280 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1282     GdkPixbuf* result;
1283     if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1284         result = pixbuf;
1285     } else {
1286         result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1287         gdk_pixbuf_unref(pixbuf);
1288     }
1289     return result;
1292 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1294 static void
1295 sp_image_update_canvas_image (SPImage *image)
1297     SPItem *item = SP_ITEM(image);
1299     if (image->pixbuf) {
1300         /* fixme: We are slightly violating spec here (Lauris) */
1301         if (!image->width._set) {
1302             image->width.computed = gdk_pixbuf_get_width(image->pixbuf);
1303         }
1304         if (!image->height._set) {
1305             image->height.computed = gdk_pixbuf_get_height(image->pixbuf);
1306         }
1307     }
1309     for (SPItemView *v = item->display; v != NULL; v = v->next) {
1310         int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1311         int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1312         nr_arena_image_set_style(NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1313         if (image->aspect_align == SP_ASPECT_NONE) {
1314             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1315                                        gdk_pixbuf_get_pixels(image->pixbuf),
1316                                        gdk_pixbuf_get_width(image->pixbuf),
1317                                        gdk_pixbuf_get_height(image->pixbuf),
1318                                        rs);
1319             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1320                                          image->x.computed, image->y.computed,
1321                                          image->width.computed, image->height.computed);
1322         } else { // preserveAspectRatio
1323             nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1324                                        gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1325                                        image->trimwidth,
1326                                        image->trimheight,
1327                                        rs);
1328             nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1329                                          image->viewx, image->viewy,
1330                                          image->viewwidth, image->viewheight);
1331         }
1332     }
1335 static void sp_image_snappoints(SPItem const *item, bool const target, SnapPointsWithType &p, Inkscape::SnapPreferences const */*snapprefs*/)
1337     /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1338     to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1339     the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1340     */
1342     g_assert(item != NULL);
1343     g_assert(SP_IS_IMAGE(item));
1345     if (item->clip_ref->getObject()) {
1346         //We are looking at a clipped image: do not return any snappoints, as these might be
1347         //far far away from the visible part from the clipped image
1348         //TODO Do return snappoints, but only when within visual bounding box
1349     } else {
1350         // The image has not been clipped: return its corners, which might be rotated for example
1351         SPImage &image = *SP_IMAGE(item);
1352         double const x0 = image.x.computed;
1353         double const y0 = image.y.computed;
1354         double const x1 = x0 + image.width.computed;
1355         double const y1 = y0 + image.height.computed;
1356         Geom::Matrix const i2d (sp_item_i2d_affine (item));
1357         Geom::Point pt;
1358         int type = target ? int(Inkscape::SNAPTARGET_CORNER) : int(Inkscape::SNAPSOURCE_CORNER);
1359         p.push_back(std::make_pair(Geom::Point(x0, y0) * i2d, type));
1360         p.push_back(std::make_pair(Geom::Point(x0, y1) * i2d, type));
1361         p.push_back(std::make_pair(Geom::Point(x1, y1) * i2d, type));
1362         p.push_back(std::make_pair(Geom::Point(x1, y0) * i2d, type));
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 sp_image_refresh_if_outdated( SPImage* image )
1587     if ( image->href && image->lastMod ) {
1588         // It *might* change
1590         struct stat st;
1591         memset(&st, 0, sizeof(st));
1592         int val = g_stat(image->pixPath, &st);
1593         if ( !val ) {
1594             // stat call worked. Check time now
1595             if ( st.st_mtime != image->lastMod ) {
1596                 SPCtx *ctx = 0;
1597                 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1598                 sp_image_update(image, ctx, flags);
1599             }
1600         }
1601     }
1604 /*
1605   Local Variables:
1606   mode:c++
1607   c-file-style:"stroustrup"
1608   c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1609   indent-tabs-mode:nil
1610   fill-column:99
1611   End:
1612 */
1613 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :