From e358e372ec79a145ee4ea89193282e6556b63926 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 17:49:14 -0500 Subject: [PATCH 01/14] fix weird CImg bug --- src/tiv.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/tiv.cpp b/src/tiv.cpp index cef3516..7c5b322 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -45,6 +45,9 @@ // CImg, the superior grafiks library #define cimg_display 0 #include "CImg.h" +// CImg defines its own min and max macros to compile, so we need to undef them +#undef min +#undef max // First include for detecting console output size, // everything else for exit codes From e72b11d3597a3475a9034c29514862644d8ebc77 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 17:52:19 -0500 Subject: [PATCH 02/14] more comments --- src/tiv.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 7c5b322..49a65cc 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -78,8 +78,8 @@ // especially when we're also using the CImg namespace // Implementation of flag representation for flags in the main() method -constexpr int FLAG_FG = 1; -constexpr int FLAG_BG = 2; +constexpr int FLAG_FG = 1; // emit fg color +constexpr int FLAG_BG = 2; // emit bg color 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 @@ -227,8 +227,8 @@ struct CharData { int codePoint; }; -// Return a CharData struct with the given code point and corresponding averag -// fg and bg colors. +// Return a CharData struct with the given code point and corresponding +// average fg and bg colors. CharData createCharData(const cimg_library::CImg &image, int x0, int y0, int codepoint, int pattern) { CharData result; @@ -267,14 +267,14 @@ CharData createCharData(const cimg_library::CImg &image, int x0, } /** - * @brief Find the best character and colors - * for a 4x8 part of the image at the given position + * @brief Find the best character and colors for the given 4x8 area of the image * - * @param image - * @param x0 - * @param y0 + * @param image The image where the pixels reside + * @param x0 The x coordinate of the top left pixel of the area + * @param y0 The y coordinate of the top left pixel of the area * @param flags - * @return CharData + * @return The @ref CharData representation of the colors and character best + * used to render the 4x8 area */ CharData findCharData(const cimg_library::CImg &image, int x0, int y0, const int &flags) { @@ -290,6 +290,7 @@ CharData findCharData(const cimg_library::CImg &image, int x0, int d = image(x0 + x, y0 + y, 0, i); min[i] = std::min(min[i], d); max[i] = std::max(max[i], d); + color = (color << 8) | d; } count_per_color[color]++; @@ -429,11 +430,14 @@ void emit_color(const int &flags, int r, int g, int b) { bool bg = (flags & FLAG_BG) != 0; if ((flags & FLAG_MODE_256) == 0) { + // 2 means we output true (RGB) colors std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' << b << 'm'; return; } + // Compute predefined color index from all 256 colors we should use + int ri = best_index(r, COLOR_STEPS, COLOR_STEP_COUNT); int gi = best_index(g, COLOR_STEPS, COLOR_STEP_COUNT); int bi = best_index(b, COLOR_STEPS, COLOR_STEP_COUNT); @@ -455,6 +459,7 @@ void emit_color(const int &flags, int r, int g, int b) { } else { color_index = 232 + gri; // 1..24 -> 232..255 } + // 38 sets the foreground color and 48 sets the background color std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; } @@ -679,7 +684,7 @@ int main(int argc, char *argv[]) { image.resize(new_size.width, new_size.height, -100, -100, 5); } - // the acutal magic which generates the output + // the actual magick which generates the output emit_image(image, flags); } catch (cimg_library::CImgIOException &e) { std::cerr << "Error: '" << filename From c001eca18d4b5aa8227c89b0283b7751e2324aed Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:01:06 -0500 Subject: [PATCH 03/14] minor optimizations & bump version make flags an int8_t replace excessive instances of endl with \n make a boolean const with better assignment --- src/tiv.cpp | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 49a65cc..6d97f46 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -277,7 +277,7 @@ CharData createCharData(const cimg_library::CImg &image, int x0, * used to render the 4x8 area */ CharData findCharData(const cimg_library::CImg &image, int x0, - int y0, const int &flags) { + int y0, const int8_t &flags) { int min[3] = {255, 255, 255}; int max[3] = {0}; std::map count_per_color; @@ -422,12 +422,12 @@ int best_index(int value, const int STEPS[], int count) { return result; } -void emit_color(const int &flags, int r, int g, int b) { +void emit_color(int r, int g, int b, const int8_t &flags) { r = clamp_byte(r); g = clamp_byte(g); b = clamp_byte(b); - bool bg = (flags & FLAG_BG) != 0; + const bool bg = (flags & FLAG_BG); if ((flags & FLAG_MODE_256) == 0) { // 2 means we output true (RGB) colors @@ -484,7 +484,7 @@ void emitCodepoint(int codepoint) { } void emit_image(const cimg_library::CImg &image, - const int &flags) { + const int8_t &flags) { CharData lastCharData; for (int y = 0; y <= image.height() - 8; y += 8) { for (int x = 0; x <= image.width() - 4; x += 4) { @@ -493,15 +493,15 @@ void emit_image(const cimg_library::CImg &image, ? createCharData(image, x, y, 0x2584, 0x0000ffff) : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) - emit_color(flags | FLAG_BG, charData.bgColor[0], - charData.bgColor[1], charData.bgColor[2]); + emit_color(charData.bgColor[0], charData.bgColor[1], + charData.bgColor[2], flags | FLAG_BG); if (x == 0 || charData.fgColor != lastCharData.fgColor) - emit_color(flags | FLAG_FG, charData.fgColor[0], - charData.fgColor[1], charData.fgColor[2]); + emit_color(charData.fgColor[0], charData.fgColor[1], + charData.fgColor[2], flags | FLAG_FG); emitCodepoint(charData.codePoint); lastCharData = charData; } - std::cout << "\x1b[0m" << std::endl; + std::cout << "\x1b[0m\n"; // clear formatting until next batch } } @@ -548,7 +548,7 @@ cimg_library::CImg load_rgb_CImg(const char *const &filename) { // Implements --help void emit_usage() { std::cerr << R"( -Terminal Image Viewer v1.2.1 +Terminal Image Viewer v1.3 usage: tiv [options] [...] -0 : No block character adjustment, always use top half block char. -2, --256 : Use 256-bit colors. Needed to display properly on macOS Terminal. @@ -603,7 +603,7 @@ int main(int argc, char *argv[]) { #endif // Reading input - char flags = 0; // bitwise representation of flags, + int8_t flags = 0; // bitwise representation of flags, // see https://stackoverflow.com/a/14295472 Mode mode = AUTO; // either THUMBNAIL or FULL_SIZE int columns = 3; @@ -686,6 +686,8 @@ int main(int argc, char *argv[]) { } // the actual magick which generates the output emit_image(image, flags); + std::cout.flush(); // replaces last endl to + // make sure we get output on screen } catch (cimg_library::CImgIOException &e) { std::cerr << "Error: '" << filename << "' has an unrecognized file format" << std::endl; @@ -726,7 +728,7 @@ int main(int argc, char *argv[]) { } } if (count) emit_image(image, flags); - std::cout << sb << std::endl << std::endl; + std::cout << sb << '\n' << std::endl; } } return ret; From 9969dd1cff4c13ad1daff423aadb34ee43bbc5dd Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:04:23 -0500 Subject: [PATCH 04/14] rename some functions change emit to print; emit will be used for things that generate strings/streams use camelCase, closer to google style specify that color is terminal color --- src/tiv.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 6d97f46..d5f30af 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -422,7 +422,7 @@ int best_index(int value, const int STEPS[], int count) { return result; } -void emit_color(int r, int g, int b, const int8_t &flags) { +void printTermColor(int r, int g, int b, const int8_t &flags) { r = clamp_byte(r); g = clamp_byte(g); b = clamp_byte(b); @@ -463,7 +463,7 @@ void emit_color(int r, int g, int b, const int8_t &flags) { std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; } -void emitCodepoint(int codepoint) { +void printCodepoint(int codepoint) { if (codepoint < 128) { std::cout << static_cast(codepoint); } else if (codepoint < 0x7ff) { @@ -483,7 +483,7 @@ void emitCodepoint(int codepoint) { } } -void emit_image(const cimg_library::CImg &image, +void printImage(const cimg_library::CImg &image, const int8_t &flags) { CharData lastCharData; for (int y = 0; y <= image.height() - 8; y += 8) { @@ -493,12 +493,12 @@ void emit_image(const cimg_library::CImg &image, ? createCharData(image, x, y, 0x2584, 0x0000ffff) : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) - emit_color(charData.bgColor[0], charData.bgColor[1], + printTermColor(charData.bgColor[0], charData.bgColor[1], charData.bgColor[2], flags | FLAG_BG); if (x == 0 || charData.fgColor != lastCharData.fgColor) - emit_color(charData.fgColor[0], charData.fgColor[1], + printTermColor(charData.fgColor[0], charData.fgColor[1], charData.fgColor[2], flags | FLAG_FG); - emitCodepoint(charData.codePoint); + printCodepoint(charData.codePoint); lastCharData = charData; } std::cout << "\x1b[0m\n"; // clear formatting until next batch @@ -546,7 +546,7 @@ cimg_library::CImg load_rgb_CImg(const char *const &filename) { } // Implements --help -void emit_usage() { +void printUsage() { std::cerr << R"( Terminal Image Viewer v1.3 usage: tiv [options] [...] @@ -612,7 +612,7 @@ int main(int argc, char *argv[]) { int ret = EX_OK; // The return code for the program if (argc <= 1) { - emit_usage(); + printUsage(); return 0; } @@ -642,11 +642,11 @@ int main(int argc, char *argv[]) { if (i < argc - 1) maxHeight = 8 * std::stoi(argv[++i]); else - emit_usage(); + printUsage(); } else if (arg == "--256" || arg == "-2" || arg == "-256") { flags |= FLAG_MODE_256; } else if (arg == "--help" || arg == "-help") { - emit_usage(); + printUsage(); } else if (arg == "-x") { flags |= FLAG_TELETEXT; } else if (arg[0] == '-') { @@ -685,7 +685,7 @@ int main(int argc, char *argv[]) { 5); } // the actual magick which generates the output - emit_image(image, flags); + printImage(image, flags); std::cout.flush(); // replaces last endl to // make sure we get output on screen } catch (cimg_library::CImgIOException &e) { @@ -727,7 +727,7 @@ int main(int argc, char *argv[]) { // Probably no image; ignore. } } - if (count) emit_image(image, flags); + if (count) printImage(image, flags); std::cout << sb << '\n' << std::endl; } } From e636a03c13cfdbf76a0bde003f8b8a80cc4d8453 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:05:50 -0500 Subject: [PATCH 05/14] separate print and string generation functions string generation during image processing now have their own functions --- src/tiv.cpp | 89 +++++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 73 insertions(+), 16 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index d5f30af..6779edb 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -422,18 +422,14 @@ int best_index(int value, const int STEPS[], int count) { return result; } -void printTermColor(int r, int g, int b, const int8_t &flags) { - r = clamp_byte(r); - g = clamp_byte(g); - b = clamp_byte(b); +std::string emitTermColor(const int8_t &flags, int r, int g, int b) { + r = clamp_byte(r), g = clamp_byte(g), b = clamp_byte(b); const bool bg = (flags & FLAG_BG); - if ((flags & FLAG_MODE_256) == 0) { + if (!(flags & FLAG_MODE_256)) { // 2 means we output true (RGB) colors - std::cout << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' - << b << 'm'; - return; + return std::format("\x1b[{};2;{};{};{}m", bg ? 48 : 38, r, g, b); } // Compute predefined color index from all 256 colors we should use @@ -460,10 +456,10 @@ void printTermColor(int r, int g, int b, const int8_t &flags) { color_index = 232 + gri; // 1..24 -> 232..255 } // 38 sets the foreground color and 48 sets the background color - std::cout << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; + return std::format("\x1b[{};5;{}m", bg ? 48 : 38, color_index); } -void printCodepoint(int codepoint) { +void emitCodepoint(int codepoint) { if (codepoint < 128) { std::cout << static_cast(codepoint); } else if (codepoint < 0x7ff) { @@ -483,21 +479,82 @@ void printCodepoint(int codepoint) { } } -void printImage(const cimg_library::CImg &image, - const int8_t &flags) { +std::string emitImage(const cimg_library::CImg &image, + const int8_t &flags) { + std::string ret; CharData lastCharData; for (int y = 0; y <= image.height() - 8; y += 8) { for (int x = 0; x <= image.width() - 4; x += 4) { + // Create CharData for the current 4x8 area of the image + // If only half-block chars are allowed, use predefined codepoint CharData charData = flags & FLAG_NOOPT ? createCharData(image, x, y, 0x2584, 0x0000ffff) : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) - printTermColor(charData.bgColor[0], charData.bgColor[1], - charData.bgColor[2], flags | FLAG_BG); + ret += emitTermColor(flags | FLAG_BG, charData.bgColor[0], + charData.bgColor[1], charData.bgColor[2]); if (x == 0 || charData.fgColor != lastCharData.fgColor) - printTermColor(charData.fgColor[0], charData.fgColor[1], - charData.fgColor[2], flags | FLAG_FG); + ret += emitTermColor(flags | FLAG_FG, charData.fgColor[0], + charData.fgColor[1], charData.fgColor[2]); + ret += (charData.codePoint); + lastCharData = charData; + } + ret += "\x1b[0m\n"; // clear formatting until next batch + } + return ret; +} + +/** + * @brief Helper function to print a codepoint in a terminal-friendly way + * + * @param codepoint The codepoint to print + */ +void printCodepoint(int codepoint) { + if (codepoint < 128) { // ASCII + std::cout << static_cast(codepoint); + } else if (codepoint < 0x7ff) { // 2-byte UTF-8 + std::cout << static_cast(0xc0 | (codepoint >> 6)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0xffff) { // 3-byte UTF-8 + std::cout << static_cast(0xe0 | (codepoint >> 12)); + std::cout << static_cast(0x80 | ((codepoint >> 6) & 0x3f)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else if (codepoint < 0x10ffff) { // 4-byte UTF-8 + std::cout << static_cast(0xf0 | (codepoint >> 18)); + std::cout << static_cast(0x80 | ((codepoint >> 12) & 0x3f)); + std::cout << static_cast(0x80 | ((codepoint >> 6) & 0x3f)); + std::cout << static_cast(0x80 | (codepoint & 0x3f)); + } else { //??? + std::cerr << std::format( + "Error: Codepoint 0x{:08x} is out of range, skipping this pixel", + codepoint); + } +} + +/** + * @brief Outputs the given image. + * + * @param image The image to output. + * @param flags + */ +void printImage(const cimg_library::CImg &image, + const int8_t &flags) { + CharData lastCharData; + for (int y = 0; y <= image.height() - 8; y += 8) { + for (int x = 0; x <= image.width() - 4; x += 4) { + // Create CharData for the current 4x8 area of the image + // If only half-block chars are allowed, use predefined codepoint + CharData charData = + flags & FLAG_NOOPT + ? createCharData(image, x, y, 0x2584, 0x0000ffff) + : findCharData(image, x, y, flags); + if (x == 0 || charData.bgColor != lastCharData.bgColor) + std::cout << emitTermColor(flags | FLAG_BG, charData.bgColor[0], + charData.bgColor[1], charData.bgColor[2]); + if (x == 0 || charData.fgColor != lastCharData.fgColor) + std::cout << emitTermColor(flags | FLAG_FG, charData.fgColor[0], + charData.fgColor[1], charData.fgColor[2]); printCodepoint(charData.codePoint); lastCharData = charData; } From f51dc08b0ad39913b0987b1c101144649b4bbeea Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:07:20 -0500 Subject: [PATCH 06/14] correct image size in warning it's 20x6, not 80x24 --- src/tiv.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 6779edb..7aa7093 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -625,7 +625,9 @@ int main(int argc, char *argv[]) { std::ios::sync_with_stdio(false); // apparently makes printing faster // Platform-specific implementations for determining console size, better - // implementations are welcome Fallback sizes when unsuccesful + // implementations are welcome + + // Fallback sizes when unsuccesful. Sizes are actually 1/4th of the actual int maxWidth = 80; int maxHeight = 24; #ifdef _POSIX_VERSION @@ -635,7 +637,7 @@ int main(int argc, char *argv[]) { if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != 0 || (w.ws_col | w.ws_row) == 0) { std::cerr << "Warning: failed to determine most reasonable size, " - "defaulting to 80x24" + "defaulting to 20x6" << std::endl; } else { maxWidth = w.ws_col * 4; @@ -651,11 +653,11 @@ int main(int argc, char *argv[]) { } else { std::cerr << "Warning: failed to determine most reasonable size: Error code" - << GetLastError() << ", defaulting to 80x24" << std::endl; + << GetLastError() << ", defaulting to 20x6" << std::endl; } #else std::cerr << "Warning: failed to determine most reasonable size: " - "unrecognized system, defaulting to 80x24" + "unrecognized system, defaulting to 20x6" << std::endl; #endif From 7b3c8286feaa037e769f62f724019334acfdec49 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:13:31 -0500 Subject: [PATCH 07/14] make positioning of flags in emitTermColor consistent with other funcs --- src/tiv.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 7aa7093..a59468d 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -422,7 +422,7 @@ int best_index(int value, const int STEPS[], int count) { return result; } -std::string emitTermColor(const int8_t &flags, int r, int g, int b) { +std::string emitTermColor(int r, int g, int b, const int8_t &flags) { r = clamp_byte(r), g = clamp_byte(g), b = clamp_byte(b); const bool bg = (flags & FLAG_BG); @@ -480,7 +480,7 @@ void emitCodepoint(int codepoint) { } std::string emitImage(const cimg_library::CImg &image, - const int8_t &flags) { + const int8_t &flags) { std::string ret; CharData lastCharData; for (int y = 0; y <= image.height() - 8; y += 8) { @@ -492,11 +492,11 @@ std::string emitImage(const cimg_library::CImg &image, ? createCharData(image, x, y, 0x2584, 0x0000ffff) : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) - ret += emitTermColor(flags | FLAG_BG, charData.bgColor[0], - charData.bgColor[1], charData.bgColor[2]); + ret += emitTermColor(charData.bgColor[0], charData.bgColor[1], + charData.bgColor[2], flags | FLAG_BG); if (x == 0 || charData.fgColor != lastCharData.fgColor) - ret += emitTermColor(flags | FLAG_FG, charData.fgColor[0], - charData.fgColor[1], charData.fgColor[2]); + ret += emitTermColor(charData.fgColor[0], charData.fgColor[1], + charData.fgColor[2], flags | FLAG_FG); ret += (charData.codePoint); lastCharData = charData; } @@ -551,10 +551,12 @@ void printImage(const cimg_library::CImg &image, : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) std::cout << emitTermColor(flags | FLAG_BG, charData.bgColor[0], - charData.bgColor[1], charData.bgColor[2]); + charData.bgColor[1], + charData.bgColor[2]); if (x == 0 || charData.fgColor != lastCharData.fgColor) std::cout << emitTermColor(flags | FLAG_FG, charData.fgColor[0], - charData.fgColor[1], charData.fgColor[2]); + charData.fgColor[1], + charData.fgColor[2]); printCodepoint(charData.codePoint); lastCharData = charData; } From d9c1b47816d9e6a142f721d1ea2b55e21da53f57 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:14:23 -0500 Subject: [PATCH 08/14] fix last commit --- src/tiv.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index a59468d..5a0b9e8 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -550,13 +550,13 @@ void printImage(const cimg_library::CImg &image, ? createCharData(image, x, y, 0x2584, 0x0000ffff) : findCharData(image, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) - std::cout << emitTermColor(flags | FLAG_BG, charData.bgColor[0], - charData.bgColor[1], - charData.bgColor[2]); + std::cout << emitTermColor( + charData.bgColor[0], charData.bgColor[1], + charData.bgColor[2], flags | FLAG_BG); if (x == 0 || charData.fgColor != lastCharData.fgColor) - std::cout << emitTermColor(flags | FLAG_FG, charData.fgColor[0], - charData.fgColor[1], - charData.fgColor[2]); + std::cout << emitTermColor( + charData.fgColor[0], charData.fgColor[1], + charData.fgColor[2], flags | FLAG_FG); printCodepoint(charData.codePoint); lastCharData = charData; } From c6b88559307ccc2e305e9d21030a2a1e99215b64 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 24 Jan 2024 18:59:13 -0500 Subject: [PATCH 09/14] fix CI to force gcc 13 --- .github/workflows/ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a798e4b..8a9fa22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: TerminalImageViewer CI +name: Build and run on: push: @@ -8,14 +8,20 @@ on: jobs: build: - runs-on: ubuntu-latest - steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: sudo apt-get install -qy imagemagick + - name: Validate gcc version + run: | + if [[ $(gcc --version | awk '/gcc/ && ($3+0)>13{print "gcc-13+"}') != "gcc-13+" ]]; then + # Script courtesy of https://stackoverflow.com/a/67791068/16134571 + sudo apt install gcc-13 g++-13 + sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13 + sudo update-alternatives --set gcc /usr/bin/gcc-13 + fi - name: Build run: make -C src - name: Test From 497b53301041a6966503c9c49a2e40e1320568f4 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 24 Jan 2024 19:08:14 -0500 Subject: [PATCH 10/14] hopefully this fixes gcc --- src/tiv.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 8114dff..3b94bb6 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -34,13 +34,14 @@ #include #include #include +#include #include #include #include #include #include -// This #define tells CImg that we use the library without any display options -- +// This #define tells CImg that we use the library without any display options, // just for loading images. #define cimg_display 0 #include "CImg.h" @@ -73,6 +74,7 @@ #define EX_CONFIG 78 /* configuration error */ #endif +// @TODO: Convert to bitset // Implementation of flag representation for flags in the main() method constexpr int FLAG_FG = 1; // emit fg color constexpr int FLAG_BG = 2; // emit bg color @@ -91,8 +93,8 @@ 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. +// 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, From 7b5d7907d1150f0be713af988763dbf648d4a365 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 18:18:50 -0500 Subject: [PATCH 11/14] fix spurious size detect error and format error messages from system TODO: fix unexpanded tilde on Windows --- .gitignore | 4 +++ src/tiv.cpp | 82 ++++++++++++++++++++++++++++------------------------- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/.gitignore b/.gitignore index af5d7fa..566b567 100644 --- a/.gitignore +++ b/.gitignore @@ -47,6 +47,10 @@ x64/ *.vcxproj.* +###Visual Studio Code### +.vscode/ + + ###Java### # Compiled class file diff --git a/src/tiv.cpp b/src/tiv.cpp index 3b94bb6..034b9a7 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -52,12 +52,16 @@ #ifdef _POSIX_VERSION // Console output size detection #include +// Error explanation, for some reason +#include // Exit codes #include #endif #ifdef _WIN32 #include +// Error explanation +#include // Following codes copied from /usr/include/sysexits.h, // license: https://opensource.org/license/BSD-3-clause/ @@ -626,43 +630,11 @@ enum Mode { AUTO, THUMBNAILS, FULL_SIZE }; int main(int argc, char *argv[]) { std::ios::sync_with_stdio(false); // apparently makes printing faster - - // Platform-specific implementations for determining console size, better - // implementations are welcome + bool detectSize = true; // Fallback sizes when unsuccesful. Sizes are actually 1/4th of the actual int maxWidth = 80; int maxHeight = 24; -#ifdef _POSIX_VERSION - struct winsize w; - // If redirecting STDOUT to one file ( col or row == 0, or the previous - // ioctl call's failed ) - if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != 0 || - (w.ws_col | w.ws_row) == 0) { - std::cerr << "Warning: failed to determine most reasonable size, " - "defaulting to 20x6" - << std::endl; - } else { - maxWidth = w.ws_col * 4; - maxHeight = w.ws_row * 8; - } -#elif defined _WIN32 - CONSOLE_SCREEN_BUFFER_INFO w; - if (GetConsoleScreenBufferInfo( - GetStdHandle(STD_OUTPUT_HANDLE), - &w)) { // just like powershell, but without the hyphens, hooray - maxWidth = w.dwSize.X * 4; - maxHeight = w.dwSize.Y * 8; - } else { - std::cerr - << "Warning: failed to determine most reasonable size: Error code" - << GetLastError() << ", defaulting to 20x6" << std::endl; - } -#else - std::cerr << "Warning: failed to determine most reasonable size: " - "unrecognized system, defaulting to 20x6" - << std::endl; -#endif // Reading input int8_t flags = 0; // bitwise representation of flags, @@ -675,7 +647,7 @@ int main(int argc, char *argv[]) { if (argc <= 1) { printUsage(); - return 0; + return EX_USAGE; } for (int i = 1; i < argc; i++) { @@ -695,16 +667,16 @@ int main(int argc, char *argv[]) { mode = FULL_SIZE; } else if (arg == "-w") { if (i < argc - 1) { - maxWidth = 4 * std::stoi(argv[++i]); + maxWidth = 4 * std::stoi(argv[++i]), detectSize = false; } else { std::cerr << "Error: -w requires a number" << std::endl; ret = EX_USAGE; } } else if (arg == "-h") { if (i < argc - 1) - maxHeight = 8 * std::stoi(argv[++i]); + maxHeight = 8 * std::stoi(argv[++i]), detectSize = false; else - printUsage(); + printUsage(); // people might confuse this with help } else if (arg == "--256" || arg == "-2" || arg == "-256") { flags |= FLAG_MODE_256; } else if (arg == "--help" || arg == "-help") { @@ -721,7 +693,7 @@ int main(int argc, char *argv[]) { if (std::filesystem::is_regular_file(p.path())) file_names.push_back(p.path().string()); } else { - // Check if file can be opened + // Check if file can be opened, @TODO find better way std::ifstream fin(arg.c_str()); if (fin) { file_names.push_back(arg); @@ -734,6 +706,40 @@ int main(int argc, char *argv[]) { } } + if (detectSize) { + // Platform-specific implementations for determining console size, + // better implementations are welcome +#ifdef _POSIX_VERSION + struct winsize w; + // If redirecting STDOUT to one file ( col or row == 0, or the previous + // ioctl call's failed ) + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != 0 || + (w.ws_col | w.ws_row) == 0) { + std::cerr << "Warning: failed to determine most reasonable size: " + << strerror(errno) << ", defaulting to 20x6" << std::endl; + } else { + maxWidth = w.ws_col * 4; + maxHeight = w.ws_row * 8; + } +#elif defined _WIN32 + CONSOLE_SCREEN_BUFFER_INFO w; + if (GetConsoleScreenBufferInfo( + GetStdHandle(STD_OUTPUT_HANDLE), + &w)) { // just like powershell, but without the hyphens, hooray + maxWidth = w.dwSize.X * 4; + maxHeight = w.dwSize.Y * 8; + } else { + std::cerr << "Warning: failed to determine most reasonable size: " + << std::system_category().message(GetLastError()) + << ", defaulting to 20x6" << std::endl; + } +#else + std::cerr << "Warning: failed to determine most reasonable size: " + "unrecognized system, defaulting to 20x6" + << std::endl; +#endif + } + if (mode == FULL_SIZE || (mode == AUTO && file_names.size() == 1)) { for (const auto &filename : file_names) { try { From e70f05fdf6f92c1ebe6578d7710ca38a331861c1 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 26 Jan 2024 23:01:37 -0500 Subject: [PATCH 12/14] test dir mode and add more images that could be tested --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8a9fa22..454b937 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,4 +25,7 @@ jobs: - name: Build run: make -C src - name: Test - run: ./src/tiv -w 80 -h 24 /usr/share/pixmaps/debian-logo.png + run: | + images=('/usr/local/share/icons/hicolor/128x128/apps/microsoft-edge.png' '/usr/local/share/icons/hicolor/128x128/apps/CMakeSetup.png' '/usr/local/doc/cmake/html/_static/file.png' '/usr/local/lib/android/sdk/extras/google/google_play_services/samples/tagmanager/cuteanimals/res/drawable/cat_1.jpg' '/usr/local/lib/android/sdk/extras/google/google_play_services/samples/wallet/res/drawable-ldpi/icon.png' '/usr/local/lib/android/sdk/extras/google/google_play_services/samples/wallet/res/drawable-hdpi/icon.png' '/usr/share/plymouth/themes/spinner/watermark.png' '/usr/share/apache2/icons/apache_pb.png' '/usr/share/doc/libpng-dev/examples/pngtest.png') + ./src/tiv -w 160 -h 48 ${images[ $RANDOM % ${#images[@]} ]} # Get random image + ./src/tiv -w 160 -h 48 /usr/share/pixmaps # Dir mode From a64e5527a985d53cc8e271ef44d0e0db13ba7be4 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 07:46:35 -0500 Subject: [PATCH 13/14] Add news and license note --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 3cc331a..4a96490 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ The shell will expand wildcards. By default, thumbnails and file names will be d - 2021-05-22: We now support Apple Clang, thanks to the C++ filesystem library being no longer experimental. Issue forms have also been added to the GitHub repository. - 2023-09-29: Today marks the 40th anniversary of the GNU project. If you haven't learned the news concerning it and Stallman, please do. In project news, @aaronliu0130 will probably be developing this project from now on as the original author has moved on to better things to do. Support for MSVC has been added and the repository is now under an Apache 2.0 or GPL3 dual license. CI building for each release will hopefully be setup soon. The main program has also adopted a mostly Google code-style because I (aaron) think it simply makes sense. `SPDX-License-Identifier: Apache-2.0 OR GPL-3.0-or-later` +- 2024-02-01: We are currently working on splitting the source code into library-agnostic library files and a client that uses CImg. ## Installation @@ -103,3 +104,7 @@ If multiple images match the filename spec, thumbnails are shown. For the example below, the top image was generated with the character optimization disabled via the `-0` option. ![Comparison](https://i.imgur.com/OzdCeh6.png) + +## Licensing + +You are free to use this code under either the GPL (3 or later) or the Apache 2.0. We also use the CImg library, which is licensed under either [CeCILL 2.0](https://spdx.org/licenses/CECILL-2.0.html) (close to GPL and compatible with it) or [CeCILL-C](https://spdx.org/licenses/CECILL-C) (close to LGPL and compatible with Apache). From bc2d21db09b307b675a529a7e9813754f21c3d5b Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Thu, 1 Feb 2024 21:51:51 +0100 Subject: [PATCH 14/14] change format to string streams, as format is only available in 0x20, which complicates compiling on a mac --- src/tiv.cpp | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 034b9a7..0f2ebaa 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -36,10 +36,13 @@ #include #include #include +#include #include #include #include +#include #include +#include // This #define tells CImg that we use the library without any display options, // just for loading images. @@ -232,9 +235,12 @@ struct CharData { int codePoint; }; +typedef std::function GetPixelFunction; + + // Return a CharData struct with the given code point and corresponding // average fg and bg colors. -CharData createCharData(const cimg_library::CImg &image, int x0, +CharData createCharData(GetPixelFunction get_pixel, int x0, int y0, int codepoint, int pattern) { CharData result; result.codePoint = codepoint; @@ -253,7 +259,7 @@ CharData createCharData(const cimg_library::CImg &image, int x0, bg_count++; } for (int i = 0; i < 3; i++) { - avg[i] += image(x0 + x, y0 + y, 0, i); + avg[i] += get_pixel(x0 + x, y0 + y, i); } mask = mask >> 1; } @@ -281,8 +287,8 @@ CharData createCharData(const cimg_library::CImg &image, int x0, * @return The @ref CharData representation of the colors and character best * used to render the 4x8 area */ -CharData findCharData(const cimg_library::CImg &image, int x0, - int y0, const int8_t &flags) { +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; @@ -292,7 +298,7 @@ CharData findCharData(const cimg_library::CImg &image, int x0, for (int x = 0; x < 4; x++) { long color = 0; for (int i = 0; i < 3; i++) { - int d = image(x0 + x, y0 + y, 0, i); + int d = get_pixel(x0 + x, y0 + y, i); min[i] = std::min(min[i], d); max[i] = std::max(max[i], d); @@ -329,7 +335,7 @@ CharData findCharData(const cimg_library::CImg &image, int x0, int shift = 16 - 8 * i; int c1 = (max_count_color_1 >> shift) & 255; int c2 = (max_count_color_2 >> shift) & 255; - int c = image(x0 + x, y0 + y, 0, i); + int c = get_pixel(x0 + x, y0 + y, i); d1 += (c1 - c) * (c1 - c); d2 += (c2 - c) * (c2 - c); } @@ -358,7 +364,7 @@ CharData findCharData(const cimg_library::CImg &image, int x0, for (int y = 0; y < 8; y++) { for (int x = 0; x < 4; x++) { bits = bits << 1; - if (image(x0 + x, y0 + y, 0, splitIndex) > splitValue) { + if (get_pixel(x0 + x, y0 + y, splitIndex) > splitValue) { bits |= 1; } } @@ -405,7 +411,7 @@ CharData findCharData(const cimg_library::CImg &image, int x0, } return result; } - return createCharData(image, x0, y0, codepoint, best_pattern); + return createCharData(get_pixel, x0, y0, codepoint, best_pattern); } int clamp_byte(int value) { @@ -432,9 +438,13 @@ std::string emitTermColor(int r, int g, int b, const int8_t &flags) { const bool bg = (flags & FLAG_BG); + std::stringstream result; + if (!(flags & FLAG_MODE_256)) { // 2 means we output true (RGB) colors - return std::format("\x1b[{};2;{};{};{}m", bg ? 48 : 38, r, g, b); + result << (bg ? "\x1b[48;2;" : "\x1b[38;2;") << r << ';' << g << ';' + << b << 'm'; + return result.str(); } // Compute predefined color index from all 256 colors we should use @@ -461,7 +471,8 @@ std::string emitTermColor(int r, int g, int b, const int8_t &flags) { color_index = 232 + gri; // 1..24 -> 232..255 } // 38 sets the foreground color and 48 sets the background color - return std::format("\x1b[{};5;{}m", bg ? 48 : 38, color_index); + result << (bg ? "\x1B[48;5;" : "\u001B[38;5;") << color_index << "m"; + return result.str(); } void emitCodepoint(int codepoint) { @@ -486,6 +497,11 @@ void emitCodepoint(int codepoint) { std::string emitImage(const cimg_library::CImg &image, const int8_t &flags) { + + GetPixelFunction get_pixel = [&](int x, int y, int channel) -> unsigned char { + return image(x, y, 0, channel); + }; + std::string ret; CharData lastCharData; for (int y = 0; y <= image.height() - 8; y += 8) { @@ -494,8 +510,8 @@ std::string emitImage(const cimg_library::CImg &image, // If only half-block chars are allowed, use predefined codepoint CharData charData = flags & FLAG_NOOPT - ? createCharData(image, x, y, 0x2584, 0x0000ffff) - : findCharData(image, x, y, flags); + ? createCharData(get_pixel, x, y, 0x2584, 0x0000ffff) + : findCharData(get_pixel, x, y, flags); if (x == 0 || charData.bgColor != lastCharData.bgColor) ret += emitTermColor(charData.bgColor[0], charData.bgColor[1], charData.bgColor[2], flags | FLAG_BG); @@ -531,9 +547,8 @@ void printCodepoint(int codepoint) { std::cout << static_cast(0x80 | ((codepoint >> 6) & 0x3f)); std::cout << static_cast(0x80 | (codepoint & 0x3f)); } else { //??? - std::cerr << std::format( - "Error: Codepoint 0x{:08x} is out of range, skipping this pixel", - codepoint); + std::cerr << "Error: Codepoint " << codepoint + << " is out of range, skipping this pixel"; } }