summaryrefslogtreecommitdiffstats
path: root/src/logo/image
diff options
context:
space:
mode:
Diffstat (limited to 'src/logo/image')
-rw-r--r--src/logo/image/im6.c27
-rw-r--r--src/logo/image/im7.c28
-rw-r--r--src/logo/image/image.c1032
-rw-r--r--src/logo/image/image.h43
4 files changed, 1130 insertions, 0 deletions
diff --git a/src/logo/image/im6.c b/src/logo/image/im6.c
new file mode 100644
index 0000000..3e9d722
--- /dev/null
+++ b/src/logo/image/im6.c
@@ -0,0 +1,27 @@
+#ifdef FF_HAVE_IMAGEMAGICK6
+
+ #include "image.h"
+ #include "common/library.h"
+
+ #include <magick/MagickCore.h>
+
+static FF_LIBRARY_SYMBOL(ResizeImage)
+
+ static void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo) {
+ return ffResizeImage(image, width, height, UndefinedFilter, 1.0, exceptionInfo);
+}
+
+FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData) {
+ FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR, "libMagickCore-6.Q16HDRI" FF_LIBRARY_EXTENSION, 8, "libMagickCore-6.Q16" FF_LIBRARY_EXTENSION, 8)
+ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+
+ FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {
+ .resizeFunc = logoResize,
+ .library = imageMagick,
+ });
+
+ imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552
+ return result;
+}
+
+#endif
diff --git a/src/logo/image/im7.c b/src/logo/image/im7.c
new file mode 100644
index 0000000..636a5fa
--- /dev/null
+++ b/src/logo/image/im7.c
@@ -0,0 +1,28 @@
+#ifdef FF_HAVE_IMAGEMAGICK7
+
+ #include "image.h"
+ #include "common/library.h"
+
+ #include <MagickCore/MagickCore.h>
+
+static FF_LIBRARY_SYMBOL(ResizeImage)
+
+ static void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo) {
+ return ffResizeImage(image, width, height, UndefinedFilter, exceptionInfo);
+}
+
+FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData) {
+ FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR, "libMagickCore-7.Q16HDRI" FF_LIBRARY_EXTENSION, 11, "libMagickCore-7.Q16" FF_LIBRARY_EXTENSION, 11, "libMagickCore-7.Q16HDRI-10" FF_LIBRARY_EXTENSION, -1 // Required for Windows
+ )
+ FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+
+ FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {
+ .resizeFunc = logoResize,
+ .library = imageMagick,
+ });
+
+ imageMagick = NULL; // leak imageMagick to prevent fastfetch from crashing #552
+ return result;
+}
+
+#endif
diff --git a/src/logo/image/image.c b/src/logo/image/image.c
new file mode 100644
index 0000000..6dd3212
--- /dev/null
+++ b/src/logo/image/image.c
@@ -0,0 +1,1032 @@
+#include "image.h"
+#include "common/io.h"
+#include "common/printing.h"
+#include "common/processing.h"
+#include "common/strutil.h"
+#include "common/base64.h"
+#include "detection/terminalsize/terminalsize.h"
+
+#include <limits.h>
+#include <math.h>
+
+#ifdef __APPLE__
+ #include <sys/syslimits.h>
+#elif _WIN32
+ #include <windows.h>
+#elif __linux__
+ #include <sys/sendfile.h>
+#elif __sun
+ #include <sys/termios.h>
+#endif
+
+static bool printImageIterm(bool printError) {
+ const FFOptionsLogo* options = &instance.config.logo;
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+ if (!ffAppendFileBuffer(options->source.chars, &buf)) {
+ if (printError) {
+ fputs("Logo (iterm): Failed to load image file\n", stderr);
+ }
+ return false;
+ }
+
+ fflush(stdout);
+
+ bool inTmux = false;
+ {
+ const char* term = getenv("TERM");
+ inTmux = term && (ffStrStartsWith(term, "screen") || ffStrStartsWith(term, "tmux"));
+ }
+
+ FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&buf);
+ ffStrbufClear(&buf);
+
+ if (!options->width || !options->height) {
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ ffStrbufAppendNC(&buf, options->paddingTop, '\n');
+ ffStrbufAppendNC(&buf, options->paddingLeft, ' ');
+ } else if (options->position == FF_LOGO_POSITION_RIGHT) {
+ if (!options->width) {
+ if (printError) {
+ fputs("Logo (iterm): Must set logo width when using position right\n", stderr);
+ }
+ return false;
+ }
+ ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);
+ }
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\ePtmux;\e");
+ }
+
+ if (options->width) {
+ ffStrbufAppendF(&buf, "\e]1337;File=inline=1;width=%u:%s\a", (unsigned) options->width, base64.chars);
+ } else {
+ ffStrbufAppendF(&buf, "\e]1337;File=inline=1:%s\a", base64.chars);
+ }
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\e\\");
+ }
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
+
+ if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
+ uint16_t X = 0, Y = 0;
+ const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X);
+ if (error) {
+ fprintf(stderr, "\nLogo (iterm): fail to query cursor position: %s\n", error);
+ return true; // We already printed image logo, don't print ascii logo then
+ }
+ if (X < options->paddingLeft + options->width) {
+ X = (uint16_t) (options->paddingLeft + options->width);
+ }
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = X + options->paddingRight - 1;
+ }
+ instance.state.logoHeight = Y;
+ fputs("\e[H", stdout);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffPrintCharTimes('\n', options->paddingRight);
+ }
+ } else {
+ ffStrbufAppendNC(&buf, options->paddingTop, '\n');
+ if (options->position == FF_LOGO_POSITION_RIGHT) {
+ ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width);
+ } else if (options->paddingLeft) {
+ ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft);
+ }
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\ePtmux;\e");
+ }
+ ffStrbufAppendF(&buf, "\e]1337;File=inline=1;width=%u;height=%u;preserveAspectRatio=%u:%s\a", (unsigned) options->width, (unsigned) options->height, (unsigned) options->preserveAspectRatio, base64.chars);
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\e\\");
+ }
+ ffStrbufAppendC(&buf, '\n');
+
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;
+ instance.state.logoHeight = options->paddingTop + options->height;
+ ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffStrbufAppendNC(&buf, options->paddingRight, '\n');
+ } else if (options->position == FF_LOGO_POSITION_RIGHT) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffStrbufAppendF(&buf, "\e[1G\e[%uA", (unsigned) options->height);
+ }
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
+ }
+
+ return true;
+}
+
+static bool printImageKittyIcat(bool printError) {
+ const FFOptionsLogo* options = &instance.config.logo;
+
+ if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) {
+ if (printError) {
+ fputs("Logo (kitty-icat): Failed to load image file\n", stderr);
+ }
+ return false;
+ }
+
+ fflush(stdout);
+
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ if (!options->width) {
+ ffStrbufAppendNC(&buf, options->paddingTop, '\n');
+ ffStrbufAppendNC(&buf, options->paddingLeft, ' ');
+ } else {
+ if (printError) {
+ fputs("Logo (kitty-icat): position top is not supported when logo width is set\n", stderr);
+ }
+ return false;
+ }
+ } else if (options->position == FF_LOGO_POSITION_RIGHT) {
+ if (printError) {
+ fputs("Logo (kitty-icat): position right is not supported\n", stderr);
+ }
+ return false;
+ }
+
+ uint32_t prevLength = buf.length;
+
+ const char* error = NULL;
+
+ if (options->width) {
+ char place[64];
+ snprintf(place,
+ ARRAY_SIZE(place),
+ "--place=%ux%u@%ux%u",
+ options->width,
+ options->height == 0 ? 9999 : options->height,
+ options->paddingLeft + 1,
+ options->paddingTop + 1);
+
+ error = ffProcessAppendStdOut(&buf, (char*[]) {
+ "kitten",
+ "icat",
+ "-n",
+ "--align=center",
+ place,
+ "--scale-up",
+ options->source.chars,
+ NULL,
+ });
+ } else {
+ error = ffProcessAppendStdOut(&buf, (char*[]) {
+ "kitten",
+ "icat",
+ "-n",
+ "--align=left",
+ options->source.chars,
+ NULL,
+ });
+ }
+ if (error) {
+ if (printError) {
+ fprintf(stderr, "Logo (kitty-icat): running `kitten icat` failed %s\n", error);
+ }
+ return false;
+ }
+
+ if (buf.length == prevLength) {
+ if (printError) {
+ fputs("Logo (kitty-icat): `kitten icat` returned empty output\n", stderr);
+ }
+ return false;
+ }
+
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
+
+ if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
+ uint16_t X = 0, Y = 0;
+ const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X);
+ if (error) {
+ fprintf(stderr, "\nLogo (kitty-icat): fail to query cursor position: %s\n", error);
+ return true; // We already printed image logo, don't print ascii logo then
+ }
+ if (X < options->paddingLeft + options->width) {
+ X = (uint16_t) (options->paddingLeft + options->width);
+ }
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = X + options->paddingRight - 1;
+ }
+ instance.state.logoHeight = Y;
+ fputs("\e[H", stdout);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffPrintCharTimes('\n', options->paddingRight);
+ }
+
+ return true;
+}
+
+static bool printImageKittyDirect(bool printError) {
+ const FFOptionsLogo* options = &instance.config.logo;
+
+ if (!ffPathExists(options->source.chars, FF_PATHTYPE_FILE)) {
+ if (printError) {
+ fputs("Logo (kitty-direct): Failed to load image file\n", stderr);
+ }
+ return false;
+ }
+
+ fflush(stdout);
+
+ bool inTmux = false;
+ {
+ const char* term = getenv("TERM");
+ inTmux = term && (ffStrStartsWith(term, "screen") || ffStrStartsWith(term, "tmux"));
+ }
+
+ FF_STRBUF_AUTO_DESTROY base64 = ffBase64EncodeStrbuf(&options->source);
+ FF_STRBUF_AUTO_DESTROY buf = ffStrbufCreate();
+
+ if (!options->width || !options->height) {
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ // We must clear the entre screen to make sure that terminal buffer won't scroll up
+ ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;%uH", (unsigned) options->paddingTop + 1, (unsigned) options->paddingLeft + 1);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ ffStrbufAppendNC(&buf, options->paddingTop, '\n');
+ ffStrbufAppendNC(&buf, options->paddingLeft, ' ');
+ } else if (options->position == FF_LOGO_POSITION_RIGHT) {
+ if (!options->width) {
+ if (printError) {
+ fputs("Logo (iterm): Must set logo width when using position right\n", stderr);
+ }
+ return false;
+ }
+ ffStrbufAppendF(&buf, "\e[2J\e[3J\e[%u;9999999H\e[%uD", (unsigned) options->paddingTop + 1, (unsigned) options->paddingRight + options->width);
+ }
+
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\ePtmux;\e");
+ }
+ if (options->width) {
+ ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f,c=%u;%s", (unsigned) options->width, base64.chars);
+ } else {
+ ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f;%s", base64.chars);
+ }
+ if (inTmux) {
+ ffStrbufAppendC(&buf, '\e');
+ }
+ ffStrbufAppendS(&buf, "\e\\");
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\e\\");
+ }
+
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
+
+ if (options->position == FF_LOGO_POSITION_LEFT || options->position == FF_LOGO_POSITION_RIGHT) {
+ uint16_t X = 0, Y = 0;
+ const char* error = ffGetTerminalResponse("\e[6n", 2, "%*[^0-9]%hu;%huR", &Y, &X);
+ if (error) {
+ if (printError) {
+ fprintf(stderr, "\nLogo (kitty-direct): fail to query cursor position: %s\n", error);
+ }
+ return true; // We already printed image logo, don't print ascii logo then
+ }
+ if (X < options->paddingLeft + options->width) {
+ X = (uint16_t) (options->paddingLeft + options->width);
+ }
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = X + options->paddingRight - 1;
+ }
+ instance.state.logoHeight = Y;
+ fputs("\e[H", stdout);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffPrintCharTimes('\n', options->paddingRight);
+ }
+ } else {
+ ffStrbufAppendNC(&buf, options->paddingTop, '\n');
+
+ if (options->position == FF_LOGO_POSITION_RIGHT) {
+ ffStrbufAppendF(&buf, "\e[9999999C\e[%uD", (unsigned) options->paddingRight + options->width);
+ } else if (options->paddingLeft) {
+ ffStrbufAppendF(&buf, "\e[%uC", (unsigned) options->paddingLeft);
+ }
+
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\ePtmux;\e");
+ }
+
+ ffStrbufAppendF(&buf, "\e_Ga=T,f=100,t=f,c=%u,r=%u;%s\e\\", (unsigned) options->width, (unsigned) options->height, base64.chars);
+ if (inTmux) {
+ ffStrbufAppendS(&buf, "\e\\");
+ }
+ ffStrbufAppendC(&buf, '\n');
+ if (options->position == FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = options->width + options->paddingLeft + options->paddingRight;
+ instance.state.logoHeight = options->paddingTop + options->height;
+ ffStrbufAppendF(&buf, "\e[%uA", (unsigned) instance.state.logoHeight);
+ } else if (options->position == FF_LOGO_POSITION_TOP) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffStrbufAppendNC(&buf, options->paddingRight, '\n');
+ } else if (options->position == FF_LOGO_POSITION_RIGHT) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ ffStrbufAppendF(&buf, "\e[1G\e[%uA", (unsigned) options->height);
+ }
+
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), &buf);
+ }
+
+ return true;
+}
+
+#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)
+
+ #define FF_KITTY_MAX_CHUNK_SIZE 4096
+
+ #define FF_CACHE_FILE_HEIGHT "height"
+ #define FF_CACHE_FILE_WIDTH "width"
+ #define FF_CACHE_FILE_SIXEL "sixel"
+ #define FF_CACHE_FILE_KITTY_COMPRESSED "kittyc"
+ #define FF_CACHE_FILE_KITTY_UNCOMPRESSED "kittyu"
+ #define FF_CACHE_FILE_CHAFA "chafa"
+
+ #include <string.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+
+ #ifndef _WIN32
+ #include <sys/ioctl.h>
+ #else
+ #include <wincon.h>
+ #include "common/path.h"
+ #endif
+
+ #ifdef FF_HAVE_ZLIB
+ #include "common/library.h"
+ #include <stdlib.h>
+ #include <zlib.h>
+
+static bool compressBlob(void** blob, size_t* length) {
+ FF_LIBRARY_LOAD(zlib, false, "libz" FF_LIBRARY_EXTENSION, 2)
+ FF_LIBRARY_LOAD_SYMBOL(zlib, compressBound, false)
+ FF_LIBRARY_LOAD_SYMBOL(zlib, compress2, false)
+
+ uLong compressedLength = ffcompressBound(*length);
+ void* compressed = malloc(compressedLength);
+ if (compressed == NULL) {
+ return false;
+ }
+
+ if (ffcompress2(compressed, &compressedLength, *blob, *length, Z_BEST_COMPRESSION) != Z_OK) {
+ free(compressed);
+ return false;
+ }
+
+ free(*blob);
+
+ *length = (size_t) compressedLength;
+ *blob = compressed;
+ return true;
+}
+
+ #endif // FF_HAVE_ZLIB
+
+ // We use only the defines from here, that are exactly the same in both versions
+ #ifdef FF_HAVE_IMAGEMAGICK7
+ #include <MagickCore/MagickCore.h>
+ #else
+ #include <magick/MagickCore.h>
+ #endif
+
+typedef struct ImageData {
+ FF_LIBRARY_SYMBOL(CopyMagickString)
+ FF_LIBRARY_SYMBOL(ImageToBlob)
+ FF_LIBRARY_SYMBOL(Base64Encode)
+
+ ImageInfo* imageInfo;
+ Image* image;
+ ExceptionInfo* exceptionInfo;
+} ImageData;
+
+static inline bool checkAllocationResult(void* data, size_t length) {
+ if (data == NULL) {
+ return false;
+ }
+
+ if (length == 0) {
+ free(data);
+ return false;
+ }
+
+ return true;
+}
+
+static void writeCacheStrbuf(FFLogoRequestData* requestData, const FFstrbuf* value, const char* cacheFileName) {
+ uint32_t cacheDirLength = requestData->cacheDir.length;
+ ffStrbufAppendS(&requestData->cacheDir, cacheFileName);
+ ffWriteFileBuffer(requestData->cacheDir.chars, value);
+ ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
+}
+
+static void writeCacheUint32(FFLogoRequestData* requestData, uint32_t value, const char* cacheFileName) {
+ FFstrbuf content;
+ content.chars = (char*) &value;
+ content.length = sizeof(value);
+ writeCacheStrbuf(requestData, &content, cacheFileName);
+}
+
+static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* result, const char* cacheFileName) {
+ const FFOptionsLogo* options = &instance.config.logo;
+ // Calculate character dimensions
+ instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight;
+ instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop - 1;
+
+ // Write cache files
+ writeCacheStrbuf(requestData, result, cacheFileName);
+
+ if (options->width == 0) {
+ writeCacheUint32(requestData, requestData->logoCharacterWidth, FF_CACHE_FILE_WIDTH);
+ }
+
+ if (options->height == 0) {
+ writeCacheUint32(requestData, requestData->logoCharacterHeight, FF_CACHE_FILE_HEIGHT);
+ }
+
+ // Write result to stdout
+ ffPrintCharTimes('\n', options->paddingTop);
+ if (options->position == FF_LOGO_POSITION_RIGHT) {
+ printf("\e[9999999C\e[%uD", (unsigned) options->paddingRight + requestData->logoCharacterWidth);
+ } else if (options->paddingLeft) {
+ printf("\e[%uC", (unsigned) options->paddingLeft);
+ }
+ fflush(stdout);
+ ffWriteFDBuffer(FFUnixFD2NativeFD(STDOUT_FILENO), result);
+
+ if (options->position != FF_LOGO_POSITION_TOP) {
+ // Go to upper left corner
+ printf("\e[1G\e[%uA", instance.state.logoHeight);
+ }
+
+ if (options->position != FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ }
+}
+
+static bool printImageSixel(FFLogoRequestData* requestData, const ImageData* imageData) {
+ imageData->ffCopyMagickString(imageData->imageInfo->magick, "SIXEL", 6);
+
+ size_t length;
+ void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);
+ if (!checkAllocationResult(blob, length)) {
+ return false;
+ }
+
+ FFstrbuf result;
+ result.chars = (char*) blob;
+ result.length = (uint32_t) length;
+
+ printImagePixels(requestData, &result, FF_CACHE_FILE_SIXEL);
+
+ free(blob);
+ return true;
+}
+
+static void appendKittyChunk(FFstrbuf* result, const char** blob, size_t* length, bool printEscapeCode) {
+ uint32_t chunkSize = *length > FF_KITTY_MAX_CHUNK_SIZE ? FF_KITTY_MAX_CHUNK_SIZE : (uint32_t) *length;
+
+ if (printEscapeCode) {
+ ffStrbufAppendS(result, "\033_G");
+ } else {
+ ffStrbufAppendC(result, ',');
+ }
+
+ ffStrbufAppendS(result, chunkSize != *length ? "m=1" : "m=0");
+ ffStrbufAppendC(result, ';');
+ ffStrbufAppendNS(result, chunkSize, *blob);
+ ffStrbufAppendS(result, "\033\\");
+ *length -= chunkSize;
+ *blob += chunkSize;
+}
+
+static bool printImageKitty(FFLogoRequestData* requestData, const ImageData* imageData) {
+ imageData->ffCopyMagickString(imageData->imageInfo->magick, "RGBA", 5);
+
+ size_t length;
+ void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);
+ if (!checkAllocationResult(blob, length)) {
+ return false;
+ }
+
+ #ifdef FF_HAVE_ZLIB
+ bool isCompressed = compressBlob(&blob, &length);
+ #else
+ bool isCompressed = false;
+ #endif
+
+ char* chars = imageData->ffBase64Encode(blob, length, &length);
+ free(blob);
+ if (!checkAllocationResult(chars, length)) {
+ return false;
+ }
+
+ FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA((uint32_t) (length + 1024));
+
+ const char* currentPos = chars;
+ size_t remainingLength = length;
+
+ ffStrbufAppendF(&result, "\033_Ga=T,f=32,s=%u,v=%u", requestData->logoPixelWidth, requestData->logoPixelHeight);
+ if (isCompressed) {
+ ffStrbufAppendS(&result, ",o=z");
+ }
+ appendKittyChunk(&result, &currentPos, &remainingLength, false);
+ while (remainingLength > 0) {
+ appendKittyChunk(&result, &currentPos, &remainingLength, true);
+ }
+
+ printImagePixels(requestData, &result, isCompressed ? FF_CACHE_FILE_KITTY_COMPRESSED : FF_CACHE_FILE_KITTY_UNCOMPRESSED);
+
+ free(chars);
+ return true;
+}
+
+ #ifdef FF_HAVE_CHAFA
+ #include <chafa.h>
+static bool printImageChafa(FFLogoRequestData* requestData, const ImageData* imageData) {
+ FF_LIBRARY_LOAD(chafa, false, "libchafa" FF_LIBRARY_EXTENSION, 1, "libchafa-0" FF_LIBRARY_EXTENSION, -1 // Required for Windows
+ )
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_new, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_apply_selectors, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_new, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_geometry, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_set_symbol_map, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_new, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_draw_all_pixels, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_print, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_unref, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_canvas_config_unref, false)
+ FF_LIBRARY_LOAD_SYMBOL(chafa, chafa_symbol_map_unref, false)
+
+ imageData->ffCopyMagickString(imageData->imageInfo->magick, "RGBA", 5);
+ size_t length;
+ void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);
+ if (!checkAllocationResult(blob, length)) {
+ return false;
+ }
+
+ ChafaSymbolMap* symbolMap = ffchafa_symbol_map_new();
+ GError* error = NULL;
+ if (!ffchafa_symbol_map_apply_selectors(symbolMap, instance.config.logo.chafaSymbols.chars, &error)) {
+ fputs(error->message, stderr);
+ }
+
+ ChafaCanvasConfig* canvasConfig = ffchafa_canvas_config_new();
+ ffchafa_canvas_config_set_geometry(canvasConfig, (gint) requestData->logoCharacterWidth, (gint) requestData->logoCharacterHeight);
+ ffchafa_canvas_config_set_symbol_map(canvasConfig, symbolMap);
+
+ if (instance.config.logo.chafaFgOnly) {
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_fg_only_enabled);
+ if (ffchafa_canvas_config_set_fg_only_enabled) {
+ ffchafa_canvas_config_set_fg_only_enabled(canvasConfig, true);
+ }
+ }
+ if (instance.config.logo.chafaCanvasMode < CHAFA_CANVAS_MODE_MAX) {
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_canvas_mode);
+ if (ffchafa_canvas_config_set_canvas_mode) {
+ ffchafa_canvas_config_set_canvas_mode(canvasConfig, (ChafaCanvasMode) instance.config.logo.chafaCanvasMode);
+ }
+ }
+ if (instance.config.logo.chafaColorSpace < CHAFA_COLOR_SPACE_MAX) {
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_color_space)
+ if (ffchafa_canvas_config_set_color_space) {
+ ffchafa_canvas_config_set_color_space(canvasConfig, (ChafaColorSpace) instance.config.logo.chafaColorSpace);
+ }
+ }
+ if (instance.config.logo.chafaDitherMode < CHAFA_DITHER_MODE_MAX) {
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, chafa_canvas_config_set_dither_mode)
+ if (ffchafa_canvas_config_set_dither_mode) {
+ ffchafa_canvas_config_set_dither_mode(canvasConfig, (ChafaDitherMode) instance.config.logo.chafaDitherMode);
+ }
+ }
+
+ ChafaCanvas* canvas = ffchafa_canvas_new(canvasConfig);
+ ffchafa_canvas_draw_all_pixels(
+ canvas,
+ CHAFA_PIXEL_RGBA8_UNASSOCIATED,
+ blob,
+ (gint) imageData->image->columns,
+ (gint) imageData->image->rows,
+ (gint) imageData->image->columns * 4);
+
+ GString* str = ffchafa_canvas_print(canvas, NULL);
+ FFstrbuf result;
+ result.allocated = (uint32_t) str->allocated_len;
+ result.length = (uint32_t) str->len;
+ result.chars = str->str;
+
+ ffLogoPrintChars(result.chars, false);
+ writeCacheStrbuf(requestData, &result, FF_CACHE_FILE_CHAFA);
+
+ // FIXME: These functions must be imported from `libglib` dlls on Windows
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_string_free);
+ if (ffg_string_free) {
+ ffg_string_free(str, TRUE);
+ }
+ if (error) {
+ FF_LIBRARY_LOAD_SYMBOL_LAZY(chafa, g_error_free)
+ if (ffg_error_free) {
+ ffg_error_free(error);
+ }
+ }
+
+ ffchafa_canvas_unref(canvas);
+ ffchafa_canvas_config_unref(canvasConfig);
+ ffchafa_symbol_map_unref(symbolMap);
+
+ return true;
+}
+ #endif
+
+FFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData) {
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL(imData->library, DestroyImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+
+ ImageData imageData;
+
+ FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+ FF_LIBRARY_LOAD_SYMBOL_VAR(imData->library, imageData, Base64Encode, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
+
+ ffMagickCoreGenesis(NULL, MagickFalse);
+
+ imageData.exceptionInfo = ffAcquireExceptionInfo();
+ if (imageData.exceptionInfo == NULL) {
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+
+ ImageInfo* imageInfoIn = ffAcquireImageInfo();
+ if (imageInfoIn == NULL) {
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+
+ //+1, because we need to copy the null byte too
+ imageData.ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1);
+
+ imageData.image = ffReadImage(imageInfoIn, imageData.exceptionInfo);
+ ffDestroyImageInfo(imageInfoIn);
+ if (imageData.image == NULL) {
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+
+ if (requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0) {
+ requestData->logoPixelWidth = (uint32_t) imageData.image->columns;
+ requestData->logoPixelHeight = (uint32_t) imageData.image->rows;
+ } else if (requestData->logoPixelWidth == 0) {
+ requestData->logoPixelWidth = (uint32_t) ((double) imageData.image->columns / (double) imageData.image->rows * requestData->logoPixelHeight);
+ } else if (requestData->logoPixelHeight == 0) {
+ requestData->logoPixelHeight = (uint32_t) ((double) imageData.image->rows / (double) imageData.image->columns * requestData->logoPixelWidth);
+ }
+
+ requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth);
+ requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight);
+
+ if (requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0 || requestData->logoCharacterWidth == 0 || requestData->logoCharacterHeight == 0) {
+ ffDestroyImage(imageData.image);
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+
+ Image* resized = imData->resizeFunc(imageData.image, requestData->logoPixelWidth, requestData->logoPixelHeight, imageData.exceptionInfo);
+ ffDestroyImage(imageData.image);
+ if (resized == NULL) {
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+ imageData.image = resized;
+
+ imageData.imageInfo = ffAcquireImageInfo();
+ if (imageData.imageInfo == NULL) {
+ ffDestroyImage(imageData.image);
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+ return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+ }
+
+ bool printSuccessful = false;
+ if (requestData->type == FF_LOGO_TYPE_IMAGE_CHAFA) {
+ #if FF_HAVE_CHAFA
+ printSuccessful = printImageChafa(requestData, &imageData);
+ #endif
+ } else if (requestData->type == FF_LOGO_TYPE_IMAGE_KITTY) {
+ printSuccessful = printImageKitty(requestData, &imageData);
+ } else if (requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL) {
+ printSuccessful = printImageSixel(requestData, &imageData);
+ }
+
+ ffDestroyImageInfo(imageData.imageInfo);
+ ffDestroyImage(imageData.image);
+ ffDestroyExceptionInfo(imageData.exceptionInfo);
+ ffMagickCoreTerminus();
+
+ return printSuccessful ? FF_LOGO_IMAGE_RESULT_SUCCESS : FF_LOGO_IMAGE_RESULT_RUN_ERROR;
+}
+
+static FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileName) {
+ uint32_t cacheDirLength = requestData->cacheDir.length;
+ ffStrbufAppendS(&requestData->cacheDir, fileName);
+ #ifndef _WIN32
+ int fd = open(requestData->cacheDir.chars, O_RDONLY
+ #ifdef O_CLOEXEC
+ | O_CLOEXEC
+ #endif
+ );
+ #else
+ HANDLE fd = CreateFileA(requestData->cacheDir.chars, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ #endif
+ ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
+ return fd;
+}
+
+static void readCachedStrbuf(FFLogoRequestData* requestData, FFstrbuf* result, const char* cacheFileName) {
+ uint32_t cacheDirLength = requestData->cacheDir.length;
+ ffStrbufAppendS(&requestData->cacheDir, cacheFileName);
+ ffAppendFileBuffer(requestData->cacheDir.chars, result);
+ ffStrbufSubstrBefore(&requestData->cacheDir, cacheDirLength);
+}
+
+static uint32_t readCachedUint32(FFLogoRequestData* requestData, const char* cacheFileName) {
+ FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
+ readCachedStrbuf(requestData, &content, cacheFileName);
+
+ uint32_t result = 0;
+
+ if (content.length != sizeof(result)) {
+ return 0;
+ }
+
+ memcpy(&result, content.chars, sizeof(result));
+
+ return result;
+}
+
+static bool printCachedChars(FFLogoRequestData* requestData, const char* cacheFileName) {
+ FF_STRBUF_AUTO_DESTROY content = ffStrbufCreate();
+ readCachedStrbuf(requestData, &content, cacheFileName);
+
+ if (content.length == 0) {
+ return false;
+ }
+
+ ffLogoPrintChars(content.chars, false);
+ return true;
+}
+
+static bool printCachedPixel(FFLogoRequestData* requestData) {
+ FFOptionsLogo* options = &instance.config.logo;
+
+ requestData->logoCharacterWidth = options->width;
+ if (requestData->logoCharacterWidth == 0) {
+ requestData->logoCharacterWidth = readCachedUint32(requestData, FF_CACHE_FILE_WIDTH);
+ if (requestData->logoCharacterWidth == 0) {
+ return false;
+ }
+ }
+
+ requestData->logoCharacterHeight = options->height;
+ if (requestData->logoCharacterHeight == 0) {
+ requestData->logoCharacterHeight = readCachedUint32(requestData, FF_CACHE_FILE_HEIGHT);
+ if (requestData->logoCharacterHeight == 0) {
+ return false;
+ }
+ }
+
+ FF_AUTO_CLOSE_FD FFNativeFD fd = FF_INVALID_FD;
+ if (requestData->type == FF_LOGO_TYPE_IMAGE_KITTY) {
+ fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_COMPRESSED);
+ if (!ffIsValidNativeFD(fd)) {
+ fd = getCacheFD(requestData, FF_CACHE_FILE_KITTY_UNCOMPRESSED);
+ }
+ } else if (requestData->type == FF_LOGO_TYPE_IMAGE_SIXEL) {
+ fd = getCacheFD(requestData, FF_CACHE_FILE_SIXEL);
+ }
+
+ if (!ffIsValidNativeFD(fd)) {
+ return false;
+ }
+
+ ffPrintCharTimes('\n', options->paddingTop);
+ if (options->position == FF_LOGO_POSITION_RIGHT) {
+ printf("\e[9999999C\e[%uD", (unsigned) options->paddingRight + requestData->logoCharacterWidth);
+ } else if (options->paddingLeft) {
+ printf("\e[%uC", (unsigned) options->paddingLeft);
+ }
+ fflush(stdout);
+
+ bool sent = false;
+ #ifdef __linux__
+ struct stat st;
+ if (fstat(fd, &st) >= 0) {
+ while (st.st_size > 0) {
+ ssize_t bytes = sendfile(STDOUT_FILENO, fd, NULL, (size_t) st.st_size);
+ if (bytes > 0) {
+ sent = true;
+ st.st_size -= bytes;
+ } else {
+ break;
+ }
+ }
+ }
+ #endif
+
+ if (!sent) {
+ char buffer[32768];
+ ssize_t readBytes;
+ while ((readBytes = ffReadFDData(fd, sizeof(buffer), buffer)) > 0) {
+ ffWriteFDData(FFUnixFD2NativeFD(STDOUT_FILENO), (size_t) readBytes, buffer);
+ }
+ }
+
+ instance.state.logoWidth = requestData->logoCharacterWidth + options->paddingLeft + options->paddingRight;
+ instance.state.logoHeight = requestData->logoCharacterHeight + options->paddingTop;
+
+ if (options->position != FF_LOGO_POSITION_TOP) {
+ // Go to upper left corner
+ printf("\e[1G\e[%uA", instance.state.logoHeight);
+ }
+
+ if (options->position != FF_LOGO_POSITION_LEFT) {
+ instance.state.logoWidth = instance.state.logoHeight = 0;
+ }
+ return true;
+}
+
+static bool getCharacterPixelDimensions(FFLogoRequestData* requestData) {
+ #ifdef _WIN32
+
+ CONSOLE_FONT_INFOEX cfi = { .cbSize = sizeof(cfi) };
+ if (GetCurrentConsoleFontEx(GetStdHandle(STD_OUTPUT_HANDLE), FALSE, &cfi)) // Only works for ConHost
+ {
+ requestData->characterPixelWidth = cfi.dwFontSize.X;
+ requestData->characterPixelHeight = cfi.dwFontSize.Y;
+ }
+ if (requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0) {
+ return true;
+ }
+ #endif
+
+ FFTerminalSizeResult termSize = {};
+ if (ffDetectTerminalSize(&termSize)) {
+ requestData->characterPixelWidth = termSize.width / (double) termSize.columns;
+ requestData->characterPixelHeight = termSize.height / (double) termSize.rows;
+ }
+
+ return requestData->characterPixelWidth > 1.0 && requestData->characterPixelHeight > 1.0;
+}
+
+static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
+ FFLogoRequestData requestData;
+ requestData.type = type;
+ requestData.characterPixelWidth = 1;
+ requestData.characterPixelHeight = 1;
+
+ if (!getCharacterPixelDimensions(&requestData)) {
+ if (printError) {
+ fputs("Logo: getCharacterPixelDimensions() failed\n", stderr);
+ }
+ return false;
+ }
+
+ requestData.logoPixelWidth = (uint32_t) ceil((double) instance.config.logo.width * requestData.characterPixelWidth);
+ requestData.logoPixelHeight = (uint32_t) ceil((double) instance.config.logo.height * requestData.characterPixelHeight);
+
+ ffStrbufInit(&requestData.cacheDir);
+ ffStrbufAppend(&requestData.cacheDir, &instance.state.platform.cacheDir);
+ ffStrbufAppendS(&requestData.cacheDir, "fastfetch/images");
+
+ ffStrbufEnsureFree(&requestData.cacheDir, PATH_MAX);
+ char* filePath = requestData.cacheDir.chars + requestData.cacheDir.length;
+ if (realpath(instance.config.logo.source.chars, filePath) == NULL) {
+ // We can safely return here, because if realpath failed, we surely won't be able to read the file
+ ffStrbufDestroy(&requestData.cacheDir);
+ if (printError) {
+ fputs("Logo: Querying realpath of the image source failed\n", stderr);
+ }
+ return false;
+ }
+
+ #ifdef _WIN32
+ filePath[1] = filePath[0]; // Drive Name
+ filePath[0] = '/';
+ #endif
+
+ ffStrbufRecalculateLength(&requestData.cacheDir);
+ ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/');
+ ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight);
+
+ if (!instance.config.logo.recache) {
+ bool cacheValid = requestData.type == FF_LOGO_TYPE_IMAGE_CHAFA
+ ? printCachedChars(&requestData, FF_CACHE_FILE_CHAFA)
+ : printCachedPixel(&requestData);
+ if (cacheValid) {
+ ffStrbufDestroy(&requestData.cacheDir);
+ return true;
+ }
+ }
+
+ FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_INIT_ERROR;
+
+ #ifdef FF_HAVE_IMAGEMAGICK7
+ result = ffLogoPrintImageIM7(&requestData);
+ #endif
+
+ #ifdef FF_HAVE_IMAGEMAGICK6
+ if (result == FF_LOGO_IMAGE_RESULT_INIT_ERROR) {
+ result = ffLogoPrintImageIM6(&requestData);
+ }
+ #endif
+
+ ffStrbufDestroy(&requestData.cacheDir);
+
+ if (result == FF_LOGO_IMAGE_RESULT_SUCCESS) {
+ return true;
+ }
+
+ if (printError) {
+ if (result == FF_LOGO_IMAGE_RESULT_INIT_ERROR) {
+ fputs("Logo: Image Magick library not found\n", stderr);
+ } else {
+ fputs("Logo: Failed to load / convert the image source\n", stderr);
+ }
+ }
+
+ return false;
+}
+
+#endif // FF_HAVE_IMAGEMAGICK{6, 7}
+
+bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) {
+ if (instance.config.display.pipe) {
+ if (printError) {
+ fputs("Logo: Image logo is not supported in pipe mode\n", stderr);
+ }
+ return false;
+ }
+
+ if (!ffPathExists(instance.config.logo.source.chars, FF_PATHTYPE_FILE)) {
+ if (printError) {
+ fprintf(stderr, "Logo: Image source \"%s\" does not exist\n", instance.config.logo.source.chars);
+ }
+ return false;
+ }
+
+ const char* term = getenv("TERM");
+ if ((term && ffStrEquals(term, "screen")) || getenv("ZELLIJ")) {
+ if (printError) {
+ fputs("Logo: Image logo is not supported in terminal multiplexers\n", stderr);
+ }
+ return false;
+ }
+
+ if (type == FF_LOGO_TYPE_IMAGE_ITERM) {
+ return printImageIterm(printError);
+ }
+
+ if (type == FF_LOGO_TYPE_IMAGE_KITTY_DIRECT) {
+ return printImageKittyDirect(printError);
+ }
+
+ if (type == FF_LOGO_TYPE_IMAGE_KITTY_ICAT) {
+ return printImageKittyIcat(printError);
+ }
+
+#if !FF_HAVE_CHAFA
+ if (type == FF_LOGO_TYPE_IMAGE_CHAFA) {
+ if (printError) {
+ fputs("Logo: Chafa support is not compiled in\n", stderr);
+ }
+ return false;
+ }
+#endif
+
+#if !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)
+ if (printError) {
+ fputs("Logo: Image Magick support is not compiled in\n", stderr);
+ }
+ return false;
+#else
+ return printImageIfExistsSlowPath(type, printError);
+#endif
+}
diff --git a/src/logo/image/image.h b/src/logo/image/image.h
new file mode 100644
index 0000000..da3b2d2
--- /dev/null
+++ b/src/logo/image/image.h
@@ -0,0 +1,43 @@
+#pragma once
+
+#include "../logo.h"
+
+#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)
+
+typedef enum FF_A_PACKED FFLogoImageResult {
+ FF_LOGO_IMAGE_RESULT_SUCCESS, // Logo printed
+ FF_LOGO_IMAGE_RESULT_INIT_ERROR, // Failed to load library, try again with next IM version
+ FF_LOGO_IMAGE_RESULT_RUN_ERROR // Failed to load / convert image, cancel whole sixel code
+} FFLogoImageResult;
+
+typedef struct FFLogoRequestData {
+ FFLogoType type;
+ FFstrbuf cacheDir;
+
+ double characterPixelWidth;
+ double characterPixelHeight;
+
+ uint32_t logoPixelWidth;
+ uint32_t logoPixelHeight;
+
+ uint32_t logoCharacterHeight;
+ uint32_t logoCharacterWidth;
+} FFLogoRequestData;
+
+typedef struct FFIMData {
+ void* library;
+ void* (*resizeFunc)(const void* image, size_t width, size_t height, void* exceptionInfo);
+} FFIMData;
+
+FFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData);
+
+#endif
+
+#ifdef FF_HAVE_IMAGEMAGICK7
+FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData);
+#endif
+
+#ifdef FF_HAVE_IMAGEMAGICK6
+ #include <math.h>
+FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData);
+#endif