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