master ohmu/fastfetch-openbsd / src / logo / image / im6.c
ohmu  ·  2026-10-15
  1#ifdef FF_HAVE_IMAGEMAGICK6
  2
  3    #include "image.h"
  4    #include "common/library.h"
  5    #include "common/mallocHelper.h"
  6    #include "common/strutil.h"
  7
  8    #include <magick/MagickCore.h>
  9    #include <stdlib.h>
 10
 11static FF_LIBRARY_SYMBOL(ResizeImage)
 12
 13    static void* logoResize(const void* image, size_t width, size_t height, void* exceptionInfo) {
 14    return ffResizeImage(image, width, height, UndefinedFilter, 1.0, exceptionInfo);
 15}
 16
 17// Decode the image source, resize it to the requested pixel size and export it as a blob
 18// in the given ImageMagick format ("RGBA" for kitty / chafa, "SIXEL" for sixel output).
 19// On success requestData->logoPixelWidth / logoPixelHeight hold the real image dimensions.
 20static FFLogoImageResult im6EncodeImage(FFLogoRequestData* requestData, const char* magick, uint32_t magickLength, void** outBlob, size_t* outLength) {
 21    // clang-format off
 22    FF_LIBRARY_LOAD(imageMagick, FF_LOGO_IMAGE_RESULT_INIT_ERROR,
 23        "libMagickCore-6.Q16HDRI" FF_LIBRARY_EXTENSION, 8,
 24        "libMagickCore-6.Q16" FF_LIBRARY_EXTENSION, 8,
 25        "libMagickCore-6" FF_LIBRARY_EXTENSION, 8
 26    )
 27    // clang-format on
 28    FF_LIBRARY_LOAD_SYMBOL_ADDRESS(imageMagick, ffResizeImage, ResizeImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 29
 30    FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreGenesis, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 31    FF_LIBRARY_LOAD_SYMBOL(imageMagick, MagickCoreTerminus, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 32    FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 33    FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyExceptionInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 34    FF_LIBRARY_LOAD_SYMBOL(imageMagick, AcquireImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 35    FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImageInfo, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 36    FF_LIBRARY_LOAD_SYMBOL(imageMagick, ReadImage, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 37    FF_LIBRARY_LOAD_SYMBOL(imageMagick, DestroyImageList, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 38    FF_LIBRARY_LOAD_SYMBOL(imageMagick, CopyMagickString, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 39    FF_LIBRARY_LOAD_SYMBOL(imageMagick, ImageToBlob, FF_LOGO_IMAGE_RESULT_INIT_ERROR)
 40
 41    FFLogoImageResult result = FF_LOGO_IMAGE_RESULT_RUN_ERROR;
 42    ExceptionInfo* exceptionInfo = nullptr;
 43    Image* image = nullptr;
 44    ImageInfo* imageInfoOut = nullptr;
 45    FF_AUTO_FREE void* blob = nullptr;
 46    size_t length = 0;
 47
 48    ffMagickCoreGenesis(nullptr, MagickFalse);
 49
 50    exceptionInfo = ffAcquireExceptionInfo();
 51    if (exceptionInfo == nullptr) {
 52        goto cleanup;
 53    }
 54
 55    {
 56        ImageInfo* imageInfoIn = ffAcquireImageInfo();
 57        if (imageInfoIn == nullptr) {
 58            goto cleanup;
 59        }
 60
 61        //+1, because we need to copy the null byte too
 62        ffCopyMagickString(imageInfoIn->filename, instance.config.logo.source.chars, instance.config.logo.source.length + 1);
 63
 64        image = ffReadImage(imageInfoIn, exceptionInfo);
 65        ffDestroyImageInfo(imageInfoIn);
 66        if (image == nullptr) {
 67            goto cleanup;
 68        }
 69    }
 70
 71    if (requestData->logoPixelWidth == 0 && requestData->logoPixelHeight == 0) {
 72        requestData->logoPixelWidth = (uint32_t) image->columns;
 73        requestData->logoPixelHeight = (uint32_t) image->rows;
 74    } else if (requestData->logoPixelWidth == 0) {
 75        requestData->logoPixelWidth = (uint32_t) ((double) image->columns / (double) image->rows * requestData->logoPixelHeight);
 76    } else if (requestData->logoPixelHeight == 0) {
 77        requestData->logoPixelHeight = (uint32_t) ((double) image->rows / (double) image->columns * requestData->logoPixelWidth);
 78    }
 79
 80    if (requestData->logoPixelWidth == 0 || requestData->logoPixelHeight == 0) {
 81        goto cleanup;
 82    }
 83
 84    {
 85        Image* resized = logoResize(image, requestData->logoPixelWidth, requestData->logoPixelHeight, exceptionInfo);
 86        // ReadImage may return a list of images (e.g. for multi-frame formats like GIF).
 87        // We only need the first frame, so destroy the whole list to avoid leaking the rest.
 88        ffDestroyImageList(image);
 89        image = resized;
 90        if (image == nullptr) {
 91            goto cleanup;
 92        }
 93    }
 94
 95    imageInfoOut = ffAcquireImageInfo();
 96    if (imageInfoOut == nullptr) {
 97        goto cleanup;
 98    }
 99
100    ffCopyMagickString(imageInfoOut->magick, magick, magickLength);
101
102    // The raw pixel coders write image->depth bits per sample, not 8: a 1-bit grayscale source comes
103    // back as columns*4/8 bytes per row and a 16-bit one as columns*8, while the RGBA caller hands
104    // the blob on as RGBA8 and derives its length from width*height*4. Pin the depth for the raw
105    // formats only -- the SIXEL coder quantises on its own, so it keeps the source depth and its
106    // output stays byte identical.
107    // ImageMagick 6 leaves no choice about where to pin it: AcquireQuantumInfo takes the depth from
108    // image->depth and never looks at image_info->depth.
109    if (ffStrEquals(magick, "RGBA")) {
110        image->depth = 8;
111    }
112
113    blob = ffImageToBlob(imageInfoOut, image, &length, exceptionInfo);
114    if (blob == nullptr || length == 0) {
115        goto cleanup;
116    }
117
118    *outBlob = blob;
119    *outLength = length;
120    blob = nullptr; // Ownership is transferred to the caller
121    result = FF_LOGO_IMAGE_RESULT_SUCCESS;
122
123cleanup:
124    if (imageInfoOut) {
125        ffDestroyImageInfo(imageInfoOut);
126    }
127    if (image) {
128        ffDestroyImageList(image);
129    }
130    if (exceptionInfo) {
131        ffDestroyExceptionInfo(exceptionInfo);
132    }
133    ffMagickCoreTerminus();
134
135    // leak imageMagick to prevent fastfetch from crashing #552
136    imageMagick = nullptr;
137    return result;
138}
139
140static void setError(FFLogoImageResult result, const char** error) {
141    if (error) {
142        *error = result == FF_LOGO_IMAGE_RESULT_INIT_ERROR
143            ? "Image Magick library not found"
144            : "Failed to load / convert the image source";
145    }
146}
147
148bool ffImageCreateIM6(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
149    void* blob = nullptr;
150    size_t length = 0;
151    FFLogoImageResult result = im6EncodeImage(requestData, "RGBA", 5, &blob, &length);
152    if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
153        setError(result, error);
154        return false;
155    }
156
157    // FFImageBuffer carries no length, so every consumer derives it from width*height*4. Refuse any
158    // other size rather than let them read past the blob -- the raw coder's depth scaling used to
159    // produce one (see the depth pin in im6EncodeImage).
160    if (length != (size_t) requestData->logoPixelWidth * requestData->logoPixelHeight * 4) {
161        if (error) {
162            *error = "Image Magick did not return an RGBA8 buffer";
163        }
164        free(blob);
165        return false;
166    }
167
168    out->data = blob;
169    out->width = requestData->logoPixelWidth;
170    out->height = requestData->logoPixelHeight;
171    return true;
172}
173
174bool ffImageSixelEncodeIM6(FFLogoRequestData* requestData, FFstrbuf* out, const char** error) {
175    FF_AUTO_FREE void* blob = nullptr;
176    size_t length = 0;
177    FFLogoImageResult result = im6EncodeImage(requestData, "SIXEL", 6, &blob, &length);
178    if (result != FF_LOGO_IMAGE_RESULT_SUCCESS) {
179        setError(result, error);
180        return false;
181    }
182
183    ffStrbufSetNS(out, (uint32_t) length, (const char*) blob);
184    return true;
185}
186
187#endif