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