|
41 | 41 | #include <string>
|
42 | 42 | #include <vector>
|
43 | 43 |
|
| 44 | +#include "tiv_lib.h" |
| 45 | + |
44 | 46 | // This #define tells CImg that we use the library without any display options
|
45 | 47 | // -- just for loading images.
|
46 | 48 | #define cimg_display 0
|
|
65 | 67 | #define EXITCODE_DATA_FORMAT_ERROR 65
|
66 | 68 | #define EXITCODE_NO_INPUT_ERROR 66
|
67 | 69 |
|
68 |
| -// Implementation of flag representation for flags in the main() method |
69 |
| -constexpr int FLAG_FG = 1; |
70 |
| -constexpr int FLAG_BG = 2; |
71 |
| -constexpr int FLAG_MODE_256 = 4; // Limit colors to 256-color mode |
72 |
| -constexpr int FLAG_24BIT = 8; // 24-bit color mode |
73 |
| -constexpr int FLAG_NOOPT = 16; // Only use the same half-block character |
74 |
| -constexpr int FLAG_TELETEXT = 32; // Use teletext characters |
75 |
| - |
76 |
| -// Color saturation value steps from 0 to 255 |
77 |
| -constexpr int COLOR_STEP_COUNT = 6; |
78 |
| -constexpr int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; |
79 |
| - |
80 |
| -// Grayscale saturation value steps from 0 to 255 |
81 |
| -constexpr int GRAYSCALE_STEP_COUNT = 24; |
82 |
| -constexpr int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = { |
83 |
| - 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, |
84 |
| - 0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee}; |
85 |
| - |
86 |
| -// An interleaved map of 4x8 bit character bitmaps (each hex digit represents a |
87 |
| -// row) to the corresponding unicode character code point. |
88 |
| -constexpr unsigned int BITMAPS[] = { |
89 |
| - 0x00000000, 0x00a0, |
90 |
| - |
91 |
| - // Block graphics |
92 |
| - // 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2 |
93 |
| - |
94 |
| - 0x0000000f, 0x2581, // lower 1/8 |
95 |
| - 0x000000ff, 0x2582, // lower 1/4 |
96 |
| - 0x00000fff, 0x2583, 0x0000ffff, 0x2584, // lower 1/2 |
97 |
| - 0x000fffff, 0x2585, 0x00ffffff, 0x2586, // lower 3/4 |
98 |
| - 0x0fffffff, 0x2587, |
99 |
| - // 0xffffffff, 0x2588, // full; redundant with inverse space |
100 |
| - |
101 |
| - 0xeeeeeeee, 0x258a, // left 3/4 |
102 |
| - 0xcccccccc, 0x258c, // left 1/2 |
103 |
| - 0x88888888, 0x258e, // left 1/4 |
104 |
| - |
105 |
| - 0x0000cccc, 0x2596, // quadrant lower left |
106 |
| - 0x00003333, 0x2597, // quadrant lower right |
107 |
| - 0xcccc0000, |
108 |
| - 0x2598, // quadrant upper left |
109 |
| - // 0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4 |
110 |
| - 0xcccc3333, 0x259a, // diagonal 1/2 |
111 |
| - // 0xffffcccc, 0x259b, // 3/4 redundant |
112 |
| - // 0xffff3333, 0x259c, // 3/4 redundant |
113 |
| - 0x33330000, 0x259d, // quadrant upper right |
114 |
| - // 0x3333cccc, 0x259e, // 3/4 redundant |
115 |
| - // 0x3333ffff, 0x259f, // 3/4 redundant |
116 |
| - |
117 |
| - // Line drawing subset: no double lines, no complex light lines |
118 |
| - |
119 |
| - 0x000ff000, 0x2501, // Heavy horizontal |
120 |
| - 0x66666666, 0x2503, // Heavy vertical |
121 |
| - |
122 |
| - 0x00077666, 0x250f, // Heavy down and right |
123 |
| - 0x000ee666, 0x2513, // Heavy down and left |
124 |
| - 0x66677000, 0x2517, // Heavy up and right |
125 |
| - 0x666ee000, 0x251b, // Heavy up and left |
126 |
| - |
127 |
| - 0x66677666, 0x2523, // Heavy vertical and right |
128 |
| - 0x666ee666, 0x252b, // Heavy vertical and left |
129 |
| - 0x000ff666, 0x2533, // Heavy down and horizontal |
130 |
| - 0x666ff000, 0x253b, // Heavy up and horizontal |
131 |
| - 0x666ff666, 0x254b, // Heavy cross |
132 |
| - |
133 |
| - 0x000cc000, 0x2578, // Bold horizontal left |
134 |
| - 0x00066000, 0x2579, // Bold horizontal up |
135 |
| - 0x00033000, 0x257a, // Bold horizontal right |
136 |
| - 0x00066000, 0x257b, // Bold horizontal down |
137 |
| - |
138 |
| - 0x06600660, 0x254f, // Heavy double dash vertical |
139 |
| - |
140 |
| - 0x000f0000, 0x2500, // Light horizontal |
141 |
| - 0x0000f000, 0x2500, // |
142 |
| - 0x44444444, 0x2502, // Light vertical |
143 |
| - 0x22222222, 0x2502, |
144 |
| - |
145 |
| - 0x000e0000, 0x2574, // light left |
146 |
| - 0x0000e000, 0x2574, // light left |
147 |
| - 0x44440000, 0x2575, // light up |
148 |
| - 0x22220000, 0x2575, // light up |
149 |
| - 0x00030000, 0x2576, // light right |
150 |
| - 0x00003000, 0x2576, // light right |
151 |
| - 0x00004444, 0x2577, // light down |
152 |
| - 0x00002222, 0x2577, // light down |
153 |
| - |
154 |
| - // Misc technical |
155 |
| - |
156 |
| - 0x44444444, 0x23a2, // [ extension |
157 |
| - 0x22222222, 0x23a5, // ] extension |
158 |
| - |
159 |
| - 0x0f000000, 0x23ba, // Horizontal scanline 1 |
160 |
| - 0x00f00000, 0x23bb, // Horizontal scanline 3 |
161 |
| - 0x00000f00, 0x23bc, // Horizontal scanline 7 |
162 |
| - 0x000000f0, 0x23bd, // Horizontal scanline 9 |
163 |
| - |
164 |
| - // Geometrical shapes. Tricky because some of them are too wide. |
165 |
| - |
166 |
| - // 0x00ffff00, 0x25fe, // Black medium small square |
167 |
| - 0x00066000, 0x25aa, // Black small square |
168 |
| - |
169 |
| - // 0x11224488, 0x2571, // diagonals |
170 |
| - // 0x88442211, 0x2572, |
171 |
| - // 0x99666699, 0x2573, |
172 |
| - // 0x000137f0, 0x25e2, // Triangles |
173 |
| - // 0x0008cef0, 0x25e3, |
174 |
| - // 0x000fec80, 0x25e4, |
175 |
| - // 0x000f7310, 0x25e5, |
176 |
| - |
177 |
| - 0, 0, // End marker for "regular" characters |
178 |
| - |
179 |
| - // Teletext / legacy graphics 3x2 block character codes. |
180 |
| - // Using a 3-2-3 pattern consistently, perhaps we should create automatic |
181 |
| - // variations.... |
182 |
| - |
183 |
| - 0xccc00000, 0xfb00, 0x33300000, 0xfb01, 0xfff00000, 0xfb02, 0x000cc000, |
184 |
| - 0xfb03, 0xccccc000, 0xfb04, 0x333cc000, 0xfb05, 0xfffcc000, 0xfb06, |
185 |
| - 0x00033000, 0xfb07, 0xccc33000, 0xfb08, 0x33333000, 0xfb09, 0xfff33000, |
186 |
| - 0xfb0a, 0x000ff000, 0xfb0b, 0xcccff000, 0xfb0c, 0x333ff000, 0xfb0d, |
187 |
| - 0xfffff000, 0xfb0e, 0x00000ccc, 0xfb0f, |
188 |
| - |
189 |
| - 0xccc00ccc, 0xfb10, 0x33300ccc, 0xfb11, 0xfff00ccc, 0xfb12, 0x000ccccc, |
190 |
| - 0xfb13, 0x333ccccc, 0xfb14, 0xfffccccc, 0xfb15, 0x00033ccc, 0xfb16, |
191 |
| - 0xccc33ccc, 0xfb17, 0x33333ccc, 0xfb18, 0xfff33ccc, 0xfb19, 0x000ffccc, |
192 |
| - 0xfb1a, 0xcccffccc, 0xfb1b, 0x333ffccc, 0xfb1c, 0xfffffccc, 0xfb1d, |
193 |
| - 0x00000333, 0xfb1e, 0xccc00333, 0xfb1f, |
194 |
| - |
195 |
| - 0x33300333, 0x1b20, 0xfff00333, 0x1b21, 0x000cc333, 0x1b22, 0xccccc333, |
196 |
| - 0x1b23, 0x333cc333, 0x1b24, 0xfffcc333, 0x1b25, 0x00033333, 0x1b26, |
197 |
| - 0xccc33333, 0x1b27, 0xfff33333, 0x1b28, 0x000ff333, 0x1b29, 0xcccff333, |
198 |
| - 0x1b2a, 0x333ff333, 0x1b2b, 0xfffff333, 0x1b2c, 0x00000fff, 0x1b2d, |
199 |
| - 0xccc00fff, 0x1b2e, 0x33300fff, 0x1b2f, |
200 |
| - |
201 |
| - 0xfff00fff, 0x1b30, 0x000ccfff, 0x1b31, 0xcccccfff, 0x1b32, 0x333ccfff, |
202 |
| - 0x1b33, 0xfffccfff, 0x1b34, 0x00033fff, 0x1b35, 0xccc33fff, 0x1b36, |
203 |
| - 0x33333fff, 0x1b37, 0xfff33fff, 0x1b38, 0x000fffff, 0x1b39, 0xcccfffff, |
204 |
| - 0x1b3a, 0x333fffff, 0x1b3b, |
205 |
| - |
206 |
| - 0, 1 // End marker for extended TELETEXT mode. |
207 |
| -}; |
208 |
| - |
209 |
| -/** |
210 |
| - * @brief Struct to represent a character to be drawn. |
211 |
| - * @param fgColor RGB |
212 |
| - * @param bgColor RGB |
213 |
| - * @param codePoint The code point of the character to be drawn. |
214 |
| - */ |
215 |
| -struct CharData { |
216 |
| - std::array<int, 3> fgColor = std::array<int, 3>{0, 0, 0}; |
217 |
| - std::array<int, 3> bgColor = std::array<int, 3>{0, 0, 0}; |
218 |
| - int codePoint; |
219 |
| -}; |
220 |
| - |
221 |
| -typedef std::function<unsigned char(int, int, int)> GetPixelFunction; |
222 |
| - |
223 |
| -// Return a CharData struct with the given code point and corresponding averag |
224 |
| -// fg and bg colors. |
225 |
| -CharData createCharData(GetPixelFunction get_pixel, int x0, int y0, |
226 |
| - int codepoint, int pattern) { |
227 |
| - CharData result; |
228 |
| - result.codePoint = codepoint; |
229 |
| - int fg_count = 0; |
230 |
| - int bg_count = 0; |
231 |
| - unsigned int mask = 0x80000000; |
232 |
| - |
233 |
| - for (int y = 0; y < 8; y++) { |
234 |
| - for (int x = 0; x < 4; x++) { |
235 |
| - int *avg; |
236 |
| - if (pattern & mask) { |
237 |
| - avg = result.fgColor.data(); |
238 |
| - fg_count++; |
239 |
| - } else { |
240 |
| - avg = result.bgColor.data(); |
241 |
| - bg_count++; |
242 |
| - } |
243 |
| - for (int i = 0; i < 3; i++) { |
244 |
| - avg[i] += get_pixel(x0 + x, y0 + y, i); |
245 |
| - } |
246 |
| - mask = mask >> 1; |
247 |
| - } |
248 |
| - } |
249 |
| - |
250 |
| - // Calculate the average color value for each bucket |
251 |
| - for (int i = 0; i < 3; i++) { |
252 |
| - if (bg_count != 0) { |
253 |
| - result.bgColor[i] /= bg_count; |
254 |
| - } |
255 |
| - if (fg_count != 0) { |
256 |
| - result.fgColor[i] /= fg_count; |
257 |
| - } |
258 |
| - } |
259 |
| - return result; |
260 |
| -} |
261 |
| - |
262 |
| -/** |
263 |
| - * @brief Find the best character and colors |
264 |
| - * for a 4x8 part of the image at the given position |
265 |
| - * |
266 |
| - * @param image |
267 |
| - * @param x0 |
268 |
| - * @param y0 |
269 |
| - * @param flags |
270 |
| - * @return CharData |
271 |
| - */ |
272 |
| -CharData findCharData(GetPixelFunction get_pixel, int x0, int y0, |
273 |
| - const int &flags) { |
274 |
| - int min[3] = {255, 255, 255}; |
275 |
| - int max[3] = {0}; |
276 |
| - std::map<long, int> count_per_color; |
277 |
| - |
278 |
| - // Determine the minimum and maximum value for each color channel |
279 |
| - for (int y = 0; y < 8; y++) { |
280 |
| - for (int x = 0; x < 4; x++) { |
281 |
| - long color = 0; |
282 |
| - for (int i = 0; i < 3; i++) { |
283 |
| - int d = get_pixel(x0 + x, y0 + y, i); |
284 |
| - min[i] = std::min(min[i], d); |
285 |
| - max[i] = std::max(max[i], d); |
286 |
| - color = (color << 8) | d; |
287 |
| - } |
288 |
| - count_per_color[color]++; |
289 |
| - } |
290 |
| - } |
291 |
| - |
292 |
| - std::multimap<int, long> color_per_count; |
293 |
| - for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) { |
294 |
| - color_per_count.insert(std::pair<int, long>(i->second, i->first)); |
295 |
| - } |
296 |
| - |
297 |
| - auto iter = color_per_count.rbegin(); |
298 |
| - int count2 = iter->first; |
299 |
| - long max_count_color_1 = iter->second; |
300 |
| - long max_count_color_2 = max_count_color_1; |
301 |
| - if ((++iter) != color_per_count.rend()) { |
302 |
| - count2 += iter->first; |
303 |
| - max_count_color_2 = iter->second; |
304 |
| - } |
305 |
| - |
306 |
| - unsigned int bits = 0; |
307 |
| - bool direct = count2 > (8 * 4) / 2; |
308 |
| - |
309 |
| - if (direct) { |
310 |
| - for (int y = 0; y < 8; y++) { |
311 |
| - for (int x = 0; x < 4; x++) { |
312 |
| - bits = bits << 1; |
313 |
| - int d1 = 0; |
314 |
| - int d2 = 0; |
315 |
| - for (int i = 0; i < 3; i++) { |
316 |
| - int shift = 16 - 8 * i; |
317 |
| - int c1 = (max_count_color_1 >> shift) & 255; |
318 |
| - int c2 = (max_count_color_2 >> shift) & 255; |
319 |
| - int c = get_pixel(x0 + x, y0 + y, i); |
320 |
| - d1 += (c1 - c) * (c1 - c); |
321 |
| - d2 += (c2 - c) * (c2 - c); |
322 |
| - } |
323 |
| - if (d1 > d2) { |
324 |
| - bits |= 1; |
325 |
| - } |
326 |
| - } |
327 |
| - } |
328 |
| - } else { |
329 |
| - // Determine the color channel with the greatest range. |
330 |
| - int splitIndex = 0; |
331 |
| - int bestSplit = 0; |
332 |
| - for (int i = 0; i < 3; i++) { |
333 |
| - if (max[i] - min[i] > bestSplit) { |
334 |
| - bestSplit = max[i] - min[i]; |
335 |
| - splitIndex = i; |
336 |
| - } |
337 |
| - } |
338 |
| - |
339 |
| - // We just split at the middle of the interval instead of computing the |
340 |
| - // median. |
341 |
| - int splitValue = min[splitIndex] + bestSplit / 2; |
342 |
| - |
343 |
| - // Compute a bitmap using the given split and sum the color values for |
344 |
| - // both buckets. |
345 |
| - for (int y = 0; y < 8; y++) { |
346 |
| - for (int x = 0; x < 4; x++) { |
347 |
| - bits = bits << 1; |
348 |
| - if (get_pixel(x0 + x, y0 + y, splitIndex) > splitValue) { |
349 |
| - bits |= 1; |
350 |
| - } |
351 |
| - } |
352 |
| - } |
353 |
| - } |
354 |
| - |
355 |
| - // Find the best bitmap match by counting the bits that don't match, |
356 |
| - // including the inverted bitmaps. |
357 |
| - int best_diff = 8; |
358 |
| - unsigned int best_pattern = 0x0000ffff; |
359 |
| - int codepoint = 0x2584; |
360 |
| - bool inverted = false; |
361 |
| - unsigned int end_marker = flags & FLAG_TELETEXT ? 1 : 0; |
362 |
| - for (int i = 0; BITMAPS[i + 1] != end_marker; i += 2) { |
363 |
| - // Skip all end markers |
364 |
| - if (BITMAPS[i + 1] < 32) { |
365 |
| - continue; |
366 |
| - } |
367 |
| - unsigned int pattern = BITMAPS[i]; |
368 |
| - for (int j = 0; j < 2; j++) { |
369 |
| - int diff = (std::bitset<32>(pattern ^ bits)).count(); |
370 |
| - if (diff < best_diff) { |
371 |
| - best_pattern = BITMAPS[i]; // pattern might be inverted. |
372 |
| - codepoint = BITMAPS[i + 1]; |
373 |
| - best_diff = diff; |
374 |
| - inverted = best_pattern != pattern; |
375 |
| - } |
376 |
| - pattern = ~pattern; |
377 |
| - } |
378 |
| - } |
379 |
| - |
380 |
| - if (direct) { |
381 |
| - CharData result; |
382 |
| - if (inverted) { |
383 |
| - long tmp = max_count_color_1; |
384 |
| - max_count_color_1 = max_count_color_2; |
385 |
| - max_count_color_2 = tmp; |
386 |
| - } |
387 |
| - for (int i = 0; i < 3; i++) { |
388 |
| - int shift = 16 - 8 * i; |
389 |
| - result.fgColor[i] = (max_count_color_2 >> shift) & 255; |
390 |
| - result.bgColor[i] = (max_count_color_1 >> shift) & 255; |
391 |
| - result.codePoint = codepoint; |
392 |
| - } |
393 |
| - return result; |
394 |
| - } |
395 |
| - return createCharData(get_pixel, x0, y0, codepoint, best_pattern); |
396 |
| -} |
397 |
| - |
398 |
| -int clamp_byte(int value) { |
399 |
| - return value < 0 ? 0 : (value > 255 ? 255 : value); |
400 |
| -} |
401 |
| - |
402 |
| -double sqr(double n) { return n * n; } |
403 |
| - |
404 |
| -int best_index(int value, const int STEPS[], int count) { |
405 |
| - int best_diff = std::abs(STEPS[0] - value); |
406 |
| - int result = 0; |
407 |
| - for (int i = 1; i < count; i++) { |
408 |
| - int diff = std::abs(STEPS[i] - value); |
409 |
| - if (diff < best_diff) { |
410 |
| - result = i; |
411 |
| - best_diff = diff; |
412 |
| - } |
413 |
| - } |
414 |
| - return result; |
415 |
| -} |
416 |
| - |
417 | 70 | void printTermColor(const int &flags, int r, int g, int b) {
|
418 | 71 | r = clamp_byte(r);
|
419 | 72 | g = clamp_byte(g);
|
|
0 commit comments