Code

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