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"
105 {
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 );
110 }
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__ );\
125 \
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 }\
140 }
141 #endif // DEBUG_LCMS
143 namespace Inkscape {
144 namespace IO {
146 class PushPull
147 {
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 )
235 {
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");
253 }
255 void user_write_data( png_structp /*png_ptr*/, png_bytep /*data*/, png_size_t /*length*/ )
256 {
257 //g_message( "user_write_data(%d)", length );
258 }
260 void user_flush_data( png_structp /*png_ptr*/ )
261 {
262 //g_message( "user_flush_data" );
263 }
266 static bool readPngAndHeaders( PushPull &youme, gint & dpiX, gint & dpiY )
267 {
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;
418 }
420 static GdkPixbuf* pixbuf_new_from_file( const char *filename, time_t &modTime, gchar*& pixPath, GError **/*error*/ )
421 {
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;
538 }
540 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
541 {
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;
549 }
552 }
553 }
555 GType
556 sp_image_get_type (void)
557 {
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;
575 }
577 static void
578 sp_image_class_init (SPImageClass * klass)
579 {
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;
603 }
605 static void sp_image_init( SPImage *image )
606 {
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;
631 }
633 static void
634 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
635 {
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);
650 }
652 static void
653 sp_image_release (SPObject *object)
654 {
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 }
691 }
693 static void
694 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
695 {
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
814 }
816 static void
817 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
818 {
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"),
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);
1022 }
1024 static void
1025 sp_image_modified (SPObject *object, unsigned int flags)
1026 {
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 }
1038 }
1040 static Inkscape::XML::Node *
1041 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1042 {
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;
1077 }
1079 static void
1080 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1081 {
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 }
1095 }
1097 static void
1098 sp_image_print (SPItem *item, SPPrintContext *ctx)
1099 {
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 }
1131 }
1133 static gchar *
1134 sp_image_description(SPItem *item)
1135 {
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 × %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;
1155 }
1157 static NRArenaItem *
1158 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1159 {
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;
1190 }
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 )
1201 {
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;
1282 }
1284 static GdkPixbuf *
1285 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1286 {
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;
1295 }
1297 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1299 static void
1300 sp_image_update_canvas_image (SPImage *image)
1301 {
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 }
1338 }
1340 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
1341 {
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 }
1367 }
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)
1376 {
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;
1413 }
1415 static GdkPixbuf *
1416 sp_image_repr_read_dataURI (const gchar * uri_data)
1417 {
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;
1470 }
1472 static GdkPixbuf *
1473 sp_image_repr_read_b64 (const gchar * uri_data)
1474 {
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;
1547 }
1549 static void
1550 sp_image_set_curve(SPImage *image)
1551 {
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 }
1573 }
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)
1580 {
1581 SPCurve *result = 0;
1582 if (image->curve) {
1583 result = image->curve->copy();
1584 }
1585 return result;
1586 }
1588 void
1589 sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb, Glib::ustring const &mime_in)
1590 {
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);
1620 }
1622 void sp_image_refresh_if_outdated( SPImage* image )
1623 {
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 }
1639 }
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 :