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