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