diff options
Diffstat (limited to 'src/sp-image.cpp')
| -rw-r--r-- | src/sp-image.cpp | 1200 |
1 files changed, 1200 insertions, 0 deletions
diff --git a/src/sp-image.cpp b/src/sp-image.cpp new file mode 100644 index 000000000..869981567 --- /dev/null +++ b/src/sp-image.cpp @@ -0,0 +1,1200 @@ +#define __SP_IMAGE_C__ + +/* + * SVG <image> implementation + * + * Authors: + * Lauris Kaplinski <lauris@kaplinski.com> + * Edward Flick (EAF) + * + * Copyright (C) 1999-2005 Authors + * Copyright (C) 2000-2001 Ximian, Inc. + * + * Released under GNU GPL, read the file 'COPYING' for more information + */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include <libnr/nr-matrix-fns.h> + +//#define GDK_PIXBUF_ENABLE_BACKEND 1 +//#include <gdk-pixbuf/gdk-pixbuf-io.h> +#include "display/nr-arena-image.h" + +//Added for preserveAspectRatio support -- EAF +#include "enums.h" +#include "attributes.h" + +#include "print.h" +#include "brokenimage.xpm" +#include "document.h" +#include "sp-image.h" +#include <glibmm/i18n.h> +#include "xml/quote.h" +#include <xml/repr.h> + +#include "io/sys.h" +#include <png.h> + +/* + * SPImage + */ + + +static void sp_image_class_init (SPImageClass * klass); +static void sp_image_init (SPImage * image); + +static void sp_image_build (SPObject * object, SPDocument * document, Inkscape::XML::Node * repr); +static void sp_image_release (SPObject * object); +static void sp_image_set (SPObject *object, unsigned int key, const gchar *value); +static void sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags); +static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags); + +static void sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags); +static void sp_image_print (SPItem * item, SPPrintContext *ctx); +static gchar * sp_image_description (SPItem * item); +static void sp_image_snappoints(SPItem const *item, SnapPointsIter p); +static NRArenaItem *sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags); +static NR::Matrix sp_image_set_transform (SPItem *item, NR::Matrix const &xform); + +GdkPixbuf * sp_image_repr_read_image (Inkscape::XML::Node * repr); +static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf); +static void sp_image_update_canvas_image (SPImage *image); +static GdkPixbuf * sp_image_repr_read_dataURI (const gchar * uri_data); +static GdkPixbuf * sp_image_repr_read_b64 (const gchar * uri_data); + +static SPItemClass *parent_class; + + +extern "C" +{ + void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length ); + void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ); + void user_flush_data( png_structp png_ptr ); + +} + +namespace Inkscape { +namespace IO { + +class PushPull +{ +public: + gboolean first; + FILE* fp; + guchar* scratch; + gsize size; + gsize used; + gsize offset; + GdkPixbufLoader *loader; + + PushPull() : first(TRUE), + fp(0), + scratch(0), + size(0), + used(0), + offset(0), + loader(0) {}; + + gboolean readMore() + { + gboolean good = FALSE; + if ( offset ) + { + g_memmove( scratch, scratch + offset, used - offset ); + used -= offset; + offset = 0; + } + if ( used < size ) + { + gsize space = size - used; + gsize got = fread( scratch + used, 1, space, fp ); + if ( got ) + { + if ( loader ) + { + GError *err = NULL; + //g_message( " __read %d bytes", (int)got ); + if ( !gdk_pixbuf_loader_write( loader, scratch + used, got, &err ) ) + { + //g_message("_error writing pixbuf data"); + } + } + + used += got; + good = TRUE; + } + else + { + good = FALSE; + } + } + return good; + } + + gsize available() const + { + return (used - offset); + } + + gsize readOut( gpointer data, gsize length ) + { + gsize giving = available(); + if ( length < giving ) + { + giving = length; + } + g_memmove( data, scratch + offset, giving ); + offset += giving; + if ( offset >= used ) + { + offset = 0; + used = 0; + } + return giving; + } + + void clear() + { + offset = 0; + used = 0; + } + +private: + PushPull& operator = (const PushPull& other); + PushPull(const PushPull& other); +}; + +void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length ) +{ +// g_message( "user_read_data(%d)", length ); + + PushPull* youme = (PushPull*)png_get_io_ptr(png_ptr); + + gsize filled = 0; + gboolean canRead = TRUE; + + while ( filled < length && canRead ) + { + gsize some = youme->readOut( data + filled, length - filled ); + filled += some; + if ( filled < length ) + { + canRead &= youme->readMore(); + } + } +// g_message("things out"); +} + +void user_write_data( png_structp png_ptr, png_bytep data, png_size_t length ) +{ + //g_message( "user_write_data(%d)", length ); +} + +void user_flush_data( png_structp png_ptr ) +{ + //g_message( "user_flush_data" ); +} + +GdkPixbuf* pixbuf_new_from_file( const char *filename, GError **error ) +{ + GdkPixbuf* buf = NULL; + PushPull youme; + gint dpiX = 0; + gint dpiY = 0; + + //buf = gdk_pixbuf_new_from_file( filename, error ); + dump_fopen_call( filename, "pixbuf_new_from_file" ); + FILE* fp = fopen_utf8name( filename, "r" ); + if ( fp ) + { + GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); + if ( loader ) + { + GError *err = NULL; + + // short buffer + guchar scratch[1024]; + gboolean latter = FALSE; + gboolean isPng = FALSE; + png_structp pngPtr = NULL; + png_infop infoPtr = NULL; + //png_infop endPtr = NULL; + + youme.fp = fp; + youme.scratch = scratch; + youme.size = sizeof(scratch); + youme.used = 0; + youme.offset = 0; + youme.loader = loader; + + while ( !feof(fp) ) + { + if ( youme.readMore() ) + { + if ( youme.first ) + { + //g_message( "First data chunk" ); + youme.first = FALSE; + isPng = !png_sig_cmp( scratch + youme.offset, 0, youme.available() ); + //g_message( " png? %s", (isPng ? "Yes":"No") ); + if ( isPng ) + { + pngPtr = png_create_read_struct( PNG_LIBPNG_VER_STRING, + NULL,//(png_voidp)user_error_ptr, + NULL,//user_error_fn, + NULL//user_warning_fn + ); + if ( pngPtr ) + { + infoPtr = png_create_info_struct( pngPtr ); + //endPtr = png_create_info_struct( pngPtr ); + + png_set_read_fn( pngPtr, &youme, user_read_data ); + //g_message( "In" ); + + //png_read_info( pngPtr, infoPtr ); + png_read_png( pngPtr, infoPtr, PNG_TRANSFORM_IDENTITY, NULL ); + + //g_message("out"); + + //png_read_end(pngPtr, endPtr); + + /* + if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_pHYs ) ) + { + g_message("pHYs chunk now valid" ); + } + if ( png_get_valid( pngPtr, infoPtr, PNG_INFO_sCAL ) ) + { + g_message("sCAL chunk now valid" ); + } + */ + + png_uint_32 res_x = 0; + png_uint_32 res_y = 0; + int unit_type = 0; + if ( png_get_pHYs( pngPtr, infoPtr, &res_x, &res_y, &unit_type) ) + { +// g_message( "pHYs yes (%d, %d) %d (%s)", (int)res_x, (int)res_y, unit_type, +// (unit_type == 1? "per meter" : "unknown") +// ); + +// g_message( " dpi: (%d, %d)", +// (int)(0.5 + ((double)res_x)/39.37), +// (int)(0.5 + ((double)res_y)/39.37) ); + if ( unit_type == PNG_RESOLUTION_METER ) + { + // TODO come up with a more accurate DPI setting + dpiX = (int)(0.5 + ((double)res_x)/39.37); + dpiY = (int)(0.5 + ((double)res_y)/39.37); + } + } + else + { +// g_message( "pHYs no" ); + } + +/* + double width = 0; + double height = 0; + int unit = 0; + if ( png_get_sCAL(pngPtr, infoPtr, &unit, &width, &height) ) + { + gchar* vals[] = { + "unknown", // PNG_SCALE_UNKNOWN + "meter", // PNG_SCALE_METER + "radian", // PNG_SCALE_RADIAN + "last", // + NULL + }; + + g_message( "sCAL: (%f, %f) %d (%s)", + width, height, unit, + ((unit >= 0 && unit < 3) ? vals[unit]:"???") + ); + } +*/ + + // now clean it up. + png_destroy_read_struct( &pngPtr, &infoPtr, NULL );//&endPtr ); + } + else + { + g_message("Error when creating PNG read struct"); + } + } + } + else if ( !latter ) + { + latter = TRUE; + //g_message(" READing latter"); + } + // Now clear out the buffer so we can read more. + // (dumping out unused) + youme.clear(); + } + } + + gboolean ok = gdk_pixbuf_loader_close(loader, &err); + if ( ok ) + { + buf = gdk_pixbuf_loader_get_pixbuf( loader ); + if ( buf ) + { + g_object_ref(buf); + + if ( dpiX ) + { + gchar *tmp = g_strdup_printf( "%d", dpiX ); + if ( tmp ) + { + //gdk_pixbuf_set_option( buf, "Inkscape::DpiX", tmp ); + g_free( tmp ); + } + } + if ( dpiY ) + { + gchar *tmp = g_strdup_printf( "%d", dpiY ); + if ( tmp ) + { + //gdk_pixbuf_set_option( buf, "Inkscape::DpiY", tmp ); + g_free( tmp ); + } + } + } + } + else + { + // do something + g_message("error loading pixbuf at close"); + } + + g_object_unref(loader); + } + else + { + g_message("error when creating pixbuf loader"); + } + fclose( fp ); + fp = NULL; + } + else + { + g_warning ("unable to open file: %s", filename); + } + +/* + if ( buf ) + { + const gchar* bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiX" ); + if ( bloop ) + { + g_message("DPI X is [%s]", bloop); + } + bloop = gdk_pixbuf_get_option( buf, "Inkscape::DpiY" ); + if ( bloop ) + { + g_message("DPI Y is [%s]", bloop); + } + } +*/ + + return buf; +} + +} +} + +GType +sp_image_get_type (void) +{ + static GType image_type = 0; + if (!image_type) { + GTypeInfo image_info = { + sizeof (SPImageClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) sp_image_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (SPImage), + 16, /* n_preallocs */ + (GInstanceInitFunc) sp_image_init, + NULL, /* value_table */ + }; + image_type = g_type_register_static (sp_item_get_type (), "SPImage", &image_info, (GTypeFlags)0); + } + return image_type; +} + +static void +sp_image_class_init (SPImageClass * klass) +{ + GObjectClass * gobject_class; + SPObjectClass * sp_object_class; + SPItemClass * item_class; + + gobject_class = (GObjectClass *) klass; + sp_object_class = (SPObjectClass *) klass; + item_class = (SPItemClass *) klass; + + parent_class = (SPItemClass*)g_type_class_ref (sp_item_get_type ()); + + sp_object_class->build = sp_image_build; + sp_object_class->release = sp_image_release; + sp_object_class->set = sp_image_set; + sp_object_class->update = sp_image_update; + sp_object_class->write = sp_image_write; + + item_class->bbox = sp_image_bbox; + item_class->print = sp_image_print; + item_class->description = sp_image_description; + item_class->show = sp_image_show; + item_class->snappoints = sp_image_snappoints; + item_class->set_transform = sp_image_set_transform; +} + +static void +sp_image_init (SPImage *image) +{ + image->x.unset(); + image->y.unset(); + image->width.unset(); + image->height.unset(); + image->aspect_align = SP_ASPECT_NONE; +} + +static void +sp_image_build (SPObject *object, SPDocument *document, Inkscape::XML::Node *repr) +{ + if (((SPObjectClass *) parent_class)->build) + ((SPObjectClass *) parent_class)->build (object, document, repr); + + sp_object_read_attr (object, "xlink:href"); + sp_object_read_attr (object, "x"); + sp_object_read_attr (object, "y"); + sp_object_read_attr (object, "width"); + sp_object_read_attr (object, "height"); + sp_object_read_attr (object, "preserveAspectRatio"); + + /* Register */ + sp_document_add_resource (document, "image", object); +} + +static void +sp_image_release (SPObject *object) +{ + SPImage *image; + + image = SP_IMAGE (object); + + if (SP_OBJECT_DOCUMENT (object)) { + /* Unregister ourselves */ + sp_document_remove_resource (SP_OBJECT_DOCUMENT (object), "image", SP_OBJECT (object)); + } + + if (image->href) { + g_free (image->href); + image->href = NULL; + } + + if (image->pixbuf) { + gdk_pixbuf_unref (image->pixbuf); + image->pixbuf = NULL; + } + + if (((SPObjectClass *) parent_class)->release) + ((SPObjectClass *) parent_class)->release (object); +} + +static void +sp_image_set (SPObject *object, unsigned int key, const gchar *value) +{ + SPImage *image; + + image = SP_IMAGE (object); + + switch (key) { + case SP_ATTR_XLINK_HREF: + g_free (image->href); + image->href = (value) ? g_strdup (value) : NULL; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); + break; + case SP_ATTR_X: + if (!image->x.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->x.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_Y: + if (!image->y.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->y.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_WIDTH: + if (!image->width.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->width.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_HEIGHT: + if (!image->height.readAbsolute(value)) { + /* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */ + image->height.unset(); + } + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + break; + case SP_ATTR_PRESERVEASPECTRATIO: + /* Do setup before, so we can use break to escape */ + image->aspect_align = SP_ASPECT_NONE; + image->aspect_clip = SP_ASPECT_MEET; + object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_OBJECT_VIEWPORT_MODIFIED_FLAG); + if (value) { + int len; + gchar c[256]; + const gchar *p, *e; + unsigned int align, clip; + p = value; + while (*p && *p == 32) p += 1; + if (!*p) break; + e = p; + while (*e && *e != 32) e += 1; + len = e - p; + if (len > 8) break; + memcpy (c, value, len); + c[len] = 0; + /* Now the actual part */ + if (!strcmp (c, "none")) { + align = SP_ASPECT_NONE; + } else if (!strcmp (c, "xMinYMin")) { + align = SP_ASPECT_XMIN_YMIN; + } else if (!strcmp (c, "xMidYMin")) { + align = SP_ASPECT_XMID_YMIN; + } else if (!strcmp (c, "xMaxYMin")) { + align = SP_ASPECT_XMAX_YMIN; + } else if (!strcmp (c, "xMinYMid")) { + align = SP_ASPECT_XMIN_YMID; + } else if (!strcmp (c, "xMidYMid")) { + align = SP_ASPECT_XMID_YMID; + } else if (!strcmp (c, "xMaxYMid")) { + align = SP_ASPECT_XMAX_YMID; + } else if (!strcmp (c, "xMinYMax")) { + align = SP_ASPECT_XMIN_YMAX; + } else if (!strcmp (c, "xMidYMax")) { + align = SP_ASPECT_XMID_YMAX; + } else if (!strcmp (c, "xMaxYMax")) { + align = SP_ASPECT_XMAX_YMAX; + } else { + break; + } + clip = SP_ASPECT_MEET; + while (*e && *e == 32) e += 1; + if (e) { + if (!strcmp (e, "meet")) { + clip = SP_ASPECT_MEET; + } else if (!strcmp (e, "slice")) { + clip = SP_ASPECT_SLICE; + } else { + break; + } + } + image->aspect_align = align; + image->aspect_clip = clip; + } + break; + default: + if (((SPObjectClass *) (parent_class))->set) + ((SPObjectClass *) (parent_class))->set (object, key, value); + break; + } +} + +static void +sp_image_update (SPObject *object, SPCtx *ctx, unsigned int flags) +{ + SPImage *image; + + image = (SPImage *) object; + + if (((SPObjectClass *) (parent_class))->update) + ((SPObjectClass *) (parent_class))->update (object, ctx, flags); + + if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) { + if (image->pixbuf) { + gdk_pixbuf_unref (image->pixbuf); + image->pixbuf = NULL; + } + if (image->href) { + GdkPixbuf *pixbuf; + pixbuf = sp_image_repr_read_image (object->repr); + if (pixbuf) { + pixbuf = sp_image_pixbuf_force_rgba (pixbuf); + image->pixbuf = pixbuf; + } + } + } + // preserveAspectRatio calculate bounds / clipping rectangle -- EAF + if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) { + int imagewidth, imageheight; + double x,y; + + imagewidth = gdk_pixbuf_get_width (image->pixbuf); + imageheight = gdk_pixbuf_get_height (image->pixbuf); + + switch (image->aspect_align) { + case SP_ASPECT_XMIN_YMIN: + x = 0.0; + y = 0.0; + break; + case SP_ASPECT_XMID_YMIN: + x = 0.5; + y = 0.0; + break; + case SP_ASPECT_XMAX_YMIN: + x = 1.0; + y = 0.0; + break; + case SP_ASPECT_XMIN_YMID: + x = 0.0; + y = 0.5; + break; + case SP_ASPECT_XMID_YMID: + x = 0.5; + y = 0.5; + break; + case SP_ASPECT_XMAX_YMID: + x = 1.0; + y = 0.5; + break; + case SP_ASPECT_XMIN_YMAX: + x = 0.0; + y = 1.0; + break; + case SP_ASPECT_XMID_YMAX: + x = 0.5; + y = 1.0; + break; + case SP_ASPECT_XMAX_YMAX: + x = 1.0; + y = 1.0; + break; + default: + x = 0.0; + y = 0.0; + break; + } + + if (image->aspect_clip == SP_ASPECT_SLICE) { + image->viewx = image->x.computed; + image->viewy = image->y.computed; + image->viewwidth = image->width.computed; + image->viewheight = image->height.computed; + if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) { + // Pixels aspect is wider than bounding box + image->trimheight = imageheight; + image->trimwidth = static_cast<int>(static_cast<double>(imageheight) * image->width.computed / image->height.computed); + image->trimy = 0; + image->trimx = static_cast<int>(static_cast<double>(imagewidth - image->trimwidth) * x); + } else { + // Pixels aspect is taller than bounding box + image->trimwidth = imagewidth; + image->trimheight = static_cast<int>(static_cast<double>(imagewidth) * image->height.computed / image->width.computed); + image->trimx = 0; + image->trimy = static_cast<int>(static_cast<double>(imageheight - image->trimheight) * y); + } + } else { + // Otherwise, assume SP_ASPECT_MEET + image->trimx = 0; + image->trimy = 0; + image->trimwidth = imagewidth; + image->trimheight = imageheight; + if ((imagewidth*image->height.computed)>(image->width.computed*imageheight)) { + // Pixels aspect is wider than bounding boz + image->viewwidth = image->width.computed; + image->viewheight = image->viewwidth * imageheight / imagewidth; + image->viewx=image->x.computed; + image->viewy=(image->height.computed - image->viewheight) * y + image->y.computed; + } else { + // Pixels aspect is taller than bounding box + image->viewheight = image->height.computed; + image->viewwidth = image->viewheight * imagewidth / imageheight; + image->viewy=image->y.computed; + image->viewx=(image->width.computed - image->viewwidth) * x + image->x.computed; + } + } + } + + sp_image_update_canvas_image ((SPImage *) object); +} + +static Inkscape::XML::Node * +sp_image_write (SPObject *object, Inkscape::XML::Node *repr, guint flags) +{ + SPImage *image; + + image = SP_IMAGE (object); + + if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { + repr = sp_repr_new ("svg:image"); + } + + repr->setAttribute("xlink:href", image->href); + /* fixme: Reset attribute if needed (Lauris) */ + if (image->x._set) sp_repr_set_svg_double(repr, "x", image->x.computed); + if (image->y._set) sp_repr_set_svg_double(repr, "y", image->y.computed); + if (image->width._set) sp_repr_set_svg_double(repr, "width", image->width.computed); + if (image->height._set) sp_repr_set_svg_double(repr, "height", image->height.computed); + repr->setAttribute("preserveAspectRatio", object->repr->attribute("preserveAspectRatio")); + + if (((SPObjectClass *) (parent_class))->write) + ((SPObjectClass *) (parent_class))->write (object, repr, flags); + + return repr; +} + +static void +sp_image_bbox(SPItem const *item, NRRect *bbox, NR::Matrix const &transform, unsigned const flags) +{ + SPImage const &image = *SP_IMAGE(item); + + if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) { + double const x0 = image.x.computed; + double const y0 = image.y.computed; + double const x1 = x0 + image.width.computed; + double const y1 = y0 + image.height.computed; + + nr_rect_union_pt(bbox, NR::Point(x0, y0) * transform); + nr_rect_union_pt(bbox, NR::Point(x1, y0) * transform); + nr_rect_union_pt(bbox, NR::Point(x1, y1) * transform); + nr_rect_union_pt(bbox, NR::Point(x0, y1) * transform); + } +} + +static void +sp_image_print (SPItem *item, SPPrintContext *ctx) +{ + SPImage *image; + NRMatrix tp, ti, s, t; + guchar *px; + int w, h, rs, pixskip; + + image = SP_IMAGE (item); + + if (!image->pixbuf) return; + if ((image->width.computed <= 0.0) || (image->height.computed <= 0.0)) return; + + px = gdk_pixbuf_get_pixels (image->pixbuf); + w = gdk_pixbuf_get_width (image->pixbuf); + h = gdk_pixbuf_get_height (image->pixbuf); + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + + if (image->aspect_align == SP_ASPECT_NONE) { + /* fixme: (Lauris) */ + nr_matrix_set_translate (&tp, image->x.computed, image->y.computed); + nr_matrix_set_scale (&s, image->width.computed, -image->height.computed); + nr_matrix_set_translate (&ti, 0.0, -1.0); + } else { // preserveAspectRatio + nr_matrix_set_translate (&tp, image->viewx, image->viewy); + nr_matrix_set_scale (&s, image->viewwidth, -image->viewheight); + nr_matrix_set_translate (&ti, 0.0, -1.0); + } + + nr_matrix_multiply (&t, &s, &tp); + nr_matrix_multiply (&t, &ti, &t); + + if (image->aspect_align == SP_ASPECT_NONE) + sp_print_image_R8G8B8A8_N (ctx, px, w, h, rs, &t, SP_OBJECT_STYLE (item)); + else // preserveAspectRatio + sp_print_image_R8G8B8A8_N (ctx, px + image->trimx*pixskip + image->trimy*rs, image->trimwidth, image->trimheight, rs, &t, SP_OBJECT_STYLE (item)); +} + +static gchar * +sp_image_description(SPItem *item) +{ + SPImage *image = SP_IMAGE(item); + char *href_desc; + if (image->href) { + href_desc = (strncmp(image->href, "data:", 5) == 0) + ? g_strdup(_("embedded")) + : xml_quote_strdup(image->href); + } else { + g_warning("Attempting to call strncmp() with a null pointer."); + href_desc = g_strdup(_("(null_pointer)")); // we call g_free() on href_desc + } + + char *ret = ( image->pixbuf == NULL + ? g_strdup_printf(_("<b>Image with bad reference</b>: %s"), href_desc) + : g_strdup_printf(_("<b>Image</b> %d × %d: %s"), + gdk_pixbuf_get_width(image->pixbuf), + gdk_pixbuf_get_height(image->pixbuf), + href_desc) ); + g_free(href_desc); + return ret; +} + +static NRArenaItem * +sp_image_show (SPItem *item, NRArena *arena, unsigned int key, unsigned int flags) +{ + int pixskip, rs; + SPImage * image; + NRArenaItem *ai; + + image = (SPImage *) item; + + ai = NRArenaImage::create(arena); + + if (image->pixbuf) { + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + if (image->aspect_align == SP_ASPECT_NONE) + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), + gdk_pixbuf_get_pixels (image->pixbuf), + gdk_pixbuf_get_width (image->pixbuf), + gdk_pixbuf_get_height (image->pixbuf), + rs); + else // preserveAspectRatio + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), + gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs, + image->trimwidth, + image->trimheight, + rs); + } else { + nr_arena_image_set_pixels (NR_ARENA_IMAGE (ai), NULL, 0, 0, 0); + } + if (image->aspect_align == SP_ASPECT_NONE) + nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->x.computed, image->y.computed, image->width.computed, image->height.computed); + else // preserveAspectRatio + nr_arena_image_set_geometry (NR_ARENA_IMAGE (ai), image->viewx, image->viewy, image->viewwidth, image->viewheight); + + return ai; +} + +/* + * utility function to try loading image from href + * + * docbase/relative_src + * absolute_src + * + */ + +GdkPixbuf * +sp_image_repr_read_image (Inkscape::XML::Node * repr) +{ + const gchar * filename, * docbase; + gchar * fullname; + GdkPixbuf * pixbuf; + + filename = repr->attribute("xlink:href"); + if (filename == NULL) filename = repr->attribute("href"); /* FIXME */ + if (filename != NULL) { + if (strncmp (filename,"file:",5) == 0) { + fullname = g_filename_from_uri(filename, NULL, NULL); + if (fullname) { + // TODO check this. Was doing a UTF-8 to filename conversion here. + pixbuf = Inkscape::IO::pixbuf_new_from_file (fullname, NULL); + if (pixbuf != NULL) return pixbuf; + } + } else if (strncmp (filename,"data:",5) == 0) { + /* data URI - embedded image */ + filename += 5; + pixbuf = sp_image_repr_read_dataURI (filename); + if (pixbuf != NULL) return pixbuf; + } else if (!g_path_is_absolute (filename)) { + /* try to load from relative pos */ + docbase = sp_repr_document_root (sp_repr_document (repr))->attribute("sodipodi:docbase"); + if (!docbase) docbase = "."; + fullname = g_build_filename(docbase, filename, NULL); + pixbuf = Inkscape::IO::pixbuf_new_from_file( fullname, NULL ); + g_free (fullname); + if (pixbuf != NULL) return pixbuf; + } else { + /* try absolute filename */ + pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL ); + if (pixbuf != NULL) return pixbuf; + } + } + /* at last try to load from sp absolute path name */ + filename = repr->attribute("sodipodi:absref"); + if (filename != NULL) { + pixbuf = Inkscape::IO::pixbuf_new_from_file( filename, NULL ); + if (pixbuf != NULL) return pixbuf; + } + /* Nope: We do not find any valid pixmap file :-( */ + pixbuf = gdk_pixbuf_new_from_xpm_data ((const gchar **) brokenimage_xpm); + + /* It should be included xpm, so if it still does not does load, */ + /* our libraries are broken */ + g_assert (pixbuf != NULL); + + return pixbuf; +} + +static GdkPixbuf * +sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf) +{ + GdkPixbuf * newbuf; + + if (gdk_pixbuf_get_has_alpha (pixbuf)) return pixbuf; + + newbuf = gdk_pixbuf_add_alpha (pixbuf, FALSE, 0, 0, 0); + gdk_pixbuf_unref (pixbuf); + + return newbuf; +} + +/* We assert that realpixbuf is either NULL or identical size to pixbuf */ + +static void +sp_image_update_canvas_image (SPImage *image) +{ + int rs, pixskip; + SPItem *item; + SPItemView *v; + + item = SP_ITEM (image); + + if (image->pixbuf) { + /* fixme: We are slightly violating spec here (Lauris) */ + if (!image->width._set) { + image->width.computed = gdk_pixbuf_get_width (image->pixbuf); + } + if (!image->height._set) { + image->height.computed = gdk_pixbuf_get_height (image->pixbuf); + } + } + + for (v = item->display; v != NULL; v = v->next) { + pixskip = gdk_pixbuf_get_n_channels (image->pixbuf) * gdk_pixbuf_get_bits_per_sample (image->pixbuf) / 8; + rs = gdk_pixbuf_get_rowstride (image->pixbuf); + if (image->aspect_align == SP_ASPECT_NONE) { + nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem), + gdk_pixbuf_get_pixels (image->pixbuf), + gdk_pixbuf_get_width (image->pixbuf), + gdk_pixbuf_get_height (image->pixbuf), + rs); + nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem), + image->x.computed, image->y.computed, + image->width.computed, image->height.computed); + } else { // preserveAspectRatio + nr_arena_image_set_pixels (NR_ARENA_IMAGE (v->arenaitem), + gdk_pixbuf_get_pixels (image->pixbuf) + image->trimx*pixskip + image->trimy*rs, + image->trimwidth, + image->trimheight, + rs); + nr_arena_image_set_geometry (NR_ARENA_IMAGE (v->arenaitem), + image->viewx, image->viewy, + image->viewwidth, image->viewheight); + } + } +} + +static void sp_image_snappoints(SPItem const *item, SnapPointsIter p) +{ + if (((SPItemClass *) parent_class)->snappoints) { + ((SPItemClass *) parent_class)->snappoints (item, p); + } +} + +/* + * Initially we'll do: + * Transform x, y, set x, y, clear translation + */ + +static NR::Matrix +sp_image_set_transform(SPItem *item, NR::Matrix const &xform) +{ + SPImage *image = SP_IMAGE(item); + + /* Calculate position in parent coords. */ + NR::Point pos( NR::Point(image->x.computed, image->y.computed) * xform ); + + /* This function takes care of translation and scaling, we return whatever parts we can't + handle. */ + NR::Matrix ret(NR::transform(xform)); + NR::Point const scale(hypot(ret[0], ret[1]), + hypot(ret[2], ret[3])); + if ( scale[NR::X] > 1e-9 ) { + ret[0] /= scale[NR::X]; + ret[1] /= scale[NR::X]; + } else { + ret[0] = 1.0; + ret[1] = 0.0; + } + if ( scale[NR::Y] > 1e-9 ) { + ret[2] /= scale[NR::Y]; + ret[3] /= scale[NR::Y]; + } else { + ret[2] = 0.0; + ret[3] = 1.0; + } + + image->width = image->width.computed * scale[NR::X]; + image->height = image->height.computed * scale[NR::Y]; + + /* Find position in item coords */ + pos = pos * ret.inverse(); + image->x = pos[NR::X]; + image->y = pos[NR::Y]; + + item->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG); + + return ret; +} + +static GdkPixbuf * +sp_image_repr_read_dataURI (const gchar * uri_data) +{ GdkPixbuf * pixbuf = NULL; + + gint data_is_image = 0; + gint data_is_base64 = 0; + + const gchar * data = uri_data; + + while (*data) { + if (strncmp (data,"base64",6) == 0) { + /* base64-encoding */ + data_is_base64 = 1; + data_is_image = 1; // Illustrator produces embedded images without MIME type, so we assume it's image no matter what + data += 6; + } + else if (strncmp (data,"image/png",9) == 0) { + /* PNG image */ + data_is_image = 1; + data += 9; + } + else if (strncmp (data,"image/jpg",9) == 0) { + /* JPEG image */ + data_is_image = 1; + data += 9; + } + else if (strncmp (data,"image/jpeg",10) == 0) { + /* JPEG image */ + data_is_image = 1; + data += 10; + } + else { /* unrecognized option; skip it */ + while (*data) { + if (((*data) == ';') || ((*data) == ',')) break; + data++; + } + } + if ((*data) == ';') { + data++; + continue; + } + if ((*data) == ',') { + data++; + break; + } + } + + if ((*data) && data_is_image && data_is_base64) { + pixbuf = sp_image_repr_read_b64 (data); + } + + return pixbuf; +} + +static GdkPixbuf * +sp_image_repr_read_b64 (const gchar * uri_data) +{ GdkPixbuf * pixbuf = NULL; + GdkPixbufLoader * loader = NULL; + + gint j; + gint k; + gint l; + gint b; + gint len; + gint eos = 0; + gint failed = 0; + + guint32 bits; + + static const gchar B64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + const gchar* btr = uri_data; + + gchar ud[4]; + + guchar bd[57]; + + loader = gdk_pixbuf_loader_new (); + + if (loader == NULL) return NULL; + + while (eos == 0) { + l = 0; + for (j = 0; j < 19; j++) { + len = 0; + for (k = 0; k < 4; k++) { + while (isspace ((int) (*btr))) { + if ((*btr) == '\0') break; + btr++; + } + if (eos) { + ud[k] = 0; + continue; + } + if (((*btr) == '\0') || ((*btr) == '=')) { + eos = 1; + ud[k] = 0; + continue; + } + ud[k] = 64; + for (b = 0; b < 64; b++) { /* There must a faster way to do this... ?? */ + if (B64[b] == (*btr)) { + ud[k] = (gchar) b; + break; + } + } + if (ud[k] == 64) { /* data corruption ?? */ + eos = 1; + ud[k] = 0; + continue; + } + btr++; + len++; + } + bits = (guint32) ud[0]; + bits = (bits << 6) | (guint32) ud[1]; + bits = (bits << 6) | (guint32) ud[2]; + bits = (bits << 6) | (guint32) ud[3]; + bd[l++] = (guchar) ((bits & 0xff0000) >> 16); + if (len > 2) { + bd[l++] = (guchar) ((bits & 0xff00) >> 8); + } + if (len > 3) { + bd[l++] = (guchar) (bits & 0xff); + } + } + + if (!gdk_pixbuf_loader_write (loader, (const guchar *) bd, (size_t) l, NULL)) { + failed = 1; + break; + } + } + + gdk_pixbuf_loader_close (loader, NULL); + + if (!failed) pixbuf = gdk_pixbuf_loader_get_pixbuf (loader); + + return pixbuf; +} + +/* + Local Variables: + mode:c++ + c-file-style:"stroustrup" + c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +)) + indent-tabs-mode:nil + fill-column:99 + End: +*/ +// vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 : |
