summaryrefslogtreecommitdiffstats
path: root/src/display/drawing-pattern.cpp
blob: 90bad039b58f5b4efacf653cba90c0e9c533e83a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
// SPDX-License-Identifier: GPL-2.0-or-later
/**
 * @file
 * Canvas belonging to SVG pattern.
 *//*
 * Authors:
 *   Tomasz Boczkowski <penginsbacon@gmail.com>
 *
 * Copyright (C) 2014 Authors
 * Released under GNU GPL v2+, read the file 'COPYING' for more information.
 */

#include "display/cairo-utils.h"
#include "display/drawing-context.h"
#include "display/drawing-pattern.h"
#include "display/drawing-surface.h"

namespace Inkscape {

DrawingPattern::DrawingPattern(Drawing &drawing, bool debug)
    : DrawingGroup(drawing)
    , _pattern_to_user(nullptr)
    , _overflow_steps(1)
    , _debug(debug)
{
}

DrawingPattern::~DrawingPattern()
{
    delete _pattern_to_user; // delete NULL; is safe
}

void
DrawingPattern::setPatternToUserTransform(Geom::Affine const &new_trans) {
    Geom::Affine current;
    if (_pattern_to_user) {
        current = *_pattern_to_user;
    }

    if (!Geom::are_near(current, new_trans, 1e-18)) {
        // mark the area where the object was for redraw.
        _markForRendering();
        if (new_trans.isIdentity()) {
            delete _pattern_to_user; // delete NULL; is safe
            _pattern_to_user = nullptr;
        } else {
            _pattern_to_user = new Geom::Affine(new_trans);
        }
        _markForUpdate(STATE_ALL, true);
    }
}

void
DrawingPattern::setTileRect(Geom::Rect const &tile_rect) {
    _tile_rect = tile_rect;
}

void
DrawingPattern::setOverflow(Geom::Affine initial_transform, int steps, Geom::Affine step_transform) {
    _overflow_initial_transform = initial_transform;
    _overflow_steps = steps;
    _overflow_step_transform = step_transform;
}

cairo_pattern_t *
DrawingPattern::renderPattern(float opacity) {
    bool needs_opacity = (1.0 - opacity) >= 1e-3;
    bool visible = opacity >= 1e-3;

    if (!visible) {
        return nullptr;
    }

    if (!_tile_rect || (_tile_rect->area() == 0)) {
        return nullptr;
    }
    Geom::Rect pattern_tile = *_tile_rect;

    //TODO: If pattern_to_user set it to identity transform

    // The DrawingSurface class handles the mapping from "logical space"
    // (coordinates in the rendering) to "physical space" (surface pixels).
    // An oversampling is done as the pattern may not pixel align with the final surface.
    // The cairo surface is created when the DrawingContext is declared.
    // Create drawing surface with size of pattern tile (in pattern space) but with number of pixels
    // based on required resolution (c).
    Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);
    Inkscape::DrawingContext dc(pattern_surface);
    dc.transform( pattern_surface.drawingTransform().inverse() );

    pattern_tile *= pattern_surface.drawingTransform();
    Geom::IntRect one_tile = pattern_tile.roundOutwards();

    // Render pattern.
    if (needs_opacity) {
        dc.pushGroup(); // this group is for pattern + opacity
    }

    if (_debug) {
        dc.setSource(0.8, 0.0, 0.8);
        dc.paint();
    }

    //FIXME: What flags to choose?
    if (_overflow_steps == 1) {
        render(dc, one_tile, RENDER_DEFAULT);
    } else {
        //Overflow transforms need to be transformed to the new coordinate system
        //introduced by dc.transform( pattern_surface.drawingTransform().inverse() );
        Geom::Affine dt = pattern_surface.drawingTransform();
        Geom::Affine idt = pattern_surface.drawingTransform().inverse();
        Geom::Affine initial_transform = idt * _overflow_initial_transform * dt;
        Geom::Affine step_transform = idt * _overflow_step_transform * dt;
        dc.transform(initial_transform);
        for (int i = 0; i < _overflow_steps; i++) {
            // render() fails to handle transforms applied here when using cache.
            render(dc, one_tile, RENDER_BYPASS_CACHE);
            dc.transform(step_transform);
            // cairo_surface_t* raw = pattern_surface.raw();
            // std::string filename = "drawing-pattern" + std::to_string(i) + ".png";
            // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );
        }
    }

    // Uncomment to debug
    // cairo_surface_t* raw = pattern_surface.raw();
    // std::cout << "  cairo_surface (sp-pattern): "
    //           << " width: "  << cairo_image_surface_get_width( raw )
    //           << " height: " << cairo_image_surface_get_height( raw )
    //           << std::endl;
    // std::string filename = "drawing-pattern.png";
    // cairo_surface_write_to_png( pattern_surface.raw(), filename.c_str() );

    if (needs_opacity) {
        dc.popGroupToSource(); // pop raw pattern
        dc.paint(opacity); // apply opacity
    }

    cairo_pattern_t *cp = cairo_pattern_create_for_surface(pattern_surface.raw());
    // Apply transformation to user space. Also compensate for oversampling.
    if (_pattern_to_user) {
        ink_cairo_pattern_set_matrix(cp, _pattern_to_user->inverse() * pattern_surface.drawingTransform());
    } else {
        ink_cairo_pattern_set_matrix(cp, pattern_surface.drawingTransform());
    }

    if (_debug) {
        cairo_pattern_set_extend(cp, CAIRO_EXTEND_NONE);
    } else {
        cairo_pattern_set_extend(cp, CAIRO_EXTEND_REPEAT);
    }

    return cp;
}

// TODO investigate if area should be used.
unsigned DrawingPattern::_updateItem(Geom::IntRect const &area, UpdateContext const &ctx, unsigned flags, unsigned reset)
{
    UpdateContext pattern_ctx;

    if (!_tile_rect || (_tile_rect->area() == 0)) {
        return STATE_NONE;
    }

    Geom::Rect pattern_tile = *_tile_rect;
    Geom::Coord det_ctm = ctx.ctm.descrim();
    Geom::Coord det_ps2user = _pattern_to_user ? _pattern_to_user->descrim() : 1.0;
    Geom::Coord det_child_transform = _child_transform ? _child_transform->descrim() : 1.0;
    const double oversampling = 2.0;
    double scale = det_ctm*det_ps2user*det_child_transform * oversampling;
    //FIXME: When scale is too big (zooming in a hatch), cairo doesn't render the pattern
    //More precisely it fails when setting pattern matrix in DrawingPattern::renderPattern
    //Fully correct solution should make use of visible area bbox and change hatch tile rect
    //accordingly
    if (scale > 25) {
        scale = 25;
    }
    Geom::Point c(pattern_tile.dimensions()*scale*oversampling);
    _pattern_resolution = c.ceil();

    Inkscape::DrawingSurface pattern_surface(pattern_tile, _pattern_resolution);

    pattern_ctx.ctm = pattern_surface.drawingTransform();
    return DrawingGroup::_updateItem(Geom::IntRect::infinite(), pattern_ctx, flags, reset);
}

} // end 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 :