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::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::Node *repr, guint flags)
1007 {
1008 SPImage *image;
1010 image = SP_IMAGE (object);
1012 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1013 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
1014 repr = xml_doc->createElement("svg:image");
1015 }
1017 repr->setAttribute("xlink:href", image->href);
1018 /* fixme: Reset attribute if needed (Lauris) */
1019 if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed);
1020 if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed);
1021 if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed);
1022 if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed);
1023 repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1024 #if ENABLE_LCMS
1025 if (image->color_profile) repr->setAttribute("color-profile", image->color_profile);
1026 #endif // ENABLE_LCMS
1028 if (((SPObjectClass *) (parent_class))->write)
1029 ((SPObjectClass *) (parent_class))->write (object, repr, flags);
1031 return repr;
1032 }
1034 static void
1035 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const /*flags*/)
1036 {
1037 SPImage const &image = *SP_IMAGE(item);
1039 if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1040 double const x0 = image.x.computed;
1041 double const y0 = image.y.computed;
1042 double const x1 = x0 + image.width.computed;
1043 double const y1 = y0 + image.height.computed;
1045 nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform);
1046 nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform);
1047 nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform);
1048 nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform);
1049 }
1050 }
1052 static void
1053 sp_image_print (SPItem *item, SPPrintContext *ctx)
1054 {
1055 SPImage *image;
1056 guchar *px;
1057 int w, h, rs, pixskip;
1059 image = SP_IMAGE (item);
1061 if (!image->pixbuf) return;
1062 if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return;
1064 px = gdk_pixbuf_get_pixels (image->pixbuf);
1065 w = gdk_pixbuf_get_width (image->pixbuf);
1066 h = gdk_pixbuf_get_height (image->pixbuf);
1067 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1068 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1070 NR::Matrix t;
1071 if (image->aspect_align == SP_ASPECT_NONE) {
1072 /* fixme: (Lauris) */
1073 NR::translate tp = NR::translate(image->x.computed, image->y.computed);
1074 NR::scale s = NR::scale(image->width.computed, -image->height.computed);
1075 NR::translate ti = NR::translate(0.0, -1.0);
1076 t = s * tp;
1077 t = ti * t;
1078 } else { // preserveAspectRatio
1079 NR::translate tp = NR::translate(image->viewx, image->viewy);
1080 NR::scale s = NR::scale(image->viewwidth, -image->viewheight);
1081 NR::translate ti = NR::translate(0.0, -1.0);
1082 t = s * tp;
1083 t = ti * t;
1084 }
1086 if (image->aspect_align == SP_ASPECT_NONE)
1087 sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1088 else // preserveAspectRatio
1089 sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item));
1090 }
1092 static gchar *
1093 sp_image_description(SPItem *item)
1094 {
1095 SPImage *image = SP_IMAGE(item);
1096 char *href_desc;
1097 if (image->href) {
1098 href_desc = (strncmp(image->href, "data:", 5) == 0)
1099 ? g_strdup(_("embedded"))
1100 : xml_quote_strdup(image->href);
1101 } else {
1102 g_warning("Attempting to call strncmp() with a null pointer.");
1103 href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1104 }
1106 char *ret = ( image->pixbuf == NULL
1107 ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1108 : g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
1109 gdk_pixbuf_get_width(image->pixbuf),
1110 gdk_pixbuf_get_height(image->pixbuf),
1111 href_desc) );
1112 g_free(href_desc);
1113 return ret;
1114 }
1116 static NRArenaItem *
1117 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1118 {
1119 int pixskip, rs;
1120 SPImage * image;
1121 NRArenaItem *ai;
1123 image = (SPImage *) item;
1125 ai = NRArenaImage::create(arena);
1127 if (image->pixbuf) {
1128 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1129 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1130 nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1131 if (image->aspect_align == SP_ASPECT_NONE)
1132 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1133 gdk_pixbuf_get_pixels (image->pixbuf),
1134 gdk_pixbuf_get_width (image->pixbuf),
1135 gdk_pixbuf_get_height (image->pixbuf),
1136 rs);
1137 else // preserveAspectRatio
1138 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1139 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1140 image->trimwidth,
1141 image->trimheight,
1142 rs);
1143 } else {
1144 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0);
1145 }
1146 if (image->aspect_align == SP_ASPECT_NONE)
1147 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1148 else // preserveAspectRatio
1149 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1151 return ai;
1152 }
1154 /*
1155 * utility function to try loading image from href
1156 *
1157 * docbase/relative_src
1158 * absolute_src
1159 *
1160 */
1162 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1163 {
1164 const gchar *filename, *docbase;
1165 gchar *fullname;
1166 GdkPixbuf *pixbuf;
1167 modTime = 0;
1168 if ( pixPath ) {
1169 g_free(pixPath);
1170 pixPath = 0;
1171 }
1173 filename = href;
1174 if (filename != NULL) {
1175 if (strncmp (filename,"file:",5) == 0) {
1176 fullname = g_filename_from_uri(filename, NULL, NULL);
1177 if (fullname) {
1178 // TODO check this. Was doing a UTF-8 to filename conversion here.
1179 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1180 if (pixbuf != NULL) return pixbuf;
1181 }
1182 } else if (strncmp (filename,"data:",5) == 0) {
1183 /* data URI - embedded image */
1184 filename += 5;
1185 pixbuf = sp_image_repr_read_dataURI (filename);
1186 if (pixbuf != NULL) return pixbuf;
1187 } else {
1189 if (!g_path_is_absolute (filename)) {
1190 /* try to load from relative pos combined with document base*/
1191 docbase = base;
1192 if (!docbase) docbase = ".";
1193 fullname = g_build_filename(docbase, filename, NULL);
1195 // document base can be wrong (on the temporary doc when importing bitmap from a
1196 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1197 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1198 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1199 pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1200 g_free (fullname);
1201 if (pixbuf != NULL) return pixbuf;
1202 }
1203 }
1205 /* try filename as absolute */
1206 if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1207 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1208 if (pixbuf != NULL) return pixbuf;
1209 }
1210 }
1211 }
1213 /* at last try to load from sp absolute path name */
1214 filename = absref;
1215 if (filename != NULL) {
1216 // using absref is outside of SVG rules, so we must at least warn the user
1217 if ( base != NULL && href != NULL )
1218 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);
1219 else
1220 g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1222 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1223 if (pixbuf != NULL) return pixbuf;
1224 }
1225 /* Nope: We do not find any valid pixmap file :-( */
1226 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1228 /* It should be included xpm, so if it still does not does load, */
1229 /* our libraries are broken */
1230 g_assert (pixbuf != NULL);
1232 return pixbuf;
1233 }
1235 static GdkPixbuf *
1236 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1237 {
1238 GdkPixbuf * newbuf;
1240 if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf;
1242 newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
1243 gdk_pixbuf_unref (pixbuf);
1245 return newbuf;
1246 }
1248 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1250 static void
1251 sp_image_update_canvas_image (SPImage *image)
1252 {
1253 int rs, pixskip;
1254 SPItem *item;
1255 SPItemView *v;
1257 item = SP_ITEM (image);
1259 if (image->pixbuf) {
1260 /* fixme: We are slightly violating spec here (Lauris) */
1261 if (!image->width._set) {
1262 image->width.computed = gdk_pixbuf_get_width (image->pixbuf);
1263 }
1264 if (!image->height._set) {
1265 image->height.computed = gdk_pixbuf_get_height (image->pixbuf);
1266 }
1267 }
1269 for (v = item->display; v != NULL; v = v->next) {
1270 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1271 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1272 nr_arena_image_set_style (NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1273 if (image->aspect_align == SP_ASPECT_NONE) {
1274 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1275 gdk_pixbuf_get_pixels (image->pixbuf),
1276 gdk_pixbuf_get_width (image->pixbuf),
1277 gdk_pixbuf_get_height (image->pixbuf),
1278 rs);
1279 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1280 image->x.computed, image->y.computed,
1281 image->width.computed, image->height.computed);
1282 } else { // preserveAspectRatio
1283 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1284 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1285 image->trimwidth,
1286 image->trimheight,
1287 rs);
1288 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1289 image->viewx, image->viewy,
1290 image->viewwidth, image->viewheight);
1291 }
1292 }
1293 }
1295 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1296 {
1297 /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1298 to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1299 the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1300 */
1302 g_assert(item != NULL);
1303 g_assert(SP_IS_IMAGE(item));
1305 if (item->clip_ref->getObject()) {
1306 //We are looking at a clipped image: do not return any snappoints, as these might be
1307 //far far away from the visible part from the clipped image
1308 } else {
1309 // The image has not been clipped: return its corners, which might be rotated for example
1310 SPImage &image = *SP_IMAGE(item);
1311 double const x0 = image.x.computed;
1312 double const y0 = image.y.computed;
1313 double const x1 = x0 + image.width.computed;
1314 double const y1 = y0 + image.height.computed;
1315 NR::Matrix const i2d (sp_item_i2d_affine (item));
1316 *p = NR::Point(x0, y0) * i2d;
1317 *p = NR::Point(x0, y1) * i2d;
1318 *p = NR::Point(x1, y1) * i2d;
1319 *p = NR::Point(x1, y0) * i2d;
1320 }
1321 }
1323 /*
1324 * Initially we'll do:
1325 * Transform x, y, set x, y, clear translation
1326 */
1328 static NR::Matrix
1329 sp_image_set_transform(SPItem *item, NR::Matrix const &xform)
1330 {
1331 SPImage *image = SP_IMAGE(item);
1333 /* Calculate position in parent coords. */
1334 NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform );
1336 /* This function takes care of translation and scaling, we return whatever parts we can't
1337 handle. */
1338 NR::Matrix ret(NR::transform(xform));
1339 NR::Point const scale(hypot(ret[0], ret[1]),
1340 hypot(ret[2], ret[3]));
1341 if ( scale[NR::X] > 1e-9 ) {
1342 ret[0] /= scale[NR::X];
1343 ret[1] /= scale[NR::X];
1344 } else {
1345 ret[0] = 1.0;
1346 ret[1] = 0.0;
1347 }
1348 if ( scale[NR::Y] > 1e-9 ) {
1349 ret[2] /= scale[NR::Y];
1350 ret[3] /= scale[NR::Y];
1351 } else {
1352 ret[2] = 0.0;
1353 ret[3] = 1.0;
1354 }
1356 image->width = image->width.computed * scale[NR::X];
1357 image->height = image->height.computed * scale[NR::Y];
1359 /* Find position in item coords */
1360 pos = pos * ret.inverse();
1361 image->x = pos[NR::X];
1362 image->y = pos[NR::Y];
1364 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1366 return ret;
1367 }
1369 static GdkPixbuf *
1370 sp_image_repr_read_dataURI (const gchar * uri_data)
1371 { GdkPixbuf * pixbuf = NULL;
1373 gint data_is_image = 0;
1374 gint data_is_base64 = 0;
1376 const gchar * data = uri_data;
1378 while (*data) {
1379 if (strncmp (data,"base64",6) == 0) {
1380 /* base64-encoding */
1381 data_is_base64 = 1;
1382 data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1383 data += 6;
1384 }
1385 else if (strncmp (data,"image/png",9) == 0) {
1386 /* PNG image */
1387 data_is_image = 1;
1388 data += 9;
1389 }
1390 else if (strncmp (data,"image/jpg",9) == 0) {
1391 /* JPEG image */
1392 data_is_image = 1;
1393 data += 9;
1394 }
1395 else if (strncmp (data,"image/jpeg",10) == 0) {
1396 /* JPEG image */
1397 data_is_image = 1;
1398 data += 10;
1399 }
1400 else { /* unrecognized option; skip it */
1401 while (*data) {
1402 if (((*data) == ';') || ((*data) == ',')) break;
1403 data++;
1404 }
1405 }
1406 if ((*data) == ';') {
1407 data++;
1408 continue;
1409 }
1410 if ((*data) == ',') {
1411 data++;
1412 break;
1413 }
1414 }
1416 if ((*data) && data_is_image && data_is_base64) {
1417 pixbuf = sp_image_repr_read_b64 (data);
1418 }
1420 return pixbuf;
1421 }
1423 static GdkPixbuf *
1424 sp_image_repr_read_b64 (const gchar * uri_data)
1425 { GdkPixbuf * pixbuf = NULL;
1426 GdkPixbufLoader * loader = NULL;
1428 gint j;
1429 gint k;
1430 gint l;
1431 gint b;
1432 gint len;
1433 gint eos = 0;
1434 gint failed = 0;
1436 guint32 bits;
1438 static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1440 const gchar* btr = uri_data;
1442 gchar ud[4];
1444 guchar bd[57];
1446 loader = gdk_pixbuf_loader_new ();
1448 if (loader == NULL) return NULL;
1450 while (eos == 0) {
1451 l = 0;
1452 for (j = 0; j < 19; j++) {
1453 len = 0;
1454 for (k = 0; k < 4; k++) {
1455 while (isspace ((int) (*btr))) {
1456 if ((*btr) == '\0') break;
1457 btr++;
1458 }
1459 if (eos) {
1460 ud[k] = 0;
1461 continue;
1462 }
1463 if (((*btr) == '\0') || ((*btr) == '=')) {
1464 eos = 1;
1465 ud[k] = 0;
1466 continue;
1467 }
1468 ud[k] = 64;
1469 for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1470 if (B64[b] == (*btr)) {
1471 ud[k] = (gchar) b;
1472 break;
1473 }
1474 }
1475 if (ud[k] == 64) { /* data corruption ?? */
1476 eos = 1;
1477 ud[k] = 0;
1478 continue;
1479 }
1480 btr++;
1481 len++;
1482 }
1483 bits = (guint32) ud[0];
1484 bits = (bits << 6) | (guint32) ud[1];
1485 bits = (bits << 6) | (guint32) ud[2];
1486 bits = (bits << 6) | (guint32) ud[3];
1487 bd[l++] = (guchar) ((bits & 0xff0000) >> 16);
1488 if (len > 2) {
1489 bd[l++] = (guchar) ((bits & 0xff00) >> 8);
1490 }
1491 if (len > 3) {
1492 bd[l++] = (guchar) (bits & 0xff);
1493 }
1494 }
1496 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) {
1497 failed = 1;
1498 break;
1499 }
1500 }
1502 gdk_pixbuf_loader_close (loader, NULL);
1504 if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1506 return pixbuf;
1507 }
1509 static void
1510 sp_image_set_curve(SPImage *image)
1511 {
1512 //create a curve at the image's boundary for snapping
1513 if ((image->height.computed < 1e-18) || (image->width.computed < 1e-18) || (image->clip_ref->getObject())) {
1514 if (image->curve) {
1515 image->curve = image->curve->unref();
1516 }
1517 return;
1518 }
1520 NRRect rect;
1521 sp_image_bbox(image, &rect, NR::identity(), 0);
1522 NR::Maybe<NR::Rect> rect2 = rect.upgrade();
1523 SPCurve *c = SPCurve::new_from_rect(rect2);
1525 if (image->curve) {
1526 image->curve = image->curve->unref();
1527 }
1529 if (c) {
1530 image->curve = c->ref();
1531 }
1533 c->unref();
1534 }
1536 /**
1537 * Return duplicate of curve (if any exists) or NULL if there is no curve
1538 */
1539 SPCurve *
1540 sp_image_get_curve (SPImage *image)
1541 {
1542 if (image->curve) {
1543 return image->curve->copy();
1544 }
1545 return NULL;
1546 }
1548 void sp_image_refresh_if_outdated( SPImage* image )
1549 {
1550 if ( image->href && image->lastMod ) {
1551 // It *might* change
1553 struct stat st;
1554 memset(&st, 0, sizeof(st));
1555 int val = g_stat(image->pixPath, &st);
1556 if ( !val ) {
1557 // stat call worked. Check time now
1558 if ( st.st_mtime != image->lastMod ) {
1559 SPCtx *ctx = 0;
1560 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1561 sp_image_update(image, ctx, flags);
1562 }
1563 }
1564 }
1565 }
1567 /*
1568 Local Variables:
1569 mode:c++
1570 c-file-style:"stroustrup"
1571 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1572 indent-tabs-mode:nil
1573 fill-column:99
1574 End:
1575 */
1576 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :