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