/* * SVG implementation * * Authors: * Lauris Kaplinski * Edward Flick (EAF) * Abhishek Sharma * Jon A. Cruz * * 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 // This has to be included prior to anything that includes setjmp.h, it croaks otherwise #include #include #include #include #include #include <2geom/rect.h> #include <2geom/transforms.h> #include #include "display/drawing-image.h" #include "display/cairo-utils.h" #include "display/curve.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 "sp-clippath.h" #include "xml/quote.h" #include "xml/repr.h" #include "snap-candidate.h" #include "preferences.h" #include "io/sys.h" #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) #include "cms-system.h" #include "color-profile.h" #if HAVE_LIBLCMS2 # include #elif HAVE_LIBLCMS1 # include #endif // HAVE_LIBLCMS2 //#define DEBUG_LCMS #ifdef DEBUG_LCMS #define DEBUG_MESSAGE(key, ...)\ {\ g_message( __VA_ARGS__ );\ } #include #else #define DEBUG_MESSAGE(key, ...) #endif // DEBUG_LCMS #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) /* * SPImage */ // TODO: give these constants better names: #define MAGIC_EPSILON 1e-9 #define MAGIC_EPSILON_TOO 1e-18 // TODO: also check if it is correct to be using two different epsilon values 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 void sp_image_modified (SPObject *object, unsigned int flags); static Inkscape::XML::Node *sp_image_write (SPObject *object, Inkscape::XML::Document *doc, Inkscape::XML::Node *repr, guint flags); static Geom::OptRect sp_image_bbox(SPItem const *item, Geom::Affine const &transform, SPItem::BBoxType type); static void sp_image_print (SPItem * item, SPPrintContext *ctx); static gchar * sp_image_description (SPItem * item); static void sp_image_snappoints(SPItem const *item, std::vector &p, Inkscape::SnapPreferences const *snapprefs); static Inkscape::DrawingItem *sp_image_show (SPItem *item, Inkscape::Drawing &drawing, unsigned int key, unsigned int flags); static Geom::Affine sp_image_set_transform (SPItem *item, Geom::Affine const &xform); static void sp_image_set_curve(SPImage *image); static GdkPixbuf *sp_image_repr_read_image( time_t& modTime, gchar*& pixPath, const gchar *href, const gchar *absref, const gchar *base ); static GdkPixbuf *sp_image_pixbuf_force_rgba (GdkPixbuf * pixbuf); static void sp_image_update_arenaitem (SPImage *img, Inkscape::DrawingImage *ai); 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 void pixbuf_set_mime_data(GdkPixbuf *pb, guchar *data, gsize len, GdkPixbufFormat *fmt); #ifdef DEBUG_LCMS extern guint update_in_progress; #define DEBUG_MESSAGE_SCISLAC(key, ...) \ {\ Inkscape::Preferences *prefs = Inkscape::Preferences::get();\ bool dump = prefs->getBool("/options/scislac/" #key);\ bool dumpD = prefs->getBool("/options/scislac/" #key "D");\ bool dumpD2 = prefs->getBool("/options/scislac/" #key "D2");\ dumpD &&= ( (update_in_progress == 0) || dumpD2 );\ if ( dump )\ {\ g_message( __VA_ARGS__ );\ \ }\ if ( dumpD )\ {\ GtkWidget *dialog = gtk_message_dialog_new(NULL,\ GTK_DIALOG_DESTROY_WITH_PARENT, \ GTK_MESSAGE_INFO, \ GTK_BUTTONS_OK, \ __VA_ARGS__ \ );\ g_signal_connect_swapped(dialog, "response",\ G_CALLBACK(gtk_widget_destroy), \ dialog); \ gtk_widget_show_all( dialog );\ }\ } #else // DEBUG_LCMS #define DEBUG_MESSAGE_SCISLAC(key, ...) #endif // DEBUG_LCMS namespace Inkscape { namespace IO { GdkPixbuf* pixbuf_new_from_file(const char *filename, time_t &modTime, gchar*& pixPath) { GdkPixbuf* buf = NULL; modTime = 0; if ( pixPath ) { g_free(pixPath); pixPath = NULL; } //test correctness of filename if (!g_file_test (filename, G_FILE_TEST_EXISTS)){ return NULL; } struct stat stdir; int val = g_stat(filename, &stdir); if (stdir.st_mode & S_IFDIR){ g_warning("Linked image file %s is a directory", filename); return NULL; } // we need to load the entire pixbuf into memory gchar *data = NULL; gsize len = 0; if (g_file_get_contents(filename, &data, &len, NULL)) { if (!val) { modTime = stdir.st_mtime; pixPath = g_strdup(filename); } GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); gdk_pixbuf_loader_write(loader, (guchar *) data, len, NULL); gdk_pixbuf_loader_close(loader, NULL); buf = gdk_pixbuf_loader_get_pixbuf(loader); if (buf) { g_object_ref(buf); buf = sp_image_pixbuf_force_rgba(buf); pixbuf_set_mime_data(buf, (guchar *) data, len, gdk_pixbuf_loader_get_format(loader)); } else { g_free(data); g_warning("Error loading pixbuf"); } // TODO: we could also read DPI, ICC profile, gamma correction, and other information // from the file. This can be done by using format-specific libraries e.g. libpng. } else { g_warning("Unable to open linked file: %s", filename); } return buf; } } } G_DEFINE_TYPE(SPImage, sp_image, SP_TYPE_ITEM); static void sp_image_class_init( SPImageClass * klass ) { SPObjectClass *sp_object_class = SP_OBJECT_CLASS(klass); SPItemClass *item_class = SP_ITEM_CLASS(klass); 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->modified = sp_image_modified; 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; image->clipbox = Geom::Rect(); image->sx = image->sy = 1.0; image->ox = image->oy = 0.0; image->curve = NULL; image->href = 0; #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) image->color_profile = 0; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) image->pixbuf = 0; image->pixPath = 0; image->lastMod = 0; } static void sp_image_build( SPObject *object, SPDocument *document, Inkscape::XML::Node *repr ) { if (((SPObjectClass *) sp_image_parent_class)->build) { ((SPObjectClass *) sp_image_parent_class)->build (object, document, repr); } object->readAttr( "xlink:href" ); object->readAttr( "x" ); object->readAttr( "y" ); object->readAttr( "width" ); object->readAttr( "height" ); object->readAttr( "preserveAspectRatio" ); object->readAttr( "color-profile" ); /* Register */ document->addResource("image", object); } static void sp_image_release( SPObject *object ) { SPImage *image = SP_IMAGE(object); if (object->document) { // Unregister ourselves object->document->removeResource("image", object); } if (image->href) { g_free (image->href); image->href = NULL; } if (image->pixbuf) { g_object_set_data(G_OBJECT(image->pixbuf), "cairo_surface", NULL); g_object_unref (image->pixbuf); image->pixbuf = NULL; } #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if (image->color_profile) { g_free (image->color_profile); image->color_profile = NULL; } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if (image->pixPath) { g_free(image->pixPath); image->pixPath = 0; } if (image->curve) { image->curve = image->curve->unref(); } if (((SPObjectClass *) sp_image_parent_class)->release) { ((SPObjectClass *) sp_image_parent_class)->release (object); } } static void sp_image_set( SPObject *object, unsigned int key, const gchar *value ) { SPImage *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; #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) case SP_PROP_COLOR_PROFILE: if ( image->color_profile ) { g_free (image->color_profile); } image->color_profile = (value) ? g_strdup (value) : NULL; if ( value ) { DEBUG_MESSAGE( lcmsFour, " color-profile set to '%s'", value ); } else { DEBUG_MESSAGE( lcmsFour, " color-profile cleared" ); } // TODO check on this HREF_MODIFIED flag object->requestDisplayUpdate(SP_OBJECT_MODIFIED_FLAG | SP_IMAGE_HREF_MODIFIED_FLAG); break; #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) default: if (((SPObjectClass *) (sp_image_parent_class))->set) ((SPObjectClass *) (sp_image_parent_class))->set (object, key, value); break; } sp_image_set_curve(image); //creates a curve at the image's boundary for snapping } static void sp_image_update( SPObject *object, SPCtx *ctx, unsigned int flags ) { SPImage *image = SP_IMAGE(object); SPDocument *doc = object->document; if (((SPObjectClass *) (sp_image_parent_class))->update) { ((SPObjectClass *) (sp_image_parent_class))->update (object, ctx, flags); } if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) { if (image->pixbuf) { g_object_unref (image->pixbuf); image->pixbuf = NULL; } if ( image->pixPath ) { g_free(image->pixPath); image->pixPath = 0; } image->lastMod = 0; if (image->href) { GdkPixbuf *pixbuf; pixbuf = sp_image_repr_read_image ( image->lastMod, image->pixPath, //XML Tree being used directly while it shouldn't be. object->getRepr()->attribute("xlink:href"), //XML Tree being used directly while it shouldn't be. object->getRepr()->attribute("sodipodi:absref"), doc->getBase()); if (pixbuf) { // BLIP #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if ( image->color_profile ) { int imagewidth = gdk_pixbuf_get_width( pixbuf ); int imageheight = gdk_pixbuf_get_height( pixbuf ); int rowstride = gdk_pixbuf_get_rowstride( pixbuf ); guchar* px = gdk_pixbuf_get_pixels( pixbuf ); if ( px ) { DEBUG_MESSAGE( lcmsFive, "in 's sp_image_update. About to call colorprofile_get_handle()" ); guint profIntent = Inkscape::RENDERING_INTENT_UNKNOWN; cmsHPROFILE prof = Inkscape::CMSSystem::getHandle( object->document, &profIntent, image->color_profile ); if ( prof ) { cmsProfileClassSignature profileClass = cmsGetDeviceClass( prof ); if ( profileClass != cmsSigNamedColorClass ) { int intent = INTENT_PERCEPTUAL; switch ( profIntent ) { case Inkscape::RENDERING_INTENT_RELATIVE_COLORIMETRIC: intent = INTENT_RELATIVE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_SATURATION: intent = INTENT_SATURATION; break; case Inkscape::RENDERING_INTENT_ABSOLUTE_COLORIMETRIC: intent = INTENT_ABSOLUTE_COLORIMETRIC; break; case Inkscape::RENDERING_INTENT_PERCEPTUAL: case Inkscape::RENDERING_INTENT_UNKNOWN: case Inkscape::RENDERING_INTENT_AUTO: default: intent = INTENT_PERCEPTUAL; } cmsHPROFILE destProf = cmsCreate_sRGBProfile(); cmsHTRANSFORM transf = cmsCreateTransform( prof, TYPE_RGBA_8, destProf, TYPE_RGBA_8, intent, 0 ); if ( transf ) { guchar* currLine = px; for ( int y = 0; y < imageheight; y++ ) { // Since the types are the same size, we can do the transformation in-place cmsDoTransform( transf, currLine, currLine, imagewidth ); currLine += rowstride; } cmsDeleteTransform( transf ); } else { DEBUG_MESSAGE( lcmsSix, "in 's sp_image_update. Unable to create LCMS transform." ); } cmsCloseProfile( destProf ); } else { DEBUG_MESSAGE( lcmsSeven, "in 's sp_image_update. Profile type is named color. Can't transform." ); } } else { DEBUG_MESSAGE( lcmsEight, "in 's sp_image_update. No profile found." ); } } } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) image->pixbuf = pixbuf; } } } 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); } } Geom::Point p(image->x.computed, image->y.computed); Geom::Point wh(image->width.computed, image->height.computed); image->clipbox = Geom::Rect(p, p + wh); image->ox = image->x.computed; image->oy = image->y.computed; int pixwidth = gdk_pixbuf_get_width (image->pixbuf); int pixheight = gdk_pixbuf_get_height (image->pixbuf); image->sx = image->width.computed / pixwidth; image->sy = image->height.computed / pixheight; // preserveAspectRatio calculate bounds / clipping rectangle -- EAF if (image->pixbuf && (image->aspect_align != SP_ASPECT_NONE)) { double x, y; 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) { double scale = std::max(image->sx, image->sy); image->sx = scale; image->sy = scale; } else { double scale = std::min(image->sx, image->sy); image->sx = scale; image->sy = scale; } double vw = pixwidth * image->sx; double vh = pixheight * image->sy; image->ox += x * (image->width.computed - vw); image->oy += y * (image->height.computed - vh); } sp_image_update_canvas_image ((SPImage *) object); } static void sp_image_modified( SPObject *object, unsigned int flags ) { SPImage *image = SP_IMAGE (object); if (((SPObjectClass *) (sp_image_parent_class))->modified) { (* ((SPObjectClass *) (sp_image_parent_class))->modified) (object, flags); } if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) { for (SPItemView *v = image->display; v != NULL; v = v->next) { Inkscape::DrawingImage *img = dynamic_cast(v->arenaitem); img->setStyle(object->style); } } } static Inkscape::XML::Node *sp_image_write( SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags ) { SPImage *image = SP_IMAGE (object); if ((flags & SP_OBJECT_WRITE_BUILD) && !repr) { repr = xml_doc->createElement("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); } //XML Tree being used directly here while it shouldn't be... repr->setAttribute("preserveAspectRatio", object->getRepr()->attribute("preserveAspectRatio")); #if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if (image->color_profile) { repr->setAttribute("color-profile", image->color_profile); } #endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2) if (((SPObjectClass *) (sp_image_parent_class))->write) { ((SPObjectClass *) (sp_image_parent_class))->write (object, xml_doc, repr, flags); } return repr; } static Geom::OptRect sp_image_bbox( SPItem const *item,Geom::Affine const &transform, SPItem::BBoxType /*type*/ ) { SPImage const &image = *SP_IMAGE(item); Geom::OptRect bbox; if ((image.width.computed > 0.0) && (image.height.computed > 0.0)) { bbox = Geom::Rect::from_xywh(image.x.computed, image.y.computed, image.width.computed, image.height.computed); *bbox *= transform; } return bbox; } static void sp_image_print( SPItem *item, SPPrintContext *ctx ) { SPImage *image = SP_IMAGE(item); if (image->pixbuf && (image->width.computed > 0.0) && (image->height.computed > 0.0) ) { GdkPixbuf *pb = gdk_pixbuf_copy(image->pixbuf); // GObject data is not copied, so we have to set the pixel format explicitly g_object_set_data_full(G_OBJECT(pb), "pixel_format", g_strdup("argb32"), g_free); ink_pixbuf_ensure_normal(pb); guchar *px = gdk_pixbuf_get_pixels(pb); int w = gdk_pixbuf_get_width(pb); int h = gdk_pixbuf_get_height(pb); int rs = gdk_pixbuf_get_rowstride(pb); int pixskip = gdk_pixbuf_get_n_channels(pb) * gdk_pixbuf_get_bits_per_sample(pb) / 8; if (image->aspect_align == SP_ASPECT_NONE) { Geom::Affine t; Geom::Translate tp(image->x.computed, image->y.computed); Geom::Scale s(image->width.computed, -image->height.computed); Geom::Translate ti(0.0, -1.0); t = s * tp; t = ti * t; sp_print_image_R8G8B8A8_N(ctx, px, w, h, rs, t, item->style); } else { // preserveAspectRatio double vw = image->width.computed / image->sx; double vh = image->height.computed / image->sy; int trimwidth = std::min(w, ceil(image->width.computed / vw * w)); int trimheight = std::min(h, ceil(image->height.computed / vh * h)); int trimx = std::max(0, floor((image->x.computed - image->ox) / vw * w)); int trimy = std::max(0, floor((image->y.computed - image->oy) / vh * h)); double vx = std::max(image->ox, image->x.computed); double vy = std::max(image->oy, image->y.computed); double vcw = std::min(image->width.computed, vw); double vch = std::min(image->height.computed, vh); Geom::Affine t; Geom::Translate tp(vx, vy); Geom::Scale s(vcw, -vch); Geom::Translate ti(0.0, -1.0); t = s * tp; t = ti * t; sp_print_image_R8G8B8A8_N(ctx, px + trimx*pixskip + trimy*rs, trimwidth, trimheight, rs, t, item->style); } g_object_unref(pb); } } 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(_("Image with bad reference: %s"), href_desc) : g_strdup_printf(_("Image %d × %d: %s"), gdk_pixbuf_get_width(image->pixbuf), gdk_pixbuf_get_height(image->pixbuf), href_desc) ); g_free(href_desc); return ret; } static Inkscape::DrawingItem *sp_image_show( SPItem *item, Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/ ) { SPImage * image = SP_IMAGE(item); Inkscape::DrawingImage *ai = new Inkscape::DrawingImage(drawing); sp_image_update_arenaitem(image, ai); return ai; } /* * utility function to try loading image from href * * docbase/relative_src * absolute_src * */ GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base ) { GdkPixbuf *pixbuf = 0; modTime = 0; if ( pixPath ) { g_free(pixPath); pixPath = 0; } gchar const *filename = href; if (filename != NULL) { if (strncmp (filename,"file:",5) == 0) { gchar *fullname = g_filename_from_uri(filename, NULL, NULL); if (fullname) { pixbuf = Inkscape::IO::pixbuf_new_from_file(fullname, modTime, pixPath); g_free(fullname); 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 combined with document base*/ const gchar *docbase = base; if (!docbase) { docbase = "."; } gchar *fullname = g_build_filename(docbase, filename, NULL); // document base can be wrong (on the temporary doc when importing bitmap from a // different dir) or unset (when doc is not saved yet), so we check for base+href existence first, // and if it fails, we also try to use bare href regardless of its g_path_is_absolute if (g_file_test (fullname, G_FILE_TEST_EXISTS) && !g_file_test (fullname, G_FILE_TEST_IS_DIR)) { pixbuf = Inkscape::IO::pixbuf_new_from_file(fullname, modTime, pixPath); g_free (fullname); if (pixbuf != NULL) { return pixbuf; } } } /* try filename as absolute */ if (g_file_test (filename, G_FILE_TEST_EXISTS) && !g_file_test (filename, G_FILE_TEST_IS_DIR)) { pixbuf = Inkscape::IO::pixbuf_new_from_file(filename, modTime, pixPath); if (pixbuf != NULL) { return pixbuf; } } } } /* at last try to load from sp absolute path name */ filename = absref; if (filename != NULL) { // using absref is outside of SVG rules, so we must at least warn the user if ( base != NULL && href != NULL ) { g_warning (" did not resolve to a valid image file (base dir is %s), now trying sodipodi:absref=\"%s\"", href, base, absref); } else { g_warning ("xlink:href did not resolve to a valid image file, now trying sodipodi:absref=\"%s\"", absref); } pixbuf = Inkscape::IO::pixbuf_new_from_file(filename, modTime, pixPath); 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* result; if (gdk_pixbuf_get_has_alpha(pixbuf)) { result = pixbuf; } else { result = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0); g_object_unref(pixbuf); } return result; } /* We assert that realpixbuf is either NULL or identical size to pixbuf */ static void sp_image_update_arenaitem (SPImage *image, Inkscape::DrawingImage *ai) { ai->setStyle(SP_OBJECT(image)->style); ai->setARGB32Pixbuf(image->pixbuf); ai->setOrigin(Geom::Point(image->ox, image->oy)); ai->setScale(image->sx, image->sy); ai->setClipbox(image->clipbox); } static void sp_image_update_canvas_image(SPImage *image) { SPItem *item = SP_ITEM(image); for (SPItemView *v = item->display; v != NULL; v = v->next) { sp_image_update_arenaitem(image, dynamic_cast(v->arenaitem)); } } static void sp_image_snappoints( SPItem const *item, std::vector &p, Inkscape::SnapPreferences const *snapprefs ) { /* An image doesn't have any nodes to snap, but still we want to be able snap one image to another. Therefore we will create some snappoints at the corner, similar to a rect. If the image is rotated, then the snappoints will rotate with it. Again, just like a rect. */ g_assert(item != NULL); g_assert(SP_IS_IMAGE(item)); if (item->clip_ref->getObject()) { //We are looking at a clipped image: do not return any snappoints, as these might be //far far away from the visible part from the clipped image //TODO Do return snappoints, but only when within visual bounding box } else { if (snapprefs->isTargetSnappable(Inkscape::SNAPTARGET_IMG_CORNER)) { // The image has not been clipped: return its corners, which might be rotated for example SPImage &image = *SP_IMAGE(item); 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; Geom::Affine const i2d (item->i2dt_affine ()); p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y0) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x0, y1) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y1) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); p.push_back(Inkscape::SnapCandidatePoint(Geom::Point(x1, y0) * i2d, Inkscape::SNAPSOURCE_IMG_CORNER, Inkscape::SNAPTARGET_IMG_CORNER)); } } } /* * Initially we'll do: * Transform x, y, set x, y, clear translation */ static Geom::Affine sp_image_set_transform( SPItem *item, Geom::Affine const &xform ) { SPImage *image = SP_IMAGE(item); /* Calculate position in parent coords. */ Geom::Point pos( Geom::Point(image->x.computed, image->y.computed) * xform ); /* This function takes care of translation and scaling, we return whatever parts we can't handle. */ Geom::Affine ret(Geom::Affine(xform).withoutTranslation()); Geom::Point const scale(hypot(ret[0], ret[1]), hypot(ret[2], ret[3])); if ( scale[Geom::X] > MAGIC_EPSILON ) { ret[0] /= scale[Geom::X]; ret[1] /= scale[Geom::X]; } else { ret[0] = 1.0; ret[1] = 0.0; } if ( scale[Geom::Y] > MAGIC_EPSILON ) { ret[2] /= scale[Geom::Y]; ret[3] /= scale[Geom::Y]; } else { ret[2] = 0.0; ret[3] = 1.0; } image->width = image->width.computed * scale[Geom::X]; image->height = image->height.computed * scale[Geom::Y]; /* Find position in item coords */ pos = pos * ret.inverse(); image->x = pos[Geom::X]; image->y = pos[Geom::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(gchar const *uri_data) { GdkPixbuf *pixbuf = NULL; GdkPixbufLoader *loader = gdk_pixbuf_loader_new(); if (!loader) return NULL; gsize decoded_len = 0; guchar *decoded = g_base64_decode(uri_data, &decoded_len); if (gdk_pixbuf_loader_write(loader, decoded, decoded_len, NULL)) { gdk_pixbuf_loader_close(loader, NULL); pixbuf = gdk_pixbuf_loader_get_pixbuf(loader); g_object_ref(pixbuf); pixbuf = sp_image_pixbuf_force_rgba(pixbuf); pixbuf_set_mime_data(pixbuf, decoded, decoded_len, gdk_pixbuf_loader_get_format(loader)); } else { g_free(decoded); } g_object_unref(loader); return pixbuf; } // takes ownership of passed data static void pixbuf_set_mime_data(GdkPixbuf *pb, guchar *data, gsize len, GdkPixbufFormat *fmt) { cairo_surface_t *s = ink_cairo_surface_get_for_pixbuf(pb); gchar const *mimetype = NULL; gchar *fmt_name = gdk_pixbuf_format_get_name(fmt); Glib::ustring name = fmt_name; g_free(fmt_name); if (name == "jpeg") { mimetype = CAIRO_MIME_TYPE_JPEG; } else if (name == "jpeg2000") { mimetype = CAIRO_MIME_TYPE_JP2; } else if (name == "png") { mimetype = CAIRO_MIME_TYPE_PNG; } if (mimetype != NULL) { cairo_surface_set_mime_data(s, mimetype, data, len, g_free, data); //g_message("Setting Cairo MIME data: %s", mimetype); } else { g_free(data); //g_message("Not setting Cairo MIME data: unknown format %s", name.c_str()); } } static void sp_image_set_curve( SPImage *image ) { //create a curve at the image's boundary for snapping if ((image->height.computed < MAGIC_EPSILON_TOO) || (image->width.computed < MAGIC_EPSILON_TOO) || (image->clip_ref->getObject())) { if (image->curve) { image->curve = image->curve->unref(); } } else { Geom::OptRect rect = sp_image_bbox(image, Geom::identity(), SPItem::VISUAL_BBOX); SPCurve *c = SPCurve::new_from_rect(*rect, true); if (image->curve) { image->curve = image->curve->unref(); } if (c) { image->curve = c->ref(); c->unref(); } } } /** * Return duplicate of curve (if any exists) or NULL if there is no curve */ SPCurve *sp_image_get_curve( SPImage *image ) { SPCurve *result = 0; if (image->curve) { result = image->curve->copy(); } return result; } void sp_embed_image(Inkscape::XML::Node *image_node, GdkPixbuf *pb) { static gchar const *mimetypes[] = { CAIRO_MIME_TYPE_JPEG, CAIRO_MIME_TYPE_JP2, CAIRO_MIME_TYPE_PNG, NULL }; static guint mimetypes_len = g_strv_length(const_cast(mimetypes)); bool free_data = false; // check whether the pixbuf has MIME data guchar *data = NULL; gsize len = 0; gchar const *data_mimetype = NULL; cairo_surface_t *s = reinterpret_cast(g_object_get_data(G_OBJECT(pb), "cairo_surface")); if (s) { for (guint i = 0; i < mimetypes_len; ++i) { unsigned long len_long = 0; cairo_surface_get_mime_data(s, mimetypes[i], const_cast(&data), &len_long); len = len_long; // this assumes that the added range of long is not needed. the code below assumes gsize range of values is sufficient. if (data != NULL) { data_mimetype = mimetypes[i]; break; } } } if (data == NULL) { Inkscape::Preferences *prefs = Inkscape::Preferences::get(); Glib::ustring quality = Glib::ustring::format(prefs->getInt("/dialogs/import/quality", 100)); // if there is no supported MIME data, embed as PNG data_mimetype = "image/png"; ink_pixbuf_ensure_normal(pb); gdk_pixbuf_save_to_buffer(pb, reinterpret_cast(&data), &len, "png", NULL, "quality", quality.c_str(), NULL); free_data = true; } // Save base64 encoded data in image node // this formula taken from Glib docs gsize needed_size = len * 4 / 3 + len * 4 / (3 * 72) + 7; needed_size += 5 + 8 + strlen(data_mimetype); // 5 bytes for data: + 8 for ;base64, gchar *buffer = (gchar *) g_malloc(needed_size); gchar *buf_work = buffer; buf_work += g_sprintf(buffer, "data:%s;base64,", data_mimetype); gint state = 0; gint save = 0; gsize written = 0; written += g_base64_encode_step(data, len, TRUE, buf_work, &state, &save); written += g_base64_encode_close(TRUE, buf_work + written, &state, &save); buf_work[written] = 0; // null terminate // TODO: this is very wasteful memory-wise. // It would be better to only keep the binary data around, // and base64 encode on the fly when saving the XML. image_node->setAttribute("xlink:href", buffer); g_free(buffer); if (free_data) g_free(data); } void sp_image_refresh_if_outdated( SPImage* image ) { if ( image->href && image->lastMod ) { // It *might* change struct stat st; memset(&st, 0, sizeof(st)); int val = 0; if (g_file_test (image->pixPath, G_FILE_TEST_EXISTS)){ val = g_stat(image->pixPath, &st); } if ( !val ) { // stat call worked. Check time now if ( st.st_mtime != image->lastMod ) { SPCtx *ctx = 0; unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG; sp_image_update(image, ctx, flags); } } } } /* 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 :