1 #include <cmath>
2 #include <iostream>
3 #include <memory>
4 #include <regex>
5 #include <stdexcept>
6 #include <string>
7 #include "common/cpuinfo.h"
8 #include "common/except.h"
9 #include "common/make_unique.h"
10 #include "common/pixel.h"
11 #include "resize/filter.h"
12 #include "resize/resize.h"
13
14 #include "apps.h"
15 #include "argparse.h"
16 #include "frame.h"
17 #include "pair_filter.h"
18 #include "table.h"
19 #include "timer.h"
20 #include "utils.h"
21
22 namespace {
23
is_set_pixel_format(const zimg::PixelFormat & format)24 constexpr bool is_set_pixel_format(const zimg::PixelFormat &format) noexcept
25 {
26 return format != zimg::PixelFormat{};
27 }
28
decode_filter(const struct ArgparseOption *,void * out,const char * param,int)29 int decode_filter(const struct ArgparseOption *, void *out, const char *param, int)
30 {
31 try {
32 std::unique_ptr<zimg::resize::Filter> *filter = static_cast<std::unique_ptr<zimg::resize::Filter> *>(out);
33 std::regex filter_regex{ R"(^(point|bilinear|bicubic|spline16|spline36|lanczos)(?::([\w.+-]+)(?::([\w.+-]+))?)?$)" };
34 std::cmatch match;
35 std::string filter_str;
36 double param_a = NAN;
37 double param_b = NAN;
38
39 if (!std::regex_match(param, match, filter_regex))
40 throw std::runtime_error{ "bad filter string" };
41
42 filter_str = match[1];
43
44 if (match.size() >= 2 && match[2].length())
45 param_a = std::stod(match[2]);
46 if (match.size() >= 3 && match[3].length())
47 param_b = std::stod(match[3]);
48
49 *filter = g_resize_table[filter_str.c_str()](param_a, param_b);
50 } catch (const std::exception &e) {
51 std::cerr << e.what() << '\n';
52 return -1;
53 }
54
55 return 0;
56 }
57
58
59 struct Arguments {
60 const char *inpath;
61 const char *outpath;
62 unsigned width_in;
63 unsigned height_in;
64 unsigned width_out;
65 unsigned height_out;
66 std::unique_ptr<zimg::resize::Filter> filter;
67 double param_a;
68 double param_b;
69 double shift_w;
70 double shift_h;
71 double subwidth;
72 double subheight;
73 zimg::PixelFormat working_format;
74 const char *visualise_path;
75 unsigned times;
76 zimg::CPUClass cpu;
77 };
78
79 const ArgparseOption program_switches[] = {
80 { OPTION_UINT, "w", "width-in", offsetof(Arguments, width_in), nullptr, "image width" },
81 { OPTION_UINT, "h", "height-in", offsetof(Arguments, height_in), nullptr, "image height"},
82 { OPTION_USER1, nullptr, "filter", offsetof(Arguments, filter), decode_filter, "select resampling filter" },
83 { OPTION_FLOAT, nullptr, "shift-w", offsetof(Arguments, shift_w), nullptr, "subpixel shift" },
84 { OPTION_FLOAT, nullptr, "shift-h", offsetof(Arguments, shift_h), nullptr, "subpixel shift" },
85 { OPTION_FLOAT, nullptr, "sub-width", offsetof(Arguments, subwidth), nullptr, "active image width" },
86 { OPTION_FLOAT, nullptr, "sub-height", offsetof(Arguments, subheight), nullptr, "active image height" },
87 { OPTION_USER1, nullptr, "format", offsetof(Arguments, working_format), arg_decode_pixfmt, "working pixel format" },
88 { OPTION_STRING, nullptr, "visualise", offsetof(Arguments, visualise_path), nullptr, "path to BMP file for visualisation" },
89 { OPTION_UINT, nullptr, "times", offsetof(Arguments, times), nullptr, "number of benchmark cycles" },
90 { OPTION_USER1, nullptr, "cpu", offsetof(Arguments, cpu), arg_decode_cpu, "select CPU type" },
91 { OPTION_NULL }
92 };
93
94 const ArgparseOption program_positional[] = {
95 { OPTION_STRING, nullptr, "inpath", offsetof(Arguments, inpath), nullptr, "input path specifier" },
96 { OPTION_STRING, nullptr, "outpath", offsetof(Arguments, outpath), nullptr, "output path specifier" },
97 { OPTION_UINT, nullptr, "width-out", offsetof(Arguments, width_out), nullptr, "output width" },
98 { OPTION_UINT, nullptr, "height-out", offsetof(Arguments, height_out), nullptr, "output height" },
99 { OPTION_NULL }
100 };
101
102 const char help_str[] =
103 "Resampling filter specifier: filter[:param_a[:param_b]]\n"
104 "filter: point, bilinear, bicubic, spline16, spline36, lanczos\n"
105 "\n"
106 PIXFMT_SPECIFIER_HELP_STR
107 "\n"
108 PATH_SPECIFIER_HELP_STR;
109
110 const ArgparseCommandLine program_def = { program_switches, program_positional, "resize", "resize images", help_str };
111
112
ns_per_sample(const ImageFrame & frame,double seconds)113 double ns_per_sample(const ImageFrame &frame, double seconds)
114 {
115 double samples = static_cast<double>(static_cast<size_t>(frame.width()) * frame.height() * frame.planes());
116 return seconds * 1e9 / samples;
117 }
118
execute(const zimg::graph::ImageFilter * filter,const ImageFrame * src_frame,ImageFrame * dst_frame,unsigned times)119 void execute(const zimg::graph::ImageFilter *filter, const ImageFrame *src_frame, ImageFrame *dst_frame, unsigned times)
120 {
121 auto results = measure_benchmark(times, FilterExecutor{ filter, nullptr, src_frame, dst_frame }, [](unsigned n, double d)
122 {
123 std::cout << '#' << n << ": " << d << '\n';
124 });
125
126 std::cout << "avg: " << results.first << " (" << ns_per_sample(*dst_frame, results.first) << " ns/sample)\n";
127 std::cout << "min: " << results.second << " (" << ns_per_sample(*dst_frame, results.second) << " ns/sample)\n";
128 }
129
130 } // namespace
131
132
resize_main(int argc,char ** argv)133 int resize_main(int argc, char **argv)
134 {
135 Arguments args{};
136 int ret;
137
138 args.param_a = NAN;
139 args.param_b = NAN;
140 args.shift_w = NAN;
141 args.shift_h = NAN;
142 args.subwidth = NAN;
143 args.subheight = NAN;
144 args.times = 1;
145
146 if ((ret = argparse_parse(&program_def, &args, argc, argv)) < 0)
147 return ret == ARGPARSE_HELP_MESSAGE ? 0 : ret;
148
149 if (std::isnan(args.shift_w))
150 args.shift_w = 0.0;
151 if (std::isnan(args.shift_h))
152 args.shift_h = 0.0;
153 if (!is_set_pixel_format(args.working_format))
154 args.working_format = zimg::PixelType::FLOAT;
155
156 try {
157 ImageFrame src_frame = imageframe::read(args.inpath, "i444s", args.width_in, args.height_in, args.working_format.type, false);
158
159 if (!args.filter)
160 args.filter = g_resize_table["bicubic"](NAN, NAN);
161 if (std::isnan(args.subwidth))
162 args.subwidth = src_frame.width();
163 if (std::isnan(args.subheight))
164 args.subheight = src_frame.height();
165
166 if (src_frame.subsample_w() || src_frame.subsample_h())
167 throw std::runtime_error{ "can only resize greyscale/4:4:4 images" };
168
169 ImageFrame dst_frame{ args.width_out, args.height_out, src_frame.pixel_type(), src_frame.planes(), src_frame.is_yuv() };
170
171 auto filter_pair = zimg::resize::ResizeConversion{ src_frame.width(), src_frame.height(), src_frame.pixel_type() }
172 .set_depth(args.working_format.depth)
173 .set_filter(args.filter.get())
174 .set_dst_width(dst_frame.width())
175 .set_dst_height(dst_frame.height())
176 .set_shift_w(args.shift_w)
177 .set_shift_h(args.shift_h)
178 .set_subwidth(args.subwidth)
179 .set_subheight(args.subheight)
180 .set_cpu(args.cpu)
181 .create();
182
183 if (filter_pair.second)
184 filter_pair.first = ztd::make_unique<PairFilter>(std::move(filter_pair.first), std::move(filter_pair.second));
185
186 execute(filter_pair.first.get(), &src_frame, &dst_frame, args.times);
187
188 if (args.visualise_path)
189 imageframe::write(dst_frame, args.visualise_path, "bmp", true);
190
191 imageframe::write(dst_frame, args.outpath, "i444s", false);
192 } catch (const zimg::error::Exception &e) {
193 std::cerr << e.what() << '\n';
194 return 2;
195 } catch (const std::exception &e) {
196 std::cerr << e.what() << '\n';
197 return 2;
198 }
199
200 return 0;
201 }
202