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 }
688 }
690 static void
691 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
692 {
693 SPImage *image;
695 image = (SPImage *) object;
696 SPDocument *doc = SP_OBJECT_DOCUMENT(object);
698 if (((SPObjectClass *) (parent_class))->update)
699 ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
701 if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
702 if (image->pixbuf) {
703 gdk_pixbuf_unref (image->pixbuf);
704 image->pixbuf = NULL;
705 }
706 if (image->href) {
707 GdkPixbuf *pixbuf;
708 pixbuf = sp_image_repr_read_image (
709 object->repr->attribute("xlink:href"),
710 object->repr->attribute("sodipodi:absref"),
711 doc->base);
712 if (pixbuf) {
713 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
714 // BLIP
715 #if ENABLE_LCMS
716 if ( image->color_profile )
717 {
718 int imagewidth = gdk_pixbuf_get_width( pixbuf );
719 int imageheight = gdk_pixbuf_get_height( pixbuf );
720 int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
721 guchar* px = gdk_pixbuf_get_pixels( pixbuf );
723 if ( px ) {
724 #ifdef DEBUG_LCMS
725 DEBUG_MESSAGE( lcmsFive, "in <image>'s sp_image_update. About to call colorprofile_get_handle()" );
726 #endif // DEBUG_LCMS
727 guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
728 cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
729 &profIntent,
730 image->color_profile );
731 if ( prof ) {
732 icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
733 if ( profileClass != icSigNamedColorClass ) {
734 int intent = INTENT_PERCEPTUAL;
735 switch ( profIntent ) {
736 case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
737 intent = INTENT_RELATIVE_COLORIMETRIC;
738 break;
739 case Inkscape::RENDERING_INTENT_SATURATION:
740 intent = INTENT_SATURATION;
741 break;
742 case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
743 intent = INTENT_ABSOLUTE_COLORIMETRIC;
744 break;
745 case Inkscape::RENDERING_INTENT_PERCEPTUAL:
746 case Inkscape::RENDERING_INTENT_UNKNOWN:
747 case Inkscape::RENDERING_INTENT_AUTO:
748 default:
749 intent = INTENT_PERCEPTUAL;
750 }
751 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
752 cmsHTRANSFORM transf = cmsCreateTransform( prof,
753 TYPE_RGBA_8,
754 destProf,
755 TYPE_RGBA_8,
756 intent, 0 );
757 if ( transf ) {
758 guchar* currLine = px;
759 for ( int y = 0; y < imageheight; y++ ) {
760 // Since the types are the same size, we can do the transformation in-place
761 cmsDoTransform( transf, currLine, currLine, imagewidth );
762 currLine += rowstride;
763 }
765 cmsDeleteTransform( transf );
766 }
767 #ifdef DEBUG_LCMS
768 else
769 {
770 DEBUG_MESSAGE( lcmsSix, "in <image>'s sp_image_update. Unable to create LCMS transform." );
771 }
772 #endif // DEBUG_LCMS
773 cmsCloseProfile( destProf );
774 }
775 #ifdef DEBUG_LCMS
776 else
777 {
778 DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
779 }
780 #endif // DEBUG_LCMS
781 }
782 #ifdef DEBUG_LCMS
783 else
784 {
785 DEBUG_MESSAGE( lcmsEight, "in <image>'s sp_image_update. No profile found." );
786 }
787 #endif // DEBUG_LCMS
788 }
789 }
790 #endif // ENABLE_LCMS
791 image->pixbuf = pixbuf;
792 }
793 }
794 }
795 // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
796 if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
797 int imagewidth, imageheight;
798 double x,y;
800 imagewidth = gdk_pixbuf_get_width (image->pixbuf);
801 imageheight = gdk_pixbuf_get_height (image->pixbuf);
803 switch (image->aspect_align) {
804 case SP_ASPECT_XMIN_YMIN:
805 x = 0.0;
806 y = 0.0;
807 break;
808 case SP_ASPECT_XMID_YMIN:
809 x = 0.5;
810 y = 0.0;
811 break;
812 case SP_ASPECT_XMAX_YMIN:
813 x = 1.0;
814 y = 0.0;
815 break;
816 case SP_ASPECT_XMIN_YMID:
817 x = 0.0;
818 y = 0.5;
819 break;
820 case SP_ASPECT_XMID_YMID:
821 x = 0.5;
822 y = 0.5;
823 break;
824 case SP_ASPECT_XMAX_YMID:
825 x = 1.0;
826 y = 0.5;
827 break;
828 case SP_ASPECT_XMIN_YMAX:
829 x = 0.0;
830 y = 1.0;
831 break;
832 case SP_ASPECT_XMID_YMAX:
833 x = 0.5;
834 y = 1.0;
835 break;
836 case SP_ASPECT_XMAX_YMAX:
837 x = 1.0;
838 y = 1.0;
839 break;
840 default:
841 x = 0.0;
842 y = 0.0;
843 break;
844 }
846 if (image->aspect_clip == SP_ASPECT_SLICE) {
847 image->viewx = image->x.computed;
848 image->viewy = image->y.computed;
849 image->viewwidth = image->width.computed;
850 image->viewheight = image->height.computed;
851 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
852 // Pixels aspect is wider than bounding box
853 image->trimheight = imageheight;
854 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
855 image->trimy = 0;
856 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
857 } else {
858 // Pixels aspect is taller than bounding box
859 image->trimwidth = imagewidth;
860 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
861 image->trimx = 0;
862 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
863 }
864 } else {
865 // Otherwise, assume SP_ASPECT_MEET
866 image->trimx = 0;
867 image->trimy = 0;
868 image->trimwidth = imagewidth;
869 image->trimheight = imageheight;
870 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
871 // Pixels aspect is wider than bounding boz
872 image->viewwidth = image->width.computed;
873 image->viewheight = image->viewwidth * imageheight / imagewidth;
874 image->viewx=image->x.computed;
875 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
876 } else {
877 // Pixels aspect is taller than bounding box
878 image->viewheight = image->height.computed;
879 image->viewwidth = image->viewheight * imagewidth / imageheight;
880 image->viewy=image->y.computed;
881 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
882 }
883 }
884 }
885 sp_image_set_curve((SPImage *) object); //creates a curve at the image's boundary for snapping
886 sp_image_update_canvas_image ((SPImage *) object);
887 }
889 static Inkscape::XML::Node *
890 sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
891 {
892 SPImage *image;
894 image = SP_IMAGE (object);
896 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
897 Inkscape::XML::Document *xml_doc = sp_document_repr_doc(SP_OBJECT_DOCUMENT(object));
898 repr = xml_doc->createElement("svg:image");
899 }
901 repr->setAttribute("xlink:href", image->href);
902 /* fixme: Reset attribute if needed (Lauris) */
903 if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed);
904 if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed);
905 if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed);
906 if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed);
907 repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
908 #if ENABLE_LCMS
909 if (image->color_profile) repr->setAttribute("color-profile", image->color_profile);
910 #endif // ENABLE_LCMS
912 if (((SPObjectClass *) (parent_class))->write)
913 ((SPObjectClass *) (parent_class))->write (object, repr, flags);
915 return repr;
916 }
918 static void
919 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags)
920 {
921 SPImage const &image = *SP_IMAGE(item);
923 if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
924 double const x0 = image.x.computed;
925 double const y0 = image.y.computed;
926 double const x1 = x0 + image.width.computed;
927 double const y1 = y0 + image.height.computed;
929 nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform);
930 nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform);
931 nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform);
932 nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform);
933 }
934 }
936 static void
937 sp_image_print (SPItem *item, SPPrintContext *ctx)
938 {
939 SPImage *image;
940 NRMatrix tp, ti, s, t;
941 guchar *px;
942 int w, h, rs, pixskip;
944 image = SP_IMAGE (item);
946 if (!image->pixbuf) return;
947 if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return;
949 px = gdk_pixbuf_get_pixels (image->pixbuf);
950 w = gdk_pixbuf_get_width (image->pixbuf);
951 h = gdk_pixbuf_get_height (image->pixbuf);
952 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
953 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
955 if (image->aspect_align == SP_ASPECT_NONE) {
956 /* fixme: (Lauris) */
957 nr_matrix_set_translate (&tp, image->x.computed, image->y.computed);
958 nr_matrix_set_scale (&s, image->width.computed, -image->height.computed);
959 nr_matrix_set_translate (&ti, 0.0, -1.0);
960 } else { // preserveAspectRatio
961 nr_matrix_set_translate (&tp, image->viewx, image->viewy);
962 nr_matrix_set_scale (&s, image->viewwidth, -image->viewheight);
963 nr_matrix_set_translate (&ti, 0.0, -1.0);
964 }
966 nr_matrix_multiply (&t, &s, &tp);
967 nr_matrix_multiply (&t, &ti, &t);
969 if (image->aspect_align == SP_ASPECT_NONE)
970 sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
971 else // preserveAspectRatio
972 sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item));
973 }
975 static gchar *
976 sp_image_description(SPItem *item)
977 {
978 SPImage *image = SP_IMAGE(item);
979 char *href_desc;
980 if (image->href) {
981 href_desc = (strncmp(image->href, "data:", 5) == 0)
982 ? g_strdup(_("embedded"))
983 : xml_quote_strdup(image->href);
984 } else {
985 g_warning("Attempting to call strncmp() with a null pointer.");
986 href_desc = g_strdup("(null_pointer)"); // we call g_free() on href_desc
987 }
989 char *ret = ( image->pixbuf == NULL
990 ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
991 : g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
992 gdk_pixbuf_get_width(image->pixbuf),
993 gdk_pixbuf_get_height(image->pixbuf),
994 href_desc) );
995 g_free(href_desc);
996 return ret;
997 }
999 static NRArenaItem *
1000 sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags)
1001 {
1002 int pixskip, rs;
1003 SPImage * image;
1004 NRArenaItem *ai;
1006 image = (SPImage *) item;
1008 ai = NRArenaImage::create(arena);
1010 if (image->pixbuf) {
1011 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1012 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1013 nr_arena_image_set_style(NR_ARENA_IMAGE(ai), SP_OBJECT_STYLE(SP_OBJECT(item)));
1014 if (image->aspect_align == SP_ASPECT_NONE)
1015 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1016 gdk_pixbuf_get_pixels (image->pixbuf),
1017 gdk_pixbuf_get_width (image->pixbuf),
1018 gdk_pixbuf_get_height (image->pixbuf),
1019 rs);
1020 else // preserveAspectRatio
1021 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
1022 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1023 image->trimwidth,
1024 image->trimheight,
1025 rs);
1026 } else {
1027 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0);
1028 }
1029 if (image->aspect_align == SP_ASPECT_NONE)
1030 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
1031 else // preserveAspectRatio
1032 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
1034 return ai;
1035 }
1037 /*
1038 * utility function to try loading image from href
1039 *
1040 * docbase/relative_src
1041 * absolute_src
1042 *
1043 */
1045 GdkPixbuf *
1046 sp_image_repr_read_image (const gchar *href, const gchar *absref, const gchar *base)
1047 {
1048 const gchar *filename, *docbase;
1049 gchar *fullname;
1050 GdkPixbuf *pixbuf;
1052 filename = href;
1053 if (filename != NULL) {
1054 if (strncmp (filename,"file:",5) == 0) {
1055 fullname = g_filename_from_uri(filename, NULL, NULL);
1056 if (fullname) {
1057 // TODO check this. Was doing a UTF-8 to filename conversion here.
1058 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, NULL);
1059 if (pixbuf != NULL) return pixbuf;
1060 }
1061 } else if (strncmp (filename,"data:",5) == 0) {
1062 /* data URI - embedded image */
1063 filename += 5;
1064 pixbuf = sp_image_repr_read_dataURI (filename);
1065 if (pixbuf != NULL) return pixbuf;
1066 } else {
1068 if (!g_path_is_absolute (filename)) {
1069 /* try to load from relative pos combined with document base*/
1070 docbase = base;
1071 if (!docbase) docbase = ".";
1072 fullname = g_build_filename(docbase, filename, NULL);
1074 // document base can be wrong (on the temporary doc when importing bitmap from a
1075 // different dir) or unset (when doc is not saved yet), so we check for base+href existence first,
1076 // and if it fails, we also try to use bare href regardless of its g_path_is_absolute
1077 if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) {
1078 pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, NULL );
1079 g_free (fullname);
1080 if (pixbuf != NULL) return pixbuf;
1081 }
1082 }
1084 /* try filename as absolute */
1085 if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) {
1086 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1087 if (pixbuf != NULL) return pixbuf;
1088 }
1089 }
1090 }
1092 /* at last try to load from sp absolute path name */
1093 filename = absref;
1094 if (filename != NULL) {
1095 // using absref is outside of SVG rules, so we must at least warn the user
1096 if ( base != NULL && href != NULL )
1097 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);
1098 else
1099 g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref);
1101 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1102 if (pixbuf != NULL) return pixbuf;
1103 }
1104 /* Nope: We do not find any valid pixmap file :-( */
1105 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1107 /* It should be included xpm, so if it still does not does load, */
1108 /* our libraries are broken */
1109 g_assert (pixbuf != NULL);
1111 return pixbuf;
1112 }
1114 static GdkPixbuf *
1115 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1116 {
1117 GdkPixbuf * newbuf;
1119 if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf;
1121 newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
1122 gdk_pixbuf_unref (pixbuf);
1124 return newbuf;
1125 }
1127 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1129 static void
1130 sp_image_update_canvas_image (SPImage *image)
1131 {
1132 int rs, pixskip;
1133 SPItem *item;
1134 SPItemView *v;
1136 item = SP_ITEM (image);
1138 if (image->pixbuf) {
1139 /* fixme: We are slightly violating spec here (Lauris) */
1140 if (!image->width._set) {
1141 image->width.computed = gdk_pixbuf_get_width (image->pixbuf);
1142 }
1143 if (!image->height._set) {
1144 image->height.computed = gdk_pixbuf_get_height (image->pixbuf);
1145 }
1146 }
1148 for (v = item->display; v != NULL; v = v->next) {
1149 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1150 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1151 nr_arena_image_set_style (NR_ARENA_IMAGE(v->arenaitem), SP_OBJECT_STYLE(SP_OBJECT(image)));
1152 if (image->aspect_align == SP_ASPECT_NONE) {
1153 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1154 gdk_pixbuf_get_pixels (image->pixbuf),
1155 gdk_pixbuf_get_width (image->pixbuf),
1156 gdk_pixbuf_get_height (image->pixbuf),
1157 rs);
1158 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1159 image->x.computed, image->y.computed,
1160 image->width.computed, image->height.computed);
1161 } else { // preserveAspectRatio
1162 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1163 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1164 image->trimwidth,
1165 image->trimheight,
1166 rs);
1167 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1168 image->viewx, image->viewy,
1169 image->viewwidth, image->viewheight);
1170 }
1171 }
1172 }
1174 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1175 {
1176 /* An image doesn't have any nodes to snap, but still we want to be able snap one image
1177 to another. Therefore we will create some snappoints at the corner, similar to a rect. If
1178 the image is rotated, then the snappoints will rotate with it. Again, just like a rect.
1179 */
1181 g_assert(item != NULL);
1182 g_assert(SP_IS_IMAGE(item));
1184 if (item->clip_ref->getObject()) {
1185 //We are looking at a clipped image: do not return any snappoints, as these might be
1186 //far far away from the visible part from the clipped image
1187 } else {
1188 // The image has not been clipped: return its corners, which might be rotated for example
1189 SPImage &image = *SP_IMAGE(item);
1190 double const x0 = image.x.computed;
1191 double const y0 = image.y.computed;
1192 double const x1 = x0 + image.width.computed;
1193 double const y1 = y0 + image.height.computed;
1194 NR::Matrix const i2d (sp_item_i2d_affine (item));
1195 *p = NR::Point(x0, y0) * i2d;
1196 *p = NR::Point(x0, y1) * i2d;
1197 *p = NR::Point(x1, y1) * i2d;
1198 *p = NR::Point(x1, y0) * i2d;
1199 }
1200 }
1202 /*
1203 * Initially we'll do:
1204 * Transform x, y, set x, y, clear translation
1205 */
1207 static NR::Matrix
1208 sp_image_set_transform(SPItem *item, NR::Matrix const &xform)
1209 {
1210 SPImage *image = SP_IMAGE(item);
1212 /* Calculate position in parent coords. */
1213 NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform );
1215 /* This function takes care of translation and scaling, we return whatever parts we can't
1216 handle. */
1217 NR::Matrix ret(NR::transform(xform));
1218 NR::Point const scale(hypot(ret[0], ret[1]),
1219 hypot(ret[2], ret[3]));
1220 if ( scale[NR::X] > 1e-9 ) {
1221 ret[0] /= scale[NR::X];
1222 ret[1] /= scale[NR::X];
1223 } else {
1224 ret[0] = 1.0;
1225 ret[1] = 0.0;
1226 }
1227 if ( scale[NR::Y] > 1e-9 ) {
1228 ret[2] /= scale[NR::Y];
1229 ret[3] /= scale[NR::Y];
1230 } else {
1231 ret[2] = 0.0;
1232 ret[3] = 1.0;
1233 }
1235 image->width = image->width.computed * scale[NR::X];
1236 image->height = image->height.computed * scale[NR::Y];
1238 /* Find position in item coords */
1239 pos = pos * ret.inverse();
1240 image->x = pos[NR::X];
1241 image->y = pos[NR::Y];
1243 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1245 return ret;
1246 }
1248 static GdkPixbuf *
1249 sp_image_repr_read_dataURI (const gchar * uri_data)
1250 { GdkPixbuf * pixbuf = NULL;
1252 gint data_is_image = 0;
1253 gint data_is_base64 = 0;
1255 const gchar * data = uri_data;
1257 while (*data) {
1258 if (strncmp (data,"base64",6) == 0) {
1259 /* base64-encoding */
1260 data_is_base64 = 1;
1261 data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1262 data += 6;
1263 }
1264 else if (strncmp (data,"image/png",9) == 0) {
1265 /* PNG image */
1266 data_is_image = 1;
1267 data += 9;
1268 }
1269 else if (strncmp (data,"image/jpg",9) == 0) {
1270 /* JPEG image */
1271 data_is_image = 1;
1272 data += 9;
1273 }
1274 else if (strncmp (data,"image/jpeg",10) == 0) {
1275 /* JPEG image */
1276 data_is_image = 1;
1277 data += 10;
1278 }
1279 else { /* unrecognized option; skip it */
1280 while (*data) {
1281 if (((*data) == ';') || ((*data) == ',')) break;
1282 data++;
1283 }
1284 }
1285 if ((*data) == ';') {
1286 data++;
1287 continue;
1288 }
1289 if ((*data) == ',') {
1290 data++;
1291 break;
1292 }
1293 }
1295 if ((*data) && data_is_image && data_is_base64) {
1296 pixbuf = sp_image_repr_read_b64 (data);
1297 }
1299 return pixbuf;
1300 }
1302 static GdkPixbuf *
1303 sp_image_repr_read_b64 (const gchar * uri_data)
1304 { GdkPixbuf * pixbuf = NULL;
1305 GdkPixbufLoader * loader = NULL;
1307 gint j;
1308 gint k;
1309 gint l;
1310 gint b;
1311 gint len;
1312 gint eos = 0;
1313 gint failed = 0;
1315 guint32 bits;
1317 static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1319 const gchar* btr = uri_data;
1321 gchar ud[4];
1323 guchar bd[57];
1325 loader = gdk_pixbuf_loader_new ();
1327 if (loader == NULL) return NULL;
1329 while (eos == 0) {
1330 l = 0;
1331 for (j = 0; j < 19; j++) {
1332 len = 0;
1333 for (k = 0; k < 4; k++) {
1334 while (isspace ((int) (*btr))) {
1335 if ((*btr) == '\0') break;
1336 btr++;
1337 }
1338 if (eos) {
1339 ud[k] = 0;
1340 continue;
1341 }
1342 if (((*btr) == '\0') || ((*btr) == '=')) {
1343 eos = 1;
1344 ud[k] = 0;
1345 continue;
1346 }
1347 ud[k] = 64;
1348 for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1349 if (B64[b] == (*btr)) {
1350 ud[k] = (gchar) b;
1351 break;
1352 }
1353 }
1354 if (ud[k] == 64) { /* data corruption ?? */
1355 eos = 1;
1356 ud[k] = 0;
1357 continue;
1358 }
1359 btr++;
1360 len++;
1361 }
1362 bits = (guint32) ud[0];
1363 bits = (bits << 6) | (guint32) ud[1];
1364 bits = (bits << 6) | (guint32) ud[2];
1365 bits = (bits << 6) | (guint32) ud[3];
1366 bd[l++] = (guchar) ((bits & 0xff0000) >> 16);
1367 if (len > 2) {
1368 bd[l++] = (guchar) ((bits & 0xff00) >> 8);
1369 }
1370 if (len > 3) {
1371 bd[l++] = (guchar) (bits & 0xff);
1372 }
1373 }
1375 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) {
1376 failed = 1;
1377 break;
1378 }
1379 }
1381 gdk_pixbuf_loader_close (loader, NULL);
1383 if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1385 return pixbuf;
1386 }
1388 static void
1389 sp_image_set_curve(SPImage *image)
1390 {
1391 //create a curve at the image's boundary for snapping
1392 if ((image->height.computed < 1e-18) || (image->width.computed < 1e-18) || (image->clip_ref->getObject())) {
1393 if (image->curve) {
1394 image->curve = sp_curve_unref(image->curve);
1395 }
1396 return;
1397 }
1399 SPCurve *c = sp_curve_new();
1401 double const x = image->x.computed;
1402 double const y = image->y.computed;
1403 double const w = image->width.computed;
1404 double const h = image->height.computed;
1406 sp_curve_moveto(c, x, y);
1407 sp_curve_lineto(c, x + w, y);
1408 sp_curve_lineto(c, x + w, y + h);
1409 sp_curve_lineto(c, x, y + h);
1410 sp_curve_lineto(c, x, y);
1412 sp_curve_closepath_current(c);
1414 if (image->curve) {
1415 image->curve = sp_curve_unref(image->curve);
1416 }
1418 if (c) {
1419 image->curve = sp_curve_ref(c);
1420 }
1422 sp_curve_unref(c);
1424 }
1426 /**
1427 * Return duplicate of curve (if any exists) or NULL if there is no curve
1428 */
1429 SPCurve *
1430 sp_image_get_curve (SPImage *image)
1431 {
1432 if (image->curve) {
1433 return sp_curve_copy(image->curve);
1434 }
1435 return NULL;
1436 }
1438 /*
1439 Local Variables:
1440 mode:c++
1441 c-file-style:"stroustrup"
1442 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1443 indent-tabs-mode:nil
1444 fill-column:99
1445 End:
1446 */
1447 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :