1 /*
2  * Copyright (C) 2015 Y.Sugahara (moveccr)
3  * Copyright (C) 2021 Tetsuya Isaki
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
15  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
16  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
17  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
18  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
19  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
20  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
21  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  */
26 
27 #include "sayaka.h"
28 #include "Diag.h"
29 #include "FdStream.h"
30 #include "FileStream.h"
31 #include "HttpClient.h"
32 #include "ImageReductor.h"
33 #include "StringUtil.h"
34 #include "SixelConverter.h"
35 #include "term.h"
36 #include <array>
37 #include <chrono>
38 #include <cstring>
39 #include <map>
40 #include <memory>
41 #include <tuple>
42 #include <err.h>
43 #include <fcntl.h>
44 #include <getopt.h>
45 #include <signal.h>
46 #include <unistd.h>
47 #include <sys/utsname.h>
48 
49 using namespace std::chrono;
50 
51 enum OutputFormat {
52 	SIXEL,
53 	GVRAM,
54 	PALETTEPNG,
55 };
56 
57 Diag diag;
58 Diag diagHttp;
59 int opt_debug_sixel = 0;
60 ReductorColorMode opt_colormode = ReductorColorMode::Fixed256;
61 int opt_graylevel = 256;
62 int opt_width = 0;
63 int opt_height = 0;
64 ResizeAxisMode opt_resizeaxis = ResizeAxisMode::Both;
65 ReductorReduceMode opt_reduce = ReductorReduceMode::HighQuality;
66 bool opt_x68k = false;
67 bool opt_outputpalette = true;
68 bool opt_ignore_error = false;
69 bool opt_ormode = false;
70 bool opt_profile = false;
71 SixelResizeMode opt_resizemode = SixelResizeMode::ByLoad;
72 OutputFormat opt_outputformat = OutputFormat::SIXEL;
73 int opt_output_x = 0;
74 int opt_output_y = 0;
75 float opt_color_factor = 1.0f;
76 ReductorDiffuseMethod opt_highqualitydiffusemethod =
77 	ReductorDiffuseMethod::RDM_FS;
78 ReductorFinderMode opt_findermode = ReductorFinderMode::RFM_Default;
79 int opt_addnoise = 0;
80 int opt_address_family = AF_UNSPEC;
81 
82 enum {
83 	OPT_8 = 0x80,
84 	OPT_16,
85 	OPT_256,
86 	OPT_addnoise,
87 	OPT_axis,
88 	OPT_color_factor,
89 	OPT_debug,
90 	OPT_debug_http,
91 	OPT_debug_sixel,
92 	OPT_finder,
93 	OPT_gray,
94 	OPT_height,
95 	OPT_help,
96 	OPT_help_all,
97 	OPT_ignore_error,
98 	OPT_ipv4,
99 	OPT_ipv6,
100 	OPT_x68k,
101 	OPT_ormode,
102 	OPT_output_format,
103 	OPT_output_x,
104 	OPT_output_y,
105 	OPT_palette,
106 	OPT_profile,
107 	OPT_resize,
108 };
109 
110 static const struct option longopts[] = {
111 	{ "8",				no_argument,		NULL,	OPT_8 },
112 	{ "16",				no_argument,		NULL,	OPT_16 },
113 	{ "256",			no_argument,		NULL,	OPT_256 },
114 	{ "addnoise",		required_argument,	NULL,	OPT_addnoise },
115 	{ "axis",			required_argument,	NULL,	OPT_axis },
116 	{ "color",			required_argument,	NULL,	'p' },
117 	{ "colors",			required_argument,	NULL,	'p' },
118 	{ "color-factor",	required_argument,	NULL,	OPT_color_factor },
119 	{ "debug",			required_argument,	NULL,	OPT_debug },
120 	{ "debug-http",		required_argument,	NULL,	OPT_debug_http },
121 	{ "debug-sixel",	required_argument,	NULL,	OPT_debug_sixel },
122 	{ "diffusion",		required_argument,	NULL,	'd' },
123 	{ "finder",			required_argument,	NULL,	OPT_finder },
124 	{ "gray",			required_argument,	NULL,	OPT_gray },
125 	{ "height",			required_argument,	NULL,	'h' },
126 	{ "ignore-error",	no_argument,		NULL,	OPT_ignore_error },
127 	{ "ipv4",			no_argument,		NULL,	OPT_ipv4 },
128 	{ "ipv6",			no_argument,		NULL,	OPT_ipv6 },
129 	{ "monochrome",		no_argument,		NULL,	'e' },
130 	{ "ormode",			required_argument,	NULL,	OPT_ormode },
131 	{ "output-format",	required_argument,	NULL,	OPT_output_format },
132 	{ "output-x",		required_argument,	NULL,	OPT_output_x },
133 	{ "output-y",		required_argument,	NULL,	OPT_output_y },
134 	{ "palette",		required_argument,	NULL,	OPT_palette },
135 	{ "profile",		no_argument,		NULL,	OPT_profile },
136 	{ "resize",			required_argument,	NULL,	OPT_resize },
137 	{ "width",			no_argument,		NULL,	'w' },
138 	{ "x68k",			no_argument,		NULL,	OPT_x68k },
139 	{ "help",			no_argument,		NULL,	OPT_help },
140 	{ "help-all",		no_argument,		NULL,	OPT_help_all },
141 	{ NULL },
142 };
143 
144 std::map<const std::string, ReductorColorMode> colormode_map = {
145 	{ "8",					ReductorColorMode::Fixed8 },
146 	{ "16",					ReductorColorMode::FixedANSI16 },
147 	{ "256",				ReductorColorMode::Fixed256 },
148 	{ "256rgbi",			ReductorColorMode::Fixed256RGBI },
149 	{ "mono",				ReductorColorMode::Mono },
150 	{ "gray",				ReductorColorMode::Gray },
151 	{ "graymean",			ReductorColorMode::GrayMean },
152 	{ "x68k",				ReductorColorMode::FixedX68k },
153 };
154 
155 std::map<const std::string, ResizeAxisMode> resizeaxis_map = {
156 	{ "both",				ResizeAxisMode::Both },
157 	{ "w",					ResizeAxisMode::Width },
158 	{ "width",				ResizeAxisMode::Width },
159 	{ "h",					ResizeAxisMode::Height },
160 	{ "height",				ResizeAxisMode::Height },
161 	{ "long",				ResizeAxisMode::Long },
162 	{ "short",				ResizeAxisMode::Short },
163 	{ "sdboth",				ResizeAxisMode::ScaleDownBoth },
164 	{ "scaledown-both",		ResizeAxisMode::ScaleDownBoth },
165 	{ "sdw",				ResizeAxisMode::ScaleDownWidth },
166 	{ "sdwidth",			ResizeAxisMode::ScaleDownWidth },
167 	{ "scaledown-width",	ResizeAxisMode::ScaleDownWidth },
168 	{ "sdh",				ResizeAxisMode::ScaleDownHeight },
169 	{ "sdheight",			ResizeAxisMode::ScaleDownHeight },
170 	{ "scaledown-height",	ResizeAxisMode::ScaleDownHeight },
171 	{ "sdlong",				ResizeAxisMode::ScaleDownLong },
172 	{ "scaledown-long",		ResizeAxisMode::ScaleDownLong },
173 	{ "sdshort",			ResizeAxisMode::ScaleDownShort },
174 	{ "scaledown-short",	ResizeAxisMode::ScaleDownShort },
175 };
176 
177 std::map<const std::string, SixelResizeMode> resizemode_map = {
178 	{ "load",				SixelResizeMode::ByLoad },
179 	{ "imagereductor",		SixelResizeMode::ByImageReductor },
180 };
181 
182 std::map<const std::string, OutputFormat> outputformat_map = {
183 	{ "sixel",				OutputFormat::SIXEL },
184 	{ "gvram",				OutputFormat::GVRAM },
185 	{ "palettepng",			OutputFormat::PALETTEPNG },
186 };
187 
188 std::map<const std::string, ReductorFinderMode> findermode_map = {
189 	{ "default",			ReductorFinderMode::RFM_Default },
190 	{ "rgb",				ReductorFinderMode::RFM_Default },
191 	{ "hsv",				ReductorFinderMode::RFM_HSV },
192 };
193 
194 #define RRM ReductorReduceMode
195 #define RDM ReductorDiffuseMethod
196 std::map<const std::string, std::pair<RRM, RDM>> reduce_map = {
197 	{ "auto",		{ RRM::HighQuality,	(RDM)-1 } },
198 	{ "none",		{ RRM::Simple,		(RDM)-1 } },
199 	{ "fast",		{ RRM::Fast,		(RDM)-1 } },
200 	{ "high",		{ RRM::HighQuality,	(RDM)-1 } },
201 	{ "fs",			{ RRM::HighQuality,	RDM::RDM_FS } },
202 	{ "atkinson",	{ RRM::HighQuality,	RDM::RDM_ATKINSON } },
203 	{ "jajuni",		{ RRM::HighQuality,	RDM::RDM_JAJUNI } },
204 	{ "stucki",		{ RRM::HighQuality,	RDM::RDM_STUCKI } },
205 	{ "burkes",		{ RRM::HighQuality,	RDM::RDM_BURKES } },
206 	{ "2",			{ RRM::HighQuality,	RDM::RDM_2 } },
207 	{ "3",			{ RRM::HighQuality,	RDM::RDM_3 } },
208 	{ "rgb",		{ RRM::HighQuality,	RDM::RDM_RGB } },
209 };
210 #undef RRM
211 #undef RDM
212 
213 [[noreturn]] static void usage(bool all = false);
214 static bool optbool(const char *arg);
215 static void Convert(const std::string& filename);
216 static void ConvertFromStream(InputStream *stream);
217 static void signal_handler(int signo);
218 
219 // map から key を検索する。
220 // 見付かればその値を返し、result に true を格納する。
221 // 見付からなければ(何かを返し) result に false を返す。
222 template <typename T> T
select_opt(const std::map<const std::string,T> & map,const char * key,bool * result)223 select_opt(const std::map<const std::string, T>& map, const char *key,
224 	bool *result)
225 {
226 	if (map.find(key) == map.end()) {
227 		*result = false;
228 		return T();
229 	}
230 	*result = true;
231 	return map.at(key);
232 }
233 
main(int ac,char * av[])234 int main(int ac, char *av[])
235 {
236 	struct utsname ut;
237 	int c;
238 	int val;
239 	bool res;
240 
241 	diag.SetClassname("sixelv");
242 	diagHttp.SetClassname("HttpClient");
243 
244 	// X68k なら、デフォルトで --x68k 相当にする。
245 	uname(&ut);
246 	if (strcmp(ut.machine, "x68k") == 0) {
247 		opt_colormode = ReductorColorMode::FixedX68k;
248 		opt_ormode = true;
249 		opt_outputpalette = false;
250 	}
251 
252 	while ((c = getopt_long(ac, av, "d:eh:p:w:", longopts, NULL)) != -1) {
253 		switch (c) {
254 		 case OPT_debug:
255 			val = stou32def(optarg, -1);
256 			if (val < 0 || val > 2) {
257 				errx(1, "--debug %s: debug level must be 0..2", optarg);
258 			}
259 			diag.SetLevel(val);
260 			break;
261 
262 		 case OPT_debug_http:
263 			val = stou32def(optarg, -1);
264 			if (val < 0 || val > 2) {
265 				errx(1, "--debug-http %s: debug level must be 0..2", optarg);
266 			}
267 			diagHttp.SetLevel(val);
268 			break;
269 
270 		 case OPT_debug_sixel:
271 			val = stou32def(optarg, -1);
272 			if (val < 0 || val > 2) {
273 				errx(1, "--debug-sixel %s: debug level must be 0..2", optarg);
274 			}
275 			opt_debug_sixel = val;
276 			break;
277 
278 		 case 'e':
279 			opt_colormode = ReductorColorMode::Mono;
280 			break;
281 
282 		 case OPT_gray:
283 			opt_graylevel = stou32def(optarg, 0);
284 			if (opt_graylevel < 2 || opt_graylevel > 256) {
285 				errx(1, "--gray %s: grayscale must be 2..256", optarg);
286 			}
287 			opt_colormode = ReductorColorMode::Gray;
288 			break;
289 
290 		 case OPT_profile:
291 			opt_profile = true;
292 			break;
293 
294 		 case 'p':
295 			opt_colormode = select_opt(colormode_map, optarg, &res);
296 			if (res == false) {
297 				errx(1, "--color %s: invalid parameter", optarg);
298 			}
299 			break;
300 
301 		 case OPT_8:
302 			opt_colormode = ReductorColorMode::Fixed8;
303 			break;
304 		 case OPT_16:
305 			opt_colormode = ReductorColorMode::FixedANSI16;
306 			break;
307 		 case OPT_256:
308 			opt_colormode = ReductorColorMode::Fixed256;
309 			break;
310 
311 		 case 'w':
312 			opt_width = stou32def(optarg, -1);
313 			if (opt_width < 0) {
314 				errno = EINVAL;
315 				err(1, "--width %s", optarg);
316 			}
317 			break;
318 		 case 'h':
319 			opt_height = stou32def(optarg, -1);
320 			if (opt_height < 0) {
321 				errno = EINVAL;
322 				err(1, "--height %s", optarg);
323 			}
324 			break;
325 
326 		 case OPT_axis:
327 			opt_resizeaxis = select_opt(resizeaxis_map, optarg, &res);
328 			if (res == false) {
329 				errx(1, "--axis %s: invalid parameter", optarg);
330 			}
331 			break;
332 
333 		 case 'd':
334 		 {
335 			auto [ reduce, diffuse ] = select_opt(reduce_map, optarg, &res);
336 			if (res == false) {
337 				errx(1, "--diffusion %s: invalid parameter", optarg);
338 			}
339 			opt_reduce = reduce;
340 			if (diffuse != (ReductorDiffuseMethod)-1) {
341 				opt_highqualitydiffusemethod = diffuse;
342 			}
343 			break;
344 		 }
345 
346 		 case OPT_x68k:
347 			opt_colormode = ReductorColorMode::FixedX68k;
348 			opt_ormode = true;
349 			opt_outputpalette = false;
350 			break;
351 
352 		 case OPT_ignore_error:
353 			opt_ignore_error = true;
354 			break;
355 
356 		 case OPT_ipv4:
357 			opt_address_family = AF_INET;
358 			break;
359 
360 		 case OPT_ipv6:
361 			opt_address_family = AF_INET6;
362 			break;
363 
364 		 case OPT_ormode:
365 			opt_ormode = optbool(optarg);
366 			break;
367 
368 		 case OPT_palette:
369 			opt_outputpalette = optbool(optarg);
370 			break;
371 
372 		 case OPT_resize:
373 			opt_resizemode = select_opt(resizemode_map, optarg, &res);
374 			if (res == false) {
375 				errx(1, "--resize %s: invalid parameter", optarg);
376 			}
377 			break;
378 
379 		 case OPT_output_format:
380 			opt_outputformat = select_opt(outputformat_map, optarg, &res);
381 			if (res == false) {
382 				errx(1, "--output-format %s: must be either 'sixel' or 'gvram'",
383 					optarg);
384 			}
385 			break;
386 
387 		 case OPT_output_x:
388 			opt_output_x = stou32def(optarg, -1);
389 			if (opt_output_x < 0) {
390 				errx(1, "--output-x %s: offset must be >= 0", optarg);
391 			}
392 			break;
393 		 case OPT_output_y:
394 			opt_output_y = stou32def(optarg, -1);
395 			if (opt_output_y < 0) {
396 				errx(1, "--output-y %s: offset must be >= 0", optarg);
397 			}
398 			break;
399 
400 		 case OPT_color_factor:
401 		 {
402 			char *end;
403 
404 			errno = 0;
405 			opt_color_factor = strtof(optarg, &end);
406 			if (end == optarg || *end != '\0') {
407 				errno = EINVAL;
408 			}
409 			if (opt_color_factor < 0) {
410 				errno = EINVAL;
411 			}
412 			if (errno) {
413 				err(1, "--color-factor %s", optarg);
414 			}
415 			break;
416 		 }
417 
418 		 case OPT_finder:
419 			opt_findermode = select_opt(findermode_map, optarg, &res);
420 			if (res == false) {
421 				errx(1, "--finder %s: must be either 'rgb' or 'hsv'", optarg);
422 			}
423 			break;
424 
425 		 case OPT_addnoise:
426 			opt_addnoise = stou32def(optarg, -1);
427 			if (opt_addnoise < 0) {
428 				errno = EINVAL;
429 				err(1, "--addnoise %s", optarg);
430 			}
431 			break;
432 
433 		 case OPT_help_all:
434 			usage(true);
435 			break;
436 
437 		 default:
438 		 case OPT_help:
439 			usage(false);
440 			break;
441 		}
442 	}
443 	ac -= optind;
444 	av += optind;
445 
446 	int nfiles;
447 	for (nfiles = 0; nfiles < ac; nfiles++) {
448 		if (nfiles > 0)
449 			printf("\n");
450 		Convert(av[nfiles]);
451 	}
452 	if (nfiles == 0) {
453 		if (!opt_ignore_error) {
454 			errx(1, "No input files");
455 		}
456 	}
457 	return 0;
458 }
459 
460 // 引数をブール値にして返す
461 static bool
optbool(const char * arg_)462 optbool(const char *arg_)
463 {
464 	std::string arg(arg_);
465 
466 	if (arg == "yes" || arg == "on" || arg == "true") {
467 		return true;
468 	}
469 	return false;
470 }
471 
472 static const char short_help[] = R"**(
473    -p <color>, --color[s]=<color> : Select color mode (default: 256)
474     <color> := 8, 16, 256, 256rgbi, mono, gray, graymean, x68k
475    -8, -16, -256      : Shortcut for -p 8, -p 16, -p 256
476    -e, --monochrome   : Shortcut for -p mono
477    --gray=<graylevel> : Specify grayscale tone from 2 to 256 (default: 256)
478    -w <width>         : Resize width to <width> pixel.
479    -h <height>        : Resize height to <height> pixel.
480    -d <type>, --diffusion=<type>  : Select diffuse algorithm (default: high)
481     <type> := none, fast, high(=fs), auto(=high)
482               fs, atkinson, jajuni, stucki, burkes, 2, 3, rgb
483    --axis={both, w, width, h, height, long, short}
484    --ignore-error                   --debug       <0..2>
485    --profile                        --debug-http  <0..2>
486    --help-all                       --debug-sixel <0..2>
487 )**";
488 
489 static const char long_help[] = R"**(
490  color options
491    -p <color>, --color[s]=<color> : Select color mode (default: 256)
492        8        : Fixed 8 colors
493        16       : Fixed 16 colors
494        256      : Fixed 256 colors (MSX SCREEN 8 compatible palette)
495        256rgbi  : Fixed 256 colors (R2G2B2I2 palette)
496        mono     : monochrome (1bit)
497        gray     : grayscale with NTSC intensity
498        graymean : grayscale with mean of RGB
499        x68k     : Fixed x68k 16 color palette
500    -8, -16, -256   : Shortcut for -p 8, -p 16, -p 256
501    -e, --monochrome: Shortcut for -p mono
502    --gray=<graylevel> : Specify grayscale tone from 2 to 256 (default: 256)
503 
504  size options
505    -w <width>, --width=<width>:    Resize width to <width> pixel.
506    -h <height>, --height=<height>: Resize height to <height> pixel.
507    --resize={load, imagereductor}
508                    : Select resize algorighm (for debug).
509 
510  algorithm options
511    -d <type>, --diffusion=<type>: Select diffuse algorithm. (default: high)
512        none : Simple (No diffuse)   fs       : Floyd Steinberg
513        fast : Fast algorithm        atkinson : Atkinson
514        high : one of 2D Diffusion   jajuni   : Jarvis, Judice, Ninke
515               algorithm listed      stucki   : Stucki
516               right column.         burkes   : Burkes
517               (default: fs)         2        : 2pixels (right, down)
518        auto : alias to high         3        : 3pixels (right, down, rightdown)
519                                     rgb      : for debug
520 
521  misc options
522    --x68k             : alias to "-p x68k --ormode=on --palette=off"
523    --ormode={on|off}  : Output OR-mode SIXEL. (default: off)
524    --palette={on|off} : Output palette definition (default: on)
525    --output-format={sixel, gvram}: Select output format (default: sixel)
526    --output-x=<xoffset>, --output-y=<yoffset>
527                       : Specify X, Y offset for gvram format file.
528    --ipv4, --ipv6
529    --ignore-error
530    --axis={both, w, width, h, height, long, short}
531    --color-factor=<factor>
532    --finder={rgb, hsv} (default: rgb)
533    --addnoise=<noiselevel>
534    --debug       <0..2>
535    --debug-http  <0..2>
536    --debug-sixel <0..2>
537    --profile
538    --help, --help-all
539 )**";
540 
541 static void
usage(bool all)542 usage(bool all)
543 {
544 	fprintf(stderr, "usage: sixelv [<options>] <file>...\n");
545 	// 定義文字列は見やすさのため先頭に改行を入れてあるのでこっちで取り除く
546 	if (all) {
547 		fprintf(stderr, "%s", long_help + 1);
548 	} else {
549 		fprintf(stderr, "%s", short_help + 1);
550 	}
551 	exit(1);
552 }
553 
554 // プロファイルID
555 enum {
556 	Profile_Start,
557 	Profile_Create,
558 	Profile_Load,
559 	Profile_Convert,
560 	Profile_Output,
561 	Profile_Max,
562 };
563 static const char *profile_name[] = {
564 	"(Start)",
565 	"Create",
566 	"Load",
567 	"Convert",
568 	"Output",
569 };
570 
571 static void
Convert(const std::string & filename)572 Convert(const std::string& filename)
573 {
574 	// ソース別にストリームを作成
575 	if (filename == "-") {
576 		Debug(diag, "Loading stdin");
577 		FdInputStream stream(STDIN_FILENO, false);
578 		ConvertFromStream(&stream);
579 
580 	} else if (filename.find("://") != std::string::npos) {
581 		Debug(diag, "Downloading %s", filename.c_str());
582 		HttpClient file;
583 		file.user_agent = "sixelv";
584 		if (file.Init(diagHttp, filename) == false) {
585 			warn("File error: %s", filename.c_str());
586 			if (opt_ignore_error) {
587 				return;
588 			}
589 			exit(1);
590 		}
591 		file.family = opt_address_family;
592 		InputStream *stream = file.GET();
593 		ConvertFromStream(stream);
594 
595 	} else {
596 		Debug(diag, "Loading %s", filename.c_str());
597 		int fd = open(filename.c_str(), O_RDONLY);
598 		if (fd < 0) {
599 			warn("File load error: %s", filename.c_str());
600 			if (opt_ignore_error) {
601 				return;
602 			}
603 			exit(1);
604 		}
605 		FdInputStream stream(fd, true);
606 		ConvertFromStream(&stream);
607 	}
608 }
609 
610 static void
ConvertFromStream(InputStream * istream)611 ConvertFromStream(InputStream *istream)
612 {
613 	// プロファイル時間
614 	time_point<system_clock> prof[Profile_Max];
615 
616 	if (opt_profile) {
617 		prof[Profile_Start] = system_clock::now();
618 	}
619 
620 	SixelConverter sx(opt_debug_sixel);
621 	ImageReductor& ir = sx.GetImageReductor();
622 
623 	// SixelConverter モード設定
624 	sx.ColorMode = opt_colormode;
625 	sx.ReduceMode = opt_reduce;
626 	sx.ResizeMode = opt_resizemode;
627 	sx.OutputPalette = opt_outputpalette;
628 	sx.GrayCount = opt_graylevel;
629 	sx.FinderMode = opt_findermode;
630 	sx.AddNoiseLevel = opt_addnoise;
631 	sx.ResizeWidth = opt_width;
632 	sx.ResizeHeight = opt_height;
633 	sx.ResizeAxis = opt_resizeaxis;
634 
635 	ir.HighQualityDiffuseMethod = opt_highqualitydiffusemethod;
636 
637 	if (opt_ormode) {
638 		sx.OutputMode = SixelOutputMode::Or;
639 	} else {
640 		sx.OutputMode = SixelOutputMode::Normal;
641 	}
642 
643 	if (opt_profile) {
644 		prof[Profile_Create] = system_clock::now();
645 	}
646 
647 	if (sx.LoadFromStream(istream) == false) {
648 		warnx("Load error");
649 		if (opt_ignore_error) {
650 			return;
651 		}
652 		exit(1);
653 	}
654 
655 	if (opt_profile) {
656 		prof[Profile_Load] = system_clock::now();
657 	}
658 
659 	if (diag >= 1) {
660 		// これ以上詳しいパラメータは --debug-sixel 1 で SixelConverter 側で
661 		// 表示できているので、ここでは不要。
662 		std::string s = string_format("Converting axis=%s",
663 			ImageReductor::RAX2str(opt_resizeaxis));
664 		if (opt_width != 0 || opt_height != 0)
665 			s += string_format(" width=%d height=%d", opt_width, opt_height);
666 		diag.Print("%s", s.c_str());
667 	}
668 	sx.ConvertToIndexed();
669 
670 	if (opt_profile) {
671 		prof[Profile_Convert] = system_clock::now();
672 	}
673 
674 	if (opt_color_factor != 1.0) {
675 		ir.ColorFactor(opt_color_factor);
676 	}
677 
678 	switch (opt_outputformat) {
679 	 case OutputFormat::SIXEL:
680 	 {
681 		signal(SIGINT, signal_handler);
682 		FileOutputStream stream(stdout, false);
683 		sx.SixelToStream(&stream);
684 		stream.Flush();
685 		break;
686 	 }
687 	 case OutputFormat::GVRAM:
688 	 {
689 		if (opt_output_y + sx.GetHeight() > 512) {
690 			warnx("Image height %d is larger than GVRAM",
691 				opt_output_y + sx.GetHeight());
692 			return;
693 		}
694 		if (ir.GetPaletteCount() <= 16) {
695 			if (opt_output_x + sx.GetWidth() > 1024) {
696 				warnx("Image width %d is larger than 16-color mode GVRAM",
697 					opt_output_x + sx.GetWidth());
698 				return;
699 			}
700 		} else {
701 			if (opt_output_x + sx.GetWidth() > 512) {
702 				warnx("Image width %d is larger than 256-color mode GVRAM",
703 					opt_output_x + sx.GetWidth());
704 				return;
705 			}
706 		}
707 
708 		std::vector<uint8_t> buf;
709 		union {
710 			uint16_t w;
711 			uint8_t b[2];
712 		} data;
713 		// バージョン番号 0x0001 in BE
714 		data.w = htobe16(0x0001);
715 		buf.emplace_back(data.b[0]);
716 		buf.emplace_back(data.b[1]);
717 
718 		// パレット数
719 		data.w = htobe16(ir.GetPaletteCount());
720 		buf.emplace_back(data.b[0]);
721 		buf.emplace_back(data.b[1]);
722 
723 		// X68k パレットを作る
724 		for (int i = 0; i < ir.GetPaletteCount(); i++) {
725 			auto col = ir.GetPalette(i);
726 			uint16_t r = col.r >> 3;
727 			uint16_t g = col.g >> 3;
728 			uint16_t b = col.b >> 3;
729 			uint I = (col.r & 0x7) + (col.g & 0x7) + (col.b & 0x7);
730 
731 			uint8_t h = g << 3 | r >> 2;
732 			uint8_t l = (r << 6 | b << 1) | (I > (21 / 2) ? 1 : 0);
733 			buf.emplace_back(h);
734 			buf.emplace_back(l);
735 		}
736 		// x, y, w, h を BE data で出す
737 		data.w = opt_output_x;
738 		buf.emplace_back(data.b[0]);
739 		buf.emplace_back(data.b[1]);
740 		data.w = opt_output_y;
741 		buf.emplace_back(data.b[0]);
742 		buf.emplace_back(data.b[1]);
743 		data.w = sx.GetWidth();
744 		buf.emplace_back(data.b[0]);
745 		buf.emplace_back(data.b[1]);
746 		data.w = sx.GetHeight();
747 		buf.emplace_back(data.b[0]);
748 		buf.emplace_back(data.b[1]);
749 
750 		fwrite(buf.data(), 1, buf.size(), stdout);
751 
752 		// GVRAM データを作る
753 		fwrite(sx.Indexed.data(), 1, sx.Indexed.size(), stdout);
754 		break;
755 	 }
756 
757 	 case OutputFormat::PALETTEPNG:
758 	 {
759 #if 0
760 		// 11 x 11 はどうなのかとか。img2sixel 合わせだが、
761 		// img2sixel 側の問題でうまくいかない。
762 		const int width = ir.GetPaletteCount() * 11;
763 		const int height = 11;
764 		const int channels = 3;
765 		const int stride = width * channels;	// ?
766 		Image img(width, height, stride, channels);
767 		uint8 *p = img.GetBuf();
768 		for (int y = 0; y < height; y++) {
769 			for (int i = 0; i < ir.GetPaletteCount(); i++) {
770 				auto col = ir.GetPalette(i);
771 				for (int x = 0; x < width; x++) {
772 					p[0] = col.r;
773 					p[1] = col.g;
774 					p[2] = col.b;
775 					p += 3;
776 				}
777 			}
778 		}
779 		// XXX PNG に出力
780 #else
781 		warn("output-format=palettepng is not supported");
782 #endif
783 		break;
784 	 }
785 	}
786 
787 	if (opt_profile) {
788 		prof[Profile_Output] = system_clock::now();
789 	}
790 
791 	if (opt_profile) {
792 		double usec;
793 		for (int i = 1; i < Profile_Max; i++) {
794 			usec = duration_cast<microseconds>(prof[i] - prof[i - 1]).count();
795 			fprintf(stderr, "%-7s %.3fms\n", profile_name[i], usec / 1000);
796 		}
797 
798 		auto& start = prof[Profile_Start];
799 		auto& end   = prof[Profile_Output];
800 		usec = duration_cast<microseconds>(end - start).count();
801 		fprintf(stderr, "Total   %.3fms\n", usec / 1000);
802 	}
803 }
804 
805 static void
signal_handler(int signo)806 signal_handler(int signo)
807 {
808 	switch (signo) {
809 	 case SIGINT:
810 		// SIXEL 出力を中断する (CAN + ST)
811 		printf(CAN ESC "\\");
812 		fflush(stdout);
813 		break;
814 	}
815 }
816