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 #endif // ENABLE_LCMS
43 /*
44 * SPImage
45 */
48 static void sp_image_class_init (SPImageClass * klass);
49 static void sp_image_init (SPImage * image);
51 static void sp_image_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr);
52 static void sp_image_release (SPObject * object);
53 static void sp_image_set (SPObject *object, unsigned int key, const gchar *value);
54 static void sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags);
55 static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags);
57 static void sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags);
58 static void sp_image_print (SPItem * item, SPPrintContext *ctx);
59 static gchar * sp_image_description (SPItem * item);
60 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p);
61 static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags);
62 static NR::Matrix sp_image_set_transform (SPItem *item, NR::Matrix const &xform);
64 GdkPixbuf * sp_image_repr_read_image (Inkscape::XML::Node * repr);
65 static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf);
66 static void sp_image_update_canvas_image (SPImage *image);
67 static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data);
68 static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data);
70 static SPItemClass *parent_class;
73 extern "C"
74 {
75 void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length );
76 void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length );
77 void user_flush_data( png_structp png_ptr );
79 }
81 namespace Inkscape {
82 namespace IO {
84 class PushPull
85 {
86 public:
87 gboolean first;
88 FILE* fp;
89 guchar* scratch;
90 gsize size;
91 gsize used;
92 gsize offset;
93 GdkPixbufLoader *loader;
95 PushPull() : first(TRUE),
96 fp(0),
97 scratch(0),
98 size(0),
99 used(0),
100 offset(0),
101 loader(0) {};
103 gboolean readMore()
104 {
105 gboolean good = FALSE;
106 if ( offset )
107 {
108 g_memmove( scratch, scratch + offset, used - offset );
109 used -= offset;
110 offset = 0;
111 }
112 if ( used < size )
113 {
114 gsize space = size - used;
115 gsize got = fread( scratch + used, 1, space, fp );
116 if ( got )
117 {
118 if ( loader )
119 {
120 GError *err = NULL;
121 //g_message( " __read %d bytes", (int)got );
122 if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) )
123 {
124 //g_message("_error writing pixbuf data");
125 }
126 }
128 used += got;
129 good = TRUE;
130 }
131 else
132 {
133 good = FALSE;
134 }
135 }
136 return good;
137 }
139 gsize available() const
140 {
141 return (used - offset);
142 }
144 gsize readOut( gpointer data, gsize length )
145 {
146 gsize giving = available();
147 if ( length < giving )
148 {
149 giving = length;
150 }
151 g_memmove( data, scratch + offset, giving );
152 offset += giving;
153 if ( offset >= used )
154 {
155 offset = 0;
156 used = 0;
157 }
158 return giving;
159 }
161 void clear()
162 {
163 offset = 0;
164 used = 0;
165 }
167 private:
168 PushPull& operator = (const PushPull& other);
169 PushPull(const PushPull& other);
170 };
172 void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length )
173 {
174 // g_message( "user_read_data(%d)", length );
176 PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr);
178 gsize filled = 0;
179 gboolean canRead = TRUE;
181 while ( filled < length && canRead )
182 {
183 gsize some = youme->readOut( data + filled, length - filled );
184 filled += some;
185 if ( filled < length )
186 {
187 canRead &= youme->readMore();
188 }
189 }
190 // g_message("things out");
191 }
193 void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length )
194 {
195 //g_message( "user_write_data(%d)", length );
196 }
198 void user_flush_data( png_structp png_ptr )
199 {
200 //g_message( "user_flush_data" );
201 }
203 GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error )
204 {
205 GdkPixbuf* buf = NULL;
206 PushPull youme;
207 gint dpiX = 0;
208 gint dpiY = 0;
210 //buf = gdk_pixbuf_new_from_file( filename, error );
211 dump_fopen_call( filename, "pixbuf_new_from_file" );
212 FILE* fp = fopen_utf8name( filename, "r" );
213 if ( fp )
214 {
215 GdkPixbufLoader *loader = gdk_pixbuf_loader_new();
216 if ( loader )
217 {
218 GError *err = NULL;
220 // short buffer
221 guchar scratch[1024];
222 gboolean latter = FALSE;
223 gboolean isPng = FALSE;
224 png_structp pngPtr = NULL;
225 png_infop infoPtr = NULL;
226 //png_infop endPtr = NULL;
228 youme.fp = fp;
229 youme.scratch = scratch;
230 youme.size = sizeof(scratch);
231 youme.used = 0;
232 youme.offset = 0;
233 youme.loader = loader;
235 while ( !feof(fp) )
236 {
237 if ( youme.readMore() )
238 {
239 if ( youme.first )
240 {
241 //g_message( "First data chunk" );
242 youme.first = FALSE;
243 isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() );
244 //g_message( " png? %s", (isPng ? "Yes":"No") );
245 if ( isPng )
246 {
247 pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING,
248 NULL,//(png_voidp)user_error_ptr,
249 NULL,//user_error_fn,
250 NULL//user_warning_fn
251 );
252 if ( pngPtr )
253 {
254 infoPtr = png_create_info_struct( pngPtr );
255 //endPtr = png_create_info_struct( pngPtr );
257 png_set_read_fn( pngPtr, &youme, user_read_data );
258 //g_message( "In" );
260 //png_read_info( pngPtr, infoPtr );
261 png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL );
263 //g_message("out");
265 //png_read_end(pngPtr, endPtr);
267 /*
268 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) )
269 {
270 g_message("pHYs chunk now valid" );
271 }
272 if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) )
273 {
274 g_message("sCAL chunk now valid" );
275 }
276 */
278 png_uint_32 res_x = 0;
279 png_uint_32 res_y = 0;
280 int unit_type = 0;
281 if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) )
282 {
283 // g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type,
284 // (unit_type == 1? "per meter" : "unknown")
285 // );
287 // g_message( " dpi: (%d, %d)",
288 // (int)(0.5 + ((double)res_x)/39.37),
289 // (int)(0.5 + ((double)res_y)/39.37) );
290 if ( unit_type == PNG_RESOLUTION_METER )
291 {
292 // TODO come up with a more accurate DPI setting
293 dpiX = (int)(0.5 + ((double)res_x)/39.37);
294 dpiY = (int)(0.5 + ((double)res_y)/39.37);
295 }
296 }
297 else
298 {
299 // g_message( "pHYs no" );
300 }
302 /*
303 double width = 0;
304 double height = 0;
305 int unit = 0;
306 if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) )
307 {
308 gchar* vals[] = {
309 "unknown", // PNG_SCALE_UNKNOWN
310 "meter", // PNG_SCALE_METER
311 "radian", // PNG_SCALE_RADIAN
312 "last", //
313 NULL
314 };
316 g_message( "sCAL: (%f, %f) %d (%s)",
317 width, height, unit,
318 ((unit >= 0 && unit < 3) ? vals[unit]:"???")
319 );
320 }
321 */
323 // now clean it up.
324 png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr );
325 }
326 else
327 {
328 g_message("Error when creating PNG read struct");
329 }
330 }
331 }
332 else if ( !latter )
333 {
334 latter = TRUE;
335 //g_message(" READing latter");
336 }
337 // Now clear out the buffer so we can read more.
338 // (dumping out unused)
339 youme.clear();
340 }
341 }
343 gboolean ok = gdk_pixbuf_loader_close(loader, &err);
344 if ( ok )
345 {
346 buf = gdk_pixbuf_loader_get_pixbuf( loader );
347 if ( buf )
348 {
349 g_object_ref(buf);
351 if ( dpiX )
352 {
353 gchar *tmp = g_strdup_printf( "%d", dpiX );
354 if ( tmp )
355 {
356 //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp );
357 g_free( tmp );
358 }
359 }
360 if ( dpiY )
361 {
362 gchar *tmp = g_strdup_printf( "%d", dpiY );
363 if ( tmp )
364 {
365 //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp );
366 g_free( tmp );
367 }
368 }
369 }
370 }
371 else
372 {
373 // do something
374 g_message("error loading pixbuf at close");
375 }
377 g_object_unref(loader);
378 }
379 else
380 {
381 g_message("error when creating pixbuf loader");
382 }
383 fclose( fp );
384 fp = NULL;
385 }
386 else
387 {
388 g_warning ("Unable to open linked file: %s", filename);
389 }
391 /*
392 if ( buf )
393 {
394 const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" );
395 if ( bloop )
396 {
397 g_message("DPI X is [%s]", bloop);
398 }
399 bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" );
400 if ( bloop )
401 {
402 g_message("DPI Y is [%s]", bloop);
403 }
404 }
405 */
407 return buf;
408 }
410 }
411 }
413 GType
414 sp_image_get_type (void)
415 {
416 static GType image_type = 0;
417 if (!image_type) {
418 GTypeInfo image_info = {
419 sizeof (SPImageClass),
420 NULL, /* base_init */
421 NULL, /* base_finalize */
422 (GClassInitFunc) sp_image_class_init,
423 NULL, /* class_finalize */
424 NULL, /* class_data */
425 sizeof (SPImage),
426 16, /* n_preallocs */
427 (GInstanceInitFunc) sp_image_init,
428 NULL, /* value_table */
429 };
430 image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0);
431 }
432 return image_type;
433 }
435 static void
436 sp_image_class_init (SPImageClass * klass)
437 {
438 GObjectClass * gobject_class;
439 SPObjectClass * sp_object_class;
440 SPItemClass * item_class;
442 gobject_class = (GObjectClass *) klass;
443 sp_object_class = (SPObjectClass *) klass;
444 item_class = (SPItemClass *) klass;
446 parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ());
448 sp_object_class->build = sp_image_build;
449 sp_object_class->release = sp_image_release;
450 sp_object_class->set = sp_image_set;
451 sp_object_class->update = sp_image_update;
452 sp_object_class->write = sp_image_write;
454 item_class->bbox = sp_image_bbox;
455 item_class->print = sp_image_print;
456 item_class->description = sp_image_description;
457 item_class->show = sp_image_show;
458 item_class->snappoints = sp_image_snappoints;
459 item_class->set_transform = sp_image_set_transform;
460 }
462 static void
463 sp_image_init (SPImage *image)
464 {
465 image->x.unset();
466 image->y.unset();
467 image->width.unset();
468 image->height.unset();
469 image->aspect_align = SP_ASPECT_NONE;
470 }
472 static void
473 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
474 {
475 if (((SPObjectClass *) parent_class)->build)
476 ((SPObjectClass *) parent_class)->build (object, document, repr);
478 sp_object_read_attr (object, "xlink:href");
479 sp_object_read_attr (object, "x");
480 sp_object_read_attr (object, "y");
481 sp_object_read_attr (object, "width");
482 sp_object_read_attr (object, "height");
483 sp_object_read_attr (object, "preserveAspectRatio");
484 sp_object_read_attr (object, "color-profile");
486 /* Register */
487 sp_document_add_resource (document, "image", object);
488 }
490 static void
491 sp_image_release (SPObject *object)
492 {
493 SPImage *image;
495 image = SP_IMAGE (object);
497 if (SP_OBJECT_DOCUMENT (object)) {
498 /* Unregister ourselves */
499 sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object));
500 }
502 if (image->href) {
503 g_free (image->href);
504 image->href = NULL;
505 }
507 if (image->pixbuf) {
508 gdk_pixbuf_unref (image->pixbuf);
509 image->pixbuf = NULL;
510 }
512 #if ENABLE_LCMS
513 if (image->color_profile) {
514 g_free (image->color_profile);
515 image->color_profile = NULL;
516 }
517 #endif // ENABLE_LCMS
519 if (((SPObjectClass *) parent_class)->release)
520 ((SPObjectClass *) parent_class)->release (object);
521 }
523 static void
524 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
525 {
526 SPImage *image;
528 image = SP_IMAGE (object);
530 switch (key) {
531 case SP_ATTR_XLINK_HREF:
532 g_free (image->href);
533 image->href = (value) ? g_strdup (value) : NULL;
534 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
535 break;
536 case SP_ATTR_X:
537 if (!image->x.readAbsolute(value)) {
538 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
539 image->x.unset();
540 }
541 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
542 break;
543 case SP_ATTR_Y:
544 if (!image->y.readAbsolute(value)) {
545 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
546 image->y.unset();
547 }
548 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
549 break;
550 case SP_ATTR_WIDTH:
551 if (!image->width.readAbsolute(value)) {
552 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
553 image->width.unset();
554 }
555 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
556 break;
557 case SP_ATTR_HEIGHT:
558 if (!image->height.readAbsolute(value)) {
559 /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
560 image->height.unset();
561 }
562 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
563 break;
564 case SP_ATTR_PRESERVEASPECTRATIO:
565 /* Do setup before, so we can use break to escape */
566 image->aspect_align = SP_ASPECT_NONE;
567 image->aspect_clip = SP_ASPECT_MEET;
568 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG);
569 if (value) {
570 int len;
571 gchar c[256];
572 const gchar *p, *e;
573 unsigned int align, clip;
574 p = value;
575 while (*p && *p == 32) p += 1;
576 if (!*p) break;
577 e = p;
578 while (*e && *e != 32) e += 1;
579 len = e - p;
580 if (len > 8) break;
581 memcpy (c, value, len);
582 c[len] = 0;
583 /* Now the actual part */
584 if (!strcmp (c, "none")) {
585 align = SP_ASPECT_NONE;
586 } else if (!strcmp (c, "xMinYMin")) {
587 align = SP_ASPECT_XMIN_YMIN;
588 } else if (!strcmp (c, "xMidYMin")) {
589 align = SP_ASPECT_XMID_YMIN;
590 } else if (!strcmp (c, "xMaxYMin")) {
591 align = SP_ASPECT_XMAX_YMIN;
592 } else if (!strcmp (c, "xMinYMid")) {
593 align = SP_ASPECT_XMIN_YMID;
594 } else if (!strcmp (c, "xMidYMid")) {
595 align = SP_ASPECT_XMID_YMID;
596 } else if (!strcmp (c, "xMaxYMid")) {
597 align = SP_ASPECT_XMAX_YMID;
598 } else if (!strcmp (c, "xMinYMax")) {
599 align = SP_ASPECT_XMIN_YMAX;
600 } else if (!strcmp (c, "xMidYMax")) {
601 align = SP_ASPECT_XMID_YMAX;
602 } else if (!strcmp (c, "xMaxYMax")) {
603 align = SP_ASPECT_XMAX_YMAX;
604 } else {
605 break;
606 }
607 clip = SP_ASPECT_MEET;
608 while (*e && *e == 32) e += 1;
609 if (e) {
610 if (!strcmp (e, "meet")) {
611 clip = SP_ASPECT_MEET;
612 } else if (!strcmp (e, "slice")) {
613 clip = SP_ASPECT_SLICE;
614 } else {
615 break;
616 }
617 }
618 image->aspect_align = align;
619 image->aspect_clip = clip;
620 }
621 break;
622 #if ENABLE_LCMS
623 case SP_PROP_COLOR_PROFILE:
624 if ( image->color_profile ) {
625 g_free (image->color_profile);
626 }
627 image->color_profile = (value) ? g_strdup (value) : NULL;
628 // TODO check on this HREF_MODIFIED flag
629 object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG);
630 break;
631 #endif // ENABLE_LCMS
632 default:
633 if (((SPObjectClass *) (parent_class))->set)
634 ((SPObjectClass *) (parent_class))->set (object, key, value);
635 break;
636 }
637 }
639 static void
640 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
641 {
642 SPImage *image;
644 image = (SPImage *) object;
646 if (((SPObjectClass *) (parent_class))->update)
647 ((SPObjectClass *) (parent_class))->update (object, ctx, flags);
649 if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
650 if (image->pixbuf) {
651 gdk_pixbuf_unref (image->pixbuf);
652 image->pixbuf = NULL;
653 }
654 if (image->href) {
655 GdkPixbuf *pixbuf;
656 pixbuf = sp_image_repr_read_image (object->repr);
657 if (pixbuf) {
658 pixbuf = sp_image_pixbuf_force_rgba (pixbuf);
659 // BLIP
660 #if ENABLE_LCMS
661 if ( image->color_profile )
662 {
663 int imagewidth = gdk_pixbuf_get_width( pixbuf );
664 int imageheight = gdk_pixbuf_get_height( pixbuf );
665 int rowstride = gdk_pixbuf_get_rowstride( pixbuf );
666 guchar* px = gdk_pixbuf_get_pixels( pixbuf );
668 if ( px ) {
669 guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN;
670 cmsHPROFILE prof = Inkscape::colorprofile_get_handle( SP_OBJECT_DOCUMENT( object ),
671 &profIntent,
672 image->color_profile );
673 if ( prof ) {
674 icProfileClassSignature profileClass = cmsGetDeviceClass( prof );
675 if ( profileClass != icSigNamedColorClass ) {
676 int intent = INTENT_PERCEPTUAL;
677 switch ( profIntent ) {
678 case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC:
679 intent = INTENT_RELATIVE_COLORIMETRIC;
680 break;
681 case Inkscape::RENDERING_INTENT_SATURATION:
682 intent = INTENT_SATURATION;
683 break;
684 case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC:
685 intent = INTENT_ABSOLUTE_COLORIMETRIC;
686 break;
687 case Inkscape::RENDERING_INTENT_PERCEPTUAL:
688 case Inkscape::RENDERING_INTENT_UNKNOWN:
689 case Inkscape::RENDERING_INTENT_AUTO:
690 default:
691 intent = INTENT_PERCEPTUAL;
692 }
693 cmsHPROFILE destProf = cmsCreate_sRGBProfile();
694 cmsHTRANSFORM transf = cmsCreateTransform( prof,
695 TYPE_RGBA_8,
696 destProf,
697 TYPE_RGBA_8,
698 intent, 0 );
699 if ( transf ) {
700 guchar* currLine = px;
701 for ( int y = 0; y < imageheight; y++ ) {
702 // Since the types are the same size, we can do the transformation in-place
703 cmsDoTransform( transf, currLine, currLine, imagewidth );
704 currLine += rowstride;
705 }
707 cmsDeleteTransform( transf );
708 }
709 cmsCloseProfile( destProf );
710 }
711 }
712 }
713 }
714 #endif // ENABLE_LCMS
715 image->pixbuf = pixbuf;
716 }
717 }
718 }
719 // preserveAspectRatio calculate bounds / clipping rectangle -- EAF
720 if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) {
721 int imagewidth, imageheight;
722 double x,y;
724 imagewidth = gdk_pixbuf_get_width (image->pixbuf);
725 imageheight = gdk_pixbuf_get_height (image->pixbuf);
727 switch (image->aspect_align) {
728 case SP_ASPECT_XMIN_YMIN:
729 x = 0.0;
730 y = 0.0;
731 break;
732 case SP_ASPECT_XMID_YMIN:
733 x = 0.5;
734 y = 0.0;
735 break;
736 case SP_ASPECT_XMAX_YMIN:
737 x = 1.0;
738 y = 0.0;
739 break;
740 case SP_ASPECT_XMIN_YMID:
741 x = 0.0;
742 y = 0.5;
743 break;
744 case SP_ASPECT_XMID_YMID:
745 x = 0.5;
746 y = 0.5;
747 break;
748 case SP_ASPECT_XMAX_YMID:
749 x = 1.0;
750 y = 0.5;
751 break;
752 case SP_ASPECT_XMIN_YMAX:
753 x = 0.0;
754 y = 1.0;
755 break;
756 case SP_ASPECT_XMID_YMAX:
757 x = 0.5;
758 y = 1.0;
759 break;
760 case SP_ASPECT_XMAX_YMAX:
761 x = 1.0;
762 y = 1.0;
763 break;
764 default:
765 x = 0.0;
766 y = 0.0;
767 break;
768 }
770 if (image->aspect_clip == SP_ASPECT_SLICE) {
771 image->viewx = image->x.computed;
772 image->viewy = image->y.computed;
773 image->viewwidth = image->width.computed;
774 image->viewheight = image->height.computed;
775 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
776 // Pixels aspect is wider than bounding box
777 image->trimheight = imageheight;
778 image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed);
779 image->trimy = 0;
780 image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x);
781 } else {
782 // Pixels aspect is taller than bounding box
783 image->trimwidth = imagewidth;
784 image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed);
785 image->trimx = 0;
786 image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y);
787 }
788 } else {
789 // Otherwise, assume SP_ASPECT_MEET
790 image->trimx = 0;
791 image->trimy = 0;
792 image->trimwidth = imagewidth;
793 image->trimheight = imageheight;
794 if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) {
795 // Pixels aspect is wider than bounding boz
796 image->viewwidth = image->width.computed;
797 image->viewheight = image->viewwidth * imageheight / imagewidth;
798 image->viewx=image->x.computed;
799 image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed;
800 } else {
801 // Pixels aspect is taller than bounding box
802 image->viewheight = image->height.computed;
803 image->viewwidth = image->viewheight * imagewidth / imageheight;
804 image->viewy=image->y.computed;
805 image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed;
806 }
807 }
808 }
810 sp_image_update_canvas_image ((SPImage *) object);
811 }
813 static Inkscape::XML::Node *
814 sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
815 {
816 SPImage *image;
818 image = SP_IMAGE (object);
820 if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) {
821 repr = sp_repr_new ("svg:image");
822 }
824 repr->setAttribute("xlink:href", image->href);
825 /* fixme: Reset attribute if needed (Lauris) */
826 if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed);
827 if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed);
828 if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed);
829 if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed);
830 repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio"));
831 #if ENABLE_LCMS
832 if (image->color_profile) repr->setAttribute("color-profile", image->color_profile);
833 #endif // ENABLE_LCMS
835 if (((SPObjectClass *) (parent_class))->write)
836 ((SPObjectClass *) (parent_class))->write (object, repr, flags);
838 return repr;
839 }
841 static void
842 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags)
843 {
844 SPImage const &image = *SP_IMAGE(item);
846 if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) {
847 double const x0 = image.x.computed;
848 double const y0 = image.y.computed;
849 double const x1 = x0 + image.width.computed;
850 double const y1 = y0 + image.height.computed;
852 nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform);
853 nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform);
854 nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform);
855 nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform);
856 }
857 }
859 static void
860 sp_image_print (SPItem *item, SPPrintContext *ctx)
861 {
862 SPImage *image;
863 NRMatrix tp, ti, s, t;
864 guchar *px;
865 int w, h, rs, pixskip;
867 image = SP_IMAGE (item);
869 if (!image->pixbuf) return;
870 if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return;
872 px = gdk_pixbuf_get_pixels (image->pixbuf);
873 w = gdk_pixbuf_get_width (image->pixbuf);
874 h = gdk_pixbuf_get_height (image->pixbuf);
875 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
876 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
878 if (image->aspect_align == SP_ASPECT_NONE) {
879 /* fixme: (Lauris) */
880 nr_matrix_set_translate (&tp, image->x.computed, image->y.computed);
881 nr_matrix_set_scale (&s, image->width.computed, -image->height.computed);
882 nr_matrix_set_translate (&ti, 0.0, -1.0);
883 } else { // preserveAspectRatio
884 nr_matrix_set_translate (&tp, image->viewx, image->viewy);
885 nr_matrix_set_scale (&s, image->viewwidth, -image->viewheight);
886 nr_matrix_set_translate (&ti, 0.0, -1.0);
887 }
889 nr_matrix_multiply (&t, &s, &tp);
890 nr_matrix_multiply (&t, &ti, &t);
892 if (image->aspect_align == SP_ASPECT_NONE)
893 sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item));
894 else // preserveAspectRatio
895 sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item));
896 }
898 static gchar *
899 sp_image_description(SPItem *item)
900 {
901 SPImage *image = SP_IMAGE(item);
902 char *href_desc;
903 if (image->href) {
904 href_desc = (strncmp(image->href, "data:", 5) == 0)
905 ? g_strdup(_("embedded"))
906 : xml_quote_strdup(image->href);
907 } else {
908 g_warning("Attempting to call strncmp() with a null pointer.");
909 href_desc = g_strdup(_("(null_pointer)")); // we call g_free() on href_desc
910 }
912 char *ret = ( image->pixbuf == NULL
913 ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc)
914 : g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
915 gdk_pixbuf_get_width(image->pixbuf),
916 gdk_pixbuf_get_height(image->pixbuf),
917 href_desc) );
918 g_free(href_desc);
919 return ret;
920 }
922 static NRArenaItem *
923 sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags)
924 {
925 int pixskip, rs;
926 SPImage * image;
927 NRArenaItem *ai;
929 image = (SPImage *) item;
931 ai = NRArenaImage::create(arena);
933 if (image->pixbuf) {
934 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
935 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
936 if (image->aspect_align == SP_ASPECT_NONE)
937 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
938 gdk_pixbuf_get_pixels (image->pixbuf),
939 gdk_pixbuf_get_width (image->pixbuf),
940 gdk_pixbuf_get_height (image->pixbuf),
941 rs);
942 else // preserveAspectRatio
943 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai),
944 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
945 image->trimwidth,
946 image->trimheight,
947 rs);
948 } else {
949 nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0);
950 }
951 if (image->aspect_align == SP_ASPECT_NONE)
952 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed);
953 else // preserveAspectRatio
954 nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight);
956 return ai;
957 }
959 /*
960 * utility function to try loading image from href
961 *
962 * docbase/relative_src
963 * absolute_src
964 *
965 */
967 GdkPixbuf *
968 sp_image_repr_read_image (Inkscape::XML::Node * repr)
969 {
970 const gchar * filename, * docbase;
971 gchar * fullname;
972 GdkPixbuf * pixbuf;
974 filename = repr->attribute("xlink:href");
975 if (filename == NULL) filename = repr->attribute("href"); /* FIXME */
976 if (filename != NULL) {
977 if (strncmp (filename,"file:",5) == 0) {
978 fullname = g_filename_from_uri(filename, NULL, NULL);
979 if (fullname) {
980 // TODO check this. Was doing a UTF-8 to filename conversion here.
981 pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, NULL);
982 if (pixbuf != NULL) return pixbuf;
983 }
984 } else if (strncmp (filename,"data:",5) == 0) {
985 /* data URI - embedded image */
986 filename += 5;
987 pixbuf = sp_image_repr_read_dataURI (filename);
988 if (pixbuf != NULL) return pixbuf;
989 } else if (!g_path_is_absolute (filename)) {
990 /* try to load from relative pos */
991 docbase = sp_repr_document_root (sp_repr_document (repr))->attribute("sodipodi:docbase");
992 if (!docbase) docbase = ".";
993 fullname = g_build_filename(docbase, filename, NULL);
994 pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, NULL );
995 g_free (fullname);
996 if (pixbuf != NULL) return pixbuf;
997 } else {
998 /* try absolute filename */
999 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1000 if (pixbuf != NULL) return pixbuf;
1001 }
1002 }
1003 /* at last try to load from sp absolute path name */
1004 filename = repr->attribute("sodipodi:absref");
1005 if (filename != NULL) {
1006 pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL );
1007 if (pixbuf != NULL) return pixbuf;
1008 }
1009 /* Nope: We do not find any valid pixmap file :-( */
1010 pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm);
1012 /* It should be included xpm, so if it still does not does load, */
1013 /* our libraries are broken */
1014 g_assert (pixbuf != NULL);
1016 return pixbuf;
1017 }
1019 static GdkPixbuf *
1020 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
1021 {
1022 GdkPixbuf * newbuf;
1024 if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf;
1026 newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0);
1027 gdk_pixbuf_unref (pixbuf);
1029 return newbuf;
1030 }
1032 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1034 static void
1035 sp_image_update_canvas_image (SPImage *image)
1036 {
1037 int rs, pixskip;
1038 SPItem *item;
1039 SPItemView *v;
1041 item = SP_ITEM (image);
1043 if (image->pixbuf) {
1044 /* fixme: We are slightly violating spec here (Lauris) */
1045 if (!image->width._set) {
1046 image->width.computed = gdk_pixbuf_get_width (image->pixbuf);
1047 }
1048 if (!image->height._set) {
1049 image->height.computed = gdk_pixbuf_get_height (image->pixbuf);
1050 }
1051 }
1053 for (v = item->display; v != NULL; v = v->next) {
1054 pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8;
1055 rs = gdk_pixbuf_get_rowstride (image->pixbuf);
1056 if (image->aspect_align == SP_ASPECT_NONE) {
1057 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1058 gdk_pixbuf_get_pixels (image->pixbuf),
1059 gdk_pixbuf_get_width (image->pixbuf),
1060 gdk_pixbuf_get_height (image->pixbuf),
1061 rs);
1062 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1063 image->x.computed, image->y.computed,
1064 image->width.computed, image->height.computed);
1065 } else { // preserveAspectRatio
1066 nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem),
1067 gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs,
1068 image->trimwidth,
1069 image->trimheight,
1070 rs);
1071 nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem),
1072 image->viewx, image->viewy,
1073 image->viewwidth, image->viewheight);
1074 }
1075 }
1076 }
1078 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1079 {
1080 if (((SPItemClass *) parent_class)->snappoints) {
1081 ((SPItemClass *) parent_class)->snappoints (item, p);
1082 }
1083 }
1085 /*
1086 * Initially we'll do:
1087 * Transform x, y, set x, y, clear translation
1088 */
1090 static NR::Matrix
1091 sp_image_set_transform(SPItem *item, NR::Matrix const &xform)
1092 {
1093 SPImage *image = SP_IMAGE(item);
1095 /* Calculate position in parent coords. */
1096 NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform );
1098 /* This function takes care of translation and scaling, we return whatever parts we can't
1099 handle. */
1100 NR::Matrix ret(NR::transform(xform));
1101 NR::Point const scale(hypot(ret[0], ret[1]),
1102 hypot(ret[2], ret[3]));
1103 if ( scale[NR::X] > 1e-9 ) {
1104 ret[0] /= scale[NR::X];
1105 ret[1] /= scale[NR::X];
1106 } else {
1107 ret[0] = 1.0;
1108 ret[1] = 0.0;
1109 }
1110 if ( scale[NR::Y] > 1e-9 ) {
1111 ret[2] /= scale[NR::Y];
1112 ret[3] /= scale[NR::Y];
1113 } else {
1114 ret[2] = 0.0;
1115 ret[3] = 1.0;
1116 }
1118 image->width = image->width.computed * scale[NR::X];
1119 image->height = image->height.computed * scale[NR::Y];
1121 /* Find position in item coords */
1122 pos = pos * ret.inverse();
1123 image->x = pos[NR::X];
1124 image->y = pos[NR::Y];
1126 item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG);
1128 return ret;
1129 }
1131 static GdkPixbuf *
1132 sp_image_repr_read_dataURI (const gchar * uri_data)
1133 { GdkPixbuf * pixbuf = NULL;
1135 gint data_is_image = 0;
1136 gint data_is_base64 = 0;
1138 const gchar * data = uri_data;
1140 while (*data) {
1141 if (strncmp (data,"base64",6) == 0) {
1142 /* base64-encoding */
1143 data_is_base64 = 1;
1144 data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what
1145 data += 6;
1146 }
1147 else if (strncmp (data,"image/png",9) == 0) {
1148 /* PNG image */
1149 data_is_image = 1;
1150 data += 9;
1151 }
1152 else if (strncmp (data,"image/jpg",9) == 0) {
1153 /* JPEG image */
1154 data_is_image = 1;
1155 data += 9;
1156 }
1157 else if (strncmp (data,"image/jpeg",10) == 0) {
1158 /* JPEG image */
1159 data_is_image = 1;
1160 data += 10;
1161 }
1162 else { /* unrecognized option; skip it */
1163 while (*data) {
1164 if (((*data) == ';') || ((*data) == ',')) break;
1165 data++;
1166 }
1167 }
1168 if ((*data) == ';') {
1169 data++;
1170 continue;
1171 }
1172 if ((*data) == ',') {
1173 data++;
1174 break;
1175 }
1176 }
1178 if ((*data) && data_is_image && data_is_base64) {
1179 pixbuf = sp_image_repr_read_b64 (data);
1180 }
1182 return pixbuf;
1183 }
1185 static GdkPixbuf *
1186 sp_image_repr_read_b64 (const gchar * uri_data)
1187 { GdkPixbuf * pixbuf = NULL;
1188 GdkPixbufLoader * loader = NULL;
1190 gint j;
1191 gint k;
1192 gint l;
1193 gint b;
1194 gint len;
1195 gint eos = 0;
1196 gint failed = 0;
1198 guint32 bits;
1200 static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
1202 const gchar* btr = uri_data;
1204 gchar ud[4];
1206 guchar bd[57];
1208 loader = gdk_pixbuf_loader_new ();
1210 if (loader == NULL) return NULL;
1212 while (eos == 0) {
1213 l = 0;
1214 for (j = 0; j < 19; j++) {
1215 len = 0;
1216 for (k = 0; k < 4; k++) {
1217 while (isspace ((int) (*btr))) {
1218 if ((*btr) == '\0') break;
1219 btr++;
1220 }
1221 if (eos) {
1222 ud[k] = 0;
1223 continue;
1224 }
1225 if (((*btr) == '\0') || ((*btr) == '=')) {
1226 eos = 1;
1227 ud[k] = 0;
1228 continue;
1229 }
1230 ud[k] = 64;
1231 for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */
1232 if (B64[b] == (*btr)) {
1233 ud[k] = (gchar) b;
1234 break;
1235 }
1236 }
1237 if (ud[k] == 64) { /* data corruption ?? */
1238 eos = 1;
1239 ud[k] = 0;
1240 continue;
1241 }
1242 btr++;
1243 len++;
1244 }
1245 bits = (guint32) ud[0];
1246 bits = (bits << 6) | (guint32) ud[1];
1247 bits = (bits << 6) | (guint32) ud[2];
1248 bits = (bits << 6) | (guint32) ud[3];
1249 bd[l++] = (guchar) ((bits & 0xff0000) >> 16);
1250 if (len > 2) {
1251 bd[l++] = (guchar) ((bits & 0xff00) >> 8);
1252 }
1253 if (len > 3) {
1254 bd[l++] = (guchar) (bits & 0xff);
1255 }
1256 }
1258 if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) {
1259 failed = 1;
1260 break;
1261 }
1262 }
1264 gdk_pixbuf_loader_close (loader, NULL);
1266 if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader);
1268 return pixbuf;
1269 }
1271 /*
1272 Local Variables:
1273 mode:c++
1274 c-file-style:"stroustrup"
1275 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))
1276 indent-tabs-mode:nil
1277 fill-column:99
1278 End:
1279 */
1280 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :