Code

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