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