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