68bafdeab9345e0c50c9e0bc43d99e4136740124
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 while ( !feof(fp) )
464 {
465 if ( youme.readMore() ) {
466 if ( youme.first ) {
467 //g_message( "First data chunk" );
468 youme.first = FALSE;
469 if (readPngAndHeaders(youme, dpiX, dpiY))
470 {
471 // TODO set the dpi to be read elsewhere
472 }
473 } else if ( !latter ) {
474 latter = TRUE;
475 //g_message(" READing latter");
476 }
477 // Now clear out the buffer so we can read more.
478 // (dumping out unused)
479 youme.clear();
480 }
481 }
483 gboolean ok = gdk_pixbuf_loader_close(loader, &err);
484 if ( ok ) {
485 buf = gdk_pixbuf_loader_get_pixbuf( loader );
486 if ( buf ) {
487 g_object_ref(buf);
489 if ( dpiX ) {
490 gchar *tmp = g_strdup_printf( "%d", dpiX );
491 if ( tmp ) {
492 //g_message("Need to set DpiX: %s", tmp);
493 //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
494 g_free( tmp );
495 }
496 }
497 if ( dpiY ) {
498 gchar *tmp = g_strdup_printf( "%d", dpiY );
499 if ( tmp ) {
500 //g_message("Need to set DpiY: %s", tmp);
501 //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
502 g_free( tmp );
503 }
504 }
505 }
506 } else {
507 // do something
508 g_message("error loading pixbuf at close");
509 }
511 g_object_unref(loader);
512 } else {
513 g_message("error when creating pixbuf loader");
514 }
515 fclose( fp );
516 fp = 0;
517 } else {
518 g_warning ("Unable to open linked file: %s", filename);
519 }
521 /*
522 if ( buf )
523 {
524 const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
525 if ( bloop )
526 {
527 g_message("DPI X is [%s]", bloop);
528 }
529 bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
530 if ( bloop )
531 {
532 g_message("DPI Y is [%s]", bloop);
533 }
534 }
535 */
537 return buf;
538 }
540 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
541 {
542 time_t modTime = 0;
543 gchar* pixPath = 0;
544 GdkPixbuf* result = pixbuf_new_from_file( filename, modTime, pixPath, error );
545 if (pixPath) {
546 g_free(pixPath);
547 }
548 return result;
549 }
552 }
553 }
555 GType
556 sp_image_get_type (void)
557 {
558 static GType image_type = 0;
559 if (!image_type) {
560 GTypeInfo image_info = {
561 sizeof (SPImageClass),
562 NULL, /* base_init */
563 NULL, /* base_finalize */
564 (GClassInitFunc) sp_image_class_init,
565 NULL, /* class_finalize */
566 NULL, /* class_data */
567 sizeof (SPImage),
568 16, /* n_preallocs */
569 (GInstanceInitFunc) sp_image_init,
570 NULL, /* value_table */
571 };
572 image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
573 }
574 return image_type;
575 }
577 static void
578 sp_image_class_init (SPImageClass * klass)
579 {
580 GObjectClass * gobject_class;
581 SPObjectClass * sp_object_class;
582 SPItemClass * item_class;
584 gobject_class = (GObjectClass *) klass;
585 sp_object_class = (SPObjectClass *) klass;
586 item_class = (SPItemClass *) klass;
588 parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
590 sp_object_class->build = sp_image_build;
591 sp_object_class->release = sp_image_release;
592 sp_object_class->set = sp_image_set;
593 sp_object_class->update = sp_image_update;
594 sp_object_class->modified = sp_image_modified;
595 sp_object_class->write = sp_image_write;
597 item_class->bbox = sp_image_bbox;
598 item_class->print = sp_image_print;
599 item_class->description = sp_image_description;
600 item_class->show = sp_image_show;
601 item_class->snappoints = sp_image_snappoints;
602 item_class->set_transform = sp_image_set_transform;
603 }
605 static void sp_image_init( SPImage *image )
606 {
607 image->x.unset();
608 image->y.unset();
609 image->width.unset();
610 image->height.unset();
611 image->aspect_align = SP_ASPECT_NONE;
613 image->trimx = 0;
614 image->trimy = 0;
615 image->trimwidth = 0;
616 image->trimheight = 0;
617 image->viewx = 0;
618 image->viewy = 0;
619 image->viewwidth = 0;
620 image->viewheight = 0;
622 image->curve = NULL;
624 image->href = 0;
625 #if ENABLE_LCMS
626 image->color_profile = 0;
627 #endif // ENABLE_LCMS
628 image->pixbuf = 0;
629 image->pixPath = 0;
630 image->lastMod = 0;
631 }
633 static void
634 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
635 {
636 if (((SPObjectClass *) parent_class)->build) {
637 ((SPObjectClass *) parent_class)->build (object, document, repr);
638 }
640 sp_object_read_attr (object, "xlink:href");
641 sp_object_read_attr (object, "x");
642 sp_object_read_attr (object, "y");
643 sp_object_read_attr (object, "width");
644 sp_object_read_attr (object, "height");
645 sp_object_read_attr (object, "preserveAspectRatio");
646 sp_object_read_attr (object, "color-profile");
648 /* Register */
649 sp_document_add_resource (document, "image", object);
650 }
652 static void
653 sp_image_release (SPObject *object)
654 {
655 SPImage *image = SP_IMAGE(object);
657 if (SP_OBJECT_DOCUMENT (object)) {
658 /* Unregister ourselves */
659 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
660 }
662 if (image->href) {
663 g_free (image->href);
664 image->href = NULL;
665 }
667 if (image->pixbuf) {
668 gdk_pixbuf_unref (image->pixbuf);
669 image->pixbuf = NULL;
670 }
672 #if ENABLE_LCMS
673 if (image->color_profile) {
674 g_free (image->color_profile);
675 image->color_profile = NULL;
676 }
677 #endif // ENABLE_LCMS
679 if (image->pixPath) {
680 g_free(image->pixPath);
681 image->pixPath = 0;
682 }
684 if (image->curve) {
685 image->curve = image->curve->unref();
686 }
688 if (((SPObjectClass *) parent_class)->release) {
689 ((SPObjectClass *) parent_class)->release (object);
690 }
691 }
693 static void
694 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
695 {
696 SPImage *image = SP_IMAGE (object);
698 switch (key) {
699 case SP_ATTR_XLINK_HREF:
700 g_free (image->href);
701 image->href = (value) ? g_strdup (value) : NULL;
702 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
703 break;
704 case SP_ATTR_X:
705 if (!image->x.readAbsolute(value)) {
706 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
707 image->x.unset();
708 }
709 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
710 break;
711 case SP_ATTR_Y:
712 if (!image->y.readAbsolute(value)) {
713 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
714 image->y.unset();
715 }
716 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
717 break;
718 case SP_ATTR_WIDTH:
719 if (!image->width.readAbsolute(value)) {
720 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
721 image->width.unset();
722 }
723 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
724 break;
725 case SP_ATTR_HEIGHT:
726 if (!image->height.readAbsolute(value)) {
727 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
728 image->height.unset();
729 }
730 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
731 break;
732 case SP_ATTR_PRESERVEASPECTRATIO:
733 /* Do setup before, so we can use break to escape */
734 image->aspect_align = SP_ASPECT_NONE;
735 image->aspect_clip = SP_ASPECT_MEET;
736 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
737 if (value) {
738 int len;
739 gchar c[256];
740 const gchar *p, *e;
741 unsigned int align, clip;
742 p = value;
743 while (*p && *p == 32) p += 1;
744 if (!*p) break;
745 e = p;
746 while (*e && *e != 32) e += 1;
747 len = e - p;
748 if (len > 8) break;
749 memcpy (c, value, len);
750 c[len] = 0;
751 /* Now the actual part */
752 if (!strcmp (c, "none")) {
753 align = SP_ASPECT_NONE;
754 } else if (!strcmp (c, "xMinYMin")) {
755 align = SP_ASPECT_XMIN_YMIN;
756 } else if (!strcmp (c, "xMidYMin")) {
757 align = SP_ASPECT_XMID_YMIN;
758 } else if (!strcmp (c, "xMaxYMin")) {
759 align = SP_ASPECT_XMAX_YMIN;
760 } else if (!strcmp (c, "xMinYMid")) {
761 align = SP_ASPECT_XMIN_YMID;
762 } else if (!strcmp (c, "xMidYMid")) {
763 align = SP_ASPECT_XMID_YMID;
764 } else if (!strcmp (c, "xMaxYMid")) {
765 align = SP_ASPECT_XMAX_YMID;
766 } else if (!strcmp (c, "xMinYMax")) {
767 align = SP_ASPECT_XMIN_YMAX;
768 } else if (!strcmp (c, "xMidYMax")) {
769 align = SP_ASPECT_XMID_YMAX;
770 } else if (!strcmp (c, "xMaxYMax")) {
771 align = SP_ASPECT_XMAX_YMAX;
772 } else {
773 break;
774 }
775 clip = SP_ASPECT_MEET;
776 while (*e && *e == 32) e += 1;
777 if (*e) {
778 if (!strcmp (e, "meet")) {
779 clip = SP_ASPECT_MEET;
780 } else if (!strcmp (e, "slice")) {
781 clip = SP_ASPECT_SLICE;
782 } else {
783 break;
784 }
785 }
786 image->aspect_align = align;
787 image->aspect_clip = clip;
788 }
789 break;
790 #if ENABLE_LCMS
791 case SP_PROP_COLOR_PROFILE:
792 if ( image->color_profile ) {
793 g_free (image->color_profile);
794 }
795 image->color_profile = (value) ? g_strdup (value) : NULL;
796 #ifdef DEBUG_LCMS
797 if ( value ) {
798 DEBUG_MESSAGE( lcmsFour, "<image> color-profile set to '%s'", value );
799 } else {
800 DEBUG_MESSAGE( lcmsFour, "<image> color-profile cleared" );
801 }
802 #endif // DEBUG_LCMS
803 // TODO check on this HREF_MODIFIED flag
804 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
805 break;
806 #endif // ENABLE_LCMS
807 default:
808 if (((SPObjectClass *) (parent_class))->set)
809 ((SPObjectClass *) (parent_class))->set (object, key, value);
810 break;
811 }
813 sp_image_set_curve(image); //creates a curve at the image's boundary for snapping
814 }
816 static void
817 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
818 {
819 SPImage *image = SP_IMAGE(object);
820 SPDocument *doc = SP_OBJECT_DOCUMENT(object);
822 if (((SPObjectClass *) (parent_class))->update) {
823 ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
824 }
826 if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
827 if (image->pixbuf) {
828 gdk_pixbuf_unref (image->pixbuf);
829 image->pixbuf = NULL;
830 }
831 if ( image->pixPath ) {
832 g_free(image->pixPath);
833 image->pixPath = 0;
834 }
835 image->lastMod = 0;
836 if (image->href) {
837 GdkPixbuf *pixbuf;
838 pixbuf = sp_image_repr_read_image (
839 image->lastMod,
840 image->pixPath,
841 object->repr->attribute("xlink:href"),
842 object->repr->attribute("sodipodi:absref"),
843 doc->base);
844 if (pixbuf) {
845 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
846 // BLIP
847 #if ENABLE_LCMS
848 if ( image->color_profile )
849 {
850 int imagewidth = gdk_pixbuf_get_width( pixbuf );
851 int imageheight = gdk_pixbuf_get_height( pixbuf );
852 int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
853 guchar* px = gdk_pixbuf_get_pixels( pixbuf );
855 if ( px ) {
856 #ifdef DEBUG_LCMS
857 DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
858 #endif // DEBUG_LCMS
859 guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
860 cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
861 &profIntent,
862 image->color_profile );
863 if ( prof ) {
864 icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
865 if ( profileClass != icSigNamedColorClass ) {
866 int intent = INTENT_PERCEPTUAL;
867 switch ( profIntent ) {
868 case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
869 intent = INTENT_RELATIVE_COLORIMETRIC;
870 break;
871 case Inkscape::RENDERING_INTENT_SATURATION:
872 intent = INTENT_SATURATION;
873 break;
874 case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
875 intent = INTENT_ABSOLUTE_COLORIMETRIC;
876 break;
877 case Inkscape::RENDERING_INTENT_PERCEPTUAL:
878 case Inkscape::RENDERING_INTENT_UNKNOWN:
879 case Inkscape::RENDERING_INTENT_AUTO:
880 default:
881 intent = INTENT_PERCEPTUAL;
882 }
883 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
884 cmsHTRANSFORM transf = cmsCreateTransform( prof,
885 TYPE_RGBA_8,
886 destProf,
887 TYPE_RGBA_8,
888 intent, 0 );
889 if ( transf ) {
890 guchar* currLine = px;
891 for ( int y = 0; y < imageheight; y++ ) {
892 // Since the types are the same size, we can do the transformation in-place
893 cmsDoTransform( transf, currLine, currLine, imagewidth );
894 currLine += rowstride;
895 }
897 cmsDeleteTransform( transf );
898 }
899 #ifdef DEBUG_LCMS
900 else
901 {
902 DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
903 }
904 #endif // DEBUG_LCMS
905 cmsCloseProfile( destProf );
906 }
907 #ifdef DEBUG_LCMS
908 else
909 {
910 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
911 }
912 #endif // DEBUG_LCMS
913 }
914 #ifdef DEBUG_LCMS
915 else
916 {
917 DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
918 }
919 #endif // DEBUG_LCMS
920 }
921 }
922 #endif // ENABLE_LCMS
923 image->pixbuf = pixbuf;
924 }
925 }
926 }
927 // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
928 if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
929 int imagewidth, imageheight;
930 double x,y;
932 imagewidth = gdk_pixbuf_get_width (image->pixbuf);
933 imageheight = gdk_pixbuf_get_height (image->pixbuf);
935 switch (image->aspect_align) {
936 case SP_ASPECT_XMIN_YMIN:
937 x = 0.0;
938 y = 0.0;
939 break;
940 case SP_ASPECT_XMID_YMIN:
941 x = 0.5;
942 y = 0.0;
943 break;
944 case SP_ASPECT_XMAX_YMIN:
945 x = 1.0;
946 y = 0.0;
947 break;
948 case SP_ASPECT_XMIN_YMID:
949 x = 0.0;
950 y = 0.5;
951 break;
952 case SP_ASPECT_XMID_YMID:
953 x = 0.5;
954 y = 0.5;
955 break;
956 case SP_ASPECT_XMAX_YMID:
957 x = 1.0;
958 y = 0.5;
959 break;
960 case SP_ASPECT_XMIN_YMAX:
961 x = 0.0;
962 y = 1.0;
963 break;
964 case SP_ASPECT_XMID_YMAX:
965 x = 0.5;
966 y = 1.0;
967 break;
968 case SP_ASPECT_XMAX_YMAX:
969 x = 1.0;
970 y = 1.0;
971 break;
972 default:
973 x = 0.0;
974 y = 0.0;
975 break;
976 }
978 if (image->aspect_clip == SP_ASPECT_SLICE) {
979 image->viewx = image->x.computed;
980 image->viewy = image->y.computed;
981 image->viewwidth = image->width.computed;
982 image->viewheight = image->height.computed;
983 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
984 // Pixels aspect is wider than bounding box
985 image->trimheight = imageheight;
986 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
987 image->trimy = 0;
988 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
989 } else {
990 // Pixels aspect is taller than bounding box
991 image->trimwidth = imagewidth;
992 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
993 image->trimx = 0;
994 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
995 }
996 } else {
997 // Otherwise, assume SP_ASPECT_MEET
998 image->trimx = 0;
999 image->trimy = 0;
1000 image->trimwidth = imagewidth;
1001 image->trimheight = imageheight;
1002 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
1003 // Pixels aspect is wider than bounding boz
1004 image->viewwidth = image->width.computed;
1005 image->viewheight = image->viewwidth * imageheight / imagewidth;
1006 image->viewx=image->x.computed;
1007 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
1008 } else {
1009 // Pixels aspect is taller than bounding box
1010 image->viewheight = image->height.computed;
1011 image->viewwidth = image->viewheight * imagewidth / imageheight;
1012 image->viewy=image->y.computed;
1013 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
1014 }
1015 }
1016 }
1017 sp_image_update_canvas_image ((SPImage *) object);
1018 }
1020 static void
1021 sp_image_modified (SPObject *object, unsigned int flags)
1022 {
1023 SPImage *image = SP_IMAGE (object);
1025 if (((SPObjectClass *) (parent_class))->modified) {
1026 (* ((SPObjectClass *) (parent_class))->modified) (object, flags);
1027 }
1029 if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
1030 for (SPItemView *v = SP_ITEM (image)->display; v != NULL; v = v->next) {
1031 nr_arena_image_set_style (NR_ARENA_IMAGE (v->arenaitem), object->style);
1032 }
1033 }
1034 }
1036 static Inkscape::XML::Node *
1037 sp_image_write (SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags)
1038 {
1039 SPImage *image = SP_IMAGE (object);
1041 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
1042 repr = xml_doc->createElement("svg:image");
1043 }
1045 repr->setAttribute("xlink:href", image->href);
1046 /* fixme: Reset attribute if needed (Lauris) */
1047 if (image->x._set) {
1048 sp_repr_set_svg_double(repr, "x", image->x.computed);
1049 }
1050 if (image->y._set) {
1051 sp_repr_set_svg_double(repr, "y", image->y.computed);
1052 }
1053 if (image->width._set) {
1054 sp_repr_set_svg_double(repr, "width", image->width.computed);
1055 }
1056 if (image->height._set) {
1057 sp_repr_set_svg_double(repr, "height", image->height.computed);
1058 }
1059 repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
1060 #if ENABLE_LCMS
1061 if (image->color_profile) {
1062 repr->setAttribute("color-profile", image->color_profile);
1063 }
1064 #endif // ENABLE_LCMS
1066 if (((SPObjectClass *) (parent_class))->write) {
1067 ((SPObjectClass *) (parent_class))->write (object, xml_doc, repr, flags);
1068 }
1070 return repr;
1071 }
1073 static void
1074 sp_image_bbox(SPItem const *item, NRRect *bbox, Geom::Matrix const &transform, unsigned const /*flags*/)
1075 {
1076 SPImage const &image = *SP_IMAGE(item);
1078 if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
1079 double const x0 = image.x.computed;
1080 double const y0 = image.y.computed;
1081 double const x1 = x0 + image.width.computed;
1082 double const y1 = y0 + image.height.computed;
1084 nr_rect_union_pt(bbox, Geom::Point(x0, y0) * transform);
1085 nr_rect_union_pt(bbox, Geom::Point(x1, y0) * transform);
1086 nr_rect_union_pt(bbox, Geom::Point(x1, y1) * transform);
1087 nr_rect_union_pt(bbox, Geom::Point(x0, y1) * transform);
1088 }
1089 }
1091 static void
1092 sp_image_print (SPItem *item, SPPrintContext *ctx)
1093 {
1094 SPImage *image = SP_IMAGE(item);
1096 if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) {
1097 guchar *px = gdk_pixbuf_get_pixels(image->pixbuf);
1098 int w = gdk_pixbuf_get_width(image->pixbuf);
1099 int h = gdk_pixbuf_get_height(image->pixbuf);
1100 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1101 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1103 Geom::Matrix t;
1104 if (image->aspect_align == SP_ASPECT_NONE) {
1105 /* fixme: (Lauris) */
1106 Geom::Translate tp(image->x.computed, image->y.computed);
1107 Geom::Scale s(image->width.computed, -image->height.computed);
1108 Geom::Translate ti(0.0, -1.0);
1109 t = s * tp;
1110 t = ti * t;
1111 } else { // preserveAspectRatio
1112 Geom::Translate tp(image->viewx, image->viewy);
1113 Geom::Scale s(image->viewwidth, -image->viewheight);
1114 Geom::Translate ti(0.0, -1.0);
1115 t = s * tp;
1116 t = ti * t;
1117 }
1119 if (image->aspect_align == SP_ASPECT_NONE) {
1120 sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
1121 } else { // preserveAspectRatio
1122 sp_print_image_R8G8B8A8_N(ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE(item));
1123 }
1124 }
1125 }
1127 static gchar *
1128 sp_image_description(SPItem *item)
1129 {
1130 SPImage *image = SP_IMAGE(item);
1131 char *href_desc;
1132 if (image->href) {
1133 href_desc = (strncmp(image->href, "data:", 5) == 0)
1134 ? g_strdup(_("embedded"))
1135 : xml_quote_strdup(image->href);
1136 } else {
1137 g_warning("Attempting to call strncmp() with a null pointer.");
1138 href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
1139 }
1141 char *ret = ( image->pixbuf == NULL
1142 ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
1143 : g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
1144 gdk_pixbuf_get_width(image->pixbuf),
1145 gdk_pixbuf_get_height(image->pixbuf),
1146 href_desc) );
1147 g_free(href_desc);
1148 return ret;
1149 }
1151 static NRArenaItem *
1152 sp_image_show (SPItem *item, NRArena *arena, unsigned int /*key*/, unsigned int /*flags*/)
1153 {
1154 SPImage * image = SP_IMAGE(item);
1155 NRArenaItem *ai = NRArenaImage::create(arena);
1157 if (image->pixbuf) {
1158 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1159 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1160 nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1161 if (image->aspect_align == SP_ASPECT_NONE) {
1162 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1163 gdk_pixbuf_get_pixels(image->pixbuf),
1164 gdk_pixbuf_get_width(image->pixbuf),
1165 gdk_pixbuf_get_height(image->pixbuf),
1166 rs);
1167 } else { // preserveAspectRatio
1168 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai),
1169 gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1170 image->trimwidth,
1171 image->trimheight,
1172 rs);
1173 }
1174 } else {
1175 nr_arena_image_set_pixels(NR_ARENA_IMAGE(ai), NULL, 0, 0, 0);
1176 }
1177 if (image->aspect_align == SP_ASPECT_NONE) {
1178 nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1179 } else { // preserveAspectRatio
1180 nr_arena_image_set_geometry(NR_ARENA_IMAGE(ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1181 }
1183 return ai;
1184 }
1186 /*
1187 * utility function to try loading image from href
1188 *
1189 * docbase/relative_src
1190 * absolute_src
1191 *
1192 */
1194 GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
1195 {
1196 GdkPixbuf *pixbuf = 0;
1197 modTime = 0;
1198 if ( pixPath ) {
1199 g_free(pixPath);
1200 pixPath = 0;
1201 }
1203 const gchar *filename = href;
1204 if (filename != NULL) {
1205 if (strncmp (filename,"file:",5) == 0) {
1206 gchar *fullname = g_filename_from_uri(filename, NULL, NULL);
1207 if (fullname) {
1208 // TODO check this. Was doing a UTF-8 to filename conversion here.
1209 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, modTime, pixPath, NULL);
1210 if (pixbuf != NULL) {
1211 return pixbuf;
1212 }
1213 }
1214 } else if (strncmp (filename,"data:",5) == 0) {
1215 /* data URI - embedded image */
1216 filename += 5;
1217 pixbuf = sp_image_repr_read_dataURI (filename);
1218 if (pixbuf != NULL) {
1219 return pixbuf;
1220 }
1221 } else {
1223 if (!g_path_is_absolute (filename)) {
1224 /* try to load from relative pos combined with document base*/
1225 const gchar *docbase = base;
1226 if (!docbase) {
1227 docbase = ".";
1228 }
1229 gchar *fullname = g_build_filename(docbase, filename, NULL);
1231 // document base can be wrong (on the temporary doc when importing bitmap from a
1232 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1233 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1234 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1235 pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, modTime, pixPath, NULL );
1236 g_free (fullname);
1237 if (pixbuf != NULL) {
1238 return pixbuf;
1239 }
1240 }
1241 }
1243 /* try filename as absolute */
1244 if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1245 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1246 if (pixbuf != NULL) {
1247 return pixbuf;
1248 }
1249 }
1250 }
1251 }
1253 /* at last try to load from sp absolute path name */
1254 filename = absref;
1255 if (filename != NULL) {
1256 // using absref is outside of SVG rules, so we must at least warn the user
1257 if ( base != NULL && href != NULL ) {
1258 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);
1259 } else {
1260 g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1261 }
1263 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, modTime, pixPath, NULL );
1264 if (pixbuf != NULL) {
1265 return pixbuf;
1266 }
1267 }
1268 /* Nope: We do not find any valid pixmap file :-( */
1269 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1271 /* It should be included xpm, so if it still does not does load, */
1272 /* our libraries are broken */
1273 g_assert (pixbuf != NULL);
1275 return pixbuf;
1276 }
1278 static GdkPixbuf *
1279 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1280 {
1281 GdkPixbuf* result;
1282 if (gdk_pixbuf_get_has_alpha(pixbuf)) {
1283 result = pixbuf;
1284 } else {
1285 result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
1286 gdk_pixbuf_unref(pixbuf);
1287 }
1288 return result;
1289 }
1291 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1293 static void
1294 sp_image_update_canvas_image (SPImage *image)
1295 {
1296 SPItem *item = SP_ITEM(image);
1298 if (image->pixbuf) {
1299 /* fixme: We are slightly violating spec here (Lauris) */
1300 if (!image->width._set) {
1301 image->width.computed = gdk_pixbuf_get_width(image->pixbuf);
1302 }
1303 if (!image->height._set) {
1304 image->height.computed = gdk_pixbuf_get_height(image->pixbuf);
1305 }
1306 }
1308 for (SPItemView *v = item->display; v != NULL; v = v->next) {
1309 int pixskip = gdk_pixbuf_get_n_channels(image->pixbuf) * gdk_pixbuf_get_bits_per_sample(image->pixbuf) / 8;
1310 int rs = gdk_pixbuf_get_rowstride(image->pixbuf);
1311 nr_arena_image_set_style(NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1312 if (image->aspect_align == SP_ASPECT_NONE) {
1313 nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1314 gdk_pixbuf_get_pixels(image->pixbuf),
1315 gdk_pixbuf_get_width(image->pixbuf),
1316 gdk_pixbuf_get_height(image->pixbuf),
1317 rs);
1318 nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1319 image->x.computed, image->y.computed,
1320 image->width.computed, image->height.computed);
1321 } else { // preserveAspectRatio
1322 nr_arena_image_set_pixels(NR_ARENA_IMAGE(v->arenaitem),
1323 gdk_pixbuf_get_pixels(image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1324 image->trimwidth,
1325 image->trimheight,
1326 rs);
1327 nr_arena_image_set_geometry(NR_ARENA_IMAGE(v->arenaitem),
1328 image->viewx, image->viewy,
1329 image->viewwidth, image->viewheight);
1330 }
1331 }
1332 }
1334 static void sp_image_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const */*snapprefs*/)
1335 {
1336 /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1337 to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1338 the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1339 */
1341 g_assert(item != NULL);
1342 g_assert(SP_IS_IMAGE(item));
1344 if (item->clip_ref->getObject()) {
1345 //We are looking at a clipped image: do not return any snappoints, as these might be
1346 //far far away from the visible part from the clipped image
1347 //TODO Do return snappoints, but only when within visual bounding box
1348 } else {
1349 // The image has not been clipped: return its corners, which might be rotated for example
1350 SPImage &image = *SP_IMAGE(item);
1351 double const x0 = image.x.computed;
1352 double const y0 = image.y.computed;
1353 double const x1 = x0 + image.width.computed;
1354 double const y1 = y0 + image.height.computed;
1355 Geom::Matrix const i2d (sp_item_i2d_affine (item));
1356 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1357 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1358 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1359 p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_CORNER, Inkscape::SNAPTARGET_CORNER));
1360 }
1361 }
1363 /*
1364 * Initially we'll do:
1365 * Transform x, y, set x, y, clear translation
1366 */
1368 static Geom::Matrix
1369 sp_image_set_transform(SPItem *item, Geom::Matrix const &xform)
1370 {
1371 SPImage *image = SP_IMAGE(item);
1373 /* Calculate position in parent coords. */
1374 Geom::Point pos( Geom::Point(image->x.computed, image->y.computed) * xform );
1376 /* This function takes care of translation and scaling, we return whatever parts we can't
1377 handle. */
1378 Geom::Matrix ret(Geom::Matrix(xform).without_translation());
1379 Geom::Point const scale(hypot(ret[0], ret[1]),
1380 hypot(ret[2], ret[3]));
1381 if ( scale[Geom::X] > MAGIC_EPSILON ) {
1382 ret[0] /= scale[Geom::X];
1383 ret[1] /= scale[Geom::X];
1384 } else {
1385 ret[0] = 1.0;
1386 ret[1] = 0.0;
1387 }
1388 if ( scale[Geom::Y] > MAGIC_EPSILON ) {
1389 ret[2] /= scale[Geom::Y];
1390 ret[3] /= scale[Geom::Y];
1391 } else {
1392 ret[2] = 0.0;
1393 ret[3] = 1.0;
1394 }
1396 image->width = image->width.computed * scale[Geom::X];
1397 image->height = image->height.computed * scale[Geom::Y];
1399 /* Find position in item coords */
1400 pos = pos * ret.inverse();
1401 image->x = pos[Geom::X];
1402 image->y = pos[Geom::Y];
1404 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1406 return ret;
1407 }
1409 static GdkPixbuf *
1410 sp_image_repr_read_dataURI (const gchar * uri_data)
1411 {
1412 GdkPixbuf * pixbuf = NULL;
1414 gint data_is_image = 0;
1415 gint data_is_base64 = 0;
1417 const gchar * data = uri_data;
1419 while (*data) {
1420 if (strncmp(data,"base64",6) == 0) {
1421 /* base64-encoding */
1422 data_is_base64 = 1;
1423 data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1424 data += 6;
1425 }
1426 else if (strncmp(data,"image/png",9) == 0) {
1427 /* PNG image */
1428 data_is_image = 1;
1429 data += 9;
1430 }
1431 else if (strncmp(data,"image/jpg",9) == 0) {
1432 /* JPEG image */
1433 data_is_image = 1;
1434 data += 9;
1435 }
1436 else if (strncmp(data,"image/jpeg",10) == 0) {
1437 /* JPEG image */
1438 data_is_image = 1;
1439 data += 10;
1440 }
1441 else { /* unrecognized option; skip it */
1442 while (*data) {
1443 if (((*data) == ';') || ((*data) == ',')) {
1444 break;
1445 }
1446 data++;
1447 }
1448 }
1449 if ((*data) == ';') {
1450 data++;
1451 continue;
1452 }
1453 if ((*data) == ',') {
1454 data++;
1455 break;
1456 }
1457 }
1459 if ((*data) && data_is_image && data_is_base64) {
1460 pixbuf = sp_image_repr_read_b64(data);
1461 }
1463 return pixbuf;
1464 }
1466 static GdkPixbuf *
1467 sp_image_repr_read_b64 (const gchar * uri_data)
1468 {
1469 GdkPixbuf * pixbuf = NULL;
1471 static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1473 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
1474 if (loader) {
1475 bool eos = false;
1476 bool failed = false;
1477 const gchar* btr = uri_data;
1478 gchar ud[4];
1479 guchar bd[57];
1481 while (!eos) {
1482 gint ell = 0;
1483 for (gint j = 0; j < 19; j++) {
1484 gint len = 0;
1485 for (gint k = 0; k < 4; k++) {
1486 while (isspace ((int) (*btr))) {
1487 if ((*btr) == '\0') break;
1488 btr++;
1489 }
1490 if (eos) {
1491 ud[k] = 0;
1492 continue;
1493 }
1494 if (((*btr) == '\0') || ((*btr) == '=')) {
1495 eos = true;
1496 ud[k] = 0;
1497 continue;
1498 }
1499 ud[k] = 64;
1500 for (gint b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1501 if (B64[b] == (*btr)) {
1502 ud[k] = (gchar) b;
1503 break;
1504 }
1505 }
1506 if (ud[k] == 64) { /* data corruption ?? */
1507 eos = true;
1508 ud[k] = 0;
1509 continue;
1510 }
1511 btr++;
1512 len++;
1513 }
1514 guint32 bits = (guint32) ud[0];
1515 bits = (bits << 6) | (guint32) ud[1];
1516 bits = (bits << 6) | (guint32) ud[2];
1517 bits = (bits << 6) | (guint32) ud[3];
1518 bd[ell++] = (guchar) ((bits & 0xff0000) >> 16);
1519 if (len > 2) {
1520 bd[ell++] = (guchar) ((bits & 0xff00) >> 8);
1521 }
1522 if (len > 3) {
1523 bd[ell++] = (guchar) (bits & 0xff);
1524 }
1525 }
1527 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) ell, NULL)) {
1528 failed = true;
1529 break;
1530 }
1531 }
1533 gdk_pixbuf_loader_close (loader, NULL);
1535 if (!failed) {
1536 pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1537 }
1538 }
1540 return pixbuf;
1541 }
1543 static void
1544 sp_image_set_curve(SPImage *image)
1545 {
1546 //create a curve at the image's boundary for snapping
1547 if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) {
1548 if (image->curve) {
1549 image->curve = image->curve->unref();
1550 }
1551 } else {
1552 NRRect rect;
1553 sp_image_bbox(image, &rect, Geom::identity(), 0);
1554 Geom::Rect rect2 = to_2geom(*rect.upgrade());
1555 SPCurve *c = SPCurve::new_from_rect(rect2, true);
1557 if (image->curve) {
1558 image->curve = image->curve->unref();
1559 }
1561 if (c) {
1562 image->curve = c->ref();
1564 c->unref();
1565 }
1566 }
1567 }
1569 /**
1570 * Return duplicate of curve (if any exists) or NULL if there is no curve
1571 */
1572 SPCurve *
1573 sp_image_get_curve (SPImage *image)
1574 {
1575 SPCurve *result = 0;
1576 if (image->curve) {
1577 result = image->curve->copy();
1578 }
1579 return result;
1580 }
1582 void
1583 sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb, Glib::ustring const &mime_in)
1584 {
1585 Glib::ustring format, mime;
1586 if (mime_in == "image/jpeg") {
1587 mime = mime_in;
1588 format = "jpeg";
1589 } else {
1590 mime = "image/png";
1591 format = "png";
1592 }
1594 gchar *data;
1595 gsize length;
1596 gdk_pixbuf_save_to_buffer(pb, &data, &length, format.data(), NULL, NULL);
1598 // Save base64 encoded data in image node
1599 // this formula taken from Glib docs
1600 guint needed_size = length * 4 / 3 + length * 4 / (3 * 72) + 7;
1601 needed_size += 5 + 8 + mime.size(); // 5 bytes for data:, 8 for ;base64,
1603 gchar *buffer = (gchar *) g_malloc(needed_size), *buf_work = buffer;
1604 buf_work += g_sprintf(buffer, "data:%s;base64,", mime.data());
1606 gint state = 0, save = 0;
1607 gsize written = 0;
1608 written += g_base64_encode_step((guchar*) data, length, TRUE, buf_work, &state, &save);
1609 written += g_base64_encode_close(TRUE, buf_work + written, &state, &save);
1610 buf_work[written] = 0; // null terminate
1612 image_node->setAttribute("xlink:href", buffer);
1613 g_free(buffer);
1614 }
1616 void sp_image_refresh_if_outdated( SPImage* image )
1617 {
1618 if ( image->href && image->lastMod ) {
1619 // It *might* change
1621 struct stat st;
1622 memset(&st, 0, sizeof(st));
1623 int val = g_stat(image->pixPath, &st);
1624 if ( !val ) {
1625 // stat call worked. Check time now
1626 if ( st.st_mtime != image->lastMod ) {
1627 SPCtx *ctx = 0;
1628 unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
1629 sp_image_update(image, ctx, flags);
1630 }
1631 }
1632 }
1633 }
1635 /*
1636 Local Variables:
1637 mode:c++
1638 c-file-style:"stroustrup"
1639 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1640 indent-tabs-mode:nil
1641 fill-column:99
1642 End:
1643 */
1644 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :