diff --git a/src/Makefile b/src/Makefile index 6da40ed..1c12274 100644 --- a/src/Makefile +++ b/src/Makefile @@ -1,7 +1,7 @@ PROGNAME = tiv -OBJECTS = tiv.o - +OBJECTS = tiv.o tiv_lib.o + CXX ?= g++ CXXFLAGS ?= -O2 -fpermissive INSTALL ?= install @@ -17,7 +17,9 @@ override LDFLAGS += -pthread all: $(PROGNAME) -tiv.o: CImg.h +tiv_lib.o: tiv_lib.h + +tiv.o: CImg.h tiv_lib.h $(PROGNAME): $(OBJECTS) $(CXX) $(LDFLAGS) $^ -o $@ $(LOADLIBES) $(LDLIBS) diff --git a/src/tiv.cpp b/src/tiv.cpp index 775d06e..f3c1c94 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -41,6 +41,8 @@ #include #include +#include "tiv_lib.h" + // This #define tells CImg that we use the library without any display options // -- just for loading images. #define cimg_display 0 @@ -65,355 +67,6 @@ #define EXITCODE_DATA_FORMAT_ERROR 65 #define EXITCODE_NO_INPUT_ERROR 66 -// Implementation of flag representation for flags in the main() method -constexpr int FLAG_FG = 1; -constexpr int FLAG_BG = 2; -constexpr int FLAG_MODE_256 = 4; // Limit colors to 256-color mode -constexpr int FLAG_24BIT = 8; // 24-bit color mode -constexpr int FLAG_NOOPT = 16; // Only use the same half-block character -constexpr int FLAG_TELETEXT = 32; // Use teletext characters - -// Color saturation value steps from 0 to 255 -constexpr int COLOR_STEP_COUNT = 6; -constexpr int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; - -// Grayscale saturation value steps from 0 to 255 -constexpr int GRAYSCALE_STEP_COUNT = 24; -constexpr int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = { - 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, - 0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee}; - -// An interleaved map of 4x8 bit character bitmaps (each hex digit represents a -// row) to the corresponding unicode character code point. -constexpr unsigned int BITMAPS[] = { - 0x00000000, 0x00a0, - - // Block graphics - // 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2 - - 0x0000000f, 0x2581, // lower 1/8 - 0x000000ff, 0x2582, // lower 1/4 - 0x00000fff, 0x2583, 0x0000ffff, 0x2584, // lower 1/2 - 0x000fffff, 0x2585, 0x00ffffff, 0x2586, // lower 3/4 - 0x0fffffff, 0x2587, - // 0xffffffff, 0x2588, // full; redundant with inverse space - - 0xeeeeeeee, 0x258a, // left 3/4 - 0xcccccccc, 0x258c, // left 1/2 - 0x88888888, 0x258e, // left 1/4 - - 0x0000cccc, 0x2596, // quadrant lower left - 0x00003333, 0x2597, // quadrant lower right - 0xcccc0000, - 0x2598, // quadrant upper left - // 0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4 - 0xcccc3333, 0x259a, // diagonal 1/2 - // 0xffffcccc, 0x259b, // 3/4 redundant - // 0xffff3333, 0x259c, // 3/4 redundant - 0x33330000, 0x259d, // quadrant upper right - // 0x3333cccc, 0x259e, // 3/4 redundant - // 0x3333ffff, 0x259f, // 3/4 redundant - - // Line drawing subset: no double lines, no complex light lines - - 0x000ff000, 0x2501, // Heavy horizontal - 0x66666666, 0x2503, // Heavy vertical - - 0x00077666, 0x250f, // Heavy down and right - 0x000ee666, 0x2513, // Heavy down and left - 0x66677000, 0x2517, // Heavy up and right - 0x666ee000, 0x251b, // Heavy up and left - - 0x66677666, 0x2523, // Heavy vertical and right - 0x666ee666, 0x252b, // Heavy vertical and left - 0x000ff666, 0x2533, // Heavy down and horizontal - 0x666ff000, 0x253b, // Heavy up and horizontal - 0x666ff666, 0x254b, // Heavy cross - - 0x000cc000, 0x2578, // Bold horizontal left - 0x00066000, 0x2579, // Bold horizontal up - 0x00033000, 0x257a, // Bold horizontal right - 0x00066000, 0x257b, // Bold horizontal down - - 0x06600660, 0x254f, // Heavy double dash vertical - - 0x000f0000, 0x2500, // Light horizontal - 0x0000f000, 0x2500, // - 0x44444444, 0x2502, // Light vertical - 0x22222222, 0x2502, - - 0x000e0000, 0x2574, // light left - 0x0000e000, 0x2574, // light left - 0x44440000, 0x2575, // light up - 0x22220000, 0x2575, // light up - 0x00030000, 0x2576, // light right - 0x00003000, 0x2576, // light right - 0x00004444, 0x2577, // light down - 0x00002222, 0x2577, // light down - - // Misc technical - - 0x44444444, 0x23a2, // [ extension - 0x22222222, 0x23a5, // ] extension - - 0x0f000000, 0x23ba, // Horizontal scanline 1 - 0x00f00000, 0x23bb, // Horizontal scanline 3 - 0x00000f00, 0x23bc, // Horizontal scanline 7 - 0x000000f0, 0x23bd, // Horizontal scanline 9 - - // Geometrical shapes. Tricky because some of them are too wide. - - // 0x00ffff00, 0x25fe, // Black medium small square - 0x00066000, 0x25aa, // Black small square - - // 0x11224488, 0x2571, // diagonals - // 0x88442211, 0x2572, - // 0x99666699, 0x2573, - // 0x000137f0, 0x25e2, // Triangles - // 0x0008cef0, 0x25e3, - // 0x000fec80, 0x25e4, - // 0x000f7310, 0x25e5, - - 0, 0, // End marker for "regular" characters - - // Teletext / legacy graphics 3x2 block character codes. - // Using a 3-2-3 pattern consistently, perhaps we should create automatic - // variations.... - - 0xccc00000, 0xfb00, 0x33300000, 0xfb01, 0xfff00000, 0xfb02, 0x000cc000, - 0xfb03, 0xccccc000, 0xfb04, 0x333cc000, 0xfb05, 0xfffcc000, 0xfb06, - 0x00033000, 0xfb07, 0xccc33000, 0xfb08, 0x33333000, 0xfb09, 0xfff33000, - 0xfb0a, 0x000ff000, 0xfb0b, 0xcccff000, 0xfb0c, 0x333ff000, 0xfb0d, - 0xfffff000, 0xfb0e, 0x00000ccc, 0xfb0f, - - 0xccc00ccc, 0xfb10, 0x33300ccc, 0xfb11, 0xfff00ccc, 0xfb12, 0x000ccccc, - 0xfb13, 0x333ccccc, 0xfb14, 0xfffccccc, 0xfb15, 0x00033ccc, 0xfb16, - 0xccc33ccc, 0xfb17, 0x33333ccc, 0xfb18, 0xfff33ccc, 0xfb19, 0x000ffccc, - 0xfb1a, 0xcccffccc, 0xfb1b, 0x333ffccc, 0xfb1c, 0xfffffccc, 0xfb1d, - 0x00000333, 0xfb1e, 0xccc00333, 0xfb1f, - - 0x33300333, 0x1b20, 0xfff00333, 0x1b21, 0x000cc333, 0x1b22, 0xccccc333, - 0x1b23, 0x333cc333, 0x1b24, 0xfffcc333, 0x1b25, 0x00033333, 0x1b26, - 0xccc33333, 0x1b27, 0xfff33333, 0x1b28, 0x000ff333, 0x1b29, 0xcccff333, - 0x1b2a, 0x333ff333, 0x1b2b, 0xfffff333, 0x1b2c, 0x00000fff, 0x1b2d, - 0xccc00fff, 0x1b2e, 0x33300fff, 0x1b2f, - - 0xfff00fff, 0x1b30, 0x000ccfff, 0x1b31, 0xcccccfff, 0x1b32, 0x333ccfff, - 0x1b33, 0xfffccfff, 0x1b34, 0x00033fff, 0x1b35, 0xccc33fff, 0x1b36, - 0x33333fff, 0x1b37, 0xfff33fff, 0x1b38, 0x000fffff, 0x1b39, 0xcccfffff, - 0x1b3a, 0x333fffff, 0x1b3b, - - 0, 1 // End marker for extended TELETEXT mode. -}; - -/** - * @brief Struct to represent a character to be drawn. - * @param fgColor RGB - * @param bgColor RGB - * @param codePoint The code point of the character to be drawn. - */ -struct CharData { - std::array fgColor = std::array{0, 0, 0}; - std::array bgColor = std::array{0, 0, 0}; - int codePoint; -}; - -typedef std::function GetPixelFunction; - -// Return a CharData struct with the given code point and corresponding averag -// fg and bg colors. -CharData createCharData(GetPixelFunction get_pixel, int x0, int y0, - int codepoint, int pattern) { - CharData result; - result.codePoint = codepoint; - int fg_count = 0; - int bg_count = 0; - unsigned int mask = 0x80000000; - - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - int *avg; - if (pattern & mask) { - avg = result.fgColor.data(); - fg_count++; - } else { - avg = result.bgColor.data(); - bg_count++; - } - for (int i = 0; i < 3; i++) { - avg[i] += get_pixel(x0 + x, y0 + y, i); - } - mask = mask >> 1; - } - } - - // Calculate the average color value for each bucket - for (int i = 0; i < 3; i++) { - if (bg_count != 0) { - result.bgColor[i] /= bg_count; - } - if (fg_count != 0) { - result.fgColor[i] /= fg_count; - } - } - return result; -} - -/** - * @brief Find the best character and colors - * for a 4x8 part of the image at the given position - * - * @param image - * @param x0 - * @param y0 - * @param flags - * @return CharData - */ -CharData findCharData(GetPixelFunction get_pixel, int x0, int y0, - const int &flags) { - int min[3] = {255, 255, 255}; - int max[3] = {0}; - std::map count_per_color; - - // Determine the minimum and maximum value for each color channel - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - long color = 0; - for (int i = 0; i < 3; i++) { - int d = get_pixel(x0 + x, y0 + y, i); - min[i] = std::min(min[i], d); - max[i] = std::max(max[i], d); - color = (color << 8) | d; - } - count_per_color[color]++; - } - } - - std::multimap color_per_count; - for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) { - color_per_count.insert(std::pair(i->second, i->first)); - } - - auto iter = color_per_count.rbegin(); - int count2 = iter->first; - long max_count_color_1 = iter->second; - long max_count_color_2 = max_count_color_1; - if ((++iter) != color_per_count.rend()) { - count2 += iter->first; - max_count_color_2 = iter->second; - } - - unsigned int bits = 0; - bool direct = count2 > (8 * 4) / 2; - - if (direct) { - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - bits = bits << 1; - int d1 = 0; - int d2 = 0; - for (int i = 0; i < 3; i++) { - int shift = 16 - 8 * i; - int c1 = (max_count_color_1 >> shift) & 255; - int c2 = (max_count_color_2 >> shift) & 255; - int c = get_pixel(x0 + x, y0 + y, i); - d1 += (c1 - c) * (c1 - c); - d2 += (c2 - c) * (c2 - c); - } - if (d1 > d2) { - bits |= 1; - } - } - } - } else { - // Determine the color channel with the greatest range. - int splitIndex = 0; - int bestSplit = 0; - for (int i = 0; i < 3; i++) { - if (max[i] - min[i] > bestSplit) { - bestSplit = max[i] - min[i]; - splitIndex = i; - } - } - - // We just split at the middle of the interval instead of computing the - // median. - int splitValue = min[splitIndex] + bestSplit / 2; - - // Compute a bitmap using the given split and sum the color values for - // both buckets. - for (int y = 0; y < 8; y++) { - for (int x = 0; x < 4; x++) { - bits = bits << 1; - if (get_pixel(x0 + x, y0 + y, splitIndex) > splitValue) { - bits |= 1; - } - } - } - } - - // Find the best bitmap match by counting the bits that don't match, - // including the inverted bitmaps. - int best_diff = 8; - unsigned int best_pattern = 0x0000ffff; - int codepoint = 0x2584; - bool inverted = false; - unsigned int end_marker = flags & FLAG_TELETEXT ? 1 : 0; - for (int i = 0; BITMAPS[i + 1] != end_marker; i += 2) { - // Skip all end markers - if (BITMAPS[i + 1] < 32) { - continue; - } - unsigned int pattern = BITMAPS[i]; - for (int j = 0; j < 2; j++) { - int diff = (std::bitset<32>(pattern ^ bits)).count(); - if (diff < best_diff) { - best_pattern = BITMAPS[i]; // pattern might be inverted. - codepoint = BITMAPS[i + 1]; - best_diff = diff; - inverted = best_pattern != pattern; - } - pattern = ~pattern; - } - } - - if (direct) { - CharData result; - if (inverted) { - long tmp = max_count_color_1; - max_count_color_1 = max_count_color_2; - max_count_color_2 = tmp; - } - for (int i = 0; i < 3; i++) { - int shift = 16 - 8 * i; - result.fgColor[i] = (max_count_color_2 >> shift) & 255; - result.bgColor[i] = (max_count_color_1 >> shift) & 255; - result.codePoint = codepoint; - } - return result; - } - return createCharData(get_pixel, x0, y0, codepoint, best_pattern); -} - -int clamp_byte(int value) { - return value < 0 ? 0 : (value > 255 ? 255 : value); -} - -double sqr(double n) { return n * n; } - -int best_index(int value, const int STEPS[], int count) { - int best_diff = std::abs(STEPS[0] - value); - int result = 0; - for (int i = 1; i < count; i++) { - int diff = std::abs(STEPS[i] - value); - if (diff < best_diff) { - result = i; - best_diff = diff; - } - } - return result; -} - void printTermColor(const int &flags, int r, int g, int b) { r = clamp_byte(r); g = clamp_byte(g); diff --git a/src/tiv_lib.cpp b/src/tiv_lib.cpp new file mode 100644 index 0000000..6d346d7 --- /dev/null +++ b/src/tiv_lib.cpp @@ -0,0 +1,351 @@ +/* + * Copyright (c) 2017-2023, Stefan Haustein, Aaron Liu + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Alternatively, you may copy, redistribute and/or modify this file under + * the terms of the Apache License, version 2.0: + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + #include "tiv_lib.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +// An interleaved map of 4x8 bit character bitmaps (each hex digit represents a +// row) to the corresponding unicode character code point. +constexpr unsigned int BITMAPS[] = { + 0x00000000, 0x00a0, + + // Block graphics + // 0xffff0000, 0x2580, // upper 1/2; redundant with inverse lower 1/2 + + 0x0000000f, 0x2581, // lower 1/8 + 0x000000ff, 0x2582, // lower 1/4 + 0x00000fff, 0x2583, 0x0000ffff, 0x2584, // lower 1/2 + 0x000fffff, 0x2585, 0x00ffffff, 0x2586, // lower 3/4 + 0x0fffffff, 0x2587, + // 0xffffffff, 0x2588, // full; redundant with inverse space + + 0xeeeeeeee, 0x258a, // left 3/4 + 0xcccccccc, 0x258c, // left 1/2 + 0x88888888, 0x258e, // left 1/4 + + 0x0000cccc, 0x2596, // quadrant lower left + 0x00003333, 0x2597, // quadrant lower right + 0xcccc0000, + 0x2598, // quadrant upper left + // 0xccccffff, 0x2599, // 3/4 redundant with inverse 1/4 + 0xcccc3333, 0x259a, // diagonal 1/2 + // 0xffffcccc, 0x259b, // 3/4 redundant + // 0xffff3333, 0x259c, // 3/4 redundant + 0x33330000, 0x259d, // quadrant upper right + // 0x3333cccc, 0x259e, // 3/4 redundant + // 0x3333ffff, 0x259f, // 3/4 redundant + + // Line drawing subset: no double lines, no complex light lines + + 0x000ff000, 0x2501, // Heavy horizontal + 0x66666666, 0x2503, // Heavy vertical + + 0x00077666, 0x250f, // Heavy down and right + 0x000ee666, 0x2513, // Heavy down and left + 0x66677000, 0x2517, // Heavy up and right + 0x666ee000, 0x251b, // Heavy up and left + + 0x66677666, 0x2523, // Heavy vertical and right + 0x666ee666, 0x252b, // Heavy vertical and left + 0x000ff666, 0x2533, // Heavy down and horizontal + 0x666ff000, 0x253b, // Heavy up and horizontal + 0x666ff666, 0x254b, // Heavy cross + + 0x000cc000, 0x2578, // Bold horizontal left + 0x00066000, 0x2579, // Bold horizontal up + 0x00033000, 0x257a, // Bold horizontal right + 0x00066000, 0x257b, // Bold horizontal down + + 0x06600660, 0x254f, // Heavy double dash vertical + + 0x000f0000, 0x2500, // Light horizontal + 0x0000f000, 0x2500, // + 0x44444444, 0x2502, // Light vertical + 0x22222222, 0x2502, + + 0x000e0000, 0x2574, // light left + 0x0000e000, 0x2574, // light left + 0x44440000, 0x2575, // light up + 0x22220000, 0x2575, // light up + 0x00030000, 0x2576, // light right + 0x00003000, 0x2576, // light right + 0x00004444, 0x2577, // light down + 0x00002222, 0x2577, // light down + + // Misc technical + + 0x44444444, 0x23a2, // [ extension + 0x22222222, 0x23a5, // ] extension + + 0x0f000000, 0x23ba, // Horizontal scanline 1 + 0x00f00000, 0x23bb, // Horizontal scanline 3 + 0x00000f00, 0x23bc, // Horizontal scanline 7 + 0x000000f0, 0x23bd, // Horizontal scanline 9 + + // Geometrical shapes. Tricky because some of them are too wide. + + // 0x00ffff00, 0x25fe, // Black medium small square + 0x00066000, 0x25aa, // Black small square + + // 0x11224488, 0x2571, // diagonals + // 0x88442211, 0x2572, + // 0x99666699, 0x2573, + // 0x000137f0, 0x25e2, // Triangles + // 0x0008cef0, 0x25e3, + // 0x000fec80, 0x25e4, + // 0x000f7310, 0x25e5, + + 0, 0, // End marker for "regular" characters + + // Teletext / legacy graphics 3x2 block character codes. + // Using a 3-2-3 pattern consistently, perhaps we should create automatic + // variations.... + + 0xccc00000, 0xfb00, 0x33300000, 0xfb01, 0xfff00000, 0xfb02, 0x000cc000, + 0xfb03, 0xccccc000, 0xfb04, 0x333cc000, 0xfb05, 0xfffcc000, 0xfb06, + 0x00033000, 0xfb07, 0xccc33000, 0xfb08, 0x33333000, 0xfb09, 0xfff33000, + 0xfb0a, 0x000ff000, 0xfb0b, 0xcccff000, 0xfb0c, 0x333ff000, 0xfb0d, + 0xfffff000, 0xfb0e, 0x00000ccc, 0xfb0f, + + 0xccc00ccc, 0xfb10, 0x33300ccc, 0xfb11, 0xfff00ccc, 0xfb12, 0x000ccccc, + 0xfb13, 0x333ccccc, 0xfb14, 0xfffccccc, 0xfb15, 0x00033ccc, 0xfb16, + 0xccc33ccc, 0xfb17, 0x33333ccc, 0xfb18, 0xfff33ccc, 0xfb19, 0x000ffccc, + 0xfb1a, 0xcccffccc, 0xfb1b, 0x333ffccc, 0xfb1c, 0xfffffccc, 0xfb1d, + 0x00000333, 0xfb1e, 0xccc00333, 0xfb1f, + + 0x33300333, 0x1b20, 0xfff00333, 0x1b21, 0x000cc333, 0x1b22, 0xccccc333, + 0x1b23, 0x333cc333, 0x1b24, 0xfffcc333, 0x1b25, 0x00033333, 0x1b26, + 0xccc33333, 0x1b27, 0xfff33333, 0x1b28, 0x000ff333, 0x1b29, 0xcccff333, + 0x1b2a, 0x333ff333, 0x1b2b, 0xfffff333, 0x1b2c, 0x00000fff, 0x1b2d, + 0xccc00fff, 0x1b2e, 0x33300fff, 0x1b2f, + + 0xfff00fff, 0x1b30, 0x000ccfff, 0x1b31, 0xcccccfff, 0x1b32, 0x333ccfff, + 0x1b33, 0xfffccfff, 0x1b34, 0x00033fff, 0x1b35, 0xccc33fff, 0x1b36, + 0x33333fff, 0x1b37, 0xfff33fff, 0x1b38, 0x000fffff, 0x1b39, 0xcccfffff, + 0x1b3a, 0x333fffff, 0x1b3b, + + 0, 1 // End marker for extended TELETEXT mode. +}; + + +CharData createCharData(GetPixelFunction get_pixel, int x0, int y0, + int codepoint, int pattern) { + CharData result; + result.codePoint = codepoint; + int fg_count = 0; + int bg_count = 0; + unsigned int mask = 0x80000000; + + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + int *avg; + if (pattern & mask) { + avg = result.fgColor.data(); + fg_count++; + } else { + avg = result.bgColor.data(); + bg_count++; + } + for (int i = 0; i < 3; i++) { + avg[i] += get_pixel(x0 + x, y0 + y, i); + } + mask = mask >> 1; + } + } + + // Calculate the average color value for each bucket + for (int i = 0; i < 3; i++) { + if (bg_count != 0) { + result.bgColor[i] /= bg_count; + } + if (fg_count != 0) { + result.fgColor[i] /= fg_count; + } + } + return result; +} + +CharData findCharData(GetPixelFunction get_pixel, int x0, int y0, + const int &flags) { + int min[3] = {255, 255, 255}; + int max[3] = {0}; + std::map count_per_color; + + // Determine the minimum and maximum value for each color channel + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + long color = 0; + for (int i = 0; i < 3; i++) { + int d = get_pixel(x0 + x, y0 + y, i); + min[i] = std::min(min[i], d); + max[i] = std::max(max[i], d); + color = (color << 8) | d; + } + count_per_color[color]++; + } + } + + std::multimap color_per_count; + for (auto i = count_per_color.begin(); i != count_per_color.end(); ++i) { + color_per_count.insert(std::pair(i->second, i->first)); + } + + auto iter = color_per_count.rbegin(); + int count2 = iter->first; + long max_count_color_1 = iter->second; + long max_count_color_2 = max_count_color_1; + if ((++iter) != color_per_count.rend()) { + count2 += iter->first; + max_count_color_2 = iter->second; + } + + unsigned int bits = 0; + bool direct = count2 > (8 * 4) / 2; + + if (direct) { + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + bits = bits << 1; + int d1 = 0; + int d2 = 0; + for (int i = 0; i < 3; i++) { + int shift = 16 - 8 * i; + int c1 = (max_count_color_1 >> shift) & 255; + int c2 = (max_count_color_2 >> shift) & 255; + int c = get_pixel(x0 + x, y0 + y, i); + d1 += (c1 - c) * (c1 - c); + d2 += (c2 - c) * (c2 - c); + } + if (d1 > d2) { + bits |= 1; + } + } + } + } else { + // Determine the color channel with the greatest range. + int splitIndex = 0; + int bestSplit = 0; + for (int i = 0; i < 3; i++) { + if (max[i] - min[i] > bestSplit) { + bestSplit = max[i] - min[i]; + splitIndex = i; + } + } + + // We just split at the middle of the interval instead of computing the + // median. + int splitValue = min[splitIndex] + bestSplit / 2; + + // Compute a bitmap using the given split and sum the color values for + // both buckets. + for (int y = 0; y < 8; y++) { + for (int x = 0; x < 4; x++) { + bits = bits << 1; + if (get_pixel(x0 + x, y0 + y, splitIndex) > splitValue) { + bits |= 1; + } + } + } + } + + // Find the best bitmap match by counting the bits that don't match, + // including the inverted bitmaps. + int best_diff = 8; + unsigned int best_pattern = 0x0000ffff; + int codepoint = 0x2584; + bool inverted = false; + unsigned int end_marker = flags & FLAG_TELETEXT ? 1 : 0; + for (int i = 0; BITMAPS[i + 1] != end_marker; i += 2) { + // Skip all end markers + if (BITMAPS[i + 1] < 32) { + continue; + } + unsigned int pattern = BITMAPS[i]; + for (int j = 0; j < 2; j++) { + int diff = (std::bitset<32>(pattern ^ bits)).count(); + if (diff < best_diff) { + best_pattern = BITMAPS[i]; // pattern might be inverted. + codepoint = BITMAPS[i + 1]; + best_diff = diff; + inverted = best_pattern != pattern; + } + pattern = ~pattern; + } + } + + if (direct) { + CharData result; + if (inverted) { + long tmp = max_count_color_1; + max_count_color_1 = max_count_color_2; + max_count_color_2 = tmp; + } + for (int i = 0; i < 3; i++) { + int shift = 16 - 8 * i; + result.fgColor[i] = (max_count_color_2 >> shift) & 255; + result.bgColor[i] = (max_count_color_1 >> shift) & 255; + result.codePoint = codepoint; + } + return result; + } + return createCharData(get_pixel, x0, y0, codepoint, best_pattern); +} + +int clamp_byte(int value) { + return value < 0 ? 0 : (value > 255 ? 255 : value); +} + +double sqr(double n) { return n * n; } + +int best_index(int value, const int STEPS[], int count) { + int best_diff = std::abs(STEPS[0] - value); + int result = 0; + for (int i = 1; i < count; i++) { + int diff = std::abs(STEPS[i] - value); + if (diff < best_diff) { + result = i; + best_diff = diff; + } + } + return result; +} diff --git a/src/tiv_lib.h b/src/tiv_lib.h new file mode 100644 index 0000000..4bdfc17 --- /dev/null +++ b/src/tiv_lib.h @@ -0,0 +1,99 @@ + +/* + * Copyright (c) 2017-2023, Stefan Haustein, Aaron Liu + * + * This file is free software: you may copy, redistribute and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation, either version 3 of the License, or (at your + * option) any later version. + * + * This file is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Alternatively, you may copy, redistribute and/or modify this file under + * the terms of the Apache License, version 2.0: + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TIV_LIB_H +#define TIV_LIB_H + + +#include +#include + +// Implementation of flag representation for flags in the main() method +constexpr int FLAG_FG = 1; +constexpr int FLAG_BG = 2; +constexpr int FLAG_MODE_256 = 4; // Limit colors to 256-color mode +constexpr int FLAG_24BIT = 8; // 24-bit color mode +constexpr int FLAG_NOOPT = 16; // Only use the same half-block character +constexpr int FLAG_TELETEXT = 32; // Use teletext characters + + +// Color saturation value steps from 0 to 255 +constexpr int COLOR_STEP_COUNT = 6; +constexpr int COLOR_STEPS[COLOR_STEP_COUNT] = {0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}; + +// Grayscale saturation value steps from 0 to 255 +constexpr int GRAYSCALE_STEP_COUNT = 24; +constexpr int GRAYSCALE_STEPS[GRAYSCALE_STEP_COUNT] = { + 0x08, 0x12, 0x1c, 0x26, 0x30, 0x3a, 0x44, 0x4e, 0x58, 0x62, 0x6c, 0x76, + 0x80, 0x8a, 0x94, 0x9e, 0xa8, 0xb2, 0xbc, 0xc6, 0xd0, 0xda, 0xe4, 0xee}; + + +typedef std::function GetPixelFunction; + +int clamp_byte(int value); + +int best_index(int value, const int STEPS[], int count); + +double sqr(double n); + +/** + * @brief Struct to represent a character to be drawn. + * @param fgColor RGB + * @param bgColor RGB + * @param codePoint The code point of the character to be drawn. + */ +struct CharData { + std::array fgColor = std::array{0, 0, 0}; + std::array bgColor = std::array{0, 0, 0}; + int codePoint; +}; + +// Return a CharData struct with the given code point and corresponding averag +// fg and bg colors. +CharData createCharData(GetPixelFunction get_pixel, int x0, int y0, + int codepoint, int pattern); + +/** + * @brief Find the best character and colors + * for a 4x8 part of the image at the given position + * + * @param image + * @param x0 + * @param y0 + * @param flags + * @return CharData + */ +CharData findCharData(GetPixelFunction get_pixel, int x0, int y0, + const int &flags); + +#endif \ No newline at end of file