1 // Copyright 2016-2021 Doug Moen
2 // Licensed under the Apache License, version 2.0
3 // See accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0
4 
5 #include "export.h"
6 
7 #include <libcurv/io/compiled_shape.h>
8 #include <libcurv/io/png.h>
9 #include <libcurv/viewer/viewer.h>
10 
11 #include <libcurv/context.h>
12 #include <libcurv/exception.h>
13 #include <libcurv/filesystem.h>
14 #include <libcurv/format.h>
15 #include <libcurv/render.h>
16 #include <libcurv/gpu_program.h>
17 #include <libcurv/json.h>
18 #include <libcurv/program.h>
19 #include <libcurv/range.h>
20 #include <libcurv/shape.h>
21 #include <libcurv/source.h>
22 #include <libcurv/viewed_shape.h>
23 
24 #include <glm/vec2.hpp>
25 
26 #include <algorithm>
27 #include <climits>
28 #include <cstdio>
29 #include <cstdlib>
30 #include <cstring>
31 #include <fstream>
32 #include <sstream>
33 
34 using namespace curv;
35 
eval(Value defl)36 Value Param::eval(Value defl)
37 {
38     if (value_.opt) {
39         auto src = make<String_Source>("",
40             stringify("-O ",name_,"=",value_.opt));
41         Program prog(params_.system_);
42         prog.compile(src, Scanner_Opts().skip_prefix(4+name_.size()));
43         auto nub = nub_phrase(prog.phrase_);
44         loc_ = nub->location();
45         if (isa<const Empty_Phrase>(nub)) {
46             if (!defl.is_missing())
47                 return defl;
48             else
49                 throw Exception(At_Program(prog), "missing argument");
50         }
51         return prog.eval();
52     } else {
53         loc_ = Src_Loc{make<Source>(params_.config_.filename_), Token{}};
54         return value_.config;
55     }
56 }
57 
to_int(int lo,int hi)58 int Param::to_int(int lo, int hi)
59 {
60     return eval().to_int(lo, hi, *this);
61 }
62 
to_double()63 double Param::to_double()
64 {
65     return eval().to_num(*this);
66 }
67 
to_double(double defl)68 double Param::to_double(double defl)
69 {
70     return eval(defl).to_num(*this);
71 }
72 
to_vec3()73 glm::dvec3 Param::to_vec3()
74 {
75     glm::dvec3 result;
76     auto list = eval().to<List>(*this);
77     list->assert_size(3, *this);
78     result.x = list->at(0).to_num(At_Index(0, *this));
79     result.y = list->at(1).to_num(At_Index(1, *this));
80     result.z = list->at(2).to_num(At_Index(2, *this));
81     return result;
82 }
83 
to_bool()84 bool Param::to_bool()
85 {
86     return eval(true).to_bool(*this);
87 }
88 
to_symbol()89 curv::Symbol_Ref Param::to_symbol()
90 {
91     auto val = eval();
92     return value_to_symbol(val, *this);
93 }
94 
to_enum(const std::vector<const char * > & e)95 int Param::to_enum(const std::vector<const char*>& e)
96 {
97     auto val = eval();
98     return value_to_enum(val, e, *this);
99 }
100 
unknown_parameter() const101 void Param::unknown_parameter() const
102 {
103     if (value_.opt) {
104         Src_Loc loc{
105             make<String_Source>("", stringify("-O ",name_,"=",value_.opt)),
106             {3, unsigned(3+name_.size())}};
107         if (params_.format_.empty()) {
108             throw Exception(At_Token{loc, params_.system_}, stringify(
109                 "'",name_,"': Unknown -O parameter.\n"
110                 "Use 'curv --help' for help."));
111         } else {
112             throw Exception(At_Token{loc, params_.system_}, stringify(
113                 "'",name_,"': "
114                 "Unknown -O parameter for output format '",params_.format_,"'.\n"
115                 "Use 'curv --help -o ",params_.format_,"' for help."));
116         }
117     }
118 }
119 
get_locations(std::list<Func_Loc> & locs) const120 void Param::get_locations(std::list<Func_Loc>& locs) const
121 {
122     locs.emplace_back(loc_);
123 }
124 
rewrite_message(Shared<const String> msg) const125 Shared<const String> Param::rewrite_message(Shared<const String> msg) const
126 {
127     if (value_.opt) {
128         return msg;
129     } else {
130         return stringify(
131             "at field .",params_.config_.branchname_,
132             ".",make_symbol(name_),": ",msg);
133     }
134 }
135 
system() const136 System& Param::system() const { return params_.system_; }
frame() const137 Frame* Param::frame() const { return nullptr; }
138 
Export_Params(Options options,const Config & config,curv::System & sys)139 Export_Params::Export_Params(
140     Options options,
141     const Config& config,
142     curv::System& sys)
143 :
144     config_(config),
145     system_(sys)
146 {
147     for (auto& opt : options)
148         map_[opt.first] = {opt.second, curv::missing};
149     if (config.branch_) {
150         config.branch_->each_field(At_System(sys),
151             [&](Symbol_Ref sym, Value val)->void {
152                 auto field = map_.find(sym.c_str());
153                 if (field != map_.end())
154                     field->second.config = val;
155                 else
156                     map_[sym.c_str()] = {nullptr, val};
157             });
158     }
159 }
160 
export_curv(Value value,Program &,const Export_Params & params,Output_File & ofile)161 void export_curv(Value value,
162     Program&,
163     const Export_Params& params,
164     Output_File& ofile)
165 {
166     for (auto& i : params.map_) {
167         Param p(params, i);
168         p.unknown_parameter();
169     }
170     ofile.open();
171     ofile.ostream() << value << "\n";
172 }
describe_render_opts(std::ostream & out)173 void describe_render_opts(std::ostream& out)
174 {
175     Render_Opts::describe_opts(out, "");
176 }
177 
parse_render_param(Param & p,Render_Opts & opts)178 bool parse_render_param(
179     Param& p,
180     Render_Opts& opts)
181 {
182     Value val = p.eval();
183     return opts.set_field(p.name_, val, p);
184 }
185 
export_cpp(Value value,Program & prog,const Export_Params & params,Output_File & ofile)186 void export_cpp(Value value,
187     Program& prog,
188     const Export_Params& params,
189     Output_File& ofile)
190 {
191     for (auto& i : params.map_) {
192         Param p{params, i};
193         p.unknown_parameter();
194     }
195     Shape_Program shape(prog);
196     if (!shape.recognize(value, nullptr)) {
197         At_Program cx(prog);
198         throw Exception(cx, "not a shape");
199     }
200     ofile.open();
201     io::export_cpp(shape, ofile.ostream());
202 }
203 
export_json(Value value,Program & prog,const Export_Params & params,Output_File & ofile)204 void export_json(Value value,
205     Program& prog,
206     const Export_Params& params,
207     Output_File& ofile)
208 {
209     for (auto& i : params.map_) {
210         Param p{params, i};
211         p.unknown_parameter();
212     }
213     ofile.open();
214     write_json_value(value, ofile.ostream());
215     ofile.ostream() << "\n";
216 }
217 
export_gpu(Value value,Program & prog,const Export_Params & params,Output_File & ofile)218 void export_gpu(Value value,
219     Program& prog, const Export_Params& params, Output_File& ofile)
220 {
221     Render_Opts opts;
222     for (auto& i : params.map_) {
223         Param p{params, i};
224         if (!parse_render_param(p, opts))
225             p.unknown_parameter();
226     }
227 
228     ofile.open();
229     At_Program cx(prog);
230     GPU_Program gprog(prog);
231     if (!gprog.recognize(value, opts))
232         throw Exception(cx, "not a shape");
233     gprog.write_curv(ofile.ostream());
234 }
235 
export_jgpu(Value value,Program & prog,const Export_Params & params,Output_File & ofile)236 void export_jgpu(Value value,
237     Program& prog, const Export_Params& params, Output_File& ofile)
238 {
239     Render_Opts opts;
240     for (auto& i : params.map_) {
241         Param p{params, i};
242         if (!parse_render_param(p, opts))
243             p.unknown_parameter();
244     }
245 
246     ofile.open();
247     At_Program cx(prog);
248     GPU_Program gprog(prog);
249     if (!gprog.recognize(value, opts))
250         throw Exception(cx, "not a shape");
251     gprog.write_json(ofile.ostream());
252 }
253 
254 // wrapper that exports image sequences if requested
export_all_png(const Shape_Program & shape,io::Image_Export & ix,double animate,Output_File & ofile)255 void export_all_png(
256     const Shape_Program& shape,
257     io::Image_Export& ix,
258     double animate,
259     Output_File& ofile)
260 {
261     if (animate <= 0.0) {
262         // export single image
263         io::export_png(shape, ix, ofile);
264         return;
265     }
266 
267     const char* ipath = ofile.path_.string().c_str();
268     const char* p = strchr(ipath, '*');
269     if (p == nullptr) {
270         throw Exception(At_System(shape.system()),
271           "'-O animate=' requires pathname in '-o pathname' to contain a '*'");
272     }
273     Range<const char*> prefix(ipath, p);
274     Range<const char*> suffix(p+1, strlen(p+1));
275 
276     unsigned count = unsigned(animate / ix.fdur_ + 0.5);
277     if (count == 0) count = 1;
278     unsigned digs = ndigits(count);
279     double fstart = ix.fstart_;
280     for (unsigned i = 0; i < count; ++i) {
281         ix.fstart_ = fstart + i * ix.fdur_;
282         char num[12];
283         snprintf(num, sizeof(num), "%0*d", digs, i);
284         auto opath = stringify(prefix, num, suffix);
285         Output_File oofile{shape.system()};
286         oofile.set_path(opath->c_str());
287         io::export_png(shape, ix, oofile);
288         oofile.commit();
289         //std::cerr << ".";
290         //std::cerr.flush();
291     }
292     //std::cerr << "done\n";
293 }
294 
describe_png_opts(std::ostream & out)295 void describe_png_opts(std::ostream& out)
296 {
297     out <<
298     "-v : verbose output logged to stderr\n"
299     "-O xsize=<image width in pixels>\n"
300     "-O ysize=<image height in pixels>\n"
301     "-O fstart=<animation frame start time, in seconds> (default 0)\n";
302     describe_render_opts(out);
303     out <<
304     "-O animate=<duration of animation> (exports an image sequence)\n";
305 }
306 
export_png(Value value,Program & prog,const Export_Params & params,Output_File & ofile)307 void export_png(Value value,
308     Program& prog,
309     const Export_Params& params,
310     Output_File& ofile)
311 {
312     io::Image_Export ix;
313 
314     int xsize = 0;
315     int ysize = 0;
316     double animate = 0.0;
317     for (auto& i : params.map_) {
318         Param p{params, i};
319         if (parse_render_param(p, ix)) {
320             ;
321         } else if (p.name_ == "xsize") {
322             xsize = p.to_int(1, INT_MAX);
323         } else if (p.name_ == "ysize") {
324             ysize = p.to_int(1, INT_MAX);
325         } else if (p.name_ == "fstart") {
326             ix.fstart_ = p.to_double();
327         } else if (p.name_ == "animate") {
328             animate = p.to_double();
329         } else {
330             p.unknown_parameter();
331         }
332     }
333 
334     Shape_Program shape(prog);
335     At_Program cx(prog);
336     if (!shape.recognize(value, &ix))
337         throw Exception(cx, "not a shape");
338     if (shape.is_2d_) {
339       #if 0
340         if (shape.bbox_.infinite2())
341             throw Exception(cx, "can't export an infinite 2D shape to PNG");
342         if (shape.bbox_.empty2())
343             throw Exception(cx, "can't export an empty 2D shape to PNG");
344       #else
345         // bug #67
346         if (shape.bbox_.infinite2() || shape.bbox_.empty2()) {
347             shape.bbox_.xmin = -10.0;
348             shape.bbox_.ymin = -10.0;
349             shape.bbox_.xmax = +10.0;
350             shape.bbox_.ymax = +10.0;
351         }
352       #endif
353         double dx = shape.bbox_.xmax - shape.bbox_.xmin;
354         double dy = shape.bbox_.ymax - shape.bbox_.ymin;
355         if (!xsize && !ysize) {
356             if (dx > dy)
357                 xsize = 500;
358             else
359                 ysize = 500;
360         }
361         if (xsize && !ysize) {
362             ix.size.x = xsize;
363             ix.pixel_size = dx / double(xsize);
364             ix.size.y = (int) round(dy / dx * double(xsize));
365             if (ix.size.y == 0) ++ix.size.y;
366         } else if (!xsize && ysize) {
367             ix.size.y = ysize;
368             ix.pixel_size = dy / double(ysize);
369             ix.size.x = (int) round(dx / dy * double(ysize));
370             if (ix.size.x == 0) ++ix.size.x;
371         } else {
372             ix.size.x = xsize;
373             ix.size.y = ysize;
374             ix.pixel_size = std::min(dx / double(xsize), dy / double(ysize));
375         }
376     } else {
377         // 3D export to PNG is basically a screenshot.
378         // We ignore the bounding box.
379         if (!xsize && !ysize)
380             ix.size.x = ix.size.y = 500;
381         else if (!xsize)
382             ix.size.x = ix.size.y = ysize;
383         else if (!ysize)
384             ix.size.x = ix.size.y = xsize;
385         else {
386             ix.size.x = xsize;
387             ix.size.y = ysize;
388         }
389     }
390     ix.verbose_ = params.verbose_;
391     if (params.verbose_) {
392         std::cerr << ix.size.x<<"×"<<ix.size.y<<" pixels";
393         if (ix.aa_ > 1)
394             std::cerr << ", " << ix.aa_<<"× antialiasing";
395         if (ix.taa_ > 1)
396             std::cerr << ", " << ix.aa_<<"× temporal antialiasing";
397         std::cerr << std::endl;
398     }
399     export_all_png(shape, ix, animate, ofile);
400 }
401 
describe_no_opts(std::ostream &)402 void describe_no_opts(std::ostream&) {}
403 
404 std::map<std::string, Exporter> exporters = {
405     {"curv", {export_curv, "Curv expression", describe_no_opts}},
406     {"stl", {export_stl, "STL mesh file (3D shape only)", describe_mesh_opts}},
407     {"obj", {export_obj, "OBJ mesh file (3D shape only)", describe_mesh_opts}},
408     {"x3d", {export_x3d, "X3D colour mesh file (3D shape only)",
409              describe_colour_mesh_opts}},
410     {"gltf", {export_gltf, "GLTF file (3D shape only)", describe_mesh_opts}},
411     {"gpu", {export_gpu, "compiled GPU program, in Curv format (shape only)",
412         describe_render_opts}},
413     {"jgpu", {export_jgpu, "compiled GPU program, in JSON format (shape only)",
414         describe_render_opts}},
415     {"json", {export_json, "JSON expression", describe_no_opts}},
416     {"cpp", {export_cpp, "C++ source file (shape only)", describe_no_opts}},
417     {"png", {export_png, "PNG image file (shape only)", describe_png_opts}},
418 };
419 
parse_viewer_config(const Export_Params & params,viewer::Viewer_Config & opts)420 void parse_viewer_config(
421     const Export_Params& params,
422     viewer::Viewer_Config& opts)
423 {
424     opts.verbose_ = params.verbose_;
425     for (auto& i : params.map_) {
426         Param p{params, i};
427         if (p.name_ == "lazy")
428             opts.lazy_ = p.to_bool();
429         else if (!parse_render_param(p, opts))
430             p.unknown_parameter();
431     }
432 }
describe_viewer_options(std::ostream & out,const char * prefix)433 void describe_viewer_options(std::ostream& out, const char* prefix)
434 {
435     Render_Opts::describe_opts(out, prefix);
436     out
437     << prefix <<
438     "-O lazy : Redraw only on user input. Disables animation & FPS counter.\n"
439     ;
440 }
441