Image (Logo): uses native image decoder on Android

Codec (Android): uses `availability` feature test
This commit is contained in:
李通洲
2026-09-18 01:10:18 +08:00
parent 97ac94eb44
commit 70d2dd37a6
7 changed files with 480 additions and 42 deletions
+16
View File
@@ -0,0 +1,16 @@
#pragma once
// The NDK marks an API unavailable whenever it is newer than the API level the build targets, and
// fastfetch keeps running on devices below the levels it uses: AImageDecoder is 30, decoding past
// the first frame is 31, and AMediaCodec_getName is 28. CMake compiles the files that reach for
// them with -D__ANDROID_UNAVAILABLE_SYMBOLS_ARE_WEAK__ -Werror=unguarded-availability, which turns
// those entry points into weak references and makes every unguarded use an error. Two things follow:
//
// * A weak reference resolves to null on an older device, so every use needs a run-time check --
// that is FF_API_AT_LEAST. Unguarded, the call would jump to a null pointer.
// * Annotating a helper with FF_REQUIRES_API keeps the check in one place: the compiler rejects
// any call of that helper which is not itself inside a guard.
//
// https://developer.android.com/ndk/guides/using-newer-apis
#define FF_REQUIRES_API(x) [[clang::availability(android, introduced = x)]]
#define FF_API_AT_LEAST(x) __builtin_available(android x, *)
+24 -23
View File
@@ -1,9 +1,7 @@
#include "codec.h"
#include "common/library.h"
#include "common/androidApi.h"
#include "common/strutil.h"
#undef __INTRODUCED_IN
#define __INTRODUCED_IN(...)
#include <media/NdkMediaCodec.h>
static const struct {
@@ -35,33 +33,25 @@ static bool ffCodecIsLikelySoftware(const char* codecName) {
ffStrStartsWith(codecName, "OMX.PV.");
}
static bool ffCodecIsHardwareAccelerated(
AMediaCodec* codec,
typeof(&AMediaCodec_getName) ffAMediaCodec_getName,
typeof(&AMediaCodec_releaseName) ffAMediaCodec_releaseName) {
// Only the name query is newer than the API level this build targets: AMediaCodec_getName and
// AMediaCodec_releaseName are API 28, while creating and deleting a codec is API 21.
FF_REQUIRES_API(28) static bool ffCodecIsHardwareAccelerated(AMediaCodec* codec) {
if (!codec) {
return false;
}
char* codecName = nullptr;
media_status_t status = ffAMediaCodec_getName(codec, &codecName);
media_status_t status = AMediaCodec_getName(codec, &codecName);
if (status != AMEDIA_OK || !codecName) {
return false;
}
bool isHardware = !ffCodecIsLikelySoftware(codecName);
ffAMediaCodec_releaseName(codec, codecName);
AMediaCodec_releaseName(codec, codecName);
return isHardware;
}
const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
FF_LIBRARY_LOAD_MESSAGE(mediandk, "libmediandk.so", 0)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createDecoderByType)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_createEncoderByType)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_delete)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_getName)
FF_LIBRARY_LOAD_SYMBOL_MESSAGE(mediandk, AMediaCodec_releaseName)
FF_REQUIRES_API(28) static const char* ffDetectCodecNativeImpl(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
FFCodecType decoders = FF_CODEC_TYPE_NONE;
FFCodecType encoders = FF_CODEC_TYPE_NONE;
@@ -70,22 +60,22 @@ const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list o
FFCodecType type = FF_CODEC_MIME_TO_TYPE[i].type;
if ((options->showType & FF_CODEC_SHOW_TYPE_DECODER) && !(decoders & type)) {
AMediaCodec* decoder = ffAMediaCodec_createDecoderByType(mime);
AMediaCodec* decoder = AMediaCodec_createDecoderByType(mime);
if (decoder) {
if (ffCodecIsHardwareAccelerated(decoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) {
if (ffCodecIsHardwareAccelerated(decoder)) {
decoders |= type;
}
ffAMediaCodec_delete(decoder);
AMediaCodec_delete(decoder);
}
}
if ((options->showType & FF_CODEC_SHOW_TYPE_ENCODER) && !(encoders & type)) {
AMediaCodec* encoder = ffAMediaCodec_createEncoderByType(mime);
AMediaCodec* encoder = AMediaCodec_createEncoderByType(mime);
if (encoder) {
if (ffCodecIsHardwareAccelerated(encoder, ffAMediaCodec_getName, ffAMediaCodec_releaseName)) {
if (ffCodecIsHardwareAccelerated(encoder)) {
encoders |= type;
}
ffAMediaCodec_delete(encoder);
AMediaCodec_delete(encoder);
}
}
}
@@ -100,3 +90,14 @@ const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list o
return nullptr;
}
const char* ffDetectCodecNative(FFCodecOptions* options, FFlist* result /*list of FFCodecResult*/) {
if (FF_API_AT_LEAST(28)) {
return ffDetectCodecNativeImpl(options, result);
}
// Reading the codec name is the only way to tell a hardware codec from a software one. Without
// it there is nothing to report, and listing the codecs as if they were accelerated would be a
// guess, so say why instead.
return "AMediaCodec_getName() requires Android 9 (API 28)";
}
+397
View File
@@ -0,0 +1,397 @@
#include "image.h"
#include "common/androidApi.h"
#include "common/io.h"
#include "common/mallocHelper.h"
#include <android/bitmap.h>
#include <android/imagedecoder.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
static inline bool androidImageDecoderError(const char** error, const char* message) {
if (error) {
*error = message;
}
return false;
}
// AImageDecoder decodes to premultiplied alpha, and it can not be asked for straight alpha once a
// target size is in effect: AImageDecoder_setUnpremultipliedRequired fails with
// ANDROID_IMAGE_DECODER_INVALID_CONVERSION, documented as "Unpremultiplied is not possible due to
// an existing scale set by AImageDecoder_setTargetSize". Premultiplied is the right domain to
// scale in anyway -- interpolating straight alpha averages the colour of fully transparent pixels
// into the edges next to them, which shows up as dark fringes -- so the decoder is left alone and
// this runs afterwards, exactly as the ImageIO backend does.
static void unPremultiplyRGBA(uint8_t* data, size_t pixelCount) {
for (size_t i = 0; i < pixelCount; ++i) {
uint8_t* const p = data + i * 4;
const uint8_t a = p[3];
if (a == 0) {
p[0] = p[1] = p[2] = 0;
} else if (a < 255) {
p[0] = (uint8_t) ((p[0] * 255 + a / 2) / a);
p[1] = (uint8_t) ((p[1] * 255 + a / 2) / a);
p[2] = (uint8_t) ((p[2] * 255 + a / 2) / a);
}
}
}
// Copy one decoded frame into a tightly packed, straight alpha buffer of its own.
//
// A copy rather than a hand-over: the animation path has to keep the decoder's own buffer intact,
// because AImageDecoder blends each frame into the one that is already there. The still path only
// needs the packed result, and going through here keeps the two identical.
//
// `premultiplied` is what the decoder reports about the pixels it produced, so a source that was
// decoded straight is copied and nothing else.
static uint8_t* androidPackFrame(const uint8_t* src, size_t stride, uint32_t width, uint32_t height, bool premultiplied) {
const size_t packedStride = (size_t) width * 4;
uint8_t* pixels = (uint8_t*) malloc(packedStride * height);
if (pixels == nullptr) {
return nullptr;
}
for (uint32_t y = 0; y < height; ++y) {
memcpy(pixels + (size_t) y * packedStride, src + (size_t) y * stride, packedStride);
}
if (premultiplied) {
unPremultiplyRGBA(pixels, (size_t) width * height);
}
return pixels;
}
// Everything the still and the animation path share: read the source size, resolve the requested
// pixel size, ask for RGBA8888 and let the decoder do the scaling.
FF_REQUIRES_API(30) static bool androidResolveDecoder(AImageDecoder* decoder, FFLogoRequestData* requestData, size_t* outStride, bool* outPremultiplied, const char** error) {
const AImageDecoderHeaderInfo* header = AImageDecoder_getHeaderInfo(decoder);
// Both are int32_t, and anything <= 0 is not a usable source
const int32_t sourceWidth = AImageDecoderHeaderInfo_getWidth(header);
const int32_t sourceHeight = AImageDecoderHeaderInfo_getHeight(header);
if (sourceWidth <= 0 || sourceHeight <= 0) {
return androidImageDecoderError(error, "invalid image dimensions");
}
// 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 = (uint32_t) sourceWidth;
height = (uint32_t) 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) {
return androidImageDecoderError(error, "invalid target dimensions");
}
if (AImageDecoder_setAndroidBitmapFormat(decoder, ANDROID_BITMAP_FORMAT_RGBA_8888) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to select the RGBA8888 output format");
}
// A source that is already the requested size needs no scaling at all, and leaving the target
// size unset buys one more thing: AImageDecoder refuses straight alpha only *because* of a
// scale, so unscaled output can be decoded straight and no conversion follows. That is also
// the more accurate of the two, having no integer rounding to recover from.
if (width == (uint32_t) sourceWidth && height == (uint32_t) sourceHeight) {
*outPremultiplied = AImageDecoder_setUnpremultipliedRequired(decoder, true) != ANDROID_IMAGE_DECODER_SUCCESS;
} else {
// Scaling is part of the decode: the decoder samples the source down (or up) as it goes, so
// there is neither a full-size intermediate nor a separate resize step.
if (AImageDecoder_setTargetSize(decoder, (int32_t) width, (int32_t) height) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to scale the image");
}
*outPremultiplied = true;
}
const size_t stride = AImageDecoder_getMinimumStride(decoder);
if (stride < (size_t) width * 4 || stride > SIZE_MAX / height) {
return androidImageDecoderError(error, "invalid target dimensions");
}
requestData->logoPixelWidth = width;
requestData->logoPixelHeight = height;
*outStride = stride;
return true;
}
bool ffImageCreateAID(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error) {
// AImageDecoder is API 30, and fastfetch still runs on devices below that, so this is a real
// run-time check and not a compile-time constant. See common/androidApi.h for why.
if (FF_API_AT_LEAST(30)) {
FF_AUTO_CLOSE_FD int fd = open(instance.config.logo.source.chars, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return androidImageDecoderError(error, "failed to open the image source");
}
AImageDecoder* decoder = nullptr;
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "unsupported or unreadable image format");
}
size_t stride = 0;
bool premultiplied = true;
if (!androidResolveDecoder(decoder, requestData, &stride, &premultiplied, error)) {
AImageDecoder_delete(decoder);
return false;
}
const uint32_t width = requestData->logoPixelWidth;
const uint32_t height = requestData->logoPixelHeight;
const size_t packedStride = (size_t) width * 4;
const size_t bufferSize = stride * height;
FF_AUTO_FREE uint8_t* decoded = (uint8_t*) malloc(bufferSize);
if (decoded == nullptr) {
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
const int result = AImageDecoder_decodeImage(decoder, decoded, stride, bufferSize);
// The decoder reads from the fd while decoding, so it outlives the decode but not the fd
AImageDecoder_delete(decoder);
if (result != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to decode the image");
}
// Without row padding the decoder's buffer is already laid out the way the contract wants,
// so it is finished in place and handed over rather than copied.
if (stride == packedStride) {
if (premultiplied) {
unPremultiplyRGBA(decoded, (size_t) width * height);
}
out->data = decoded;
decoded = nullptr; // Ownership is transferred to `out`
} else {
out->data = androidPackFrame(decoded, stride, width, height, premultiplied);
if (out->data == nullptr) {
return androidImageDecoderError(error, "out of memory");
}
}
out->width = width;
out->height = height;
return true;
} else {
return androidImageDecoderError(error, "AImageDecoder requires Android 11 (API 30) or newer");
}
}
// ---------------------------------------------------------------------------------------------
// Animation
//
// AImageDecoder composes the frames itself. Given the same buffer for every call, it decodes the
// part of the canvas a frame actually covers and blends it over what is already there, and it
// restores the buffer for a DISPOSE_OP_PREVIOUS frame on its own. So unlike the Windows backend,
// there is no canvas to maintain here: the session keeps the decoder's buffer and hands out a copy
// of it per frame.
//
// Two things follow from the decoder only ever walking forwards:
//
// * The frame count and every gap have to be collected up front by advancing through the whole
// animation and rewinding, which is what the session contract asks for anyway -- a negative
// --logo-animation-frame has to be resolved before the first frame is taken.
// * Reaching a frame that is not the next one means rewinding and walking forward again, which is
// slower than refusing it but keeps the iterator usable after the last frame.
//
// The loop count is the one thing AImageDecoder does not expose: it has no API for it at any
// level, so the animation is reported as looping forever, which is what an animated GIF with no
// NETSCAPE block does in every viewer anyway.
// ---------------------------------------------------------------------------------------------
typedef struct FFAndroidAnimation {
AImageDecoder* decoder;
uint8_t* canvas; // the decoder's buffer; every frame is blended into what is already in it
size_t stride;
size_t size;
uint32_t width;
uint32_t height;
uint32_t nextIndex; // the frame the decoder is positioned on
bool premultiplied; // whether the decoder's frames need un-premultiplying
int32_t minGap;
int32_t* delaysCs; // one per frame, in centiseconds
} FFAndroidAnimation;
FF_REQUIRES_API(31) static bool androidAnimationGetFrame(FFImageAnimation* animation, uint32_t index, FFImageFrame* out, const char** error) {
FFAndroidAnimation* session = (FFAndroidAnimation*) ffImageAnimationGetImpl(animation);
// Frames come out of the decoder in order, so anything before the current one means starting
// over. AImageDecoder_rewind needs an animated source; it is reached here only once a frame
// has already been taken, which means the source is one.
if (index < session->nextIndex) {
if (AImageDecoder_rewind(session->decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to rewind the animation");
}
session->nextIndex = 0;
}
// Skipped frames still have to be decoded: each frame is blended into the buffer that the
// following one builds on, so the buffer would be wrong without them.
while (session->nextIndex < index) {
if (AImageDecoder_decodeImage(session->decoder, session->canvas, session->stride, session->size) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to decode an animation frame");
}
if (AImageDecoder_advanceFrame(session->decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to advance to the next animation frame");
}
++session->nextIndex;
}
if (AImageDecoder_decodeImage(session->decoder, session->canvas, session->stride, session->size) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "failed to decode the animation frame");
}
uint8_t* frame = androidPackFrame(session->canvas, session->stride, session->width, session->height, session->premultiplied);
if (frame == nullptr) {
return androidImageDecoderError(error, "out of memory");
}
// Position on the following frame. Past the last one this reports FINISHED, which is expected
// and harmless: the frame just handed out is complete, and anything else the caller asks for
// restarts above.
AImageDecoder_advanceFrame(session->decoder);
++session->nextIndex;
// The same mapping the ImageIO backend settled on: the raw gap is in centiseconds, a floor of
// 100 ms applies only when every frame is zero, and what is left at <= 0 means gapless.
const int32_t gap = (session->delaysCs[index] > session->minGap ? session->delaysCs[index] : session->minGap) * 10;
out->data = frame;
out->delayMs = gap > 0 ? gap : -1;
return true;
}
FF_REQUIRES_API(31) static void androidAnimationDestroy(FFImageAnimation* animation) {
FFAndroidAnimation* session = (FFAndroidAnimation*) ffImageAnimationGetImpl(animation);
if (session == nullptr) {
return;
}
AImageDecoder_delete(session->decoder);
free(session->canvas);
free(session->delaysCs);
free(session);
}
bool ffImageAnimationOpenAID(FFLogoRequestData* requestData, FFImageAnimation** out, const char** error) {
// Decoding past the first frame needs API 31: AImageDecoder_advanceFrame and
// AImageDecoderFrameInfo are both introduced there, and AImageDecoder_decodeImage only
// documents decoding "all of the frames" from that level on.
if (FF_API_AT_LEAST(31)) {
FF_AUTO_CLOSE_FD int fd = open(instance.config.logo.source.chars, O_RDONLY | O_CLOEXEC);
if (fd < 0) {
return androidImageDecoderError(error, "failed to open the image source");
}
AImageDecoder* decoder = nullptr;
if (AImageDecoder_createFromFd(fd, &decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
return androidImageDecoderError(error, "unsupported or unreadable image format");
}
size_t stride = 0;
bool premultiplied = true;
if (!androidResolveDecoder(decoder, requestData, &stride, &premultiplied, error)) {
AImageDecoder_delete(decoder);
return false;
}
AImageDecoderFrameInfo* frameInfo = AImageDecoderFrameInfo_create();
if (frameInfo == nullptr) {
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
// Walk the animation once, recording every gap on the way, so that both the frame count
// and each frame's timing are known before the first frame is decoded.
uint32_t capacity = 16;
uint32_t frameCount = 0;
int32_t* delays = (int32_t*) malloc(capacity * sizeof(*delays));
if (delays == nullptr) {
AImageDecoderFrameInfo_delete(frameInfo);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
int32_t minGap = 10;
for (;;) {
if (frameCount == capacity) {
const uint32_t grown = capacity * 2;
int32_t* resized = (int32_t*) realloc(delays, grown * sizeof(*delays));
if (resized == nullptr) {
free(delays);
AImageDecoderFrameInfo_delete(frameInfo);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
delays = resized;
capacity = grown;
}
int64_t nanos = 0;
if (AImageDecoder_getFrameInfo(decoder, frameInfo) == ANDROID_IMAGE_DECODER_SUCCESS) {
nanos = AImageDecoderFrameInfo_getDuration(frameInfo);
}
const int32_t cs = nanos > 0 ? (int32_t) (nanos / 10000000) : 0;
delays[frameCount++] = cs;
if (cs > 0) {
minGap = 0;
}
if (AImageDecoder_advanceFrame(decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
break;
}
}
AImageDecoderFrameInfo_delete(frameInfo);
// A single frame source never advanced, so it is still positioned on its only frame and
// has nothing to rewind. Everything else stopped in the finished state.
if (frameCount > 1 && AImageDecoder_rewind(decoder) != ANDROID_IMAGE_DECODER_SUCCESS) {
free(delays);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "failed to rewind the animation");
}
FFAndroidAnimation* session = (FFAndroidAnimation*) calloc(1, sizeof(*session));
if (session == nullptr) {
free(delays);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
session->decoder = decoder;
session->stride = stride;
session->size = stride * requestData->logoPixelHeight;
session->width = requestData->logoPixelWidth;
session->height = requestData->logoPixelHeight;
session->premultiplied = premultiplied;
session->minGap = minGap;
session->delaysCs = delays;
session->canvas = (uint8_t*) calloc(1, session->size);
if (session->canvas == nullptr) {
free(session->delaysCs);
free(session);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
// 0: AImageDecoder has no API for the loop count, and looping forever is what a viewer
// does with an animation that does not declare one.
FFImageAnimation* animation = ffImageAnimationCreate(frameCount, 0, session, androidAnimationGetFrame, androidAnimationDestroy);
if (animation == nullptr) {
free(session->canvas);
free(session->delaysCs);
free(session);
AImageDecoder_delete(decoder);
return androidImageDecoderError(error, "out of memory");
}
*out = animation;
return true;
} else {
return androidImageDecoderError(error, "animation support requires Android 12 (API 31) or newer");
}
}
+14 -10
View File
@@ -447,7 +447,7 @@ static bool printImageKittyDirect(bool printError) {
return true;
}
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(__APPLE__) || defined(FF_HAVE_SIXEL)
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(__APPLE__) || defined(__ANDROID__) || defined(FF_HAVE_SIXEL)
#define FF_KITTY_MAX_CHUNK_SIZE 4096
@@ -807,6 +807,8 @@ bool ffImageCreate(FFLogoRequestData* requestData, FFImageBuffer* out, const cha
return ffImageCreateWIC(requestData, out, error);
#elif defined(__APPLE__)
return ffImageCreateImageIO(requestData, out, error);
#elif defined(__ANDROID__)
return ffImageCreateAID(requestData, out, error);
#else
#ifdef FF_HAVE_IMAGEMAGICK7
if (ffImageCreateIM7(requestData, out, error)) {
@@ -879,6 +881,8 @@ bool ffImageAnimationOpen(FFLogoRequestData* requestData, FFImageAnimation** out
return ffImageAnimationOpenWIC(requestData, out, error);
#elif defined(__APPLE__)
return ffImageAnimationOpenImageIO(requestData, out, error);
#elif defined(__ANDROID__)
return ffImageAnimationOpenAID(requestData, out, error);
#elif defined(FF_HAVE_IMAGEMAGICK7)
return ffImageAnimationOpenIM7(requestData, out, error);
#else
@@ -915,10 +919,10 @@ void ffImageAnimationClose(FFImageAnimation* animation) {
}
bool ffImageSixelEncode(FFLogoRequestData* requestData, FFstrbuf* out, const char** error) {
// Windows (WIC) and macOS (ImageIO) decode and resize to RGBA first, then the embedded
// libsixel encoder takes over. Other platforms let ImageMagick encode straight from the
// decoded image without an RGBA round trip.
#if defined(_WIN32) || defined(__APPLE__)
// Windows (WIC), macOS (ImageIO) and Android (AImageDecoder) decode and resize to RGBA first,
// then the embedded libsixel encoder takes over. Other platforms let ImageMagick encode
// straight from the decoded image without an RGBA round trip.
#if defined(_WIN32) || defined(__APPLE__) || defined(__ANDROID__)
#ifdef FF_HAVE_SIXEL
FFImageBuffer buffer = {};
if (!ffImageCreate(requestData, &buffer, error)) {
@@ -950,10 +954,10 @@ bool ffImageSixelEncode(FFLogoRequestData* requestData, FFstrbuf* out, const cha
}
bool ffImageSixelEncodeBuffer(const FFImageBuffer* buffer, FFstrbuf* out, const char** error) {
// Windows and macOS hand the pixels to the embedded encoder. ImageMagick has no way to encode
// pixels it was not given an Image for, so its SIXEL coder is reached through a ConstituteImage
// round trip instead.
#if defined(_WIN32) || defined(__APPLE__)
// Windows, macOS and Android hand the pixels to the embedded encoder. ImageMagick has no way to
// encode pixels it was not given an Image for, so its SIXEL coder is reached through a
// ConstituteImage round trip instead.
#if defined(_WIN32) || defined(__APPLE__) || defined(__ANDROID__)
#ifdef FF_HAVE_SIXEL
return ffSixelEncode(buffer, out, error);
#else
@@ -1780,7 +1784,7 @@ bool ffLogoPrintImageIfExists(FFLogoType type, bool printError) {
}
#endif
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)
#if !defined(_WIN32) && !defined(__APPLE__) && !defined(__ANDROID__) && !defined(FF_HAVE_IMAGEMAGICK7) && !defined(FF_HAVE_IMAGEMAGICK6)
if (printError) {
fputs("Logo: Image Magick support is not compiled in\n", stderr);
}
+6 -1
View File
@@ -2,7 +2,7 @@
#include "../logo.h"
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(__APPLE__) || defined(FF_HAVE_SIXEL)
#if defined(FF_HAVE_IMAGEMAGICK7) || defined(FF_HAVE_IMAGEMAGICK6) || defined(_WIN32) || defined(__APPLE__) || defined(__ANDROID__) || defined(FF_HAVE_SIXEL)
typedef enum FFLogoImageResult: uint8_t {
FF_LOGO_IMAGE_RESULT_SUCCESS, // Logo printed
@@ -119,6 +119,11 @@ bool ffImageCreateImageIO(FFLogoRequestData* requestData, FFImageBuffer* out, co
bool ffImageAnimationOpenImageIO(FFLogoRequestData* requestData, FFImageAnimation** out, const char** error);
#endif
#ifdef __ANDROID__
bool ffImageCreateAID(FFLogoRequestData* requestData, FFImageBuffer* out, const char** error);
bool ffImageAnimationOpenAID(FFLogoRequestData* requestData, FFImageAnimation** out, const char** error);
#endif
#ifdef FF_HAVE_SIXEL
bool ffSixelEncode(const FFImageBuffer* buffer, FFstrbuf* result, const char** error);
#endif