summaryrefslogtreecommitdiffstats
path: root/src/display/drawing-image.cpp
diff options
context:
space:
mode:
authorJasper van de Gronde <jasper.vandegronde@gmail.com>2013-03-21 11:15:43 +0000
committerJaspervdg <th.v.d.gronde@hccnet.nl>2013-03-21 11:15:43 +0000
commit6febb5a75ac5b7d8b41c4cf6c49799c094b98199 (patch)
treea80e69c84cf2bb678abcd1e859c4cdb796ca8f00 /src/display/drawing-image.cpp
parentKeys. Bug #253552 (Repeat effect shortcut). (diff)
downloadinkscape-6febb5a75ac5b7d8b41c4cf6c49799c094b98199.tar.gz
inkscape-6febb5a75ac5b7d8b41c4cf6c49799c094b98199.zip
Handle bitmap downsampling ourselves, see bug #804162 in launchpad.
(bzr r12227)
Diffstat (limited to 'src/display/drawing-image.cpp')
-rw-r--r--src/display/drawing-image.cpp124
1 files changed, 112 insertions, 12 deletions
diff --git a/src/display/drawing-image.cpp b/src/display/drawing-image.cpp
index 0c8ac9681..185b62285 100644
--- a/src/display/drawing-image.cpp
+++ b/src/display/drawing-image.cpp
@@ -22,6 +22,7 @@ DrawingImage::DrawingImage(Drawing &drawing)
: DrawingItem(drawing)
, _pixbuf(NULL)
, _surface(NULL)
+ , _new_surface(NULL)
, _style(NULL)
{}
@@ -30,6 +31,7 @@ DrawingImage::~DrawingImage()
if (_style)
sp_style_unref(_style);
if (_pixbuf) {
+ if (_new_surface) cairo_surface_destroy(_new_surface);
cairo_surface_destroy(_surface);
g_object_unref(_pixbuf);
}
@@ -118,26 +120,124 @@ unsigned DrawingImage::_renderItem(DrawingContext &ct, Geom::IntRect const &/*ar
if (!outline) {
if (!_pixbuf) return RENDER_OK;
-
+
Inkscape::DrawingContext::Save save(ct);
ct.transform(_ctm);
ct.newPath();
ct.rectangle(_clipbox);
ct.clip();
- ct.translate(_origin);
- ct.scale(_scale);
- ct.setSource(_surface, 0, 0);
+ /////////////////////////////////////////////////////////////////////////////
+ // BEGIN: Hack to avoid Cairo bug
+ // The total transform (which is RIGHT-multiplied with the item points to get display points) equals:
+ // scale*translate_origin*_ctm = scale*translate(origin)*expansion*expansionInv*_ctm
+ // = scale*expansion*translate(origin*expansion)*expansionInv*_ctm
+ // To avoid a Cairo bug, we handle the scale*expansion part ourselves.
+ // See https://bugs.launchpad.net/inkscape/+bug/804162
+
+ Geom::Scale expansion(_ctm.expansion());
+ int orgwidth = cairo_image_surface_get_width(_surface);
+ int orgheight = cairo_image_surface_get_height(_surface);
+
+ if (_scale[Geom::X]*expansion[Geom::X]*orgwidth*255.0<1.0 || _scale[Geom::Y]*expansion[Geom::Y]*orgheight*255.0<1.0) {
+ // Resized image too small to actually see anything
+ return RENDER_OK;
+ }
+
+ // Split scale*expansion in a part that is <= 1.0 and a part that is >= 1.0. We only take care of the part <= 1.0.
+ Geom::Scale scaleExpansionSmall(std::min<Geom::Coord>(fabs(_scale[Geom::X]*expansion[Geom::X]),1),std::min<Geom::Coord>(fabs(_scale[Geom::Y]*expansion[Geom::Y]),1));
+ Geom::Scale scaleExpansionLarge(_scale[Geom::X]*expansion[Geom::X]/scaleExpansionSmall[Geom::X],_scale[Geom::Y]*expansion[Geom::Y]/scaleExpansionSmall[Geom::Y]);
+
+ Geom::Point newSize(Geom::Point(orgwidth,orgheight)*scaleExpansionSmall);
+ if ((newSize-Geom::Point(orgwidth,orgheight)).length()<0.1) {
+ // Just use _surface directly.
+ ct.scale(expansion.inverse()); // This should not include scale (see derivation above)
+ ct.translate(_origin*expansion);
+ ct.scale(scaleExpansionLarge);
+ ct.setSource(_surface, 0, 0);
+ } else if (!_new_surface || (newSize-_rescaledSize).length()>0.1) {
+ // Rescaled image is sufficiently different from cached image to recompute
+ if (_new_surface) cairo_surface_destroy(_new_surface);
+ _rescaledSize = newSize;
+
+ // This essentially considers an image to be composed of rectangular pixels and computes the least-squares approximation of the original.
+ // When the scale factor is really large or small this essentially results in using a box filter, while for scale factors approaching 1 it is more like a "tent" kernel.
+ int newwidth = static_cast<int>(floor(orgwidth*scaleExpansionSmall[Geom::X])+1);
+ int newheight = static_cast<int>(floor(orgheight*scaleExpansionSmall[Geom::Y])+1);
+ std::vector<int> xBegin(newwidth, -1), yBegin(newheight, -1);
+ std::vector< std::vector<float> > xCoefs(xBegin.size()), yCoefs(yBegin.size());
+ for(int x=0; x<orgwidth; x++) {
+ double coordBegin = x*static_cast<double>(scaleExpansionSmall[Geom::X]); // x-coord in target coordinates where the current source pixel begins
+ double coordEnd = (x+1)*static_cast<double>(scaleExpansionSmall[Geom::X]); // x-coord in target coordinates where the current source pixel ends
+ int begin = static_cast<int>(floor(coordBegin)); // First pixel (x-coord) affected by the current source pixel
+ int end = static_cast<int>(ceil(coordEnd)); // First pixel (x-coord) NOT affected by the current source pixel (a zero contribution is counted as not affecting the pixel)
+ for(int nx=begin; nx<end; nx++) {
+ // Set xBegin if this is the first source pixel contributing to the target pixel.
+ if (xBegin[nx]==-1) xBegin[nx] = x;
+ // This computes the fraction of the current target pixel (at nx) that is covered by the source pixel (at x).
+ xCoefs[nx].push_back(static_cast<float>(std::min<double>(nx+1,coordEnd) - std::max<double>(nx,coordBegin)));
+ }
+ }
+ for(int y=0; y<orgheight; y++) {
+ double coordBegin = y*static_cast<double>(scaleExpansionSmall[Geom::Y]); // y-coord in target coordinates where the current source pixel begins
+ double coordEnd = (y+1)*static_cast<double>(scaleExpansionSmall[Geom::Y]); // y-coord in target coordinates where the current source pixel ends
+ int begin = static_cast<int>(floor(coordBegin)); // First pixel (y-coord) affected by the current source pixel
+ int end = static_cast<int>(ceil(coordEnd)); // First pixel (y-coord) NOT affected by the current source pixel (a zero contribution is counted as not affecting the pixel)
+ for(int ny=begin; ny<end; ny++) {
+ // Set yBegin if this is the first source pixel contributing to the target pixel.
+ if (yBegin[ny]==-1) yBegin[ny] = y;
+ // This computes the fraction of the current target pixel (at ny) that is covered by the source pixel (at y).
+ yCoefs[ny].push_back(static_cast<float>(std::min<double>(ny+1,coordEnd) - std::max<double>(ny,coordBegin)));
+ }
+ }
+
+ _new_surface = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, newwidth,newheight);
+ unsigned char * orgdata = cairo_image_surface_get_data(_surface);
+ unsigned char * newdata = cairo_image_surface_get_data(_new_surface);
+ int orgstride = cairo_image_surface_get_stride(_surface);
+ int newstride = cairo_image_surface_get_stride(_new_surface);
+
+ cairo_surface_flush(_surface);
+ cairo_surface_flush(_new_surface);
+
+ for(int y=0; y<newheight; y++) {
+ for(int x=0; x<newwidth; x++) {
+ float tempSum[4] = {0,0,0,0};
+ for(int oy=0; oy<static_cast<int>(yCoefs[y].size()); oy++) {
+ for(int ox=0; ox<static_cast<int>(xCoefs[x].size()); ox++) {
+ for(int c=0; c<4; c++) {
+ tempSum[c] += xCoefs[x][ox]*yCoefs[y][oy]*orgdata[c+4*(xBegin[x]+ox)+orgstride*(yBegin[y]+oy)];
+ }
+ }
+ }
+ for(int c=0; c<4; c++) {
+ newdata[c+4*x+newstride*y] = static_cast<unsigned char>(tempSum[c]);
+ }
+ }
+ }
+
+ cairo_surface_mark_dirty(_new_surface);
+
+ ct.scale(expansion.inverse()); // This should not include scale (see derivation above)
+ ct.translate(_origin*expansion);
+ ct.scale(scaleExpansionLarge);
+ ct.setSource(_new_surface, 0, 0);
+ } else {
+ // No need to regenerate, but we do draw from _new_surface.
+ ct.scale(expansion.inverse()); // This should not include scale (see derivation above)
+ ct.translate(_origin*expansion);
+ ct.scale(scaleExpansionLarge);
+ ct.setSource(_new_surface, 0, 0);
+ }
+
+ // END: Hack to avoid Cairo bug
+ /////////////////////////////////////////////////////////////////////////////
- cairo_matrix_t tt;
- Geom::Affine total;
- cairo_get_matrix(ct.raw(), &tt);
- ink_matrix_to_2geom(total, tt);
+ // TODO: If Cairo's problems are gone, uncomment the following:
+ //ct.translate(_origin);
+ //ct.scale(_scale);
+ //ct.setSource(_surface, 0, 0);
- if (total.expansionX() > 1.0 || total.expansionY() > 1.0) {
- cairo_pattern_t *p = cairo_get_source(ct.raw());
- cairo_pattern_set_filter(p, CAIRO_FILTER_NEAREST);
- }
//ct.paint(_opacity);
ct.paint();