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(&centx, &centy);          // 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