Logo (Image): drops IM in favor of WIC on Windows

This commit is contained in:
李通洲
2026-09-13 22:22:06 +08:00
parent 98e9b6eef1
commit e8350a0d6e
8 changed files with 624 additions and 208 deletions
+3
View File
@@ -281,6 +281,9 @@ void ffListFeatures(void) {
#if FF_ENABLE_WCWIDTH
"Embedded wcwidth\n"
#endif
#if FF_HAVE_SIXEL
"Embedded sixel\n"
#endif
#if FF_HAVE_WINRT
"WinRT headers\n"
#endif
+135 -6
View File
@@ -2,8 +2,10 @@
#include "image.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include <magick/MagickCore.h>
#include <stdlib.h>
static FF_LIBRARY_SYMBOL(ResizeImage)
@@ -11,7 +13,10 @@ static FF_LIBRARY_SYMBOL(ResizeImage)
return ffResizeImage(image, width, height, UndefinedFilter, 1.0, exceptionInfo);
}
FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData) {
// Decode the image source, resize it to the requested pixel size and export it as a blob
// in the given ImageMagick format ("RGBA" for kitty / chafa, "SIXEL" for sixel output).
// On success requestData->logoPixelWidth / logoPixelHeight hold the real image dimensions.
static FFLogoImageResult im6EncodeImage(FFLogoRequestData* requestData, const char* magick, uint32_t magickLength, void** outBlob, size_t* outLength) {
// clang-format off
FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,
"libMagickCore-6.Q16HDRI" FF_LIBRARY_EXTENSION, 8,
@@ -21,13 +26,137 @@ FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData) {
// clang-format on
FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {
.resizeFunc = logoResize,
.library = imageMagick,
});
FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
imageMagick = nullptr; // leak imageMagick to prevent fastfetch from crashing #552
FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_RUN_ERROR;
ExceptionInfo* exceptionInfo = nullptr;
Image* image = nullptr;
ImageInfo* imageInfoOut = nullptr;
FF_AUTO_FREE void* blob = nullptr;
size_t length = 0;
ffMagickCoreGenesis(nullptr, MagickFalse);
exceptionInfo = ffAcquireExceptionInfo();
if (exceptionInfo == nullptr) {
goto cleanup;
}
{
ImageInfo* imageInfoIn = ffAcquireImageInfo();
if (imageInfoIn == nullptr) {
goto cleanup;
}
//+1, because we need to copy the null byte too
ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1);
image = ffReadImage(imageInfoIn, exceptionInfo);
ffDestroyImageInfo(imageInfoIn);
if (image == nullptr) {
goto cleanup;
}
}
if (requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0) {
requestData->logoPixelWidth = (uint32_t) image->columns;
requestData->logoPixelHeight = (uint32_t) image->rows;
} else if (requestData->logoPixelWidth == 0) {
requestData->logoPixelWidth = (uint32_t) ((double) image->columns / (double) image->rows * requestData->logoPixelHeight);
} else if (requestData->logoPixelHeight == 0) {
requestData->logoPixelHeight = (uint32_t) ((double) image->rows / (double) image->columns * requestData->logoPixelWidth);
}
if (requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0) {
goto cleanup;
}
{
Image* resized = logoResize(image, requestData->logoPixelWidth, requestData->logoPixelHeight, exceptionInfo);
ffDestroyImage(image);
image = resized;
if (image == nullptr) {
goto cleanup;
}
}
imageInfoOut = ffAcquireImageInfo();
if (imageInfoOut == nullptr) {
goto cleanup;
}
ffCopyMagickString(imageInfoOut->magick, magick, magickLength);
blob = ffImageToBlob(imageInfoOut, image, &length, exceptionInfo);
if (blob == nullptr || length == 0) {
goto cleanup;
}
*outBlob = blob;
*outLength = length;
blob = nullptr; // Ownership is transferred to the caller
result = FF_LOGO_IMAGE_RESULT_SUCCESS;
cleanup:
if (imageInfoOut) {
ffDestroyImageInfo(imageInfoOut);
}
if (image) {
ffDestroyImage(image);
}
if (exceptionInfo) {
ffDestroyExceptionInfo(exceptionInfo);
}
ffMagickCoreTerminus();
// leak imageMagick to prevent fastfetch from crashing #552
imageMagick = nullptr;
return result;
}
static void setError(FFLogoImageResult result, const char** error) {
if (error) {
*error = result == FF_LOGO_IMAGE_RESULT_INIT_ERROR
? "Image Magick library not found"
: "Failed to load / convert the image source";
}
}
bool ffImageCreateIM6(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
void* blob = nullptr;
size_t length = 0;
FFLogoImageResult result = im6EncodeImage(requestData, "RGBA", 5, &blob, &length);
if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
setError(result, error);
return false;
}
out->data = blob;
out->width = requestData->logoPixelWidth;
out->height = requestData->logoPixelHeight;
return true;
}
bool ffImageSixelEncodeIM6(FFLogoRequestData* requestData, FFstrbuf* out, const char** error) {
FF_AUTO_FREE void* blob = nullptr;
size_t length = 0;
FFLogoImageResult result = im6EncodeImage(requestData, "SIXEL", 6, &blob, &length);
if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
setError(result, error);
return false;
}
ffStrbufSetNS(out, (uint32_t) length, (const char*) blob);
return true;
}
#endif
+135 -6
View File
@@ -2,8 +2,10 @@
#include "image.h"
#include "common/library.h"
#include "common/mallocHelper.h"
#include <MagickCore/MagickCore.h>
#include <stdlib.h>
static FF_LIBRARY_SYMBOL(ResizeImage)
@@ -11,7 +13,10 @@ static FF_LIBRARY_SYMBOL(ResizeImage)
return ffResizeImage(image, width, height, UndefinedFilter, exceptionInfo);
}
FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData) {
// Decode the image source, resize it to the requested pixel size and export it as a blob
// in the given ImageMagick format ("RGBA" for kitty / chafa, "SIXEL" for sixel output).
// On success requestData->logoPixelWidth / logoPixelHeight hold the real image dimensions.
static FFLogoImageResult im7EncodeImage(FFLogoRequestData* requestData, const char* magick, uint32_t magickLength, void** outBlob, size_t* outLength) {
// clang-format off
#if _WIN32
FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,
@@ -27,13 +32,137 @@ FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData) {
// clang-format on
FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FFLogoImageResult result = ffLogoPrintImageImpl(requestData, &(FFIMData) {
.resizeFunc = logoResize,
.library = imageMagick,
});
FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
FF_LIBRARY_LOAD_SYMBOL(imageMagick, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
imageMagick = nullptr; // leak imageMagick to prevent fastfetch from crashing #552
FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_RUN_ERROR;
ExceptionInfo* exceptionInfo = nullptr;
Image* image = nullptr;
ImageInfo* imageInfoOut = nullptr;
FF_AUTO_FREE void* blob = nullptr;
size_t length = 0;
ffMagickCoreGenesis(nullptr, MagickFalse);
exceptionInfo = ffAcquireExceptionInfo();
if (exceptionInfo == nullptr) {
goto cleanup;
}
{
ImageInfo* imageInfoIn = ffAcquireImageInfo();
if (imageInfoIn == nullptr) {
goto cleanup;
}
//+1, because we need to copy the null byte too
ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1);
image = ffReadImage(imageInfoIn, exceptionInfo);
ffDestroyImageInfo(imageInfoIn);
if (image == nullptr) {
goto cleanup;
}
}
if (requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0) {
requestData->logoPixelWidth = (uint32_t) image->columns;
requestData->logoPixelHeight = (uint32_t) image->rows;
} else if (requestData->logoPixelWidth == 0) {
requestData->logoPixelWidth = (uint32_t) ((double) image->columns / (double) image->rows * requestData->logoPixelHeight);
} else if (requestData->logoPixelHeight == 0) {
requestData->logoPixelHeight = (uint32_t) ((double) image->rows / (double) image->columns * requestData->logoPixelWidth);
}
if (requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0) {
goto cleanup;
}
{
Image* resized = logoResize(image, requestData->logoPixelWidth, requestData->logoPixelHeight, exceptionInfo);
ffDestroyImage(image);
image = resized;
if (image == nullptr) {
goto cleanup;
}
}
imageInfoOut = ffAcquireImageInfo();
if (imageInfoOut == nullptr) {
goto cleanup;
}
ffCopyMagickString(imageInfoOut->magick, magick, magickLength);
blob = ffImageToBlob(imageInfoOut, image, &length, exceptionInfo);
if (blob == nullptr || length == 0) {
goto cleanup;
}
*outBlob = blob;
*outLength = length;
blob = nullptr; // Ownership is transferred to the caller
result = FF_LOGO_IMAGE_RESULT_SUCCESS;
cleanup:
if (imageInfoOut) {
ffDestroyImageInfo(imageInfoOut);
}
if (image) {
ffDestroyImage(image);
}
if (exceptionInfo) {
ffDestroyExceptionInfo(exceptionInfo);
}
ffMagickCoreTerminus();
// leak imageMagick to prevent fastfetch from crashing #552
imageMagick = nullptr;
return result;
}
static void setError(FFLogoImageResult result, const char** error) {
if (error) {
*error = result == FF_LOGO_IMAGE_RESULT_INIT_ERROR
? "Image Magick library not found"
: "Failed to load / convert the image source";
}
}
bool ffImageCreateIM7(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
void* blob = nullptr;
size_t length = 0;
FFLogoImageResult result = im7EncodeImage(requestData, "RGBA", 5, &blob, &length);
if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
setError(result, error);
return false;
}
out->data = blob;
out->width = requestData->logoPixelWidth;
out->height = requestData->logoPixelHeight;
return true;
}
bool ffImageSixelEncodeIM7(FFLogoRequestData* requestData, FFstrbuf* out, const char** error) {
FF_AUTO_FREE void* blob = nullptr;
size_t length = 0;
FFLogoImageResult result = im7EncodeImage(requestData, "SIXEL", 6, &blob, &length);
if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
setError(result, error);
return false;
}
ffStrbufSetNS(out, (uint32_t) length, (const char*) blob);
return true;
}
#endif
+129 -184
View File
@@ -1,5 +1,6 @@
#include "image.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include "common/printing.h"
#include "common/processing.h"
#include "common/strutil.h"
@@ -340,7 +341,7 @@ static bool printImageKittyDirect(bool printError) {
return true;
}
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(FF_HAVE_SIXEL)
#define FF_KITTY_MAX_CHUNK_SIZE 4096
@@ -351,6 +352,7 @@ static bool printImageKittyDirect(bool printError) {
#define FF_CACHE_FILE_KITTY_UNCOMPRESSED "kittyu"
#define FF_CACHE_FILE_CHAFA "chafa"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
@@ -364,7 +366,6 @@ static bool printImageKittyDirect(bool printError) {
#ifdef FF_HAVE_ZLIB
#include "common/library.h"
#include <stdlib.h>
#include <zlib.h>
static bool compressBlob(void** blob, size_t* length) {
@@ -372,13 +373,21 @@ static bool compressBlob(void** blob, size_t* length) {
FF_LIBRARY_LOAD_SYMBOL(zlib, compressBound, false)
FF_LIBRARY_LOAD_SYMBOL(zlib, compress2, false)
uLong compressedLength = ffcompressBound(*length);
#if _WIN32
// zlib's uLong is 32-bit on Windows (LLP64), so a >4 GiB source can't be
// compressed through this API; reject it instead of silently truncating
if (*length > (size_t) ULONG_MAX) {
return false;
}
#endif
uLong compressedLength = ffcompressBound((uLong) *length);
void* compressed = malloc(compressedLength);
if (compressed == nullptr) {
return false;
}
if (ffcompress2(compressed, &compressedLength, *blob, *length, Z_BEST_COMPRESSION) != Z_OK) {
if (ffcompress2(compressed, &compressedLength, *blob, (uLong) *length, Z_BEST_COMPRESSION) != Z_OK) {
free(compressed);
return false;
}
@@ -392,36 +401,6 @@ static bool compressBlob(void** blob, size_t* length) {
#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 == nullptr) {
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);
@@ -473,22 +452,18 @@ static void printImagePixels(FFLogoRequestData* requestData, const FFstrbuf* res
}
}
static bool printImageSixel(FFLogoRequestData* requestData, const ImageData* imageData) {
imageData->ffCopyMagickString(imageData->imageInfo->magick, "SIXEL", 6);
// The backends report the real pixel dimensions; the character dimensions are derived here
static void fillCharacterDimensions(FFLogoRequestData* requestData) {
requestData->logoCharacterWidth = (uint32_t) ceil((double) requestData->logoPixelWidth / requestData->characterPixelWidth);
requestData->logoCharacterHeight = (uint32_t) ceil((double) requestData->logoPixelHeight / requestData->characterPixelHeight);
}
size_t length;
void* blob = imageData->ffImageToBlob(imageData->imageInfo, imageData->image, &length, imageData->exceptionInfo);
if (!checkAllocationResult(blob, length)) {
static bool printImageSixel(FFLogoRequestData* requestData, const FFstrbuf* result) {
if (result->length == 0) {
return false;
}
FFstrbuf result;
result.chars = (char*) blob;
result.length = (uint32_t) length;
printImagePixels(requestData, &result, FF_CACHE_FILE_SIXEL);
free(blob);
printImagePixels(requestData, result, FF_CACHE_FILE_SIXEL);
return true;
}
@@ -509,14 +484,13 @@ static void appendKittyChunk(FFstrbuf* result, const char** blob, size_t* length
*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)) {
static bool printImageKitty(FFLogoRequestData* requestData, const FFImageBuffer* buffer) {
size_t length = (size_t) buffer->width * buffer->height * 4;
FF_AUTO_FREE void* blob = malloc(length);
if (blob == nullptr) {
return false;
}
memcpy(blob, buffer->data, length);
#ifdef FF_HAVE_ZLIB
bool isCompressed = compressBlob(&blob, &length);
@@ -524,18 +498,16 @@ static bool printImageKitty(FFLogoRequestData* requestData, const ImageData* ima
bool isCompressed = false;
#endif
char* chars = imageData->ffBase64Encode(blob, length, &length);
free(blob);
if (!checkAllocationResult(chars, length)) {
return false;
}
// base64 output is 4 * ceil(length / 3) bytes, plus the terminating null byte
FF_STRBUF_AUTO_DESTROY base64 = ffStrbufCreateA((uint32_t) (10 + length * 4 / 3));
ffBase64EncodeRaw((uint32_t) length, (const char*) blob, &base64.length, base64.chars);
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA((uint32_t) (length + 1024));
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreateA(base64.length + 1024);
const char* currentPos = chars;
size_t remainingLength = length;
const char* currentPos = base64.chars;
size_t remainingLength = base64.length;
ffStrbufAppendF(&result, "\033_Ga=T,f=32,s=%u,v=%u", requestData->logoPixelWidth, requestData->logoPixelHeight);
ffStrbufAppendF(&result, "\033_Ga=T,f=32,s=%u,v=%u", buffer->width, buffer->height);
if (isCompressed) {
ffStrbufAppendS(&result, ",o=z");
}
@@ -545,19 +517,17 @@ static bool printImageKitty(FFLogoRequestData* requestData, const ImageData* ima
}
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) {
#if _WIN32
static bool printImageChafa(FFLogoRequestData* requestData, const FFImageBuffer* buffer) {
#if _WIN32
FF_LIBRARY_LOAD(chafa, false, "libchafa-0" FF_LIBRARY_EXTENSION, 0)
#else
#else
FF_LIBRARY_LOAD(chafa, false, "libchafa" FF_LIBRARY_EXTENSION, 1)
#endif
#endif
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)
@@ -570,13 +540,6 @@ static bool printImageChafa(FFLogoRequestData* requestData, const ImageData* ima
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 = nullptr;
if (!ffchafa_symbol_map_apply_selectors(symbolMap, instance.config.logo.chafaSymbols.chars, &error)) {
@@ -616,10 +579,10 @@ static bool printImageChafa(FFLogoRequestData* requestData, const ImageData* ima
ffchafa_canvas_draw_all_pixels(
canvas,
CHAFA_PIXEL_RGBA8_UNASSOCIATED,
blob,
(gint) imageData->image->columns,
(gint) imageData->image->rows,
(gint) imageData->image->columns * 4);
buffer->data,
(gint) buffer->width,
(gint) buffer->height,
(gint) buffer->width * 4);
GString* str = ffchafa_canvas_print(canvas, nullptr);
FFstrbuf result;
@@ -650,101 +613,62 @@ static bool printImageChafa(FFLogoRequestData* requestData, const ImageData* ima
}
#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(nullptr, MagickFalse);
imageData.exceptionInfo = ffAcquireExceptionInfo();
if (imageData.exceptionInfo == nullptr) {
ffMagickCoreTerminus();
return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
bool ffImageCreate(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
#ifdef _WIN32
return ffImageCreateWIC(requestData, out, error);
#else
#ifdef FF_HAVE_IMAGEMAGICK7
if (ffImageCreateIM7(requestData, out, error)) {
return true;
}
ImageInfo* imageInfoIn = ffAcquireImageInfo();
if (imageInfoIn == nullptr) {
ffDestroyExceptionInfo(imageData.exceptionInfo);
ffMagickCoreTerminus();
return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
#endif
#ifdef FF_HAVE_IMAGEMAGICK6
if (ffImageCreateIM6(requestData, out, error)) {
return true;
}
//+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 == nullptr) {
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 == nullptr) {
ffDestroyExceptionInfo(imageData.exceptionInfo);
ffMagickCoreTerminus();
return FF_LOGO_IMAGE_RESULT_RUN_ERROR;
}
imageData.image = resized;
imageData.imageInfo = ffAcquireImageInfo();
if (imageData.imageInfo == nullptr) {
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
return false;
#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);
}
void ffImageDestroy(FFImageBuffer* buffer) {
free(buffer->data);
buffer->data = nullptr;
buffer->width = 0;
buffer->height = 0;
}
bool ffImageSixelEncode(FFLogoRequestData* requestData, FFstrbuf* out, const char** error) {
#ifdef _WIN32
// Windows: WIC decodes and resizes to RGBA, then the embedded libsixel encoder takes over
#ifdef FF_HAVE_SIXEL
FFImageBuffer buffer = {};
if (!ffImageCreate(requestData, &buffer, error)) {
return false;
}
ffDestroyImageInfo(imageData.imageInfo);
ffDestroyImage(imageData.image);
ffDestroyExceptionInfo(imageData.exceptionInfo);
ffMagickCoreTerminus();
return printSuccessful ? FF_LOGO_IMAGE_RESULT_SUCCESS : FF_LOGO_IMAGE_RESULT_RUN_ERROR;
bool ok = ffSixelEncode(&buffer, out, error);
ffImageDestroy(&buffer);
return ok;
#else
if (error) {
*error = "sixel support is not compiled in";
}
return false;
#endif
#else
// Off Windows: ImageMagick encodes straight from the decoded image, without an RGBA round trip
#ifdef FF_HAVE_IMAGEMAGICK7
if (ffImageSixelEncodeIM7(requestData, out, error)) {
return true;
}
#endif
#ifdef FF_HAVE_IMAGEMAGICK6
if (ffImageSixelEncodeIM6(requestData, out, error)) {
return true;
}
#endif
return false;
#endif
}
static FFNativeFD getCacheFD(FFLogoRequestData* requestData, const char* fileName) {
@@ -937,6 +861,17 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
ffStrbufRecalculateLength(&requestData.cacheDir);
ffStrbufEnsureEndsWithC(&requestData.cacheDir, '/');
// The cache is indexed by source path and pixel size only, so it has to be namespaced
// by backend: different backends (and different sixel encoders) produce different bytes
#ifdef _WIN32
ffStrbufAppendS(&requestData.cacheDir, "wic/");
#elif defined(FF_HAVE_IMAGEMAGICK7)
ffStrbufAppendS(&requestData.cacheDir, "im7/");
#elif defined(FF_HAVE_IMAGEMAGICK6)
ffStrbufAppendS(&requestData.cacheDir, "im6/");
#endif
ffStrbufAppendF(&requestData.cacheDir, "%u*%u/", requestData.logoPixelWidth, requestData.logoPixelHeight);
if (!instance.config.logo.recache) {
@@ -949,30 +884,40 @@ static bool printImageIfExistsSlowPath(FFLogoType type, bool printError) {
}
}
FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_INIT_ERROR;
const char* error = nullptr;
bool printSuccessful = false;
#ifdef FF_HAVE_IMAGEMAGICK7
result = ffLogoPrintImageIM7(&requestData);
if (requestData.type == FF_LOGO_TYPE_IMAGE_SIXEL) {
// The sixel encoder belongs to the backend, so it is not fed through ffImageCreate
FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate();
if (ffImageSixelEncode(&requestData, &result, &error)) {
fillCharacterDimensions(&requestData);
printSuccessful = printImageSixel(&requestData, &result);
}
} else {
FFImageBuffer buffer = {};
if (ffImageCreate(&requestData, &buffer, &error)) {
fillCharacterDimensions(&requestData);
if (requestData.type == FF_LOGO_TYPE_IMAGE_KITTY) {
printSuccessful = printImageKitty(&requestData, &buffer);
}
#if FF_HAVE_CHAFA
else if (requestData.type == FF_LOGO_TYPE_IMAGE_CHAFA) {
printSuccessful = printImageChafa(&requestData, &buffer);
}
#endif
#ifdef FF_HAVE_IMAGEMAGICK6
if (result == FF_LOGO_IMAGE_RESULT_INIT_ERROR) {
result = ffLogoPrintImageIM6(&requestData);
ffImageDestroy(&buffer);
}
}
#endif
ffStrbufDestroy(&requestData.cacheDir);
if (result == FF_LOGO_IMAGE_RESULT_SUCCESS) {
if (printSuccessful) {
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);
}
fprintf(stderr, "Logo: %s\n", error ? error : "Failed to load / convert the image source");
}
return false;
@@ -1024,7 +969,7 @@ bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) {
}
#endif
#if !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)
#if !defined(_WIN32) && !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)
if (printError) {
fputs("Logo: Image Magick support is not compiled in\n", stderr);
}
+31 -9
View File
@@ -2,7 +2,7 @@
#include "../logo.h"
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6)
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(FF_HAVE_SIXEL)
typedef enum FFLogoImageResult: uint8_t {
FF_LOGO_IMAGE_RESULT_SUCCESS, // Logo printed
@@ -24,20 +24,42 @@ typedef struct FFLogoRequestData {
uint32_t logoCharacterWidth;
} FFLogoRequestData;
typedef struct FFIMData {
void* library;
void* (*resizeFunc)(const void* image, size_t width, size_t height, void* exceptionInfo);
} FFIMData;
// Decoded and resized image: RGBA8, straight (unassociated) alpha, no row padding
typedef struct FFImageBuffer {
uint8_t* data;
uint32_t width;
uint32_t height;
} FFImageBuffer;
FFLogoImageResult ffLogoPrintImageImpl(FFLogoRequestData* requestData, const FFIMData* imData);
// Backend contract. Both functions update requestData->logoPixelWidth / logoPixelHeight
// with the real dimensions of the produced image, so the caller can derive the character
// dimensions afterwards.
// Decode the image source and resize it to the requested pixel size
bool ffImageCreate(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error);
void ffImageDestroy(FFImageBuffer* buffer);
// Encode the image source as a sixel byte stream (including the DCS envelope).
// The encoder belongs to the backend: ImageMagick's SIXEL coder off Windows,
// the embedded libsixel on Windows.
bool ffImageSixelEncode(FFLogoRequestData* requestData, FFstrbuf* out, const char** error);
#endif
#ifdef FF_HAVE_IMAGEMAGICK7
FFLogoImageResult ffLogoPrintImageIM7(FFLogoRequestData* requestData);
bool ffImageCreateIM7(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error);
bool ffImageSixelEncodeIM7(FFLogoRequestData* requestData, FFstrbuf* out, const char** error);
#endif
#ifdef FF_HAVE_IMAGEMAGICK6
#include <math.h>
FFLogoImageResult ffLogoPrintImageIM6(FFLogoRequestData* requestData);
bool ffImageCreateIM6(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error);
bool ffImageSixelEncodeIM6(FFLogoRequestData* requestData, FFstrbuf* out, const char** error);
#endif
#ifdef _WIN32
bool ffImageCreateWIC(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error);
#endif
#ifdef FF_HAVE_SIXEL
bool ffSixelEncode(const FFImageBuffer* buffer, FFstrbuf* result, const char** error);
#endif
+54
View File
@@ -0,0 +1,54 @@
#include "image.h"
#ifdef FF_HAVE_SIXEL
#include <sixel.h> // src/3rdparty/sixel/sixel.h
static int sixelWriteCallback(char* data, int size, void* priv) {
ffStrbufAppendNS((FFstrbuf*) priv, (uint32_t) size, data);
return 1; // non-zero means "keep going"
}
bool ffSixelEncode(const FFImageBuffer* buffer, FFstrbuf* result, const char** error) {
sixel_dither_t* dither = nullptr;
if (sixel_dither_new(&dither, 256, nullptr) != SIXEL_OK) {
if (error) {
*error = "sixel_dither_new() failed";
}
return false;
}
// Feed RGBA8 directly and let libsixel do the palette quantization and dithering
if (sixel_dither_initialize(dither, buffer->data, (int) buffer->width, (int) buffer->height, SIXEL_PIXELFORMAT_RGBA8888, SIXEL_LARGE_AUTO, SIXEL_REP_AUTO, SIXEL_QUALITY_HIGH) != SIXEL_OK) {
sixel_dither_unref(dither);
if (error) {
*error = "sixel_dither_initialize() failed";
}
return false;
}
sixel_output_t* output = nullptr;
if (sixel_output_new(&output, sixelWriteCallback, result, nullptr) != SIXEL_OK) {
sixel_dither_unref(dither);
if (error) {
*error = "sixel_output_new() failed";
}
return false;
}
// The depth parameter is unused by libsixel; the DCS envelope is emitted by default
SIXELSTATUS status = sixel_encode(buffer->data, (int) buffer->width, (int) buffer->height, 4, dither, output);
sixel_output_unref(output);
sixel_dither_unref(dither);
if (status != SIXEL_OK || result->length == 0) {
if (error) {
*error = "sixel_encode() failed";
}
return false;
}
return true;
}
#endif
+127
View File
@@ -0,0 +1,127 @@
extern "C" {
#include "image.h"
#include "common/mallocHelper.h"
#include "common/windows/com.h"
#include "common/windows/nt.h"
}
#include <initguid.h>
#include <windows.h>
#include <wincodec.h>
bool ffImageCreateWIC(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
const char* comError = ffInitCom();
if (comError) {
if (error) *error = comError;
return false;
}
FF_AUTO_RELEASE_COM_OBJECT IWICImagingFactory* factory = nullptr;
FF_AUTO_RELEASE_COM_OBJECT IWICBitmapDecoder* decoder = nullptr;
FF_AUTO_RELEASE_COM_OBJECT IWICBitmapFrameDecode* frame = nullptr;
FF_AUTO_RELEASE_COM_OBJECT IWICBitmapScaler* scaler = nullptr;
FF_AUTO_RELEASE_COM_OBJECT IWICFormatConverter* premultiplyConverter = nullptr;
FF_AUTO_RELEASE_COM_OBJECT IWICFormatConverter* converter = nullptr;
if (FAILED(CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER,
IID_IWICImagingFactory, (void**) &factory))) {
if (error) *error = "WIC imaging factory is unavailable";
return false;
}
// The source path is UTF-8, WIC only accepts UTF-16
wchar_t widePath[MAX_PATH + 1];
if (!NT_SUCCESS(RtlUTF8ToUnicodeN(widePath, (ULONG) sizeof(widePath), nullptr,
instance.config.logo.source.chars, (ULONG) instance.config.logo.source.length + 1))) {
if (error) *error = "failed to convert the image path to UTF-16";
return false;
}
if (FAILED(factory->CreateDecoderFromFilename(widePath, nullptr, GENERIC_READ,
WICDecodeMetadataCacheOnDemand, &decoder))) {
if (error) *error = "unsupported or unreadable image format";
return false;
}
// Only the first frame, matching ImageMagick's ReadImage (neither handles GIF animation)
if (FAILED(decoder->GetFrame(0, &frame))) {
if (error) *error = "failed to get the first frame";
return false;
}
UINT sourceWidth = 0, sourceHeight = 0;
frame->GetSize(&sourceWidth, &sourceHeight);
if (sourceWidth == 0 || sourceHeight == 0) {
if (error) *error = "invalid image dimensions";
return false;
}
// Fill in the missing dimension, keeping the source aspect ratio (same as the IM path)
uint32_t width = requestData->logoPixelWidth;
uint32_t height = requestData->logoPixelHeight;
if (width == 0 && height == 0) {
width = sourceWidth;
height = sourceHeight;
} else if (width == 0) {
width = (uint32_t) ((double) sourceWidth / (double) sourceHeight * height);
} else if (height == 0) {
height = (uint32_t) ((double) sourceHeight / (double) sourceWidth * width);
}
if (width == 0 || height == 0) {
if (error) *error = "invalid target dimensions";
return false;
}
requestData->logoPixelWidth = width;
requestData->logoPixelHeight = height;
IWICBitmapSource* source = nullptr;
if (width == sourceWidth && height == sourceHeight) {
// Same size: don't resample. ImageMagick clones the image in this case too,
// and resampling would only blur it
source = frame;
} else {
// Scale premultiplied alpha to avoid transparent RGB values bleeding into
// the visible edge pixels. The final output is converted back to straight
// alpha below, matching FFImageBuffer's RGBA8 contract.
if (FAILED(factory->CreateFormatConverter(&premultiplyConverter)) ||
FAILED(premultiplyConverter->Initialize(frame, GUID_WICPixelFormat32bppPBGRA,
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom)) ||
FAILED(factory->CreateBitmapScaler(&scaler)) ||
FAILED(scaler->Initialize(premultiplyConverter, width, height,
(WICBitmapInterpolationMode) 0x4 /* WICBitmapInterpolationModeHighQualityCubic */))) {
if (error) *error = "image scaling failed";
return false;
}
source = scaler;
}
// Normalize to straight-alpha RGBA8: kitty (f=32) and chafa
// (CHAFA_PIXEL_RGBA8_UNASSOCIATED) both consume exactly this
if (FAILED(factory->CreateFormatConverter(&converter)) ||
FAILED(converter->Initialize(source, GUID_WICPixelFormat32bppRGBA,
WICBitmapDitherTypeNone, nullptr, 0.0, WICBitmapPaletteTypeCustom))) {
if (error) *error = "pixel format conversion failed";
return false;
}
UINT stride = width * 4;
FF_AUTO_FREE uint8_t* pixels = (uint8_t*) malloc((size_t) stride * height);
if (pixels == nullptr) {
if (error) *error = "out of memory";
return false;
}
// prc == nullptr means the whole image; WIC fills the buffer using the stride we pass in
if (FAILED(converter->CopyPixels(nullptr, stride, stride * height, pixels))) {
if (error) *error = "pixel copy failed";
return false;
}
out->data = pixels;
out->width = width;
out->height = height;
pixels = nullptr; // Ownership is transferred to `out`
return true;
}