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