Code

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