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