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