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