1 /*
2 * Copyright (C) 2016 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 "ImageReductor.h"
28 #include <cstdio>
29 #include <cstdlib>
30 #include <cstring>
31 #include <limits.h>
32
33 #ifndef __packed
34 #define __packed __attribute__((__packed__))
35 #endif
36
37 //
38 // 分数計算機
39 // DDA 計算の基礎となる I + N / D 型の分数ステップ加減算計算機。
40 //
41 struct StepRational
42 {
43 int I; // 整数項
44 int N; // 分子
45 int D; // 分母
46 };
47
48 static StepRational
StepRationalCreate(int i,int n,int d)49 StepRationalCreate(int i, int n, int d)
50 {
51 StepRational rv;
52 rv.I = i;
53 if (n < d) {
54 rv.N = n;
55 } else {
56 rv.I += n / d;
57 rv.N = n % d;
58 }
59 rv.D = d;
60 return rv;
61 }
62
63 static void
StepRationalAdd(StepRational * sr,StepRational * x)64 StepRationalAdd(StepRational *sr, StepRational *x)
65 {
66 sr->I += x->I;
67 sr->N += x->N;
68 if (sr->N < 0) {
69 sr->I--;
70 sr->N += sr->D;
71 } else if (sr->N >= sr->D) {
72 sr->I++;
73 sr->N -= sr->D;
74 }
75 }
76
77
78 //
79 // ImageReductor
80 //
81
82 // 初期化
83 void
Init(const Diag & diag_)84 ImageReductor::Init(const Diag& diag_)
85 {
86 diag = diag_;
87 }
88
89 //
90 // パレット
91 //
92
93 // 固定 2 色白黒パレット
94 /*static*/ const ColorRGBuint8
95 ImageReductor::Palette_Mono[] = {
96 { 0, 0, 0 },
97 { 255, 255, 255 },
98 };
99
100 // 色 c を固定 2 色白黒パレットコードへを変換する。
101 int
FindColor_Mono(ColorRGBuint8 c)102 ImageReductor::FindColor_Mono(ColorRGBuint8 c)
103 {
104 return ((int)c.r + (int)c.g + (int)c.b > 128 * 3);
105 }
106
107 // count 階調のグレースケールパレットを設定する。
108 void
SetPalette_Gray(int count)109 ImageReductor::SetPalette_Gray(int count)
110 {
111 Palette = Palette_Custom;
112 PaletteCount = count;
113 for (int i = 0; i < count; i++) {
114 uint8_t c = i * 255 / (count - 1);
115 Palette_Custom[i].r = c;
116 Palette_Custom[i].g = c;
117 Palette_Custom[i].b = c;
118 }
119 }
120
121 // グレースケールパレット時に、NTSC 輝度が最も近いパレット番号を返す。
122 int
FindColor_Gray(ColorRGBuint8 c)123 ImageReductor::FindColor_Gray(ColorRGBuint8 c)
124 {
125 int I = (((int)c.r * 76 + (int)c.g * 153 + (int)c.b * 26) *
126 (PaletteCount - 1) + (255 / PaletteCount)) / 255 / 255;
127 if (I >= PaletteCount)
128 return PaletteCount - 1;
129 return I;
130 }
131
132 // グレースケールパレット時に、RGB 平均で最も近いパレット番号を返す。
133 int
FindColor_GrayMean(ColorRGBuint8 c)134 ImageReductor::FindColor_GrayMean(ColorRGBuint8 c)
135 {
136 int I = ((int)c.r + (int)c.g + (int)c.b + (255 / PaletteCount) * 3) *
137 (PaletteCount - 1) / 3 / 255;
138 if (I >= PaletteCount)
139 return PaletteCount - 1;
140 return I;
141 }
142
143 // 固定 8 色パレット
144 /*static*/ const ColorRGBuint8
145 ImageReductor::Palette_Fixed8[] = {
146 { 0, 0, 0 },
147 { 255, 0, 0 },
148 { 0, 255, 0 },
149 { 255, 255, 0 },
150 { 0, 0, 255 },
151 { 255, 0, 255 },
152 { 0, 255, 255 },
153 { 255, 255, 255 },
154 };
155
156 // 色 c を固定 8 色パレットコードへ変換する。
157 int
FindColor_Fixed8(ColorRGBuint8 c)158 ImageReductor::FindColor_Fixed8(ColorRGBuint8 c)
159 {
160 int R = (c.r >= 128);
161 int G = (c.g >= 128);
162 int B = (c.b >= 128);
163 return R + (G << 1) + (B << 2);
164 }
165
166 // X68k 固定 16 色パレット
167 // NetBSD/x68k デフォルトテキストパレットより
168 /*static*/ const ColorRGBuint8
169 ImageReductor::Palette_FixedX68k[] = {
170 { 0, 0, 0 }, // 透明
171 { 252, 4, 4 },
172 { 4, 252, 4 },
173 { 252, 252, 4 },
174 { 4, 4, 252 },
175 { 252, 4, 252 },
176 { 4, 252, 252 },
177 { 252, 252, 252 },
178 { 4, 4, 4 }, // 黒
179 { 124, 4, 4 },
180 { 4, 124, 4 },
181 { 124, 124, 4 },
182 { 4, 4, 124 },
183 { 124, 4, 124 },
184 { 4, 124, 124 },
185 { 124, 124, 124 },
186 };
187
188 // 色 c を X68k 固定 16 色パレットへ変換する。
189 int
FindColor_FixedX68k(ColorRGBuint8 c)190 ImageReductor::FindColor_FixedX68k(ColorRGBuint8 c)
191 {
192 int I = (int)c.r + (int)c.g + (int)c.b;
193 int R;
194 int G;
195 int B;
196 if (c.r >= 192 || c.g >= 192 || c.b >= 192) {
197 R = (c.r >= 192);
198 G = (c.g >= 192);
199 B = (c.b >= 192);
200 if (R == G && G == B) {
201 return 7;
202 }
203 return R + (G << 1) + (B << 2);
204 } else {
205 R = (c.r >= 64);
206 G = (c.g >= 64);
207 B = (c.b >= 64);
208 if (R == G && G == B) {
209 if (I >= 64 * 3) {
210 return 15;
211 } else {
212 return 8;
213 }
214 }
215 return (R + (G << 1) + (B << 2)) | 8;
216 }
217 }
218
219 // ANSI 固定 16 色パレット
220 // Standard VGA colors を基準とし、
221 // ただしパレット4 を Brown ではなく Yellow になるようにしてある。
222 /*static*/ const ColorRGBuint8
223 ImageReductor::Palette_FixedANSI16[] = {
224 { 0, 0, 0 },
225 { 170, 0, 0 },
226 { 0, 170, 0 },
227 { 170, 170, 0 },
228 { 0, 0, 170 },
229 { 170, 0, 170 },
230 { 0, 170, 170 },
231 { 170, 170, 170 },
232 { 85, 85, 85 },
233 { 255, 85, 85 },
234 { 85, 255, 85 },
235 { 255, 255, 85 },
236 { 85, 85, 255 },
237 { 255, 85, 255 },
238 { 85, 255, 255 },
239 { 255, 255, 255 },
240 };
241
242 // 色 c を ANSI 固定 16 色パレットへ変換する。
243 int
FindColor_FixedANSI16(ColorRGBuint8 c)244 ImageReductor::FindColor_FixedANSI16(ColorRGBuint8 c)
245 {
246 int I = (int)c.r + (int)c.g + (int)c.b;
247 int R;
248 int G;
249 int B;
250 if (c.r >= 213 || c.g >= 213 || c.b >= 213) {
251 R = (c.r >= 213);
252 G = (c.g >= 213);
253 B = (c.b >= 213);
254 if (R == G && G == B) {
255 if (I >= 224 * 3) {
256 return 15;
257 } else {
258 return 7;
259 }
260 }
261 return (R + (G << 1) + (B << 2)) | 8;
262 } else {
263 R = (c.r >= 85);
264 G = (c.g >= 85);
265 B = (c.b >= 85);
266 if (R == G && G == B) {
267 if (I >= 128 * 3) {
268 return 7;
269 } else if (I >= 42 * 3) {
270 return 8;
271 } else {
272 return 0;
273 }
274 }
275 return R + (G << 1) + (B << 2);
276 }
277 }
278
279 // R3,G3,B2 bit の固定 256 色パレットを生成する。
280 void
SetPalette_Fixed256()281 ImageReductor::SetPalette_Fixed256()
282 {
283 Palette = Palette_Custom;
284 PaletteCount = 256;
285
286 for (int i = 0; i < 256; i++) {
287 Palette_Custom[i].r = (((i >> 5) & 0x07) * 255 / 7);
288 Palette_Custom[i].g = (((i >> 2) & 0x07) * 255 / 7);
289 Palette_Custom[i].b = (((i ) & 0x03) * 255 / 3);
290 }
291 }
292
293 // 固定 256 色時に、c に最も近いパレット番号を返す。
294 int
FindColor_Fixed256(ColorRGBuint8 c)295 ImageReductor::FindColor_Fixed256(ColorRGBuint8 c)
296 {
297 // 0 1 2 3 4 5 6 7 8 9 a b c d e f
298 // 0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7
299 int R = c.r >> 5;
300 int G = c.g >> 5;
301 int B = c.b >> 6;
302 return (R << 5) + (G << 2) + B;
303 }
304
305 // R2,G2,B2,I2 bit の 256色固定パレットを生成する。
306 void
SetPalette_Fixed256RGBI()307 ImageReductor::SetPalette_Fixed256RGBI()
308 {
309 Palette = Palette_Custom;
310 PaletteCount = 256;
311
312 for (int i = 0; i < 256; i++) {
313 uint8_t R, G, B, I;
314 R = (i >> 6) & 3;
315 G = (i >> 4) & 3;
316 B = (i >> 2) & 3;
317 I = (i ) & 3;
318
319 Palette_Custom[i].r = (R << 6) + (I * 63 / 3);
320 Palette_Custom[i].g = (G << 6) + (I * 63 / 3);
321 Palette_Custom[i].b = (B << 6) + (I * 63 / 3);
322 }
323 }
324
325 // R2,G2,B2,I2 bit の 固定256色時に、最も近いパレット番号を返す。
326 int
FindColor_Fixed256RGBI(ColorRGBuint8 c)327 ImageReductor::FindColor_Fixed256RGBI(ColorRGBuint8 c)
328 {
329 uint8_t R, G, B, I;
330 R = c.r >> 6;
331 G = c.g >> 6;
332 B = c.b >> 6;
333 // 最も強い成分で I を決める
334 if (R > G && R > B) {
335 I = ((c.r & 0x3f) + 10) / 21;
336 } else if (G > R && G > B) {
337 I = ((c.g & 0x3f) + 10) / 21;
338 } else if (B > R && B > G) {
339 I = ((c.b & 0x3f) + 10) / 21;
340 } else {
341 // グレーなら I は平均で決める
342 I = ((c.r & 0x3f) + (c.g & 0x3f) + (c.b & 0x3f) + 31) / 63;
343 }
344 return (R << 6) | (G << 4) | (B << 2) | I;
345 }
346
347 // 円錐型 HSV を計算する。
348 // H = 0..239, 255
349 // S = 0..255
350 // V = 0..255
351 /*static*/ ColorHSVuint8
RGBtoHSV(ColorRGBuint8 c)352 ImageReductor::RGBtoHSV(ColorRGBuint8 c)
353 {
354 ColorHSVuint8 rv;
355
356 int min = std::min(std::min(c.r, c.g), c.b);
357 int max = std::max(std::max(c.r, c.g), c.b);
358 rv.s = max - min;
359 rv.v = max;
360 if (rv.s == 0) {
361 rv.h = 255; // gray
362 } else if (min == c.b) {
363 rv.h = 40 * (c.g - c.r) / rv.s + 40;
364 } else if (min == c.r) {
365 rv.h = 40 * (c.b - c.g) / rv.s + 120;
366 } else {
367 rv.h = 40 * (c.r - c.b) / rv.s + 200;
368 }
369 return rv;
370 }
371
372 // RGB パレットから HSV パレットを作成する。
373 void
CreateHSVPalette()374 ImageReductor::CreateHSVPalette()
375 {
376 for (int i = 0; i < PaletteCount; i++) {
377 HSVPalette[i] = RGBtoHSV(Palette[i]);
378 }
379 }
380
381 /*static*/ int
FindColor_HSV_subr(ColorHSVuint8 hsvpal,ColorHSVuint8 hsv)382 ImageReductor::FindColor_HSV_subr(ColorHSVuint8 hsvpal, ColorHSVuint8 hsv)
383 {
384 int d;
385 int dh, ds, dv;
386
387 dv = (int)hsvpal.v - hsv.v;
388 ds = (int)hsvpal.s - hsv.s;
389 dh = (int)hsvpal.h - hsv.h;
390 if (hsv.s != 0 && hsvpal.s == 0) {
391 dh = 120;
392 ds = 120;
393 }
394 if (dh > 120) dh -= 240;
395 if (dh < -120) dh += 240;
396
397 d = abs(dh)*(hsv.s + 1) / 32 + abs(ds) * 3 + abs(dv) * 5;
398 return d;
399 }
400
401 int
FindColor_HSV(ColorRGBuint8 c)402 ImageReductor::FindColor_HSV(ColorRGBuint8 c)
403 {
404 ColorHSVuint8 hsv = RGBtoHSV(c);
405
406 int min_d = FindColor_HSV_subr(HSVPalette[0], hsv);
407 int min_d_i = 0;
408 for (int i = 1; i < PaletteCount; i++) {
409 int d = FindColor_HSV_subr(HSVPalette[i], hsv);
410 if (min_d > d) {
411 min_d = d;
412 min_d_i = i;
413 }
414 }
415 return min_d_i;
416 }
417
418 // カラーモードとカラーファインダを設定する。
419 void
SetColorMode(ReductorColorMode mode,ReductorFinderMode finder,int count)420 ImageReductor::SetColorMode(ReductorColorMode mode, ReductorFinderMode finder,
421 int count)
422 {
423 switch (mode) {
424 case RCM_Mono:
425 Palette = Palette_Mono;
426 PaletteCount = 2;
427 ColorFinder = &ImageReductor::FindColor_Mono;
428 break;
429 case RCM_Gray:
430 SetPalette_Gray(count);
431 ColorFinder = &ImageReductor::FindColor_Gray;
432 break;
433 case RCM_GrayMean:
434 SetPalette_Gray(count);
435 ColorFinder = &ImageReductor::FindColor_GrayMean;
436 break;
437 case RCM_Fixed8:
438 Palette = Palette_Fixed8;
439 PaletteCount = 8;
440 ColorFinder = &ImageReductor::FindColor_Fixed8;
441 break;
442 case RCM_FixedX68k:
443 Palette = Palette_FixedX68k;
444 PaletteCount = 16;
445 ColorFinder = &ImageReductor::FindColor_FixedX68k;
446 break;
447 case RCM_FixedANSI16:
448 Palette = Palette_FixedANSI16;
449 PaletteCount = 16;
450 ColorFinder = &ImageReductor::FindColor_FixedANSI16;
451 break;
452 case RCM_Fixed256:
453 SetPalette_Fixed256();
454 ColorFinder = &ImageReductor::FindColor_Fixed256;
455 break;
456 case RCM_Fixed256RGBI:
457 SetPalette_Fixed256RGBI();
458 ColorFinder = &ImageReductor::FindColor_Fixed256RGBI;
459 break;
460 case RCM_Custom:
461 ColorFinder = &ImageReductor::FindColor_HSV;
462 break;
463 }
464
465 switch (finder) {
466 case RFM_HSV:
467 CreateHSVPalette();
468 ColorFinder = &ImageReductor::FindColor_HSV;
469 break;
470 default:
471 break;
472 }
473 }
474
475
476 // その他のサブルーチン
477
478 /*static*/ uint8_t
Saturate_uint8(int x)479 ImageReductor::Saturate_uint8(int x)
480 {
481 if (x < 0)
482 return 0;
483 if (x > 255)
484 return 255;
485 return (uint8_t)x;
486 }
487
488 /*static*/ int
RoundDownPow2(int x)489 ImageReductor::RoundDownPow2(int x)
490 {
491 x |= x >> 1;
492 x |= x >> 2;
493 x |= x >> 4;
494 x |= x >> 8;
495 x |= x >> 16;
496 x += 1;
497 return x >> 1;
498 }
499
500 // -level .. +level までの乱数を返します。
501 /*static*/ int
rnd(int level)502 ImageReductor::rnd(int level)
503 {
504 static uint32_t y = (uint32_t)24539283060L;
505 y = y ^ (y << 13);
506 y = y ^ (y >> 17);
507 y = y ^ (y << 5);
508 int rv = (((int)(y >> 4) % ((level + 16) * 2 + 1)) - (level + 16)) / 16;
509 return rv;
510 }
511
512 //
513 // 変換関数
514 //
515
516 void
Convert(ReductorReduceMode mode,Image & img,std::vector<uint8_t> & dst,int toWidth,int toHeight)517 ImageReductor::Convert(ReductorReduceMode mode, Image& img,
518 std::vector<uint8_t>& dst, int toWidth, int toHeight)
519 {
520 switch (mode) {
521 case ReductorReduceMode::Fast:
522 ConvertFast(img, dst, toWidth, toHeight);
523 break;
524 case ReductorReduceMode::Simple:
525 ConvertSimple(img, dst, toWidth, toHeight);
526 break;
527 case ReductorReduceMode::HighQuality:
528 ConvertHighQuality(img, dst, toWidth, toHeight);
529 break;
530 default:
531 Debug(diag, "Unknown ReduceMode=%s", RRM2str(mode));
532 break;
533 }
534 }
535
536 // 画像を縮小しながら減色して変換する。
537 // 出来る限り高速に、それなりの品質で変換する。
538 // dst : 色コードを出力するバッファ。
539 // dstWidth * dstHeight バイト以上を保証すること。
540 // dstWidth : 出力の幅。
541 // dstHeight : 出力の高さ。
542 // src : 入力ピクセルデータ (R,G,B または R,G,B,A)。
543 // srcWidth : 入力の幅。
544 // srcHeight : 入力の高さ。
545 // srcNch : 入力のチャンネル数。3 か 4 を保証すること。
546 // srcStride : 入力のストライドのバイト長さ。
547 void
ConvertFast(Image & img,std::vector<uint8_t> & dst_,int dstWidth,int dstHeight)548 ImageReductor::ConvertFast(Image& img, std::vector<uint8_t>& dst_,
549 int dstWidth, int dstHeight)
550 {
551 uint8_t *dst = dst_.data();
552 uint8_t *src = img.GetBuf();
553 int srcWidth = img.GetWidth();
554 int srcHeight = img.GetHeight();
555 int srcStride = img.GetStride();
556 int srcNch = img.GetChannels();
557
558 Debug(diag, "%s dst=(%d,%d) src=(%d,%d)", __func__,
559 dstWidth, dstHeight, srcWidth, srcHeight);
560
561 // 螺旋状に一次元誤差分散させる。
562 // 当然画像処理的には正しくないが、視覚的にはそんなに遜色が無い。
563
564 ColorRGBint col;
565 const int level = 256;
566
567 // 水平方向はスキップサンプリング
568 // 垂直方向はスキップサンプリング
569
570 StepRational sr_y = StepRationalCreate(0, 0, dstHeight);
571 StepRational sr_ystep = StepRationalCreate(0, srcHeight, dstHeight);
572
573 StepRational sr_x = StepRationalCreate(0, 0, dstWidth);
574 StepRational sr_xstep = StepRationalCreate(0, srcWidth, dstWidth);
575
576 for (int y = 0; y < dstHeight; y++) {
577 uint8_t *srcRaster = &src[sr_y.I * srcStride];
578 StepRationalAdd(&sr_y, &sr_ystep);
579
580 sr_x.I = 0;
581 sr_x.N = 0;
582
583 ColorRGBint ce = { 0, 0, 0 };
584
585 for (int x = 0; x < dstWidth; x++) {
586 int sx0 = sr_x.I;
587 StepRationalAdd(&sr_x, &sr_xstep);
588
589 uint8_t *srcPix = &srcRaster[sx0 * srcNch];
590 col.r = srcPix[0];
591 col.g = srcPix[1];
592 col.b = srcPix[2];
593
594 col.r += ce.r;
595 col.g += ce.g;
596 col.b += ce.b;
597
598 ColorRGBuint8 c8 = {
599 Saturate_uint8(col.r),
600 Saturate_uint8(col.g),
601 Saturate_uint8(col.b),
602 };
603
604 int colorCode = (this->*(ColorFinder))(c8);
605
606 ce.r = (col.r - Palette[colorCode].r) * level / 256;
607 ce.g = (col.g - Palette[colorCode].g) * level / 256;
608 ce.b = (col.b - Palette[colorCode].b) * level / 256;
609
610 // ランダムノイズを加える
611 if (AddNoiseLevel > 0) {
612 ce.r += rnd(AddNoiseLevel);
613 ce.g += rnd(AddNoiseLevel);
614 ce.b += rnd(AddNoiseLevel);
615 }
616
617 *dst++ = colorCode;
618 }
619 }
620 }
621
622 // 画像を縮小しながら減色して変換する。
623 // 単純減色法を適用する。
624 // dst : 色コードを出力するバッファ。
625 // dstWidth * dstHeight バイト以上を保証すること。
626 // dstWidth : 出力の幅。
627 // dstHeight : 出力の高さ。
628 // src : 入力ピクセルデータ (R,G,B または R,G,B,A)。
629 // srcWidth : 入力の幅。
630 // srcHeight : 入力の高さ。
631 // srcNch : 入力のチャンネル数。3 か 4 を保証すること。
632 // srcStride : 入力のストライドのバイト長さ。
633 void
ConvertSimple(Image & img,std::vector<uint8_t> & dst_,int dstWidth,int dstHeight)634 ImageReductor::ConvertSimple(Image& img, std::vector<uint8_t>& dst_,
635 int dstWidth, int dstHeight)
636 {
637 uint8_t *dst = dst_.data();
638 uint8_t *src = img.GetBuf();
639 int srcWidth = img.GetWidth();
640 int srcHeight = img.GetHeight();
641 int srcStride = img.GetStride();
642 int srcNch = img.GetChannels();
643
644 Debug(diag, "%s dst=(%d,%d) src=(%d,%d)", __func__,
645 dstWidth, dstHeight, srcWidth, srcHeight);
646
647 // 水平方向はスキップサンプリング
648 // 垂直方向はスキップサンプリング
649
650 ColorRGBuint8 col = { 0, 0, 0 };
651 StepRational sr_y = StepRationalCreate(0, 0, dstHeight);
652 StepRational sr_ystep = StepRationalCreate(0, srcHeight, dstHeight);
653
654 StepRational sr_x = StepRationalCreate(0, 0, dstWidth);
655 StepRational sr_xstep = StepRationalCreate(0, srcWidth, dstWidth);
656
657 for (int y = 0; y < dstHeight; y++) {
658 uint8_t *srcRaster = &src[sr_y.I * srcStride];
659 StepRationalAdd(&sr_y, &sr_ystep);
660
661 sr_x.I = 0;
662 sr_x.N = 0;
663
664 for (int x = 0; x < dstWidth; x++) {
665 int sx0 = sr_x.I;
666 StepRationalAdd(&sr_x, &sr_xstep);
667
668 uint8_t *srcPix = &srcRaster[sx0 * srcNch];
669 col.r = srcPix[0];
670 col.g = srcPix[1];
671 col.b = srcPix[2];
672
673 int colorCode = (this->*(ColorFinder))(col);
674
675 *dst++ = colorCode;
676 }
677 }
678 }
679
680 /*static*/ int16_t
Saturate_adderr(int16_t a,int b)681 ImageReductor::Saturate_adderr(int16_t a, int b)
682 {
683 int16_t x = a + b;
684 if (x < -512) {
685 return -512;
686 } else if (x > 511) {
687 return 511;
688 } else {
689 return x;
690 }
691 }
692
693 // eb[x] += col * ratio / 256;
694 /*static*/ void
set_err(ColorRGBint16 eb[],int x,ColorRGBint col,int ratio)695 ImageReductor::set_err(ColorRGBint16 eb[], int x, ColorRGBint col, int ratio)
696 {
697 eb[x].r = Saturate_adderr(eb[x].r, col.r * ratio / 256);
698 eb[x].g = Saturate_adderr(eb[x].g, col.g * ratio / 256);
699 eb[x].b = Saturate_adderr(eb[x].b, col.b * ratio / 256);
700 }
701
702 // 画像を縮小しながら減色して変換する。
703 // 二次元誤差分散法を使用して、出来る限り高品質に変換する。
704 // dst : 色コードを出力するバッファ。
705 // dstWidth * dstHeight バイト以上を保証すること。
706 // dstWidth : 出力の幅。
707 // dstHeight : 出力の高さ。
708 // src : 入力ピクセルデータ (R,G,B または R,G,B,A)。
709 // srcWidth : 入力の幅。
710 // srcHeight : 入力の高さ。
711 // srcNch : 入力のチャンネル数。3 か 4 を保証すること。
712 // srcStride : 入力のストライドのバイト長さ。
713 void
ConvertHighQuality(Image & img,std::vector<uint8_t> & dst_,int dstWidth,int dstHeight)714 ImageReductor::ConvertHighQuality(Image& img, std::vector<uint8_t>& dst_,
715 int dstWidth, int dstHeight)
716 {
717 uint8_t *dst = dst_.data();
718 uint8_t *src = img.GetBuf();
719 int srcWidth = img.GetWidth();
720 int srcHeight = img.GetHeight();
721 int srcStride = img.GetStride();
722 int srcNch = img.GetChannels();
723
724 Debug(diag, "%s dst=(%p,%d,%d) src=(%p,%d,%d)", __func__,
725 dst, dstWidth, dstHeight, src, srcWidth, srcHeight);
726
727 // 水平方向はピクセルを平均
728 // 垂直方向はピクセルを平均
729 // 真に高品質にするには補間法を適用するべきだがそこまではしない。
730
731 StepRational sr_y = StepRationalCreate(0, 0, dstHeight);
732 StepRational sr_ystep = StepRationalCreate(0, srcHeight, dstHeight);
733
734 StepRational sr_x = StepRationalCreate(0, 0, dstWidth);
735 StepRational sr_xstep = StepRationalCreate(0, srcWidth, dstWidth);
736
737 // 誤差バッファ
738 const int errbuf_count = 3;
739 const int errbuf_left = 2;
740 const int errbuf_right = 2;
741 int errbuf_width = dstWidth + errbuf_left + errbuf_right;
742 int errbuf_len = errbuf_width * sizeof(ColorRGBint16);
743 int errbuf_mem_len = errbuf_len * errbuf_count;
744
745 ColorRGBint16 *errbuf_mem;
746 ColorRGBint16 *errbuf[errbuf_count];
747 errbuf_mem = (ColorRGBint16 *)malloc(errbuf_mem_len);
748 memset(errbuf_mem, 0, errbuf_mem_len);
749 for (int i = 0; i < errbuf_count; i++) {
750 errbuf[i] = errbuf_mem + errbuf_left + errbuf_width * i;
751 }
752
753 int isAlpha = 0;
754 if (srcNch == 4) {
755 isAlpha = 1;
756 }
757
758 for (int y = 0; y < dstHeight; y++) {
759 int sy0 = sr_y.I;
760 StepRationalAdd(&sr_y, &sr_ystep);
761 int sy1 = sr_y.I;
762
763 if (sy0 == sy1)
764 sy1 += 1;
765
766 sr_x.I = 0;
767 sr_x.N = 0;
768
769 for (int x = 0; x < dstWidth; x++) {
770 ColorRGBint col = { 0, 0, 0 };
771 int alpha = 0;
772
773 int sx0 = sr_x.I;
774 StepRationalAdd(&sr_x, &sr_xstep);
775 int sx1 = sr_x.I;
776 if (sx0 == sx1)
777 sx1 += 1;
778
779 // 画素の平均を求める
780 for (int sy = sy0; sy < sy1; sy++) {
781 uint8_t *srcRaster = &src[sy * srcStride];
782 uint8_t *srcPix = &srcRaster[sx0 * srcNch];
783 for (int sx = sx0; sx < sx1; sx++) {
784 col.r += srcPix[0];
785 col.g += srcPix[1];
786 col.b += srcPix[2];
787 if (isAlpha) {
788 alpha += srcPix[3];
789 }
790 srcPix += srcNch;
791 }
792 }
793
794 int D = (sy1 - sy0) * (sx1 - sx0);
795
796 col.r /= D;
797 col.g /= D;
798 col.b /= D;
799
800 col.r += errbuf[0][x].r;
801 col.g += errbuf[0][x].g;
802 col.b += errbuf[0][x].b;
803
804 ColorRGBuint8 c8 = {
805 Saturate_uint8(col.r),
806 Saturate_uint8(col.g),
807 Saturate_uint8(col.b),
808 };
809
810 int colorCode;
811 if (isAlpha && alpha == 0) {
812 // XXX パレットがアルファ対応かとか。
813 colorCode = 0;
814 } else {
815 colorCode = (this->*(ColorFinder))(c8);
816 }
817
818 col.r -= Palette[colorCode].r;
819 col.g -= Palette[colorCode].g;
820 col.b -= Palette[colorCode].b;
821
822 // ランダムノイズを加える
823 if (AddNoiseLevel > 0) {
824 col.r += rnd(AddNoiseLevel);
825 col.g += rnd(AddNoiseLevel);
826 col.b += rnd(AddNoiseLevel);
827 }
828
829 switch (HighQualityDiffuseMethod) {
830 case RDM_FS:
831 // Floyd Steinberg Method
832 set_err(errbuf[0], x + 1, col, 112);
833 set_err(errbuf[1], x - 1, col, 48);
834 set_err(errbuf[1], x , col, 80);
835 set_err(errbuf[1], x + 1, col, 16);
836 break;
837 case RDM_ATKINSON:
838 // Atkinson
839 set_err(errbuf[0], x + 1, col, 32);
840 set_err(errbuf[0], x + 2, col, 32);
841 set_err(errbuf[1], x - 1, col, 32);
842 set_err(errbuf[1], x, col, 32);
843 set_err(errbuf[1], x + 1, col, 32);
844 set_err(errbuf[2], x, col, 32);
845 break;
846 case RDM_JAJUNI:
847 // Jarvis, Judice, Ninke
848 set_err(errbuf[0], x + 1, col, 37);
849 set_err(errbuf[0], x + 2, col, 27);
850 set_err(errbuf[1], x - 2, col, 16);
851 set_err(errbuf[1], x - 1, col, 27);
852 set_err(errbuf[1], x, col, 37);
853 set_err(errbuf[1], x + 1, col, 27);
854 set_err(errbuf[1], x + 2, col, 16);
855 set_err(errbuf[2], x - 2, col, 5);
856 set_err(errbuf[2], x - 1, col, 16);
857 set_err(errbuf[2], x, col, 27);
858 set_err(errbuf[2], x + 1, col, 16);
859 set_err(errbuf[2], x + 2, col, 5);
860 break;
861 case RDM_STUCKI:
862 // Stucki
863 set_err(errbuf[0], x + 1, col, 43);
864 set_err(errbuf[0], x + 2, col, 21);
865 set_err(errbuf[1], x - 2, col, 11);
866 set_err(errbuf[1], x - 1, col, 21);
867 set_err(errbuf[1], x, col, 43);
868 set_err(errbuf[1], x + 1, col, 21);
869 set_err(errbuf[1], x + 2, col, 11);
870 set_err(errbuf[2], x - 2, col, 5);
871 set_err(errbuf[2], x - 1, col, 11);
872 set_err(errbuf[2], x, col, 21);
873 set_err(errbuf[2], x + 1, col, 11);
874 set_err(errbuf[2], x + 2, col, 5);
875 break;
876 case RDM_BURKES:
877 // Burkes
878 set_err(errbuf[0], x + 1, col, 64);
879 set_err(errbuf[0], x + 2, col, 32);
880 set_err(errbuf[1], x - 2, col, 16);
881 set_err(errbuf[1], x - 1, col, 32);
882 set_err(errbuf[1], x, col, 64);
883 set_err(errbuf[1], x + 1, col, 32);
884 set_err(errbuf[1], x + 2, col, 16);
885 break;
886 case RDM_2:
887 // (x+1,y), (x,y+1)
888 set_err(errbuf[0], x + 1, col, 128);
889 set_err(errbuf[1], x, col, 128);
890 break;
891 case RDM_3:
892 // (x+1,y), (x,y+1), (x+1,y+1)
893 set_err(errbuf[0], x + 1, col, 102);
894 set_err(errbuf[1], x, col, 102);
895 set_err(errbuf[1], x + 1, col, 51);
896 break;
897 case RDM_RGB:
898 errbuf[0][x].r = Saturate_adderr(errbuf[0][x].r, col.r);
899 errbuf[1][x].b = Saturate_adderr(errbuf[1][x].b, col.b);
900 errbuf[1][x+1].g = Saturate_adderr(errbuf[1][x+1].g, col.g);
901 break;
902 }
903
904 *dst++ = colorCode;
905 }
906
907 // 誤差バッファをローテート
908 ColorRGBint16 *tmp = errbuf[0];
909 for (int i = 0; i < errbuf_count - 1; i++) {
910 errbuf[i] = errbuf[i + 1];
911 }
912 errbuf[errbuf_count - 1] = tmp;
913 // errbuf[y] には左マージンがあるのを考慮する
914 memset(errbuf[errbuf_count - 1] - errbuf_left, 0, errbuf_len);
915 }
916
917 free(errbuf_mem);
918 }
919
920
921 static uint8
saturate_mul_f(uint8 a,float b)922 saturate_mul_f(uint8 a, float b)
923 {
924 auto f = a * b;
925 if (f < 0)
926 return 0;
927 if (f > 255)
928 return 255;
929 return (uint8)f;
930 }
931
932 void
ColorFactor(float factor)933 ImageReductor::ColorFactor(float factor)
934 {
935 // 定義済み(読み込み専用)パレットを使用中なら、
936 // その内容をカスタムパレットに移す。
937 if (Palette != Palette_Custom) {
938 for (int i = 0; i < PaletteCount; i++) {
939 Palette_Custom[i] = Palette[i];
940 }
941 Palette = Palette_Custom;
942 }
943
944 // 現在の値に factor をかける
945 for (int i = 0; i < PaletteCount; i++) {
946 auto& col = Palette_Custom[i];
947 col.r = saturate_mul_f(col.r, factor);
948 col.g = saturate_mul_f(col.g, factor);
949 col.b = saturate_mul_f(col.b, factor);
950 }
951 }
952
953 //
954 // enum を文字列にしたやつ orz
955 //
956
957 static const char *RRM2str_[] = {
958 "Fast",
959 "Simple",
960 "HighQuality",
961 };
962
963 static const char *RCM2str_[] = {
964 "Mono",
965 "Gray",
966 "GrayMean",
967 "Fixed8",
968 "FixedX68k",
969 "FixedANSI16",
970 "Fixed256",
971 "Fixed256RGBI",
972 "Custom",
973 };
974
975 static const char *RFM2str_[] = {
976 "Default",
977 "HSV",
978 };
979
980 static const char *RDM2str_[] = {
981 "FS",
982 "Atkinson",
983 "Jajuni",
984 "Stucki",
985 "Burkes",
986 "2",
987 "3",
988 "RGB",
989 };
990
991 static const char *RAX2str_[] = {
992 "Both",
993 "Width",
994 "Height",
995 "Long",
996 "Short",
997 "ScaleDownBoth",
998 "ScaleDownWidth",
999 "ScaleDownHeight",
1000 "ScaleDownLong",
1001 "ScaleDownShort",
1002 };
1003
1004 /*static*/ const char *
RRM2str(ReductorReduceMode val)1005 ImageReductor::RRM2str(ReductorReduceMode val)
1006 {
1007 return ::RRM2str_[(int)val];
1008 }
1009
1010 /*static*/ const char *
RCM2str(ReductorColorMode val)1011 ImageReductor::RCM2str(ReductorColorMode val)
1012 {
1013 return ::RCM2str_[(int)val];
1014 }
1015
1016 /*static*/ const char *
RFM2str(ReductorFinderMode val)1017 ImageReductor::RFM2str(ReductorFinderMode val)
1018 {
1019 return ::RFM2str_[(int)val];
1020 }
1021
1022 /*static*/ const char *
RDM2str(ReductorDiffuseMethod val)1023 ImageReductor::RDM2str(ReductorDiffuseMethod val)
1024 {
1025 return ::RDM2str_[(int)val];
1026 }
1027
1028 /*static*/ const char *
RAX2str(ResizeAxisMode val)1029 ImageReductor::RAX2str(ResizeAxisMode val)
1030 {
1031 return ::RAX2str_[(int)val];
1032 }
1033