Code

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