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