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