sp-image.cpp revision dd8175230e8bd2cd804b0f37f2c432f7db668265
/*
* SVG <image> implementation
*
* Authors:
* Lauris Kaplinski <lauris@kaplinski.com>
* Edward Flick (EAF)
* Abhishek Sharma
* Jon A. Cruz <jon@joncruz.org>
*
* 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 <png.h>
#include <cstring>
#include <algorithm>
#include <string>
#include "display/drawing-image.h"
#include "display/cairo-utils.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 "snap-candidate.h"
#include "preferences.h"
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
#include "cms-system.h"
#include "color-profile.h"
#if HAVE_LIBLCMS2
# include <lcms2.h>
# include <lcms.h>
#endif // HAVE_LIBLCMS2
//#define DEBUG_LCMS
#ifdef DEBUG_LCMS
#define DEBUG_MESSAGE(key, ...)\
{\
g_message( __VA_ARGS__ );\
}
#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 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_snappoints(SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &p, Inkscape::SnapPreferences const *snapprefs);
static Inkscape::DrawingItem *sp_image_show (SPItem *item, Inkscape::Drawing &drawing, unsigned int key, unsigned int flags);
static GdkPixbuf *sp_image_repr_read_image( time_t& modTime, gchar*& pixPath, const gchar *href, const gchar *absref, const gchar *base );
#ifdef DEBUG_LCMS
extern guint update_in_progress;
#define DEBUG_MESSAGE_SCISLAC(key, ...) \
{\
if ( dump )\
{\
g_message( __VA_ARGS__ );\
\
}\
if ( dumpD )\
{\
);\
dialog); \
}\
}
#else // DEBUG_LCMS
#define DEBUG_MESSAGE_SCISLAC(key, ...)
#endif // DEBUG_LCMS
namespace Inkscape {
namespace IO {
{
modTime = 0;
if ( pixPath ) {
}
//test correctness of filename
return NULL;
}
return NULL;
}
// we need to load the entire pixbuf into memory
if (!val) {
}
if (buf) {
} else {
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 {
}
return buf;
}
}
}
{
}
{
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
image->color_profile = 0;
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
}
{
}
/* Register */
}
{
// Unregister ourselves
}
}
}
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
if (image->color_profile) {
}
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
}
}
}
}
{
switch (key) {
case SP_ATTR_XLINK_HREF:
break;
case SP_ATTR_X:
/* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
}
break;
case SP_ATTR_Y:
/* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
}
break;
case SP_ATTR_WIDTH:
/* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
}
break;
case SP_ATTR_HEIGHT:
/* fixme: em, ex, % are probably valid, but require special treatment (Lauris) */
}
break;
/* Do setup before, so we can use break to escape */
if (value) {
int len;
gchar c[256];
const gchar *p, *e;
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;
c[len] = 0;
/* Now the actual part */
if (!strcmp (c, "none")) {
} else if (!strcmp (c, "xMinYMin")) {
} else if (!strcmp (c, "xMidYMin")) {
} else if (!strcmp (c, "xMaxYMin")) {
} else if (!strcmp (c, "xMinYMid")) {
} else if (!strcmp (c, "xMidYMid")) {
} else if (!strcmp (c, "xMaxYMid")) {
} else if (!strcmp (c, "xMinYMax")) {
} else if (!strcmp (c, "xMidYMax")) {
} else if (!strcmp (c, "xMaxYMax")) {
} else {
break;
}
while (*e && *e == 32) e += 1;
if (*e) {
if (!strcmp (e, "meet")) {
} else if (!strcmp (e, "slice")) {
} else {
break;
}
}
}
break;
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
case SP_PROP_COLOR_PROFILE:
if ( image->color_profile ) {
}
if ( value ) {
} else {
}
// TODO check on this HREF_MODIFIED flag
break;
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
default:
break;
}
}
{
}
if (flags & SP_IMAGE_HREF_MODIFIED_FLAG) {
}
}
//XML Tree being used directly while it shouldn't be.
//XML Tree being used directly while it shouldn't be.
if (pixbuf) {
// BLIP
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
if ( image->color_profile )
{
if ( px ) {
image->color_profile );
if ( prof ) {
if ( profileClass != cmsSigNamedColorClass ) {
int intent = INTENT_PERCEPTUAL;
switch ( profIntent ) {
break;
break;
break;
case Inkscape::RENDERING_INTENT_UNKNOWN:
case Inkscape::RENDERING_INTENT_AUTO:
default:
}
intent, 0 );
if ( transf ) {
for ( int y = 0; y < imageheight; y++ ) {
// Since the types are the same size, we can do the transformation in-place
}
} else {
}
} else {
DEBUG_MESSAGE( lcmsSeven, "in <image>'s sp_image_update. Profile type is named color. Can't transform." );
}
} else {
}
}
}
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
}
}
}
/* fixme: We are slightly violating spec here (Lauris) */
}
}
}
// preserveAspectRatio calculate bounds / clipping rectangle -- EAF
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;
}
} else {
}
}
}
{
}
if (flags & SP_OBJECT_STYLE_MODIFIED_FLAG) {
}
}
}
static Inkscape::XML::Node *sp_image_write( SPObject *object, Inkscape::XML::Document *xml_doc, Inkscape::XML::Node *repr, guint flags )
{
}
/* fixme: Reset attribute if needed (Lauris) */
}
}
}
}
//XML Tree being used directly here while it shouldn't be...
#if defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
if (image->color_profile) {
}
#endif // defined(HAVE_LIBLCMS1) || defined(HAVE_LIBLCMS2)
}
return repr;
}
static Geom::OptRect sp_image_bbox( SPItem const *item,Geom::Affine const &transform, SPItem::BBoxType /*type*/ )
{
bbox = Geom::Rect::from_xywh(image.x.computed, image.y.computed, image.width.computed, image.height.computed);
}
return bbox;
}
{
// GObject data is not copied, so we have to set the pixel format explicitly
int w = gdk_pixbuf_get_width(pb);
int h = gdk_pixbuf_get_height(pb);
t = s * tp;
t = ti * t;
} else { // preserveAspectRatio
t = s * tp;
t = ti * t;
sp_print_image_R8G8B8A8_N(ctx, px + trimx*pixskip + trimy*rs, trimwidth, trimheight, rs, t, item->style);
}
}
}
{
char *href_desc;
? g_strdup(_("embedded"))
} else {
g_warning("Attempting to call strncmp() with a null pointer.");
}
: g_strdup_printf(_("<b>Image</b> %d × %d: %s"),
href_desc) );
return ret;
}
static Inkscape::DrawingItem *sp_image_show( SPItem *item, Inkscape::Drawing &drawing, unsigned int /*key*/, unsigned int /*flags*/ )
{
return ai;
}
/*
* utility function to try loading image from href
*
* absolute_src
*
*/
GdkPixbuf *sp_image_repr_read_image( time_t& modTime, char*& pixPath, const gchar *href, const gchar *absref, const gchar *base )
{
modTime = 0;
if ( pixPath ) {
pixPath = 0;
}
if (fullname) {
return pixbuf;
}
}
/* data URI - embedded image */
filename += 5;
return pixbuf;
}
} else {
if (!g_path_is_absolute (filename)) {
/* try to load from relative pos combined with document base*/
if (!docbase) {
docbase = ".";
}
// 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
return pixbuf;
}
}
}
/* try filename as absolute */
return pixbuf;
}
}
}
}
/* at last try to load from sp absolute path name */
// using absref is outside of SVG rules, so we must at least warn the user
g_warning ("<image xlink:href=\"%s\"> 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);
}
return pixbuf;
}
}
/* Nope: We do not find any valid pixmap file :-( */
/* It should be included xpm, so if it still does not does load, */
/* our libraries are broken */
return pixbuf;
}
{
if (gdk_pixbuf_get_has_alpha(pixbuf)) {
} else {
}
return result;
}
/* We assert that realpixbuf is either NULL or identical size to pixbuf */
static void
{
}
{
}
}
static void sp_image_snappoints( SPItem const *item, std::vector<Inkscape::SnapCandidatePoint> &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.
*/
//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 {
// The image has not been clipped: return its corners, which might be rotated for example
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
*/
{
/* Calculate position in parent coords. */
/* This function takes care of translation and scaling, we return whatever parts we can't
handle. */
} else {
ret[0] = 1.0;
}
} else {
}
/* Find position in item coords */
return ret;
}
{
gint data_is_image = 0;
gint data_is_base64 = 0;
while (*data) {
/* 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;
}
/* PNG image */
data_is_image = 1;
data += 9;
}
/* JPEG image */
data_is_image = 1;
data += 9;
}
/* JPEG image */
data_is_image = 1;
data += 10;
}
else { /* unrecognized option; skip it */
while (*data) {
break;
}
data++;
}
}
if ((*data) == ';') {
data++;
continue;
}
if ((*data) == ',') {
data++;
break;
}
}
}
return pixbuf;
}
{
gsize decoded_len = 0;
} else {
}
return pixbuf;
}
// takes ownership of passed data
{
if (name == "jpeg") {
} else if (name == "jpeg2000") {
} else if (name == "png") {
}
//g_message("Setting Cairo MIME data: %s", mimetype);
} else {
//g_message("Not setting Cairo MIME data: unknown format %s", name.c_str());
}
}
{
//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())) {
}
} else {
}
if (c) {
c->unref();
}
}
}
/**
* Return duplicate of curve (if any exists) or NULL if there is no curve
*/
{
}
return result;
}
{
bool free_data = false;
// check whether the pixbuf has MIME data
cairo_surface_t *s = reinterpret_cast<cairo_surface_t*>(g_object_get_data(G_OBJECT(pb), "cairo_surface"));
if (s) {
for (guint i = 0; i < mimetypes_len; ++i) {
unsigned long len_long = 0;
len = len_long; // this assumes that the added range of long is not needed. the code below assumes gsize range of values is sufficient.
data_mimetype = mimetypes[i];
break;
}
}
}
// if there is no supported MIME data, embed as PNG
data_mimetype = "image/png";
free_data = true;
}
// Save base64 encoded data in image node
// this formula taken from Glib docs
// 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.
}
{
// It *might* change
int val = 0;
}
if ( !val ) {
// stat call worked. Check time now
unsigned int flags = SP_IMAGE_HREF_MODIFIED_FLAG;
}
}
}
}
/*
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 :