1 /*
2  *  OpenSCAD (www.openscad.org)
3  *  Copyright (C) 2009-2011 Clifford Wolf <clifford@clifford.at> and
4  *                          Marius Kintel <marius@kintel.net>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  As a special exception, you have permission to link this program
12  *  with the CGAL library and distribute executables, as long as you
13  *  follow the requirements of the GNU GPL in regard to all of the
14  *  software in the executable aside from CGAL.
15  *
16  *  This program is distributed in the hope that it will be useful,
17  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
18  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  *  GNU General Public License for more details.
20  *
21  *  You should have received a copy of the GNU General Public License
22  *  along with this program; if not, write to the Free Software
23  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24  *
25  */
26 
27 #include "module.h"
28 #include "ModuleInstantiation.h"
29 #include "node.h"
30 #include "polyset.h"
31 #include "evalcontext.h"
32 #include "builtin.h"
33 #include "printutils.h"
34 #include "fileutils.h"
35 #include "handle_dep.h"
36 #include "ext/lodepng/lodepng.h"
37 
38 #include <cstdint>
39 #include <array>
40 #include <sstream>
41 #include <fstream>
42 #include <unordered_map>
43 #include "boost-utils.h"
44 #include <boost/functional/hash.hpp>
45 #include <boost/tokenizer.hpp>
46 #include <boost/lexical_cast.hpp>
47 #include <boost/algorithm/string.hpp>
48 #include <boost/assign/std/vector.hpp>
49 using namespace boost::assign; // bring 'operator+=()' into scope
50 
51 #include <boost/filesystem.hpp>
52 namespace fs = boost::filesystem;
53 
54 class SurfaceModule : public AbstractModule
55 {
56 public:
SurfaceModule()57 	SurfaceModule() { }
58 	AbstractNode *instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const override;
59 };
60 
61 typedef std::unordered_map<std::pair<int,int>, double, boost::hash<std::pair<int,int>>> img_data_t;
62 
63 class SurfaceNode : public LeafNode
64 {
65 public:
66 	VISITABLE();
SurfaceNode(const ModuleInstantiation * mi,const std::shared_ptr<EvalContext> & ctx)67 	SurfaceNode(const ModuleInstantiation *mi, const std::shared_ptr<EvalContext> &ctx) : LeafNode(mi, ctx), center(false), invert(false), convexity(1) { }
68 	std::string toString() const override;
name() const69 	std::string name() const override { return "surface"; }
70 
71 	Filename filename;
72 	bool center;
73 	bool invert;
74 	int convexity;
75 
76 	const Geometry *createGeometry() const override;
77 private:
78 	void convert_image(img_data_t &data, std::vector<uint8_t> &img, unsigned int width, unsigned int height) const;
79 	bool is_png(std::vector<uint8_t> &img) const;
80 	img_data_t read_dat(std::string filename) const;
81 	img_data_t read_png_or_dat(std::string filename) const;
82 };
83 
instantiate(const std::shared_ptr<Context> & ctx,const ModuleInstantiation * inst,const std::shared_ptr<EvalContext> & evalctx) const84 AbstractNode *SurfaceModule::instantiate(const std::shared_ptr<Context>& ctx, const ModuleInstantiation *inst, const std::shared_ptr<EvalContext>& evalctx) const
85 {
86 	auto node = new SurfaceNode(inst, evalctx);
87 
88 	AssignmentList args{assignment("file"), assignment("center"), assignment("convexity")};
89 	AssignmentList optargs{assignment("center"),assignment("invert")};
90 
91 	ContextHandle<Context> c{Context::create<Context>(ctx)};
92 	c->setVariables(evalctx, args, optargs);
93 
94 	const auto &fileval = c->lookup_variable("file");
95 	auto filename = lookup_file(fileval.isUndefined() ? "" : fileval.toString(), inst->path(), c->documentPath());
96 	node->filename = filename;
97 	handle_dep(fs::path(filename).generic_string());
98 
99 	const auto &center = c->lookup_variable("center", true);
100 	if (center.type() == Value::Type::BOOL) {
101 		node->center = center.toBool();
102 	}
103 
104 	const auto &convexity = c->lookup_variable("convexity", true);
105 	if (convexity.type() == Value::Type::NUMBER) {
106 		node->convexity = static_cast<int>(convexity.toDouble());
107 	}
108 
109 	const auto &invert = c->lookup_variable("invert", true);
110 	if (invert.type() == Value::Type::BOOL) {
111 		node->invert = invert.toBool();
112 	}
113 
114 	return node;
115 }
116 
convert_image(img_data_t & data,std::vector<uint8_t> & img,unsigned int width,unsigned int height) const117 void SurfaceNode::convert_image(img_data_t &data, std::vector<uint8_t> &img, unsigned int width, unsigned int height) const
118 {
119 	for (unsigned int y = 0; y < height; ++y) {
120 		for (unsigned int x = 0; x < width; ++x) {
121 			long idx = 4 * (y * width + x);
122 			double pixel = 0.2126 * img[idx] + 0.7152 * img[idx + 1] + 0.0722 * img[idx + 2];
123 			double z = 100.0/255 * (invert ? 1 - pixel : pixel);
124 			data[std::make_pair(height - 1 - y, x)] = z;
125 		}
126 	}
127 }
128 
is_png(std::vector<uint8_t> & png) const129 bool SurfaceNode::is_png(std::vector<uint8_t> &png) const
130 {
131 	return (png.size() >= 8 &&
132 					std::memcmp(png.data(),
133 											std::array<uint8_t, 8>({{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a}}).data(), 8) == 0);
134 }
135 
read_png_or_dat(std::string filename) const136 img_data_t SurfaceNode::read_png_or_dat(std::string filename) const
137 {
138 	img_data_t data;
139 	std::vector<uint8_t> png;
140 	int ret_val = 0;
141 	try{
142 		 ret_val = lodepng::load_file(png, filename);
143 	}catch(std::bad_alloc &ba){
144 
145 		LOG(message_group::Warning,Location::NONE,"","bad_alloc caught for '%1$s'.",ba.what());
146 		return data;
147 	}
148 
149 	if(ret_val == 78){
150 		LOG(message_group::Warning,Location::NONE,"","The file '%1$s' couldn't be opened.",filename);
151 		return data;
152 	}
153 
154 	if (!is_png(png)) {
155 		png.clear();
156 		return read_dat(filename);
157 	}
158 
159 	unsigned int width, height;
160 	std::vector<uint8_t> img;
161 	auto error = lodepng::decode(img, width, height, png);
162 	if (error) {
163 		LOG(message_group::Warning,Location::NONE,"","Can't read PNG image '%1$s'",filename);
164 		data.clear();
165 		return data;
166 	}
167 
168 	convert_image(data, img, width, height);
169 
170 	return data;
171 }
172 
read_dat(std::string filename) const173 img_data_t SurfaceNode::read_dat(std::string filename) const
174 {
175 	img_data_t data;
176 	std::ifstream stream(filename.c_str());
177 
178 	if (!stream.good()) {
179 		LOG(message_group::Warning,Location::NONE,"","Can't open DAT file '%1$s'.",filename);
180 		return data;
181 	}
182 
183 	int lines = 0, columns = 0;
184 	double min_val = 0;
185 
186 	typedef boost::tokenizer<boost::char_separator<char>> tokenizer;
187 	boost::char_separator<char> sep(" \t");
188 
189 	while (!stream.eof()) {
190 		std::string line;
191 		while (!stream.eof() && (line.size() == 0 || line[0] == '#')) {
192 			std::getline(stream, line);
193 			boost::trim(line);
194 		}
195 		if (line.size() == 0 && stream.eof()) break;
196 
197 		int col = 0;
198 		tokenizer tokens(line, sep);
199 		try {
200 			for(const auto &token : tokens) {
201 				auto v = boost::lexical_cast<double>(token);
202 				data[std::make_pair(lines, col++)] = v;
203 				if (col > columns) columns = col;
204 				min_val = std::min(v-1, min_val);
205 			}
206 		}
207 		catch (const boost::bad_lexical_cast &blc) {
208 			if (!stream.eof()) {
209 				LOG(message_group::Warning,Location::NONE,"","Illegal value in '%1$s': %2$s",filename,blc.what());
210 			}
211 			break;
212   	}
213 		lines++;
214 	}
215 
216 	return data;
217 }
218 
createGeometry() const219 const Geometry *SurfaceNode::createGeometry() const
220 {
221 	auto data = read_png_or_dat(filename);
222 
223 	auto p = new PolySet(3);
224 	p->setConvexity(convexity);
225 
226 	int lines = 0;
227 	int columns = 0;
228 	double min_val = 0;
229 	for (const auto &entry : data) {
230 		lines = std::max(lines, entry.first.first + 1);
231 		columns = std::max(columns, entry.first.second + 1);
232 		min_val = std::min(entry.second - 1, min_val);
233 	}
234 
235 	double ox = center ? -(columns-1)/2.0 : 0;
236 	double oy = center ? -(lines-1)/2.0 : 0;
237 
238 	for (int i = 1; i < lines; ++i)
239 	for (int j = 1; j < columns; ++j)
240 	{
241 		double v1 = data[std::make_pair(i-1, j-1)];
242 		double v2 = data[std::make_pair(i-1, j)];
243 		double v3 = data[std::make_pair(i, j-1)];
244 		double v4 = data[std::make_pair(i, j)];
245 		double vx = (v1 + v2 + v3 + v4) / 4;
246 
247 		p->append_poly();
248 		p->append_vertex(ox + j-1, oy + i-1, v1);
249 		p->append_vertex(ox + j, oy + i-1, v2);
250 		p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
251 
252 		p->append_poly();
253 		p->append_vertex(ox + j, oy + i-1, v2);
254 		p->append_vertex(ox + j, oy + i, v4);
255 		p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
256 
257 		p->append_poly();
258 		p->append_vertex(ox + j, oy + i, v4);
259 		p->append_vertex(ox + j-1, oy + i, v3);
260 		p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
261 
262 		p->append_poly();
263 		p->append_vertex(ox + j-1, oy + i, v3);
264 		p->append_vertex(ox + j-1, oy + i-1, v1);
265 		p->append_vertex(ox + j-0.5, oy + i-0.5, vx);
266 	}
267 
268 	for (int i = 1; i < lines; ++i)
269 	{
270 		p->append_poly();
271 		p->append_vertex(ox + 0, oy + i-1, min_val);
272 		p->append_vertex(ox + 0, oy + i-1, data[std::make_pair(i-1, 0)]);
273 		p->append_vertex(ox + 0, oy + i, data[std::make_pair(i, 0)]);
274 		p->append_vertex(ox + 0, oy + i, min_val);
275 
276 		p->append_poly();
277 		p->insert_vertex(ox + columns-1, oy + i-1, min_val);
278 		p->insert_vertex(ox + columns-1, oy + i-1, data[std::make_pair(i-1, columns-1)]);
279 		p->insert_vertex(ox + columns-1, oy + i, data[std::make_pair(i, columns-1)]);
280 		p->insert_vertex(ox + columns-1, oy + i, min_val);
281 	}
282 
283 	for (int i = 1; i < columns; ++i)
284 	{
285 		p->append_poly();
286 		p->insert_vertex(ox + i-1, oy + 0, min_val);
287 		p->insert_vertex(ox + i-1, oy + 0, data[std::make_pair(0, i-1)]);
288 		p->insert_vertex(ox + i, oy + 0, data[std::make_pair(0, i)]);
289 		p->insert_vertex(ox + i, oy + 0, min_val);
290 
291 		p->append_poly();
292 		p->append_vertex(ox + i-1, oy + lines-1, min_val);
293 		p->append_vertex(ox + i-1, oy + lines-1, data[std::make_pair(lines-1, i-1)]);
294 		p->append_vertex(ox + i, oy + lines-1, data[std::make_pair(lines-1, i)]);
295 		p->append_vertex(ox + i, oy + lines-1, min_val);
296 	}
297 
298 	if (columns > 1 && lines > 1) {
299 		p->append_poly();
300 		for (int i = 0; i < columns-1; ++i)
301 			p->insert_vertex(ox + i, oy + 0, min_val);
302 		for (int i = 0; i < lines-1; ++i)
303 			p->insert_vertex(ox + columns-1, oy + i, min_val);
304 		for (int i = columns-1; i > 0; i--)
305 			p->insert_vertex(ox + i, oy + lines-1, min_val);
306 		for (int i = lines-1; i > 0; i--)
307 			p->insert_vertex(ox + 0, oy + i, min_val);
308 	}
309 
310 	return p;
311 }
312 
toString() const313 std::string SurfaceNode::toString() const
314 {
315 	std::ostringstream stream;
316 	fs::path path{static_cast<std::string>(this->filename)}; // gcc-4.6
317 
318 	stream << this->name() << "(file = " << this->filename
319 		<< ", center = " << (this->center ? "true" : "false")
320 		<< ", invert = " << (this->invert ? "true" : "false")
321 				 << ", " "timestamp = " << (fs::exists(path) ? fs::last_write_time(path) : 0)
322 				 << ")";
323 
324 	return stream.str();
325 }
326 
register_builtin_surface()327 void register_builtin_surface()
328 {
329 	Builtins::init("surface", new SurfaceModule(),
330 				{
331 					"surface(string, center = false, invert = false, number)",
332 				});
333 }
334