From 077b0e99ea1388e6767c5514a7d7c4e04eb04283 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 24 Jan 2024 18:59:13 -0500 Subject: [PATCH 1/6] 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 7e69848d8415e3907eb98439b352522a407dc7f6 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Wed, 24 Jan 2024 19:08:14 -0500 Subject: [PATCH 2/6] hopefully this fixes gcc --- src/tiv.cpp | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/tiv.cpp b/src/tiv.cpp index 3a24205..e51cc1b 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -35,6 +35,25 @@ * limitations under the License. */ +#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. +#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 + #ifdef _POSIX_VERSION // Console output size detection #include @@ -61,25 +80,6 @@ #define EX_CONFIG 78 /* configuration error */ #endif -#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. -#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 - // @TODO: Convert to bitset // Implementation of flag representation for flags in the main() method constexpr int FLAG_FG = 1; // emit fg color From 352e3f1a7be915731c2b1a63c630fc20e0853151 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 26 Jan 2024 22:38:36 -0500 Subject: [PATCH 3/6] fix spurious size detect error and format error messages from system TODO: fix unexpanded tilde on Windows --- .gitignore | 4 +++ src/tiv.cpp | 92 +++++++++++++++++++++++++++++------------------------ 2 files changed, 54 insertions(+), 42 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 e51cc1b..8aef5a0 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -57,6 +57,8 @@ #ifdef _POSIX_VERSION // Console output size detection #include +// Error explanation, for some reason +#include // Exit codes #include #endif @@ -64,6 +66,8 @@ #ifdef _WIN32 // Console output size detection #include +// Error explanation +#include // Following codes copied from /usr/include/sysexits.h, // license: https://opensource.org/license/BSD-3-clause/ @@ -487,7 +491,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) { @@ -503,7 +507,7 @@ std::string emitImage(const cimg_library::CImg &image, charData.bgColor[1], charData.bgColor[2]); if (x == 0 || charData.fgColor != lastCharData.fgColor) ret += emitTermColor(flags | FLAG_FG, charData.fgColor[0], - charData.fgColor[1], charData.fgColor[2]); + charData.fgColor[1], charData.fgColor[2]); ret += (charData.codePoint); lastCharData = charData; } @@ -558,10 +562,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; } @@ -630,43 +636,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, @@ -679,7 +653,7 @@ int main(int argc, char *argv[]) { if (argc <= 1) { printUsage(); - return 0; + return EX_USAGE; } for (int i = 1; i < argc; i++) { @@ -699,16 +673,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") { @@ -725,7 +699,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); @@ -738,6 +712,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 38f0de444dbcedaa71bceae830fb1ae8e094e14a Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Fri, 26 Jan 2024 23:01:37 -0500 Subject: [PATCH 4/6] 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 a20067a28b211b96b073801f044793ea39a39239 Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 1 Feb 2024 07:46:35 -0500 Subject: [PATCH 5/6] 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 a5f0555de1bc40be2ccf8460bfb2e4e971ae1943 Mon Sep 17 00:00:00 2001 From: Stefan Haustein Date: Thu, 1 Feb 2024 21:51:51 +0100 Subject: [PATCH 6/6] 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 8aef5a0..3fe671e 100644 --- a/src/tiv.cpp +++ b/src/tiv.cpp @@ -41,10 +41,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. @@ -238,9 +241,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; @@ -259,7 +265,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; } @@ -287,8 +293,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; @@ -298,7 +304,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); @@ -335,7 +341,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); } @@ -364,7 +370,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; } } @@ -411,7 +417,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) { @@ -438,9 +444,13 @@ std::string emitTermColor(const int8_t &flags, int r, int g, int b) { 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 @@ -467,7 +477,8 @@ std::string emitTermColor(const int8_t &flags, int r, int g, int b) { 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) { @@ -492,6 +503,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) { @@ -500,8 +516,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(flags | FLAG_BG, charData.bgColor[0], charData.bgColor[1], charData.bgColor[2]); @@ -537,9 +553,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"; } }