Code

bulk trailing spaces removal. consistency through MD5 of binary
[inkscape.git] / src / sp-image.cpp
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 )
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");
190 void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length )
192     //g_message( "user_write_data(%d)", length );
195 void user_flush_data( png_structp png_ptr )
197     //g_message( "user_flush_data" );
200 GdkPixbuf*  pixbuf_new_from_file( const char *filename, GError **error )
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;
410 GType
411 sp_image_get_type (void)
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;
432 static void
433 sp_image_class_init (SPImageClass * klass)
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;
459 static void
460 sp_image_init (SPImage *image)
462         image->x.unset();
463         image->y.unset();
464         image->width.unset();
465         image->height.unset();
466         image->aspect_align = SP_ASPECT_NONE;
469 static void
470 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
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);
486 static void
487 sp_image_release (SPObject *object)
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);
512 static void
513 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
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         }
618 static void
619 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
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);
736 static Inkscape::XML::Node *
737 sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
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;
761 static void
762 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags)
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         }
779 static void
780 sp_image_print (SPItem *item, SPPrintContext *ctx)
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));
818 static gchar *
819 sp_image_description(SPItem *item)
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 &#215; %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;
842 static NRArenaItem *
843 sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags)
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;
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)
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;
939 static GdkPixbuf *
940 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
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;
952 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
954 static void
955 sp_image_update_canvas_image (SPImage *image)
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         }
998 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1000      if (((SPItemClass *) parent_class)->snappoints) {
1001          ((SPItemClass *) parent_class)->snappoints (item, p);
1002      }
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)
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;
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;
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;
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 :