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