Code

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