summaryrefslogtreecommitdiffstats
path: root/src/display/drawing-image.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/drawing-image.cpp')
-rw-r--r--src/display/drawing-image.cpp270
1 files changed, 270 insertions, 0 deletions
diff --git a/src/display/drawing-image.cpp b/src/display/drawing-image.cpp
new file mode 100644
index 0000000..e82c8d0
--- /dev/null
+++ b/src/display/drawing-image.cpp
@@ -0,0 +1,270 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/**
+ * @file
+ * Bitmap image belonging to an SVG drawing.
+ *//*
+ * Authors:
+ * Krzysztof KosiƄski <tweenk.pl@gmail.com>
+ *
+ * Copyright (C) 2011 Authors
+ * Released under GNU GPL v2+, read the file 'COPYING' for more information.
+ */
+
+#include <2geom/bezier-curve.h>
+
+#include "drawing.h"
+#include "drawing-context.h"
+#include "drawing-image.h"
+#include "cairo-utils.h"
+#include "cairo-templates.h"
+
+namespace Inkscape {
+
+DrawingImage::DrawingImage(Drawing &drawing)
+ : DrawingItem(drawing)
+ , style_image_rendering(SP_CSS_IMAGE_RENDERING_AUTO)
+{
+}
+
+void DrawingImage::setPixbuf(std::shared_ptr<Inkscape::Pixbuf const> pixbuf)
+{
+ defer([this, pixbuf = std::move(pixbuf)] () mutable {
+ _pixbuf = std::move(pixbuf);
+ _markForUpdate(STATE_ALL, false);
+ });
+}
+
+void DrawingImage::setScale(double sx, double sy)
+{
+ defer([=] {
+ _scale = Geom::Scale(sx, sy);
+ _markForUpdate(STATE_ALL, false);
+ });
+}
+
+void DrawingImage::setOrigin(Geom::Point const &origin)
+{
+ defer([=] {
+ _origin = origin;
+ _markForUpdate(STATE_ALL, false);
+ });
+}
+
+void DrawingImage::setClipbox(Geom::Rect const &box)
+{
+ defer([=] {
+ _clipbox = box;
+ _markForUpdate(STATE_ALL, false);
+ });
+}
+
+Geom::Rect DrawingImage::bounds() const
+{
+ if (!_pixbuf) return _clipbox;
+
+ double pw = _pixbuf->width();
+ double ph = _pixbuf->height();
+ double vw = pw * _scale[Geom::X];
+ double vh = ph * _scale[Geom::Y];
+ Geom::Point wh(vw, vh);
+ Geom::Rect view(_origin, _origin+wh);
+ Geom::OptRect res = _clipbox & view;
+ Geom::Rect ret = res ? *res : _clipbox;
+
+ return ret;
+}
+
+void DrawingImage::setStyle(SPStyle const *style, SPStyle const *context_style)
+{
+ DrawingItem::setStyle(style, context_style);
+
+ auto image_rendering = SP_CSS_IMAGE_RENDERING_AUTO;
+ if (_style) {
+ image_rendering = _style->image_rendering.computed;
+ }
+
+ defer([=] {
+ style_image_rendering = image_rendering;
+ });
+}
+
+unsigned DrawingImage::_updateItem(Geom::IntRect const &, UpdateContext const &, unsigned, unsigned)
+{
+ // Calculate bbox
+ if (_pixbuf) {
+ Geom::Rect r = bounds() * _ctm;
+ _bbox = r.roundOutwards();
+ } else {
+ _bbox = Geom::OptIntRect();
+ }
+
+ return STATE_ALL;
+}
+
+unsigned DrawingImage::_renderItem(DrawingContext &dc, RenderContext &rc, Geom::IntRect const &/*area*/, unsigned flags, DrawingItem const */*stop_at*/) const
+{
+ bool const outline = (flags & RENDER_OUTLINE) && !_drawing.imageOutlineMode();
+
+ if (!outline) {
+ if (!_pixbuf) return RENDER_OK;
+
+ Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ dc.newPath();
+ dc.rectangle(_clipbox);
+ dc.clip();
+
+ dc.translate(_origin);
+ dc.scale(_scale);
+ // const_cast required since Cairo needs to modify the internal refcount variable, but we do not want to give up the
+ // benefits of const for the rest of our code. The underlying object is guaranteed to be non-const, so this is well-defined.
+ // It is also thread-safe to modify the refcount in this way, since Cairo uses atomics internally.
+ dc.setSource(const_cast<cairo_surface_t*>(_pixbuf->getSurfaceRaw()), 0, 0);
+ dc.patternSetExtend(CAIRO_EXTEND_PAD);
+
+ // See: http://www.w3.org/TR/SVG/painting.html#ImageRenderingProperty
+ // https://drafts.csswg.org/css-images-3/#the-image-rendering
+ // style.h/style.cpp, cairo-render-context.cpp
+ //
+ // CSS 3 defines:
+ // 'optimizeSpeed' as alias for "pixelated"
+ // 'optimizeQuality' as alias for "smooth"
+ switch (style_image_rendering) {
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZESPEED:
+ case SP_CSS_IMAGE_RENDERING_PIXELATED:
+ // we don't have an implementation for crisp-edges, but it should *not* smooth or blur
+ case SP_CSS_IMAGE_RENDERING_CRISPEDGES:
+ dc.patternSetFilter( CAIRO_FILTER_NEAREST );
+ break;
+ case SP_CSS_IMAGE_RENDERING_AUTO:
+ case SP_CSS_IMAGE_RENDERING_OPTIMIZEQUALITY:
+ default:
+ // In recent Cairo, BEST used Lanczos3, which is prohibitively slow
+ dc.patternSetFilter( CAIRO_FILTER_GOOD );
+ break;
+ }
+
+ // Handle an exceptional case where the greyscale color mode needs to be applied per-image.
+ bool const greyscale_exception = (flags & RENDER_OUTLINE) && _drawing.colorMode() == ColorMode::GRAYSCALE;
+ if (greyscale_exception) {
+ dc.pushGroup();
+ }
+
+ dc.paint();
+
+ if (greyscale_exception) {
+ ink_cairo_surface_filter(dc.rawTarget(), dc.rawTarget(), _drawing.grayscaleMatrix());
+ dc.popGroupToSource();
+ dc.paint();
+ }
+
+ } else { // outline; draw a rect instead
+
+ auto rgba = _drawing.imageOutlineColor();
+
+ { Inkscape::DrawingContext::Save save(dc);
+ dc.transform(_ctm);
+ dc.newPath();
+
+ Geom::Rect r = bounds();
+ Geom::Point c00 = r.corner(0);
+ Geom::Point c01 = r.corner(3);
+ Geom::Point c11 = r.corner(2);
+ Geom::Point c10 = r.corner(1);
+
+ dc.moveTo(c00);
+ // the box
+ dc.lineTo(c10);
+ dc.lineTo(c11);
+ dc.lineTo(c01);
+ dc.lineTo(c00);
+ // the diagonals
+ dc.lineTo(c11);
+ dc.moveTo(c10);
+ dc.lineTo(c01);
+ }
+
+ dc.setLineWidth(0.5);
+ dc.setSource(rgba);
+ dc.stroke();
+ }
+ return RENDER_OK;
+}
+
+/** Calculates the closest distance from p to the segment a1-a2*/
+static double distance_to_segment(Geom::Point const &p, Geom::Point const &a1, Geom::Point const &a2)
+{
+ Geom::LineSegment l(a1, a2);
+ Geom::Point np = l.pointAt(l.nearestTime(p));
+ return Geom::distance(np, p);
+}
+
+DrawingItem *DrawingImage::_pickItem(Geom::Point const &p, double delta, unsigned flags)
+{
+ if (!_pixbuf) return nullptr;
+
+ bool outline = (flags & PICK_OUTLINE) && !_drawing.imageOutlineMode();
+
+ if (outline) {
+ Geom::Rect r = bounds();
+ Geom::Point pick = p * _ctm.inverse();
+
+ // find whether any side or diagonal is within delta
+ // to do so, iterate over all pairs of corners
+ for (unsigned i = 0; i < 3; ++i) { // for i=3, there is nothing to do
+ for (unsigned j = i+1; j < 4; ++j) {
+ if (distance_to_segment(pick, r.corner(i), r.corner(j)) < delta) {
+ return this;
+ }
+ }
+ }
+ return nullptr;
+
+ } else {
+ auto pixels = _pixbuf->pixels();
+ int width = _pixbuf->width();
+ int height = _pixbuf->height();
+ size_t rowstride = _pixbuf->rowstride();
+
+ Geom::Point tp = p * _ctm.inverse();
+ Geom::Rect r = bounds();
+
+ if (!r.contains(tp))
+ return nullptr;
+
+ double vw = width * _scale[Geom::X];
+ double vh = height * _scale[Geom::Y];
+ int ix = floor((tp[Geom::X] - _origin[Geom::X]) / vw * width);
+ int iy = floor((tp[Geom::Y] - _origin[Geom::Y]) / vh * height);
+
+ if ((ix < 0) || (iy < 0) || (ix >= width) || (iy >= height))
+ return nullptr;
+
+ auto pix_ptr = pixels + iy * rowstride + ix * 4;
+ // pick if the image is less than 99% transparent
+ guint32 alpha = 0;
+ if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_CAIRO) {
+ guint32 px = *reinterpret_cast<guint32 const *>(pix_ptr);
+ alpha = (px & 0xff000000) >> 24;
+ } else if (_pixbuf->pixelFormat() == Inkscape::Pixbuf::PF_GDK) {
+ alpha = pix_ptr[3];
+ } else {
+ throw std::runtime_error("Unrecognized pixel format");
+ }
+ float alpha_f = (alpha / 255.0f) * _opacity;
+ return alpha_f > 0.01 ? this : nullptr;
+ }
+}
+
+} // namespace Inkscape
+
+/*
+ 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:fileencoding=utf-8:textwidth=99 :