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