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 }
265 static GdkPixbuf* pixbuf_new_from_file( const char *filename, time_t &modTime, gchar*& pixPath, GError **/*error*/ )
266 {
267 GdkPixbuf* buf = NULL;
268 PushPull youme;
269 gint dpiX = 0;
270 gint dpiY = 0;
271 modTime = 0;
272 if ( pixPath ) {
273 g_free(pixPath);
274 pixPath = 0;
275 }
277 //buf = gdk_pixbuf_new_from_file( filename, error );
278 dump_fopen_call( filename, "pixbuf_new_from_file" );
279 FILE* fp = fopen_utf8name( filename, "r" );
280 if ( fp )
281 {
282 {
283 struct stat st;
284 memset(&st, 0, sizeof(st));
285 int val = g_stat(filename, &st);
286 if ( !val ) {
287 modTime = st.st_mtime;
288 pixPath = g_strdup(filename);
289 }
290 }
292 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
293 if ( loader )
294 {
295 GError *err = NULL;
297 // short buffer
298 guchar scratch[1024];
299 gboolean latter = FALSE;
300 gboolean isPng = FALSE;
301 png_structp pngPtr = NULL;
302 png_infop infoPtr = NULL;
303 //png_infop endPtr = NULL;
305 youme.fp = fp;
306 youme.scratch = scratch;
307 youme.size = sizeof(scratch);
308 youme.used = 0;
309 youme.offset = 0;
310 youme.loader = loader;
312 while ( !feof(fp) )
313 {
314 if ( youme.readMore() )
315 {
316 if ( youme.first )
317 {
318 //g_message( "First data chunk" );
319 youme.first = FALSE;
320 isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() );
321 //g_message( " png? %s", (isPng ? "Yes":"No") );
322 if ( isPng )
323 {
324 pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
325 NULL,//(png_voidp)user_error_ptr,
326 NULL,//user_error_fn,
327 NULL//user_warning_fn
328 );
329 if ( pngPtr )
330 {
331 if ( setjmp(png_jmpbuf(pngPtr)) )
332 {
333 // libpng calls longjmp to return here if an error occurs.
334 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );
335 fclose(fp);
336 gdk_pixbuf_loader_close(loader, NULL);
337 g_object_unref(loader);
338 return NULL;
339 }
341 infoPtr = png_create_info_struct( pngPtr );
342 //endPtr = png_create_info_struct( pngPtr );
344 png_set_read_fn( pngPtr, &youme, user_read_data );
345 //g_message( "In" );
347 //png_read_info( pngPtr, infoPtr );
348 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL );
350 //g_message("out");
352 //png_read_end(pngPtr, endPtr);
354 /*
355 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
356 {
357 g_message("pHYs chunk now valid" );
358 }
359 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
360 {
361 g_message("sCAL chunk now valid" );
362 }
363 */
365 png_uint_32 res_x = 0;
366 png_uint_32 res_y = 0;
367 int unit_type = 0;
368 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) )
369 {
370 // g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
371 // (unit_type == 1? "per meter" : "unknown")
372 // );
374 // g_message( " dpi: (%d, %d)",
375 // (int)(0.5 + ((double)res_x)/39.37),
376 // (int)(0.5 + ((double)res_y)/39.37) );
377 if ( unit_type == PNG_RESOLUTION_METER )
378 {
379 // TODO come up with a more accurate DPI setting
380 dpiX = (int)(0.5 + ((double)res_x)/39.37);
381 dpiY = (int)(0.5 + ((double)res_y)/39.37);
382 }
383 }
384 else
385 {
386 // g_message( "pHYs no" );
387 }
389 /*
390 double width = 0;
391 double height = 0;
392 int unit = 0;
393 if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
394 {
395 gchar* vals[] = {
396 "unknown", // PNG_SCALE_UNKNOWN
397 "meter", // PNG_SCALE_METER
398 "radian", // PNG_SCALE_RADIAN
399 "last", //
400 NULL
401 };
403 g_message( "sCAL: (%f, %f) %d (%s)",
404 width, height, unit,
405 ((unit >= 0 && unit < 3) ? vals[unit]:"???")
406 );
407 }
408 */
410 #if defined(PNG_sRGB_SUPPORTED)
411 {
412 int intent = 0;
413 if ( png_get_sRGB(pngPtr, infoPtr, &intent) ) {
414 // g_message("Found an sRGB png chunk");
415 }
416 }
417 #endif // defined(PNG_sRGB_SUPPORTED)
419 #if defined(PNG_cHRM_SUPPORTED)
420 {
421 double white_x = 0;
422 double white_y = 0;
423 double red_x = 0;
424 double red_y = 0;
425 double green_x = 0;
426 double green_y = 0;
427 double blue_x = 0;
428 double blue_y = 0;
430 if ( png_get_cHRM(pngPtr, infoPtr,
431 &white_x, &white_y,
432 &red_x, &red_y,
433 &green_x, &green_y,
434 &blue_x, &blue_y) ) {
435 // g_message("Found a cHRM png chunk");
436 }
437 }
438 #endif // defined(PNG_cHRM_SUPPORTED)
440 #if defined(PNG_gAMA_SUPPORTED)
441 {
442 double file_gamma = 0;
443 if ( png_get_gAMA(pngPtr, infoPtr, &file_gamma) ) {
444 // g_message("Found a gAMA png chunk");
445 }
446 }
447 #endif // defined(PNG_gAMA_SUPPORTED)
449 #if defined(PNG_iCCP_SUPPORTED)
450 {
451 char* name = 0;
452 int compression_type = 0;
453 char* profile = 0;
454 png_uint_32 proflen = 0;
455 if ( png_get_iCCP(pngPtr, infoPtr, &name, &compression_type, &profile, &proflen) ) {
456 // g_message("Found an iCCP chunk named [%s] with %d bytes and comp %d", name, proflen, compression_type);
457 }
458 }
459 #endif // defined(PNG_iCCP_SUPPORTED)
462 // now clean it up.
463 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr );
464 }
465 else
466 {
467 // g_message("Error when creating PNG read struct");
468 }
469 }
470 }
471 else if ( !latter )
472 {
473 latter = TRUE;
474 //g_message(" READing latter");
475 }
476 // Now clear out the buffer so we can read more.
477 // (dumping out unused)
478 youme.clear();
479 }
480 }
482 gboolean ok = gdk_pixbuf_loader_close(loader, &err);
483 if ( ok )
484 {
485 buf = gdk_pixbuf_loader_get_pixbuf( loader );
486 if ( buf )
487 {
488 g_object_ref(buf);
490 if ( dpiX )
491 {
492 gchar *tmp = g_strdup_printf( "%d", dpiX );
493 if ( tmp )
494 {
495 // g_message("Need to set DpiX: %s", tmp);
496 //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
497 g_free( tmp );
498 }
499 }
500 if ( dpiY )
501 {
502 gchar *tmp = g_strdup_printf( "%d", dpiY );
503 if ( tmp )
504 {
505 // g_message("Need to set DpiY: %s", tmp);
506 //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
507 g_free( tmp );
508 }
509 }
510 }
511 }
512 else
513 {
514 // do something
515 g_message("error loading pixbuf at close");
516 }
518 g_object_unref(loader);
519 }
520 else
521 {
522 g_message("error when creating pixbuf loader");
523 }
524 fclose( fp );
525 fp = NULL;
526 }
527 else
528 {
529 g_warning ("Unable to open linked file: %s", filename);
530 }
532 /*
533 if ( buf )
534 {
535 const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
536 if ( bloop )
537 {
538 g_message("DPI X is [%s]", bloop);
539 }
540 bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
541 if ( bloop )
542 {
543 g_message("DPI Y is [%s]", bloop);
544 }
545 }
546 */
548 return buf;
549 }
551 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
552 {
553 time_t modTime = 0;
554 gchar* pixPath = 0;
555 GdkPixbuf* result = pixbuf_new_from_file( filename, modTime, pixPath, error );
556 if (pixPath) {
557 g_free(pixPath);
558 }
559 return result;
560 }
563 }
564 }
566 GType
567 sp_image_get_type (void)
568 {
569 static GType image_type = 0;
570 if (!image_type) {
571 GTypeInfo image_info = {
572 sizeof (SPImageClass),
573 NULL, /* base_init */
574 NULL, /* base_finalize */
575 (GClassInitFunc) sp_image_class_init,
576 NULL, /* class_finalize */
577 NULL, /* class_data */
578 sizeof (SPImage),
579 16, /* n_preallocs */
580 (GInstanceInitFunc) sp_image_init,
581 NULL, /* value_table */
582 };
583 image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
584 }
585 return image_type;
586 }
588 static void
589 sp_image_class_init (SPImageClass * klass)
590 {
591 GObjectClass * gobject_class;
592 SPObjectClass * sp_object_class;
593 SPItemClass * item_class;
595 gobject_class = (GObjectClass *) klass;
596 sp_object_class = (SPObjectClass *) klass;
597 item_class = (SPItemClass *) klass;
599 parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
601 sp_object_class->build = sp_image_build;
602 sp_object_class->release = sp_image_release;
603 sp_object_class->set = sp_image_set;
604 sp_object_class->update = sp_image_update;
605 sp_object_class->modified = sp_image_modified;
606 sp_object_class->write = sp_image_write;
608 item_class->bbox = sp_image_bbox;
609 item_class->print = sp_image_print;
610 item_class->description = sp_image_description;
611 item_class->show = sp_image_show;
612 item_class->snappoints = sp_image_snappoints;
613 item_class->set_transform = sp_image_set_transform;
614 }
616 static void sp_image_init( SPImage *image )
617 {
618 image->x.unset();
619 image->y.unset();
620 image->width.unset();
621 image->height.unset();
622 image->aspect_align = SP_ASPECT_NONE;
624 image->trimx = 0;
625 image->trimy = 0;
626 image->trimwidth = 0;
627 image->trimheight = 0;
628 image->viewx = 0;
629 image->viewy = 0;
630 image->viewwidth = 0;
631 image->viewheight = 0;
633 image->curve = NULL;
635 image->href = 0;
636 #if ENABLE_LCMS
637 image->color_profile = 0;
638 #endif // ENABLE_LCMS
639 image->pixbuf = 0;
640 image->pixPath = 0;
641 image->lastMod = 0;
642 }
644 static void
645 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
646 {
647 if (((SPObjectClass *) parent_class)->build) {
648 ((SPObjectClass *) parent_class)->build (object, document, repr);
649 }
651 sp_object_read_attr (object, "xlink:href");
652 sp_object_read_attr (object, "x");
653 sp_object_read_attr (object, "y");
654 sp_object_read_attr (object, "width");
655 sp_object_read_attr (object, "height");
656 sp_object_read_attr (object, "preserveAspectRatio");
657 sp_object_read_attr (object, "color-profile");
659 /* Register */
660 sp_document_add_resource (document, "image", object);
661 }
663 static void
664 sp_image_release (SPObject *object)
665 {
666 SPImage *image = SP_IMAGE(object);
668 if (SP_OBJECT_DOCUMENT (object)) {
669 /* Unregister ourselves */
670 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
671 }
673 if (image->href) {
674 g_free (image->href);
675 image->href = NULL;
676 }
678 if (image->pixbuf) {
679 gdk_pixbuf_unref (image->pixbuf);
680 image->pixbuf = NULL;
681 }
683 #if ENABLE_LCMS
684 if (image->color_profile) {
685 g_free (image->color_profile);
686 image->color_profile = NULL;
687 }
688 #endif // ENABLE_LCMS
690 if (image->pixPath) {
691 g_free(image->pixPath);
692 image->pixPath = 0;
693 }
695 if (image->curve) {
696 image->curve = image->curve->unref();
697 }
699 if (((SPObjectClass *) parent_class)->release) {
700 ((SPObjectClass *) parent_class)->release (object);
701 }
702 }
704 static void
705 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
706 {
707 SPImage *image = SP_IMAGE (object);
709 switch (key) {
710 case SP_ATTR_XLINK_HREF:
711 g_free (image->href);
712 image->href = (value) ? g_strdup (value) : NULL;
713 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
714 break;
715 case SP_ATTR_X:
716 if (!image->x.readAbsolute(value)) {
717 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
718 image->x.unset();
719 }
720 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
721 break;
722 case SP_ATTR_Y:
723 if (!image->y.readAbsolute(value)) {
724 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
725 image->y.unset();
726 }
727 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
728 break;
729 case SP_ATTR_WIDTH:
730 if (!image->width.readAbsolute(value)) {
731 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
732 image->width.unset();
733 }
734 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
735 break;
736 case SP_ATTR_HEIGHT:
737 if (!image->height.readAbsolute(value)) {
738 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
739 image->height.unset();
740 }
741 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
742 break;
743 case SP_ATTR_PRESERVEASPECTRATIO:
744 /* Do setup before, so we can use break to escape */
745 image->aspect_align = SP_ASPECT_NONE;
746 image->aspect_clip = SP_ASPECT_MEET;
747 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
748 if (value) {
749 int len;
750 gchar c[256];
751 const gchar *p, *e;
752 unsigned int align, clip;
753 p = value;
754 while (*p && *p == 32) p += 1;
755 if (!*p) break;
756 e = p;
757 while (*e && *e != 32) e += 1;
758 len = e - p;
759 if (len > 8) break;
760 memcpy (c, value, len);
761 c[len] = 0;
762 /* Now the actual part */
763 if (!strcmp (c, "none")) {
764 align = SP_ASPECT_NONE;
765 } else if (!strcmp (c, "xMinYMin")) {
766 align = SP_ASPECT_XMIN_YMIN;
767 } else if (!strcmp (c, "xMidYMin")) {
768 align = SP_ASPECT_XMID_YMIN;
769 } else if (!strcmp (c, "xMaxYMin")) {
770 align = SP_ASPECT_XMAX_YMIN;
771 } else if (!strcmp (c, "xMinYMid")) {
772 align = SP_ASPECT_XMIN_YMID;
773 } else if (!strcmp (c, "xMidYMid")) {
774 align = SP_ASPECT_XMID_YMID;
775 } else if (!strcmp (c, "xMaxYMid")) {
776 align = SP_ASPECT_XMAX_YMID;
777 } else if (!strcmp (c, "xMinYMax")) {
778 align = SP_ASPECT_XMIN_YMAX;
779 } else if (!strcmp (c, "xMidYMax")) {
780 align = SP_ASPECT_XMID_YMAX;
781 } else if (!strcmp (c, "xMaxYMax")) {
782 align = SP_ASPECT_XMAX_YMAX;
783 } else {
784 break;
785 }
786 clip = SP_ASPECT_MEET;
787 while (*e && *e == 32) e += 1;
788 if (*e) {
789 if (!strcmp (e, "meet")) {
790 clip = SP_ASPECT_MEET;
791 } else if (!strcmp (e, "slice")) {
792 clip = SP_ASPECT_SLICE;
793 } else {
794 break;
795 }
796 }
797 image->aspect_align = align;
798 image->aspect_clip = clip;
799 }
800 break;
801 #if ENABLE_LCMS
802 case SP_PROP_COLOR_PROFILE:
803 if ( image->color_profile ) {
804 g_free (image->color_profile);
805 }
806 image->color_profile = (value) ? g_strdup (value) : NULL;
807 #ifdef DEBUG_LCMS
808 if ( value ) {
809 DEBUG_MESSAGE( lcmsFour, "<image> color-profile set to '%s'", value );
810 } else {
811 DEBUG_MESSAGE( lcmsFour, "<image> color-profile cleared" );
812 }
813 #endif // DEBUG_LCMS
814 // TODO check on this HREF_MODIFIED flag
815 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
816 break;
817 #endif // ENABLE_LCMS
818 default:
819 if (((SPObjectClass *) (parent_class))->set)
820 ((SPObjectClass *) (parent_class))->set (object, key, value);
821 break;
822 }
824 sp_image_set_curve(image); //creates a curve at the image's boundary for snapping
825 }
827 static void
828 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
829 {
830 SPImage *image = SP_IMAGE(object);
831 SPDocument *doc = SP_OBJECT_DOCUMENT(object);
833 if (((SPObjectClass *) (parent_class))->update) {
834 ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
835 }
837 if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
838 if (image->pixbuf) {
839 gdk_pixbuf_unref (image->pixbuf);
840 image->pixbuf = NULL;
841 }
842 if ( image->pixPath ) {
843 g_free(image->pixPath);
844 image->pixPath = 0;
845 }
846 image->lastMod = 0;
847 if (image->href) {
848 GdkPixbuf *pixbuf;
849 pixbuf = sp_image_repr_read_image (
850 image->lastMod,
851 image->pixPath,
852 object->repr->attribute("xlink:href"),
853 object->repr->attribute("sodipodi:absref"),
854 doc->base);
855 if (pixbuf) {
856 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
857 // BLIP
858 #if ENABLE_LCMS
859 if ( image->color_profile )
860 {
861 int imagewidth = gdk_pixbuf_get_width( pixbuf );
862 int imageheight = gdk_pixbuf_get_height( pixbuf );
863 int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
864 guchar* px = gdk_pixbuf_get_pixels( pixbuf );
866 if ( px ) {
867 #ifdef DEBUG_LCMS
868 DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
869 #endif // DEBUG_LCMS
870 guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
871 cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
872 &profIntent,
873 image->color_profile );
874 if ( prof ) {
875 icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
876 if ( profileClass != icSigNamedColorClass ) {
877 int intent = INTENT_PERCEPTUAL;
878 switch ( profIntent ) {
879 case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
880 intent = INTENT_RELATIVE_COLORIMETRIC;
881 break;
882 case Inkscape::RENDERING_INTENT_SATURATION:
883 intent = INTENT_SATURATION;
884 break;
885 case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
886 intent = INTENT_ABSOLUTE_COLORIMETRIC;
887 break;
888 case Inkscape::RENDERING_INTENT_PERCEPTUAL:
889 case Inkscape::RENDERING_INTENT_UNKNOWN:
890 case Inkscape::RENDERING_INTENT_AUTO:
891 default:
892 intent = INTENT_PERCEPTUAL;
893 }
894 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
895 cmsHTRANSFORM transf = cmsCreateTransform( prof,
896 TYPE_RGBA_8,
897 destProf,
898 TYPE_RGBA_8,
899 intent, 0 );
900 if ( transf ) {
901 guchar* currLine = px;
902 for ( int y = 0; y < imageheight; y++ ) {
903 // Since the types are the same size, we can do the transformation in-place
904 cmsDoTransform( transf, currLine, currLine, imagewidth );
905 currLine += rowstride;
906 }
908 cmsDeleteTransform( transf );
909 }
910 #ifdef DEBUG_LCMS
911 else
912 {
913 DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
914 }
915 #endif // DEBUG_LCMS
916 cmsCloseProfile( destProf );
917 }
918 #ifdef DEBUG_LCMS
919 else
920 {
921 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
922 }
923 #endif // DEBUG_LCMS
924 }
925 #ifdef DEBUG_LCMS
926 else
927 {
928 DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
929 }
930 #endif // DEBUG_LCMS
931 }
932 }
933 #endif // ENABLE_LCMS
934 image->pixbuf = pixbuf;
935 }
936 }
937 }
938 // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
939 if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
940 int imagewidth, imageheight;
941 double x,y;
943 imagewidth = gdk_pixbuf_get_width (image->pixbuf);
944 imageheight = gdk_pixbuf_get_height (image->pixbuf);
946 switch (image->aspect_align) {
947 case SP_ASPECT_XMIN_YMIN:
948 x = 0.0;
949 y = 0.0;
950 break;
951 case SP_ASPECT_XMID_YMIN:
952 x = 0.5;
953 y = 0.0;
954 break;
955 case SP_ASPECT_XMAX_YMIN:
956 x = 1.0;
957 y = 0.0;
958 break;
959 case SP_ASPECT_XMIN_YMID:
960 x = 0.0;
961 y = 0.5;
962 break;
963 case SP_ASPECT_XMID_YMID:
964 x = 0.5;
965 y = 0.5;
966 break;
967 case SP_ASPECT_XMAX_YMID:
968 x = 1.0;
969 y = 0.5;
970 break;
971 case SP_ASPECT_XMIN_YMAX:
972 x = 0.0;
973 y = 1.0;
974 break;
975 case SP_ASPECT_XMID_YMAX:
976 x = 0.5;
977 y = 1.0;
978 break;
979 case SP_ASPECT_XMAX_YMAX:
980 x = 1.0;
981 y = 1.0;
982 break;
983 default:
984 x = 0.0;
985 y = 0.0;
986 break;
987 }
989 if (image->aspect_clip == SP_ASPECT_SLICE) {
990 image->viewx = image->x.computed;
991 image->viewy = image->y.computed;
992 image->viewwidth = image->width.computed;
993 image->viewheight = image->height.computed;
994 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
995 // Pixels aspect is wider than bounding box
996 image->trimheight = imageheight;
997 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
998 image->trimy = 0;
999 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
1000 } else {
1001 // Pixels aspect is taller than bounding box
1002 image->trimwidth = imagewidth;
1003 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
1004 image->trimx = 0;
1005 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
1006 }
1007 } else {
1008 // Otherwise, assume SP_ASPECT_MEET
1009 image->trimx = 0;
1010 image->trimy = 0;
1011 image->trimwidth = imagewidth;
1012 image->trimheight = imageheight;
1013 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
1014 // Pixels aspect is wider than bounding boz
1015 image->viewwidth = image->width.computed;
1016 image->viewheight = image->viewwidth * imageheight / imagewidth;
1017 image->viewx=image->x.computed;
1018 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
1019 } else {
1020 // Pixels aspect is taller than bounding box
1021 image->viewheight = image->height.computed;
1022 image->viewwidth = image->viewheight * imagewidth / imageheight;
1023 image->viewy=image->y.computed;
1024 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
1025 }
1026 }
1027 }
1028 sp_image_update_canvas_image ((SPImage *) object);
1029 }
1031 static void
1032 sp_image_modified (SPObject *object, unsigned int flags)
1033 {
1034 SPImage *image = SP_IMAGE (object);
1036 if (((SPObjectClass *) (parent_class))->modified) {
1037 (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
1038 }
1040 if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
1041 for (SPItemView *v = SP_ITEM (image)->display; v != NULL; v = v->next) {
1042 nr_arena_image_set_style (NR_ARENA_IMAGE (v->arenaitem), object->style);
1043 }
1044 }
1045 }
1047 static Inkscape::XML::Node *
1048 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1049 {
1050 SPImage *image = SP_IMAGE (object);
1052 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1053 repr = xml_doc->createElement("svg:image");
1054 }
1056 repr->setAttribute("xlink:href", image->href);
1057 /* fixme: Reset attribute if needed (Lauris) */
1058 if (image->x._set) {
1059 sp_repr_set_svg_double(repr, "x", image->x.computed);
1060 }
1061 if (image->y._set) {
1062 sp_repr_set_svg_double(repr, "y", image->y.computed);
1063 }
1064 if (image->width._set) {
1065 sp_repr_set_svg_double(repr, "width", image->width.computed);
1066 }
1067 if (image->height._set) {
1068 sp_repr_set_svg_double(repr, "height", image->height.computed);
1069 }
1070 repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1071 #if ENABLE_LCMS
1072 if (image->color_profile) {
1073 repr->setAttribute("color-profile", image->color_profile);
1074 }
1075 #endif // ENABLE_LCMS
1077 if (((SPObjectClass *) (parent_class))->write) {
1078 ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags);
1079 }
1081 return repr;
1082 }
1084 static void
1085 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1086 {
1087 SPImage const &image = *SP_IMAGE(item);
1089 if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1090 double const x0 = image.x.computed;
1091 double const y0 = image.y.computed;
1092 double const x1 = x0 + image.width.computed;
1093 double const y1 = y0 + image.height.computed;
1095 nr_rect_union_pt(bbox, Geom::Point(x0, y0) * transform);
1096 nr_rect_union_pt(bbox, Geom::Point(x1, y0) * transform);
1097 nr_rect_union_pt(bbox, Geom::Point(x1, y1) * transform);
1098 nr_rect_union_pt(bbox, Geom::Point(x0, y1) * transform);
1099 }
1100 }
1102 static void
1103 sp_image_print (SPItem *item, SPPrintContext *ctx)
1104 {
1105 SPImage *image = SP_IMAGE(item);
1107 if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) {
1108 guchar *px = gdk_pixbuf_get_pixels(image->pixbuf);
1109 int w = gdk_pixbuf_get_width(image->pixbuf);
1110 int h = gdk_pixbuf_get_height(image->pixbuf);
1111 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1112 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1114 Geom::Matrix t;
1115 if (image->aspect_align == SP_ASPECT_NONE) {
1116 /* fixme: (Lauris) */
1117 Geom::Translate tp(image->x.computed, image->y.computed);
1118 Geom::Scale s(image->width.computed, -image->height.computed);
1119 Geom::Translate ti(0.0, -1.0);
1120 t = s * tp;
1121 t = ti * t;
1122 } else { // preserveAspectRatio
1123 Geom::Translate tp(image->viewx, image->viewy);
1124 Geom::Scale s(image->viewwidth, -image->viewheight);
1125 Geom::Translate ti(0.0, -1.0);
1126 t = s * tp;
1127 t = ti * t;
1128 }
1130 if (image->aspect_align == SP_ASPECT_NONE) {
1131 sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1132 } else { // preserveAspectRatio
1133 sp_print_image_R8G8B8A8_N(ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE(item));
1134 }
1135 }
1136 }
1138 static gchar *
1139 sp_image_description(SPItem *item)
1140 {
1141 SPImage *image = SP_IMAGE(item);
1142 char *href_desc;
1143 if (image->href) {
1144 href_desc = (strncmp(image->href, "data:", 5) == 0)
1145 ? g_strdup(_("embedded"))
1146 : xml_quote_strdup(image->href);
1147 } else {
1148 g_warning("Attempting to call strncmp() with a null pointer.");
1149 href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1150 }
1152 char *ret = ( image->pixbuf == NULL
1153 ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1154 : g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
1155 gdk_pixbuf_get_width(image->pixbuf),
1156 gdk_pixbuf_get_height(image->pixbuf),
1157 href_desc) );
1158 g_free(href_desc);
1159 return ret;
1160 }
1162 static NRArenaItem *
1163 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1164 {
1165 SPImage * image = SP_IMAGE(item);
1166 NRArenaItem *ai = NRArenaImage::create(arena);
1168 if (image->pixbuf) {
1169 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1170 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1171 nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1172 if (image->aspect_align == SP_ASPECT_NONE) {
1173 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1174 gdk_pixbuf_get_pixels(image->pixbuf),
1175 gdk_pixbuf_get_width(image->pixbuf),
1176 gdk_pixbuf_get_height(image->pixbuf),
1177 rs);
1178 } else { // preserveAspectRatio
1179 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1180 gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1181 image->trimwidth,
1182 image->trimheight,
1183 rs);
1184 }
1185 } else {
1186 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai), NULL, 0, 0, 0);
1187 }
1188 if (image->aspect_align == SP_ASPECT_NONE) {
1189 nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1190 } else { // preserveAspectRatio
1191 nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1192 }
1194 return ai;
1195 }
1197 /*
1198 * utility function to try loading image from href
1199 *
1200 * docbase/relative_src
1201 * absolute_src
1202 *
1203 */
1205 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1206 {
1207 GdkPixbuf *pixbuf = 0;
1208 modTime = 0;
1209 if ( pixPath ) {
1210 g_free(pixPath);
1211 pixPath = 0;
1212 }
1214 const gchar *filename = href;
1215 if (filename != NULL) {
1216 if (strncmp (filename,"file:",5) == 0) {
1217 gchar *fullname = g_filename_from_uri(filename, NULL, NULL);
1218 if (fullname) {
1219 // TODO check this. Was doing a UTF-8 to filename conversion here.
1220 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1221 if (pixbuf != NULL) {
1222 return pixbuf;
1223 }
1224 }
1225 } else if (strncmp (filename,"data:",5) == 0) {
1226 /* data URI - embedded image */
1227 filename += 5;
1228 pixbuf = sp_image_repr_read_dataURI (filename);
1229 if (pixbuf != NULL) {
1230 return pixbuf;
1231 }
1232 } else {
1234 if (!g_path_is_absolute (filename)) {
1235 /* try to load from relative pos combined with document base*/
1236 const gchar *docbase = base;
1237 if (!docbase) {
1238 docbase = ".";
1239 }
1240 gchar *fullname = g_build_filename(docbase, filename, NULL);
1242 // document base can be wrong (on the temporary doc when importing bitmap from a
1243 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1244 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1245 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1246 pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1247 g_free (fullname);
1248 if (pixbuf != NULL) {
1249 return pixbuf;
1250 }
1251 }
1252 }
1254 /* try filename as absolute */
1255 if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1256 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1257 if (pixbuf != NULL) {
1258 return pixbuf;
1259 }
1260 }
1261 }
1262 }
1264 /* at last try to load from sp absolute path name */
1265 filename = absref;
1266 if (filename != NULL) {
1267 // using absref is outside of SVG rules, so we must at least warn the user
1268 if ( base != NULL && href != NULL ) {
1269 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);
1270 } else {
1271 g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1272 }
1274 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1275 if (pixbuf != NULL) {
1276 return pixbuf;
1277 }
1278 }
1279 /* Nope: We do not find any valid pixmap file :-( */
1280 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1282 /* It should be included xpm, so if it still does not does load, */
1283 /* our libraries are broken */
1284 g_assert (pixbuf != NULL);
1286 return pixbuf;
1287 }
1289 static GdkPixbuf *
1290 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1291 {
1292 GdkPixbuf* result;
1293 if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1294 result = pixbuf;
1295 } else {
1296 result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1297 gdk_pixbuf_unref(pixbuf);
1298 }
1299 return result;
1300 }
1302 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1304 static void
1305 sp_image_update_canvas_image (SPImage *image)
1306 {
1307 SPItem *item = SP_ITEM(image);
1309 if (image->pixbuf) {
1310 /* fixme: We are slightly violating spec here (Lauris) */
1311 if (!image->width._set) {
1312 image->width.computed = gdk_pixbuf_get_width(image->pixbuf);
1313 }
1314 if (!image->height._set) {
1315 image->height.computed = gdk_pixbuf_get_height(image->pixbuf);
1316 }
1317 }
1319 for (SPItemView *v = item->display; v != NULL; v = v->next) {
1320 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1321 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1322 nr_arena_image_set_style(NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1323 if (image->aspect_align == SP_ASPECT_NONE) {
1324 nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1325 gdk_pixbuf_get_pixels(image->pixbuf),
1326 gdk_pixbuf_get_width(image->pixbuf),
1327 gdk_pixbuf_get_height(image->pixbuf),
1328 rs);
1329 nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1330 image->x.computed, image->y.computed,
1331 image->width.computed, image->height.computed);
1332 } else { // preserveAspectRatio
1333 nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1334 gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1335 image->trimwidth,
1336 image->trimheight,
1337 rs);
1338 nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1339 image->viewx, image->viewy,
1340 image->viewwidth, image->viewheight);
1341 }
1342 }
1343 }
1345 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
1346 {
1347 /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1348 to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1349 the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1350 */
1352 g_assert(item != NULL);
1353 g_assert(SP_IS_IMAGE(item));
1355 if (item->clip_ref->getObject()) {
1356 //We are looking at a clipped image: do not return any snappoints, as these might be
1357 //far far away from the visible part from the clipped image
1358 //TODO Do return snappoints, but only when within visual bounding box
1359 } else {
1360 // The image has not been clipped: return its corners, which might be rotated for example
1361 SPImage &image = *SP_IMAGE(item);
1362 double const x0 = image.x.computed;
1363 double const y0 = image.y.computed;
1364 double const x1 = x0 + image.width.computed;
1365 double const y1 = y0 + image.height.computed;
1366 Geom::Matrix const i2d (sp_item_i2d_affine (item));
1367 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1368 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1369 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1370 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1371 }
1372 }
1374 /*
1375 * Initially we'll do:
1376 * Transform x, y, set x, y, clear translation
1377 */
1379 static Geom::Matrix
1380 sp_image_set_transform(SPItem *item, Geom::Matrix const &xform)
1381 {
1382 SPImage *image = SP_IMAGE(item);
1384 /* Calculate position in parent coords. */
1385 Geom::Point pos( Geom::Point(image->x.computed, image->y.computed) * xform );
1387 /* This function takes care of translation and scaling, we return whatever parts we can't
1388 handle. */
1389 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
1390 Geom::Point const scale(hypot(ret[0], ret[1]),
1391 hypot(ret[2], ret[3]));
1392 if ( scale[Geom::X] > MAGIC_EPSILON ) {
1393 ret[0] /= scale[Geom::X];
1394 ret[1] /= scale[Geom::X];
1395 } else {
1396 ret[0] = 1.0;
1397 ret[1] = 0.0;
1398 }
1399 if ( scale[Geom::Y] > MAGIC_EPSILON ) {
1400 ret[2] /= scale[Geom::Y];
1401 ret[3] /= scale[Geom::Y];
1402 } else {
1403 ret[2] = 0.0;
1404 ret[3] = 1.0;
1405 }
1407 image->width = image->width.computed * scale[Geom::X];
1408 image->height = image->height.computed * scale[Geom::Y];
1410 /* Find position in item coords */
1411 pos = pos * ret.inverse();
1412 image->x = pos[Geom::X];
1413 image->y = pos[Geom::Y];
1415 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1417 return ret;
1418 }
1420 static GdkPixbuf *
1421 sp_image_repr_read_dataURI (const gchar * uri_data)
1422 {
1423 GdkPixbuf * pixbuf = NULL;
1425 gint data_is_image = 0;
1426 gint data_is_base64 = 0;
1428 const gchar * data = uri_data;
1430 while (*data) {
1431 if (strncmp(data,"base64",6) == 0) {
1432 /* base64-encoding */
1433 data_is_base64 = 1;
1434 data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1435 data += 6;
1436 }
1437 else if (strncmp(data,"image/png",9) == 0) {
1438 /* PNG image */
1439 data_is_image = 1;
1440 data += 9;
1441 }
1442 else if (strncmp(data,"image/jpg",9) == 0) {
1443 /* JPEG image */
1444 data_is_image = 1;
1445 data += 9;
1446 }
1447 else if (strncmp(data,"image/jpeg",10) == 0) {
1448 /* JPEG image */
1449 data_is_image = 1;
1450 data += 10;
1451 }
1452 else { /* unrecognized option; skip it */
1453 while (*data) {
1454 if (((*data) == ';') || ((*data) == ',')) {
1455 break;
1456 }
1457 data++;
1458 }
1459 }
1460 if ((*data) == ';') {
1461 data++;
1462 continue;
1463 }
1464 if ((*data) == ',') {
1465 data++;
1466 break;
1467 }
1468 }
1470 if ((*data) && data_is_image && data_is_base64) {
1471 pixbuf = sp_image_repr_read_b64(data);
1472 }
1474 return pixbuf;
1475 }
1477 static GdkPixbuf *
1478 sp_image_repr_read_b64 (const gchar * uri_data)
1479 {
1480 GdkPixbuf * pixbuf = NULL;
1482 static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1484 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
1485 if (loader) {
1486 bool eos = false;
1487 bool failed = false;
1488 const gchar* btr = uri_data;
1489 gchar ud[4];
1490 guchar bd[57];
1492 while (!eos) {
1493 gint ell = 0;
1494 for (gint j = 0; j < 19; j++) {
1495 gint len = 0;
1496 for (gint k = 0; k < 4; k++) {
1497 while (isspace ((int) (*btr))) {
1498 if ((*btr) == '\0') break;
1499 btr++;
1500 }
1501 if (eos) {
1502 ud[k] = 0;
1503 continue;
1504 }
1505 if (((*btr) == '\0') || ((*btr) == '=')) {
1506 eos = true;
1507 ud[k] = 0;
1508 continue;
1509 }
1510 ud[k] = 64;
1511 for (gint b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1512 if (B64[b] == (*btr)) {
1513 ud[k] = (gchar) b;
1514 break;
1515 }
1516 }
1517 if (ud[k] == 64) { /* data corruption ?? */
1518 eos = true;
1519 ud[k] = 0;
1520 continue;
1521 }
1522 btr++;
1523 len++;
1524 }
1525 guint32 bits = (guint32) ud[0];
1526 bits = (bits << 6) | (guint32) ud[1];
1527 bits = (bits << 6) | (guint32) ud[2];
1528 bits = (bits << 6) | (guint32) ud[3];
1529 bd[ell++] = (guchar) ((bits & 0xff0000) >> 16);
1530 if (len > 2) {
1531 bd[ell++] = (guchar) ((bits & 0xff00) >> 8);
1532 }
1533 if (len > 3) {
1534 bd[ell++] = (guchar) (bits & 0xff);
1535 }
1536 }
1538 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) ell, NULL)) {
1539 failed = true;
1540 break;
1541 }
1542 }
1544 gdk_pixbuf_loader_close (loader, NULL);
1546 if (!failed) {
1547 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1548 }
1549 }
1551 return pixbuf;
1552 }
1554 static void
1555 sp_image_set_curve(SPImage *image)
1556 {
1557 //create a curve at the image's boundary for snapping
1558 if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) {
1559 if (image->curve) {
1560 image->curve = image->curve->unref();
1561 }
1562 } else {
1563 NRRect rect;
1564 sp_image_bbox(image, &rect, Geom::identity(), 0);
1565 Geom::Rect rect2 = to_2geom(*rect.upgrade());
1566 SPCurve *c = SPCurve::new_from_rect(rect2);
1568 if (image->curve) {
1569 image->curve = image->curve->unref();
1570 }
1572 if (c) {
1573 image->curve = c->ref();
1575 c->unref();
1576 }
1577 }
1578 }
1580 /**
1581 * Return duplicate of curve (if any exists) or NULL if there is no curve
1582 */
1583 SPCurve *
1584 sp_image_get_curve (SPImage *image)
1585 {
1586 SPCurve *result = 0;
1587 if (image->curve) {
1588 result = image->curve->copy();
1589 }
1590 return result;
1591 }
1593 void sp_image_refresh_if_outdated( SPImage* image )
1594 {
1595 if ( image->href && image->lastMod ) {
1596 // It *might* change
1598 struct stat st;
1599 memset(&st, 0, sizeof(st));
1600 int val = g_stat(image->pixPath, &st);
1601 if ( !val ) {
1602 // stat call worked. Check time now
1603 if ( st.st_mtime != image->lastMod ) {
1604 SPCtx *ctx = 0;
1605 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1606 sp_image_update(image, ctx, flags);
1607 }
1608 }
1609 }
1610 }
1612 /*
1613 Local Variables:
1614 mode:c++
1615 c-file-style:"stroustrup"
1616 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1617 indent-tabs-mode:nil
1618 fill-column:99
1619 End:
1620 */
1621 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :