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