Code

CodingStyle: const placement
[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>
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 )
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");
192 void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length )
194     //g_message( "user_write_data(%d)", length );
197 void user_flush_data( png_structp png_ptr )
199     //g_message( "user_flush_data" );
202 GdkPixbuf*  pixbuf_new_from_file( const char *filename, GError **error )
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;
412 GType
413 sp_image_get_type (void)
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;
434 static void
435 sp_image_class_init (SPImageClass * klass)
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;
461 static void
462 sp_image_init (SPImage *image)
464         image->x.unset();
465         image->y.unset();
466         image->width.unset();
467         image->height.unset();
468         image->aspect_align = SP_ASPECT_NONE;
471 static void
472 sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr)
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);
489 static void
490 sp_image_release (SPObject *object)
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);
522 static void
523 sp_image_set (SPObject *object, unsigned int key, const gchar *value)
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         }
638 static void
639 sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags)
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);
792 static Inkscape::XML::Node *
793 sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags)
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;
820 static void
821 sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags)
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         }
838 static void
839 sp_image_print (SPItem *item, SPPrintContext *ctx)
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));
877 static gchar *
878 sp_image_description(SPItem *item)
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 &#215; %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;
901 static NRArenaItem *
902 sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags)
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;
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)
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;
998 static GdkPixbuf *
999 sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf)
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;
1011 /* We assert that realpixbuf is either NULL or identical size to pixbuf */
1013 static void
1014 sp_image_update_canvas_image (SPImage *image)
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         }
1057 static void sp_image_snappoints(SPItem const *item, SnapPointsIter p)
1059      if (((SPItemClass *) parent_class)->snappoints) {
1060          ((SPItemClass *) parent_class)->snappoints (item, p);
1061      }
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)
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;
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;
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;
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 :