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