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