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