Code

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