1 // tiledCanvas.cpp
2 // this file is part of Context Free
3 // ---------------------
4 // Copyright (C) 2006-2016 John Horigan - john@glyphic.com
5 //
6 // This program is free software; you can redistribute it and/or
7 // modify it under the terms of the GNU General Public License
8 // as published by the Free Software Foundation; either version 2
9 // of the License, or (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 //
20 // John Horigan can be contacted at john@glyphic.com or at
21 // John Horigan, 1209 Villa St., Mountain View, CA 94041-1123, USA
22 //
23 //
24
25 #include "tiledCanvas.h"
26 #include <cmath>
27 #include "primShape.h"
28 #include "bounds.h"
29 #include <cstdlib>
30
start(bool clear,const agg::rgba & bk,int w,int h)31 void tiledCanvas::start(bool clear, const agg::rgba& bk, int w, int h)
32 {
33 mWidth = w;
34 mHeight = h;
35 mTile->start(clear, bk, w, h);
36 }
37
end()38 void tiledCanvas::end()
39 {
40 mTile->end();
41 }
42
primitive(int shape,RGBA8 c,agg::trans_affine tr,agg::comp_op_e blend)43 void tiledCanvas::primitive(int shape, RGBA8 c, agg::trans_affine tr, agg::comp_op_e blend)
44 {
45 if (shape == primShape::fillType) {
46 mTile->primitive(shape, c, tr, blend);
47 return;
48 }
49 for (auto&& tile: mTileList) {
50 agg::trans_affine t(tr);
51 t.tx += tile.x;
52 t.ty += tile.y;
53 mTile->primitive(shape, c, t, blend);
54 }
55 }
56
path(RGBA8 c,agg::trans_affine tr,const AST::CommandInfo & attr)57 void tiledCanvas::path(RGBA8 c, agg::trans_affine tr, const AST::CommandInfo& attr)
58 {
59 for (auto&& tile: mTileList) {
60 agg::trans_affine t(tr);
61 t.tx += tile.x;
62 t.ty += tile.y;
63 mTile->path(c, t, attr);
64 }
65 }
66
67 inline bool
checkTile(const Bounds & b,const agg::rect_d & canvas,double dx,double dy)68 tiledCanvas::checkTile(const Bounds& b, const agg::rect_d& canvas, double dx, double dy)
69 {
70 mOffset.transform(&dx, &dy);
71
72 // If the tile might touch the canvas then record it
73 agg::rect_d shape(b.mMin_X + dx, b.mMin_Y + dy, b.mMax_X + dx, b.mMax_Y + dy);
74 bool hit = shape.overlaps(canvas);
75 if (hit)
76 mTileList.emplace_back(dx, dy);
77 return hit;
78 }
79
80 void
tileTransform(const Bounds & b)81 tiledCanvas::tileTransform(const Bounds& b)
82 // Compute a list of tiling offsets for all tiled copies of the shape that overlap
83 // the canvas. Used for subsequent drawing.
84 {
85 double centx = (b.mMin_X + b.mMax_X) * 0.5;
86 double centy = (b.mMin_Y + b.mMax_Y) * 0.5;
87 mInvert.transform(¢x, ¢y); // transform to unit square tessellation
88 centx = std::floor(centx + 0.5); // round to nearest integer
89 centy = std::floor(centy + 0.5); // round to nearest integer
90
91 mTileList.clear();
92 double dx = -centx, dy = -centy;
93 mOffset.transform(&dx, &dy);
94 mTileList.emplace_back(dx, dy);
95 agg::rect_d canvas(-5, -5, static_cast<double>(mWidth + 9), static_cast<double>(mHeight + 9));
96
97 if (mFrieze)
98 centx = centy = centx + centy; // one will be zero, set them both to the other one
99
100 for (int ring = 1; ; ring++) {
101 bool hit = false;
102 if (mFrieze) {
103 // Works for x frieze and y frieze, the other dimension gets zeroed
104 hit = checkTile(b, canvas, ring - centx, ring - centy);
105 hit = checkTile(b, canvas, -ring - centx, -ring - centy) || hit;
106 } else {
107 for (int pos = -ring; pos < ring; pos++) {
108 hit = checkTile(b, canvas, pos - centx, -ring - centy) || hit;
109 hit = checkTile(b, canvas, ring - centx, pos - centy) || hit;
110 hit = checkTile(b, canvas, -pos - centx, ring - centy) || hit;
111 hit = checkTile(b, canvas, -ring - centx, -pos - centy) || hit;
112 }
113 }
114
115 if (!hit) return;
116 }
117 }
118
tiledCanvas(Canvas * tile,const agg::trans_affine & tr,CFDG::frieze_t f)119 tiledCanvas::tiledCanvas(Canvas* tile, const agg::trans_affine& tr, CFDG::frieze_t f)
120 : Canvas(tile->mWidth, tile->mHeight),
121 mTile(tile),
122 mTileTransform(tr),
123 mFrieze(f)
124 {
125 }
126
scale(double scaleFactor)127 void tiledCanvas::scale(double scaleFactor)
128 {
129 agg::trans_affine_scaling scale(scaleFactor);
130
131 // Generate the tiling transform in pixel units
132 mOffset = mTileTransform * scale;
133
134 // The mInvert transform can transform coordinates from the pixel unit tiling
135 // to the unit square tiling.
136 if (mFrieze) {
137 mInvert.reset();
138 mInvert.sx = mOffset.sx == 0.0 ? 0.0 : 1/mOffset.sx;
139 mInvert.sy = mOffset.sy == 0.0 ? 0.0 : 1/mOffset.sy;
140 } else {
141 mInvert = ~mOffset;
142 }
143 }
144
145 inline bool
checkTileInt(const agg::rect_i & screen,const agg::trans_affine & screenTessellation,int x,int y,tileList & points)146 tiledCanvas::checkTileInt(const agg::rect_i& screen,
147 const agg::trans_affine& screenTessellation,
148 int x, int y, tileList& points)
149 {
150 double dx = x;
151 double dy = y;
152 screenTessellation.transform(&dx, &dy);
153 int px = static_cast<int>(std::floor(dx + 0.5));
154 int py = static_cast<int>(std::floor(dy + 0.5));
155
156 // If the tile is visible then record it
157 agg::rect_i tile(px, py, px + mWidth - 1, py + mHeight - 1);
158 bool hit = tile.overlaps(screen);
159 if (hit)
160 points.emplace_back(px, py);
161 return hit;
162 }
163
getTessellation(int w,int h,int x1,int y1,bool flipY)164 tileList tiledCanvas::getTessellation(int w, int h, int x1, int y1, bool flipY)
165 {
166 tileList tessPoints;
167 // Produce an integer version of mOffset that is centered in the w x h screen
168 agg::trans_affine tess(mWidth, std::floor(mOffset.shy + 0.5), std::floor(mOffset.shx + 0.5),
169 flipY ? -mHeight : mHeight, x1, y1);
170 agg::rect_i screen(0, 0, w - 1, h - 1);
171 if (mFrieze == CFDG::frieze_x)
172 tess.sy = 0.0;
173 if (mFrieze == CFDG::frieze_y)
174 tess.sx = 0.0;
175
176 tessPoints.push_back(agg::point_i(x1, y1)); // always include the center tile
177
178 // examine rings of tile units around the center unit until you encounter a
179 // ring that doesn't have any tile units that intersect the screen. Then stop.
180 for (int ring = 1; ; ring++) {
181 bool hit = false;
182 if (mFrieze) {
183 hit = checkTileInt(screen, tess, ring, ring, tessPoints);
184 hit = checkTileInt(screen, tess, -ring, -ring, tessPoints) || hit;
185 } else {
186 for (int pos = -ring; pos < ring; pos++) {
187 hit = checkTileInt(screen, tess, pos, -ring, tessPoints) || hit;
188 hit = checkTileInt(screen, tess, ring, pos, tessPoints) || hit;
189 hit = checkTileInt(screen, tess, -pos, ring, tessPoints) || hit;
190 hit = checkTileInt(screen, tess, -ring, -pos, tessPoints) || hit;
191 }
192 }
193
194 if (!hit) break;
195 }
196 return tessPoints;
197 }
198
199