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 "FileStream.h"
28 #include "Image.h"
29 #include "ImageLoaderJPEG.h"
30 #include "ImageLoaderPNG.h"
31 #include "SixelConverter.h"
32 #include "StringUtil.h"
33 #include "sayaka.h"
34 #include <cassert>
35 #include <cstring>
36 #include <errno.h>
37
38 // コンストラクタ
SixelConverter()39 SixelConverter::SixelConverter()
40 {
41 diag.SetClassname("SixelConverter");
42 }
43
44 // コンストラクタ
SixelConverter(int debuglv)45 SixelConverter::SixelConverter(int debuglv)
46 : SixelConverter()
47 {
48 diag.SetLevel(debuglv);
49 ir.Init(diag);
50 }
51
52 // stream から画像を img に読み込む。
53 // 成功すれば true、失敗すれば false を返す。
54 bool
LoadFromStream(InputStream * stream)55 SixelConverter::LoadFromStream(InputStream *stream)
56 {
57 Debug(diag, "ResizeMode=%s", SRM2str(ResizeMode));
58
59 {
60 ImageLoaderJPEG loader(stream, diag);
61 if (loader.Check()) {
62 Trace(diag, "%s filetype is JPEG", __func__);
63 if (loader.Load(img)) {
64 LoadAfter();
65 return true;
66 }
67 return false;
68 }
69 }
70 {
71 ImageLoaderPNG loader(stream, diag);
72 if (loader.Check()) {
73 Trace(diag, "%s filetype is PNG", __func__);
74 if (loader.Load(img)) {
75 LoadAfter();
76 return true;
77 }
78 return false;
79 }
80 }
81
82 printf("Unknown picture format");
83 return false;
84 }
85
86 void
LoadAfter()87 SixelConverter::LoadAfter()
88 {
89 Width = img.GetWidth();
90 Height = img.GetHeight();
91
92 Debug(diag, "Loaded size=(%d,%d) bits=%d nCh=%d rowstride=%d",
93 Width,
94 Height,
95 img.GetChDepth(),
96 img.GetChannels(),
97 img.GetStride());
98 }
99
100
101 // リサイズ計算
102 void
CalcResize(int * widthp,int * heightp)103 SixelConverter::CalcResize(int *widthp, int *heightp)
104 {
105 int& width = *widthp;
106 int& height = *heightp;
107
108 auto ra = ResizeAxis;
109 bool scaledown =
110 (ra == ResizeAxisMode::ScaleDownBoth)
111 || (ra == ResizeAxisMode::ScaleDownWidth)
112 || (ra == ResizeAxisMode::ScaleDownHeight)
113 || (ra == ResizeAxisMode::ScaleDownLong)
114 || (ra == ResizeAxisMode::ScaleDownShort);
115
116 // 条件を丸めていく
117 switch (ra) {
118 case ResizeAxisMode::Both:
119 case ResizeAxisMode::ScaleDownBoth:
120 if (ResizeWidth == 0) {
121 ra = ResizeAxisMode::Height;
122 } else if (ResizeHeight == 0) {
123 ra = ResizeAxisMode::Width;
124 } else {
125 ra = ResizeAxisMode::Both;
126 }
127 break;
128
129 case ResizeAxisMode::Long:
130 case ResizeAxisMode::ScaleDownLong:
131 if (Width >= Height) {
132 ra = ResizeAxisMode::Width;
133 } else {
134 ra = ResizeAxisMode::Height;
135 }
136 break;
137
138 case ResizeAxisMode::Short:
139 case ResizeAxisMode::ScaleDownShort:
140 if (Width <= Height) {
141 ra = ResizeAxisMode::Width;
142 } else {
143 ra = ResizeAxisMode::Height;
144 }
145 break;
146
147 case ResizeAxisMode::ScaleDownWidth:
148 ra = ResizeAxisMode::Width;
149 break;
150
151 case ResizeAxisMode::ScaleDownHeight:
152 ra = ResizeAxisMode::Height;
153 break;
154
155 default:
156 __builtin_unreachable();
157 break;
158 }
159
160 auto rw = ResizeWidth;
161 auto rh = ResizeHeight;
162
163 if (rw <= 0)
164 rw = Width;
165 if (rh <= 0)
166 rh = Height;
167
168 // 縮小のみ指示
169 if (scaledown) {
170 if (Width < rw)
171 rw = Width;
172 if (Height < rh)
173 rh = Height;
174 }
175
176 // 確定したので計算
177 switch (ra) {
178 case ResizeAxisMode::Both:
179 width = rw;
180 height = rh;
181 break;
182 case ResizeAxisMode::Width:
183 width = rw;
184 height = Height * width / Width;
185 break;
186 case ResizeAxisMode::Height:
187 height = rh;
188 width = Width * height / Height;
189 break;
190 default:
191 __builtin_unreachable();
192 break;
193 }
194 }
195
196 //
197 // ----- 前処理
198 //
199
200 // インデックスカラーに変換する。
201 void
ConvertToIndexed()202 SixelConverter::ConvertToIndexed()
203 {
204 // リサイズ
205 int width = 0;
206 int height = 0;
207 CalcResize(&width, &height);
208
209 Debug(diag, "Resize to (%d,%d)", width, height);
210
211 Width = width;
212 Height = height;
213
214 Indexed.resize(Width * Height);
215
216 Debug(diag, "SetColorMode(%s, %s, %d)",
217 ImageReductor::RCM2str(ColorMode),
218 ImageReductor::RFM2str(FinderMode),
219 GrayCount);
220 ir.SetColorMode(ColorMode, FinderMode, GrayCount);
221
222 Debug(diag, "SetAddNoiseLevel=%d", AddNoiseLevel);
223 ir.SetAddNoiseLevel(AddNoiseLevel);
224
225 ir.Convert(ReduceMode, img, Indexed, Width, Height);
226 Trace(diag, "Converted");
227 }
228
229 //
230 // ----- Sixel 出力
231 //
232
233 #define ESC "\x1b"
234 #define DCS ESC "P"
235
236 // Sixel の開始コードとパレットを文字列で返す。
237 std::string
SixelPreamble()238 SixelConverter::SixelPreamble()
239 {
240 std::string linebuf;
241
242 // Sixel 開始コード
243 linebuf += DCS;
244 linebuf += string_format("7;%d;q\"1;1;%d;%d", OutputMode, Width, Height);
245
246 // パレットを出力
247 if (OutputPalette) {
248 for (int i = 0; i < ir.GetPaletteCount(); i++) {
249 const auto& col = ir.GetPalette(i);
250 linebuf += string_format("#%d;%d;%d;%d;%d", i, 2,
251 col.r * 100 / 255,
252 col.g * 100 / 255,
253 col.b * 100 / 255);
254 }
255 }
256
257 return linebuf;
258 }
259
260 static int
MyLog2(int n)261 MyLog2(int n)
262 {
263 for (int i = 0; i < 8; i++) {
264 if (n <= (1 << i)) {
265 return i;
266 }
267 }
268 return 8;
269 }
270
271 // OR モードで Sixel コア部分を stream に出力する。
272 // 成功すれば true、(書き込みに)失敗すれば false を返す。
273 bool
SixelToStreamCore_ORmode(OutputStream * stream)274 SixelConverter::SixelToStreamCore_ORmode(OutputStream *stream)
275 {
276 uint8 *p0 = Indexed.data();
277 int w = Width;
278 int h = Height;
279 int n;
280
281 // パレットのビット数
282 int bcnt = MyLog2(ir.GetPaletteCount());
283 Debug(diag, "%s bcnt=%d\n", __func__, bcnt);
284
285 uint8 sixelbuf[(w + 5) * bcnt];
286
287 uint8 *p = p0;
288 int y;
289 // 一つ手前の SIXEL 行まで変換
290 for (y = 0; y < h - 6; y += 6) {
291 int len = sixel_image_to_sixel_h6_ormode(sixelbuf, p, w, 6, bcnt);
292 n = stream->Write(sixelbuf, len);
293 if (n < len) {
294 return false;
295 }
296 p += w * 6;
297 }
298 // 最終 SIXEL 行を変換
299 int len = sixel_image_to_sixel_h6_ormode(sixelbuf, p, w, h - y, bcnt);
300 n = stream->Write(sixelbuf, len);
301 if (n < len) {
302 return false;
303 }
304 return true;
305 }
306
307 // Sixel コア部分を stream に出力する。
308 // 成功すれば true、(書き込みに)失敗すれば false を返す。
309 bool
SixelToStreamCore(OutputStream * stream)310 SixelConverter::SixelToStreamCore(OutputStream *stream)
311 {
312 // 030 ターゲット
313
314 uint8 *p0 = Indexed.data();
315 int w = Width;
316 int h = Height;
317 int src = 0;
318
319 int PaletteCount = ir.GetPaletteCount();
320 Debug(diag, "%s Output=Normal PaletteCount=%d", __func__, PaletteCount);
321
322 // カラー番号ごとの、X 座標の min, max を計算する。
323 // short でいいよね…
324 int16 min_x[PaletteCount];
325 int16 max_x[PaletteCount];
326
327 for (int16 y = 0; y < h; y += 6) {
328 std::string linebuf;
329
330 src = y * w;
331
332 memset(min_x, -1, sizeof(min_x));
333 memset(max_x, 0, sizeof(max_x));
334
335 // h が 6 の倍数でない時には溢れてしまうので、上界を計算する
336 int16 max_dy = 6;
337 if (y + max_dy > h) {
338 max_dy = (int16)(h - y);
339 }
340
341 // 各カラーの X 座標範囲を計算する
342 for (int16 dy = 0; dy < max_dy; dy++) {
343 for (int16 x = 0; x < w; x++) {
344 uint8 I = p0[src++];
345 if (min_x[I] < 0 || min_x[I] > x)
346 min_x[I] = x;
347 if (max_x[I] < x)
348 max_x[I] = x;
349 }
350 }
351
352 for (;;) {
353 // 出力するべきカラーがなくなるまでのループ
354 Verbose(diag, "for1");
355 int16 mx = -1;
356
357 for (;;) {
358 // 1行の出力で出力できるカラーのループ
359 Verbose(diag, "for2");
360
361 uint8 min_color = 0;
362 int16 min = INT16_MAX;
363
364 // min_x から、mx より大きいもののうち最小のカラーを探して、
365 // 塗っていく
366 for (int16 c = 0; c < PaletteCount; c++) {
367 if (mx < min_x[c] && min_x[c] < min) {
368 min_color = (uint8)c;
369 min = min_x[c];
370 }
371 }
372 // なければ抜ける
373 if (min_x[min_color] <= mx) {
374 break;
375 }
376
377 // Sixel に色コードを出力
378 linebuf += string_format("#%d", min_color);
379
380 // 相対 X シーク処理
381 int16 space = min_x[min_color] - (mx + 1);
382 if (space > 0) {
383 linebuf += SixelRepunit(space, 0);
384 }
385
386 // パターンが変わったら、それまでのパターンを出していく
387 // アルゴリズム
388 uint8 prev_t = 0;
389 int16 n = 0;
390 for (int16 x = min_x[min_color]; x <= max_x[min_color]; x++) {
391 uint8 t = 0;
392 for (int16 dy = 0; dy < max_dy; dy++) {
393 uint8 I = p0[(y + dy) * w + x];
394 if (I == min_color) {
395 t |= 1 << dy;
396 }
397 }
398
399 if (prev_t != t) {
400 if (n > 0) {
401 linebuf += SixelRepunit(n, prev_t);
402 }
403 prev_t = t;
404 n = 1;
405 } else {
406 n++;
407 }
408 }
409 // 最後のパターン
410 if (prev_t != 0 && n > 0) {
411 linebuf += SixelRepunit(n, prev_t);
412 }
413
414 // X 位置を更新
415 mx = max_x[min_color];
416 // 済んだ印
417 min_x[min_color] = -1;
418 }
419
420 linebuf += '$';
421
422 // 最後までやったら抜ける
423 if (mx == -1)
424 break;
425 }
426
427 linebuf += '-';
428
429 if (stream->Write(linebuf) == false) {
430 return false;
431 }
432 }
433 return true;
434 }
435
436 // Sixel の終了コードを文字列で返す
437 std::string
SixelPostamble()438 SixelConverter::SixelPostamble()
439 {
440 return ESC "\\";
441 }
442
443 // Sixel を stream に出力する。
444 // 成功すれば true、失敗すれば false を返す。
445 bool
SixelToStream(OutputStream * stream)446 SixelConverter::SixelToStream(OutputStream *stream)
447 {
448 Trace(diag, "%s", __func__);
449 assert(ir.GetPaletteCount() != 0);
450
451 // 開始コードとかの出力
452 if (stream->Write(SixelPreamble()) == false) {
453 return false;
454 }
455
456 if (OutputMode == SixelOutputMode::Or) {
457 if (SixelToStreamCore_ORmode(stream) == false) {
458 return false;
459 }
460 } else {
461 if (SixelToStreamCore(stream) == false) {
462 return false;
463 }
464 }
465
466 if (stream->Write(SixelPostamble()) == false) {
467 return false;
468 }
469 return true;
470 }
471
472 // 繰り返しのコードを考慮して、Sixel パターン文字列を返す
473 /*static*/ std::string
SixelRepunit(int n,uint8 ptn)474 SixelConverter::SixelRepunit(int n, uint8 ptn)
475 {
476 std::string v;
477
478 if (n >= 4) {
479 v = string_format("!%d%c", n, ptn + 0x3f);
480 } else {
481 v = std::string(n, ptn + 0x3f);
482 }
483 return v;
484 }
485
486 //
487 // enum を文字列にしたやつ orz
488 //
489
490 static const char *SRM2str_[] = {
491 "ByLoad",
492 "ByImageReductor",
493 };
494
495 /*static*/ const char *
SOM2str(SixelOutputMode val)496 SixelConverter::SOM2str(SixelOutputMode val)
497 {
498 if (val == Normal) return "Normal";
499 if (val == Or) return "Or";
500 return "?";
501 }
502
503 /*static*/ const char *
SRM2str(SixelResizeMode val)504 SixelConverter::SRM2str(SixelResizeMode val)
505 {
506 return ::SRM2str_[(int)val];
507 }
508