mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 07:44:06 +01:00
Logo (Image): drops IM in favor of WIC on Windows
This commit is contained in:
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user