mangayomi-mirror/lib/ffi/image_decoder.cpp

1155 lines
35 KiB
C++

#include "image_decoder.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#ifdef __cplusplus
extern "C" {
#endif
// ============================================================================
// 1. Detection algorithm for white/black/transparent margins
// ============================================================================
static void find_margins_rgba(unsigned char* pixels, int w, int h, int* out_left, int* out_top, int* out_right, int* out_bottom) {
// 0: transparent, 1: white, 2: black, 3: other
int corners[4] = { 0, 0, 0, 0 };
int corner_indices[4] = {
0,
(w - 1) * 4,
(h - 1) * w * 4,
((h - 1) * w + (w - 1)) * 4
};
for (int i = 0; i < 4; i++) {
int idx = corner_indices[i];
unsigned char r = pixels[idx];
unsigned char g = pixels[idx + 1];
unsigned char b = pixels[idx + 2];
unsigned char a = pixels[idx + 3];
if (a < 30) {
corners[i] = 0; // Transparent
} else if (r > 240 && g > 240 && b > 240) {
corners[i] = 1; // White
} else if (r < 25 && g < 25 && b < 25) {
corners[i] = 2; // Black
} else {
corners[i] = 3; // Other
}
}
int counts[4] = { 0, 0, 0, 0 };
for (int i = 0; i < 4; i++) counts[corners[i]]++;
int bg_type = 3;
if (counts[0] >= 3) bg_type = 0;
else if (counts[1] >= 3) bg_type = 1;
else if (counts[2] >= 3) bg_type = 2;
if (bg_type == 3) {
*out_left = 0;
*out_top = 0;
*out_right = w;
*out_bottom = h;
return;
}
auto is_bg = [&](int x, int y) {
int idx = (y * w + x) * 4;
unsigned char r = pixels[idx];
unsigned char g = pixels[idx + 1];
unsigned char b = pixels[idx + 2];
unsigned char a = pixels[idx + 3];
if (bg_type == 0) return a < 40;
if (bg_type == 1) return (r > 235 && g > 235 && b > 235 && a > 100);
if (bg_type == 2) return (r < 30 && g < 30 && b < 30 && a > 100);
return false;
};
// Scan Top
int top = 0;
for (int y = 0; y < h; y++) {
int non_bg_count = 0;
for (int x = 0; x < w; x++) {
if (!is_bg(x, y)) non_bg_count++;
}
if (non_bg_count > w / 100 + 1) {
top = y;
break;
}
}
// Scan Bottom
int bottom = h;
for (int y = h - 1; y >= 0; y--) {
int non_bg_count = 0;
for (int x = 0; x < w; x++) {
if (!is_bg(x, y)) non_bg_count++;
}
if (non_bg_count > w / 100 + 1) {
bottom = y + 1;
break;
}
}
// Scan Left
int left = 0;
for (int x = 0; x < w; x++) {
int non_bg_count = 0;
for (int y = 0; y < h; y++) {
if (!is_bg(x, y)) non_bg_count++;
}
if (non_bg_count > h / 100 + 1) {
left = x;
break;
}
}
// Scan Right
int right = w;
for (int x = w - 1; x >= 0; x--) {
int non_bg_count = 0;
for (int y = 0; y < h; y++) {
if (!is_bg(x, y)) non_bg_count++;
}
if (non_bg_count > h / 100 + 1) {
right = x + 1;
break;
}
}
if (right - left < w / 4 || bottom - top < h / 4) {
*out_left = 0;
*out_top = 0;
*out_right = w;
*out_bottom = h;
} else {
*out_left = left;
*out_top = top;
*out_right = right;
*out_bottom = bottom;
}
}
// ============================================================================
// 2. Custom Regional BMP Decoder (Multiplatform)
// ============================================================================
struct BmpDecoderContext {
FILE* file;
int width;
int height;
int pixel_offset;
bool is_top_down;
};
static BmpDecoderContext* init_bmp_decoder(const char* file_path, int* out_width, int* out_height) {
FILE* f = fopen(file_path, "rb");
if (!f) return NULL;
unsigned char header[54];
if (fread(header, 1, 54, f) != 54) {
fclose(f);
return NULL;
}
if (header[0] != 'B' || header[1] != 'M') {
fclose(f);
return NULL;
}
int width = *(int*)&header[18];
int height = *(int*)&header[22];
int pixel_offset = *(int*)&header[10];
int bits_per_pixel = *(short*)&header[28];
if (bits_per_pixel != 32) {
fclose(f);
return NULL;
}
bool is_top_down = false;
if (height < 0) {
height = -height;
is_top_down = true;
}
BmpDecoderContext* ctx = (BmpDecoderContext*)malloc(sizeof(BmpDecoderContext));
ctx->file = f;
ctx->width = width;
ctx->height = height;
ctx->pixel_offset = pixel_offset;
ctx->is_top_down = is_top_down;
*out_width = width;
*out_height = height;
return ctx;
}
static bool decode_bmp_region(BmpDecoderContext* ctx, int left, int top, int right, int bottom, int sample_size, unsigned char* out_rgba_buffer) {
if (!ctx || !ctx->file || !out_rgba_buffer) return false;
int dest_width = (right - left) / sample_size;
int dest_height = (bottom - top) / sample_size;
int row_stride = ctx->width * 4;
for (int dy = 0; dy < dest_height; dy++) {
int sy = top + dy * sample_size;
int file_y = ctx->is_top_down ? sy : (ctx->height - 1 - sy);
unsigned char* dest_row = out_rgba_buffer + dy * dest_width * 4;
for (int dx = 0; dx < dest_width; dx++) {
int sx = left + dx * sample_size;
long offset = ctx->pixel_offset + (long)file_y * row_stride + sx * 4;
if (fseek(ctx->file, offset, SEEK_SET) != 0) {
return false;
}
unsigned char bgra[4];
if (fread(bgra, 1, 4, ctx->file) == 4) {
dest_row[dx * 4] = bgra[2]; // R
dest_row[dx * 4 + 1] = bgra[1]; // G
dest_row[dx * 4 + 2] = bgra[0]; // B
dest_row[dx * 4 + 3] = bgra[3]; // A
} else {
return false;
}
}
}
return true;
}
static void free_bmp_decoder(BmpDecoderContext* ctx) {
if (ctx) {
if (ctx->file) fclose(ctx->file);
free(ctx);
}
}
// ============================================================================
// 3. Definition of the unified context and decoder selector
// ============================================================================
enum DecoderType {
TYPE_BMP,
TYPE_NATIVE
};
#ifdef _WIN32
#include <windows.h>
#include <wincodec.h>
struct WinNativeContext {
IWICImagingFactory* factory;
IWICBitmapDecoder* decoder;
IWICBitmapFrameDecode* frame;
};
#endif
#ifdef __APPLE__
#include <CoreGraphics/CoreGraphics.h>
#include <ImageIO/ImageIO.h>
#endif
struct ImageDecoderContext {
DecoderType type;
int width;
int height;
int crop_left;
int crop_top;
int crop_width;
int crop_height;
union {
BmpDecoderContext* bmp_ctx;
#ifdef __APPLE__
CGImageSourceRef apple_source;
#endif
#ifdef __ANDROID__
struct {
unsigned char* file_bytes;
size_t file_size;
} android_mem;
#endif
#ifdef _WIN32
WinNativeContext win_ctx;
#endif
};
};
static void perform_bmp_autocrop(ImageDecoderContext* ctx) {
int w = ctx->width;
int h = ctx->height;
int sample = 16;
int dest_w = w / sample;
int dest_h = h / sample;
if (dest_w <= 0 || dest_h <= 0) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
unsigned char* temp_buf = (unsigned char*)malloc(dest_w * dest_h * 4);
if (!temp_buf) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
if (decode_bmp_region(ctx->bmp_ctx, 0, 0, w, h, sample, temp_buf)) {
int left = 0, top = 0, right = dest_w, bottom = dest_h;
find_margins_rgba(temp_buf, dest_w, dest_h, &left, &top, &right, &bottom);
ctx->crop_left = left * sample;
ctx->crop_top = top * sample;
ctx->crop_width = (right - left) * sample;
ctx->crop_height = (bottom - top) * sample;
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
free(temp_buf);
}
// ============================================================================
// APPLE Implementation (macOS / iOS)
// ============================================================================
#ifdef __APPLE__
static void perform_apple_autocrop(ImageDecoderContext* ctx) {
int w = ctx->width;
int h = ctx->height;
CFTypeRef keys[3] = { kCGImageSourceCreateThumbnailFromImageAlways, kCGImageSourceThumbnailMaxPixelSize, kCGImageSourceCreateThumbnailWithTransform };
int max_size = 256;
CFNumberRef max_size_num = CFNumberCreate(NULL, kCFNumberIntType, &max_size);
CFTypeRef values[3] = { kCFBooleanTrue, max_size_num, kCFBooleanTrue };
CFDictionaryRef options = CFDictionaryCreate(NULL, keys, values, 3, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFRelease(max_size_num);
CGImageRef thumb = CGImageSourceCreateThumbnailAtIndex(ctx->apple_source, 0, options);
CFRelease(options);
if (!thumb) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
int thumb_w = (int)CGImageGetWidth(thumb);
int thumb_h = (int)CGImageGetHeight(thumb);
unsigned char* temp_buf = (unsigned char*)malloc(thumb_w * thumb_h * 4);
if (!temp_buf) {
CGImageRelease(thumb);
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
temp_buf,
thumb_w,
thumb_h,
8,
thumb_w * 4,
color_space,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
);
CGColorSpaceRelease(color_space);
if (context) {
CGRect rect = CGRectMake(0, 0, thumb_w, thumb_h);
CGContextDrawImage(context, rect, thumb);
CGContextRelease(context);
int left = 0, top = 0, right = thumb_w, bottom = thumb_h;
find_margins_rgba(temp_buf, thumb_w, thumb_h, &left, &top, &right, &bottom);
ctx->crop_left = (left * w) / thumb_w;
ctx->crop_top = (top * h) / thumb_h;
ctx->crop_width = ((right - left) * w) / thumb_w;
ctx->crop_height = ((bottom - top) * h) / thumb_h;
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
free(temp_buf);
CGImageRelease(thumb);
}
ImageDecoderContext* init_decoder(const char* file_path, bool crop_borders, int* out_width, int* out_height) {
BmpDecoderContext* bmp = init_bmp_decoder(file_path, out_width, out_height);
if (bmp) {
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_BMP;
ctx->width = *out_width;
ctx->height = *out_height;
ctx->bmp_ctx = bmp;
if (crop_borders) {
perform_bmp_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = ctx->width;
ctx->crop_height = ctx->height;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
CFStringRef path_str = CFStringCreateWithCString(NULL, file_path, kCFStringEncodingUTF8);
if (!path_str) return NULL;
CFURLRef url = CFURLCreateWithFileSystemPath(NULL, path_str, kCFURLPOSIXPathStyle, false);
CFRelease(path_str);
if (!url) return NULL;
CGImageSourceRef source = CGImageSourceCreateWithURL(url, NULL);
CFRelease(url);
if (!source) return NULL;
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
if (!properties) {
CFRelease(source);
return NULL;
}
int w = 0, h = 0;
CFNumberRef width_num = (CFNumberRef)CFDictionaryGetValue(properties, kCGImagePropertyPixelWidth);
CFNumberRef height_num = (CFNumberRef)CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (width_num) CFNumberGetValue(width_num, kCFNumberIntType, &w);
if (height_num) CFNumberGetValue(height_num, kCFNumberIntType, &h);
CFRelease(properties);
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_NATIVE;
ctx->width = w;
ctx->height = h;
ctx->apple_source = source;
if (crop_borders) {
perform_apple_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
bool decode_region(ImageDecoderContext* ctx, int left, int top, int right, int bottom, int sample_size, unsigned char* out_rgba_buffer) {
if (!ctx || !out_rgba_buffer) return false;
// Translate coordinates from cropped space to base space
int raw_left = left + ctx->crop_left;
int raw_top = top + ctx->crop_top;
int raw_right = right + ctx->crop_left;
int raw_bottom = bottom + ctx->crop_top;
if (ctx->type == TYPE_BMP) {
return decode_bmp_region(ctx->bmp_ctx, raw_left, raw_top, raw_right, raw_bottom, sample_size, out_rgba_buffer);
}
CGImageRef full_image = CGImageSourceCreateImageAtIndex(ctx->apple_source, 0, NULL);
if (!full_image) return false;
CGRect crop_rect = CGRectMake(raw_left, raw_top, raw_right - raw_left, raw_bottom - raw_top);
CGImageRef cropped_image = CGImageCreateWithImageInRect(full_image, crop_rect);
CGImageRelease(full_image);
if (!cropped_image) return false;
int dest_width = (raw_right - raw_left) / sample_size;
int dest_height = (raw_bottom - raw_top) / sample_size;
CGColorSpaceRef color_space = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(
out_rgba_buffer,
dest_width,
dest_height,
8,
dest_width * 4,
color_space,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big
);
CGColorSpaceRelease(color_space);
if (!context) {
CGImageRelease(cropped_image);
return false;
}
CGRect dest_rect = CGRectMake(0, 0, dest_width, dest_height);
CGContextDrawImage(context, dest_rect, cropped_image);
CGContextRelease(context);
CGImageRelease(cropped_image);
return true;
}
void free_decoder(ImageDecoderContext* ctx) {
if (ctx) {
if (ctx->type == TYPE_BMP) {
free_bmp_decoder(ctx->bmp_ctx);
} else {
if (ctx->apple_source) {
CFRelease(ctx->apple_source);
}
}
free(ctx);
}
}
#endif
// ============================================================================
// ANDROID Implementation (Dynamic NDK Loading)
// ============================================================================
#ifdef __ANDROID__
#include <android/rect.h>
#include <dlfcn.h>
#include <fcntl.h>
#include <unistd.h>
typedef struct AImageDecoder AImageDecoder;
typedef struct AImageDecoderHeaderInfo AImageDecoderHeaderInfo;
typedef int (*fn_AImageDecoder_createFromFd)(int, AImageDecoder**);
typedef int (*fn_AImageDecoder_createFromBuffer)(const void*, size_t, AImageDecoder**);
typedef const AImageDecoderHeaderInfo* (*fn_AImageDecoder_getHeaderInfo)(const AImageDecoder*);
typedef int32_t (*fn_AImageDecoderHeaderInfo_getWidth)(const AImageDecoderHeaderInfo*);
typedef int32_t (*fn_AImageDecoderHeaderInfo_getHeight)(const AImageDecoderHeaderInfo*);
typedef void (*fn_AImageDecoder_delete)(AImageDecoder*);
typedef int (*fn_AImageDecoder_setCrop)(AImageDecoder*, ARect);
typedef int (*fn_AImageDecoder_setTargetSize)(AImageDecoder*, int32_t, int32_t);
typedef int (*fn_AImageDecoder_setAndroidBitmapFormat)(AImageDecoder*, int32_t);
typedef int (*fn_AImageDecoder_decodeImage)(AImageDecoder*, void*, size_t, size_t);
static fn_AImageDecoder_createFromFd pAImageDecoder_createFromFd = NULL;
static fn_AImageDecoder_createFromBuffer pAImageDecoder_createFromBuffer = NULL;
static fn_AImageDecoder_getHeaderInfo pAImageDecoder_getHeaderInfo = NULL;
static fn_AImageDecoderHeaderInfo_getWidth pAImageDecoderHeaderInfo_getWidth = NULL;
static fn_AImageDecoderHeaderInfo_getHeight pAImageDecoderHeaderInfo_getHeight = NULL;
static fn_AImageDecoder_delete pAImageDecoder_delete = NULL;
static fn_AImageDecoder_setCrop pAImageDecoder_setCrop = NULL;
static fn_AImageDecoder_setTargetSize pAImageDecoder_setTargetSize = NULL;
static fn_AImageDecoder_setAndroidBitmapFormat pAImageDecoder_setAndroidBitmapFormat = NULL;
static fn_AImageDecoder_decodeImage pAImageDecoder_decodeImage = NULL;
static bool load_imagedecoder_symbols() {
static bool resolved = false;
static bool available = false;
if (resolved) return available;
void* handle = dlopen("libjnigraphics.so", RTLD_NOW | RTLD_GLOBAL);
if (!handle) {
resolved = true;
available = false;
return false;
}
pAImageDecoder_createFromFd = (fn_AImageDecoder_createFromFd)dlsym(handle, "AImageDecoder_createFromFd");
pAImageDecoder_createFromBuffer = (fn_AImageDecoder_createFromBuffer)dlsym(handle, "AImageDecoder_createFromBuffer");
pAImageDecoder_getHeaderInfo = (fn_AImageDecoder_getHeaderInfo)dlsym(handle, "AImageDecoder_getHeaderInfo");
pAImageDecoderHeaderInfo_getWidth = (fn_AImageDecoderHeaderInfo_getWidth)dlsym(handle, "AImageDecoderHeaderInfo_getWidth");
pAImageDecoderHeaderInfo_getHeight = (fn_AImageDecoderHeaderInfo_getHeight)dlsym(handle, "AImageDecoderHeaderInfo_getHeight");
pAImageDecoder_delete = (fn_AImageDecoder_delete)dlsym(handle, "AImageDecoder_delete");
pAImageDecoder_setCrop = (fn_AImageDecoder_setCrop)dlsym(handle, "AImageDecoder_setCrop");
pAImageDecoder_setTargetSize = (fn_AImageDecoder_setTargetSize)dlsym(handle, "AImageDecoder_setTargetSize");
pAImageDecoder_setAndroidBitmapFormat = (fn_AImageDecoder_setAndroidBitmapFormat)dlsym(handle, "AImageDecoder_setAndroidBitmapFormat");
pAImageDecoder_decodeImage = (fn_AImageDecoder_decodeImage)dlsym(handle, "AImageDecoder_decodeImage");
available = (pAImageDecoder_createFromFd && pAImageDecoder_createFromBuffer && pAImageDecoder_getHeaderInfo &&
pAImageDecoderHeaderInfo_getWidth && pAImageDecoderHeaderInfo_getHeight &&
pAImageDecoder_delete && pAImageDecoder_setCrop &&
pAImageDecoder_setTargetSize && pAImageDecoder_setAndroidBitmapFormat &&
pAImageDecoder_decodeImage);
resolved = true;
return available;
}
static void perform_android_autocrop(ImageDecoderContext* ctx) {
int w = ctx->width;
int h = ctx->height;
if (!ctx->android_mem.file_bytes || !load_imagedecoder_symbols()) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
AImageDecoder* decoder = NULL;
int result = pAImageDecoder_createFromBuffer(ctx->android_mem.file_bytes, ctx->android_mem.file_size, &decoder);
if (result != 0 || !decoder) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
int thumb_w = 256;
int thumb_h = (h * thumb_w) / w;
if (thumb_h <= 0) thumb_h = 1;
result = pAImageDecoder_setTargetSize(decoder, thumb_w, thumb_h);
if (result != 0) {
pAImageDecoder_delete(decoder);
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
pAImageDecoder_setAndroidBitmapFormat(decoder, 1);
unsigned char* temp_buf = (unsigned char*)malloc(thumb_w * thumb_h * 4);
if (!temp_buf) {
pAImageDecoder_delete(decoder);
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
size_t stride = thumb_w * 4;
size_t buffer_size = stride * thumb_h;
result = pAImageDecoder_decodeImage(decoder, temp_buf, stride, buffer_size);
if (result == 0) {
int left = 0, top = 0, right = thumb_w, bottom = thumb_h;
find_margins_rgba(temp_buf, thumb_w, thumb_h, &left, &top, &right, &bottom);
ctx->crop_left = (left * w) / thumb_w;
ctx->crop_top = (top * h) / thumb_h;
ctx->crop_width = ((right - left) * w) / thumb_w;
ctx->crop_height = ((bottom - top) * h) / thumb_h;
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
free(temp_buf);
pAImageDecoder_delete(decoder);
}
ImageDecoderContext* init_decoder(const char* file_path, bool crop_borders, int* out_width, int* out_height) {
BmpDecoderContext* bmp = init_bmp_decoder(file_path, out_width, out_height);
if (bmp) {
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_BMP;
ctx->width = *out_width;
ctx->height = *out_height;
ctx->bmp_ctx = bmp;
if (crop_borders) {
perform_bmp_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = ctx->width;
ctx->crop_height = ctx->height;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
if (!load_imagedecoder_symbols()) {
return NULL;
}
FILE* f = fopen(file_path, "rb");
if (!f) return NULL;
fseek(f, 0, SEEK_END);
long size = ftell(f);
fseek(f, 0, SEEK_SET);
unsigned char* file_bytes = (unsigned char*)malloc(size);
if (!file_bytes) {
fclose(f);
return NULL;
}
size_t read_bytes = fread(file_bytes, 1, size, f);
fclose(f);
if (read_bytes < size) {
free(file_bytes);
return NULL;
}
AImageDecoder* decoder = NULL;
int result = pAImageDecoder_createFromBuffer(file_bytes, size, &decoder);
if (result != 0 || !decoder) {
free(file_bytes);
return NULL;
}
const AImageDecoderHeaderInfo* info = pAImageDecoder_getHeaderInfo(decoder);
int32_t w = pAImageDecoderHeaderInfo_getWidth(info);
int32_t h = pAImageDecoderHeaderInfo_getHeight(info);
pAImageDecoder_delete(decoder);
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_NATIVE;
ctx->width = w;
ctx->height = h;
ctx->android_mem.file_bytes = file_bytes;
ctx->android_mem.file_size = size;
if (crop_borders) {
perform_android_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
bool decode_region(ImageDecoderContext* ctx, int left, int top, int right, int bottom, int sample_size, unsigned char* out_rgba_buffer) {
if (!ctx || !out_rgba_buffer) return false;
int raw_left = left + ctx->crop_left;
int raw_top = top + ctx->crop_top;
int raw_right = right + ctx->crop_left;
int raw_bottom = bottom + ctx->crop_top;
if (ctx->type == TYPE_BMP) {
return decode_bmp_region(ctx->bmp_ctx, raw_left, raw_top, raw_right, raw_bottom, sample_size, out_rgba_buffer);
}
if (!ctx->android_mem.file_bytes || !load_imagedecoder_symbols()) return false;
AImageDecoder* decoder = NULL;
int result = pAImageDecoder_createFromBuffer(ctx->android_mem.file_bytes, ctx->android_mem.file_size, &decoder);
if (result != 0 || !decoder) {
return false;
}
int total_dest_width = ctx->width / sample_size;
int total_dest_height = ctx->height / sample_size;
result = pAImageDecoder_setTargetSize(decoder, total_dest_width, total_dest_height);
if (result != 0) {
pAImageDecoder_delete(decoder);
return false;
}
int dest_width = (raw_right - raw_left) / sample_size;
int dest_height = (raw_bottom - raw_top) / sample_size;
int scaled_left = raw_left / sample_size;
int scaled_top = raw_top / sample_size;
int scaled_right = scaled_left + dest_width;
int scaled_bottom = scaled_top + dest_height;
ARect crop_rect = { scaled_left, scaled_top, scaled_right, scaled_bottom };
result = pAImageDecoder_setCrop(decoder, crop_rect);
if (result != 0) {
pAImageDecoder_delete(decoder);
return false;
}
pAImageDecoder_setAndroidBitmapFormat(decoder, 1);
size_t stride = dest_width * 4;
size_t buffer_size = stride * dest_height;
result = pAImageDecoder_decodeImage(decoder, out_rgba_buffer, stride, buffer_size);
pAImageDecoder_delete(decoder);
return result == 0;
}
void free_decoder(ImageDecoderContext* ctx) {
if (ctx) {
if (ctx->type == TYPE_BMP) {
free_bmp_decoder(ctx->bmp_ctx);
} else {
if (ctx->android_mem.file_bytes) {
free(ctx->android_mem.file_bytes);
}
}
free(ctx);
}
}
#endif
// ============================================================================
// WINDOWS Implementation (WIC - Windows Imaging Component)
// ============================================================================
#ifdef _WIN32
static void perform_win_autocrop(ImageDecoderContext* ctx) {
int w = ctx->width;
int h = ctx->height;
int thumb_w = 256;
int thumb_h = (h * thumb_w) / w;
if (thumb_h <= 0) thumb_h = 1;
IWICBitmapScaler* scaler = NULL;
HRESULT hr = ctx->win_ctx.factory->CreateBitmapScaler(&scaler);
if (FAILED(hr)) {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
hr = scaler->Initialize(ctx->win_ctx.frame, thumb_w, thumb_h, WICBitmapInterpolationModeLinear);
if (FAILED(hr)) {
scaler->Release();
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
IWICFormatConverter* converter = NULL;
hr = ctx->win_ctx.factory->CreateFormatConverter(&converter);
if (FAILED(hr)) {
scaler->Release();
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
hr = converter->Initialize(
scaler,
GUID_WICPixelFormat32bppRGBA,
WICBitmapDitherTypeNone,
NULL,
0.0f,
WICBitmapPaletteTypeCustom
);
scaler->Release();
if (FAILED(hr)) {
converter->Release();
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
unsigned char* temp_buf = (unsigned char*)malloc(thumb_w * thumb_h * 4);
if (!temp_buf) {
converter->Release();
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
return;
}
hr = converter->CopyPixels(
NULL,
thumb_w * 4,
thumb_w * thumb_h * 4,
temp_buf
);
converter->Release();
if (SUCCEEDED(hr)) {
int left = 0, top = 0, right = thumb_w, bottom = thumb_h;
find_margins_rgba(temp_buf, thumb_w, thumb_h, &left, &top, &right, &bottom);
ctx->crop_left = (left * w) / thumb_w;
ctx->crop_top = (top * h) / thumb_h;
ctx->crop_width = ((right - left) * w) / thumb_w;
ctx->crop_height = ((bottom - top) * h) / thumb_h;
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = w;
ctx->crop_height = h;
}
free(temp_buf);
}
ImageDecoderContext* init_decoder(const char* file_path, bool crop_borders, int* out_width, int* out_height) {
BmpDecoderContext* bmp = init_bmp_decoder(file_path, out_width, out_height);
if (bmp) {
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_BMP;
ctx->width = *out_width;
ctx->height = *out_height;
ctx->bmp_ctx = bmp;
if (crop_borders) {
perform_bmp_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = ctx->width;
ctx->crop_height = ctx->height;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
if (FAILED(hr) && hr != RPC_E_CHANGED_MODE) {
return NULL;
}
IWICImagingFactory* factory = NULL;
hr = CoCreateInstance(
CLSID_WICImagingFactory,
NULL,
CLSCTX_INPROC_SERVER,
IID_PPV_ARGS(&factory)
);
if (FAILED(hr) || !factory) {
return NULL;
}
int wlen = MultiByteToWideChar(CP_UTF8, 0, file_path, -1, NULL, 0);
if (wlen <= 0) {
factory->Release();
return NULL;
}
wchar_t* wpath = (wchar_t*)malloc(wlen * sizeof(wchar_t));
MultiByteToWideChar(CP_UTF8, 0, file_path, -1, wpath, wlen);
IWICBitmapDecoder* decoder = NULL;
hr = factory->CreateDecoderFromFilename(
wpath,
NULL,
GENERIC_READ,
WICDecodeMetadataCacheOnDemand,
&decoder
);
free(wpath);
if (FAILED(hr) || !decoder) {
factory->Release();
return NULL;
}
IWICBitmapFrameDecode* frame = NULL;
hr = decoder->GetFrame(0, &frame);
if (FAILED(hr) || !frame) {
decoder->Release();
factory->Release();
return NULL;
}
UINT w = 0, h = 0;
frame->GetSize(&w, &h);
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_NATIVE;
ctx->width = (int)w;
ctx->height = (int)h;
ctx->win_ctx.factory = factory;
ctx->win_ctx.decoder = decoder;
ctx->win_ctx.frame = frame;
if (crop_borders) {
perform_win_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = ctx->width;
ctx->crop_height = ctx->height;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
bool decode_region(ImageDecoderContext* ctx, int left, int top, int right, int bottom, int sample_size, unsigned char* out_rgba_buffer) {
if (!ctx || !out_rgba_buffer) return false;
int raw_left = left + ctx->crop_left;
int raw_top = top + ctx->crop_top;
int raw_right = right + ctx->crop_left;
int raw_bottom = bottom + ctx->crop_top;
if (ctx->type == TYPE_BMP) {
return decode_bmp_region(ctx->bmp_ctx, raw_left, raw_top, raw_right, raw_bottom, sample_size, out_rgba_buffer);
}
IWICBitmapClipper* clipper = NULL;
HRESULT hr = ctx->win_ctx.factory->CreateBitmapClipper(&clipper);
if (FAILED(hr)) return false;
WICRect rect = { raw_left, raw_top, raw_right - raw_left, raw_bottom - raw_top };
hr = clipper->Initialize(ctx->win_ctx.frame, &rect);
if (FAILED(hr)) {
clipper->Release();
return false;
}
int dest_width = (raw_right - raw_left) / sample_size;
int dest_height = (raw_bottom - raw_top) / sample_size;
IWICBitmapScaler* scaler = NULL;
hr = ctx->win_ctx.factory->CreateBitmapScaler(&scaler);
if (FAILED(hr)) {
clipper->Release();
return false;
}
hr = scaler->Initialize(clipper, dest_width, dest_height, WICBitmapInterpolationModeLinear);
clipper->Release();
if (FAILED(hr)) {
scaler->Release();
return false;
}
IWICFormatConverter* converter = NULL;
hr = ctx->win_ctx.factory->CreateFormatConverter(&converter);
if (FAILED(hr)) {
scaler->Release();
return false;
}
hr = converter->Initialize(
scaler,
GUID_WICPixelFormat32bppRGBA,
WICBitmapDitherTypeNone,
NULL,
0.0f,
WICBitmapPaletteTypeCustom
);
scaler->Release();
if (FAILED(hr)) {
converter->Release();
return false;
}
hr = converter->CopyPixels(
NULL,
dest_width * 4,
dest_width * dest_height * 4,
out_rgba_buffer
);
converter->Release();
return SUCCEEDED(hr);
}
void free_decoder(ImageDecoderContext* ctx) {
if (ctx) {
if (ctx->type == TYPE_BMP) {
free_bmp_decoder(ctx->bmp_ctx);
} else {
if (ctx->win_ctx.frame) ctx->win_ctx.frame->Release();
if (ctx->win_ctx.decoder) ctx->win_ctx.decoder->Release();
if (ctx->win_ctx.factory) ctx->win_ctx.factory->Release();
CoUninitialize();
}
free(ctx);
}
}
#endif
// ============================================================================
// Fallback / LINUX (BMP decoder support and explicit messages)
// ============================================================================
#if !defined(__APPLE__) && !defined(__ANDROID__) && !defined(_WIN32)
ImageDecoderContext* init_decoder(const char* file_path, bool crop_borders, int* out_width, int* out_height) {
BmpDecoderContext* bmp = init_bmp_decoder(file_path, out_width, out_height);
if (bmp) {
ImageDecoderContext* ctx = (ImageDecoderContext*)malloc(sizeof(ImageDecoderContext));
ctx->type = TYPE_BMP;
ctx->width = *out_width;
ctx->height = *out_height;
ctx->bmp_ctx = bmp;
if (crop_borders) {
perform_bmp_autocrop(ctx);
} else {
ctx->crop_left = 0;
ctx->crop_top = 0;
ctx->crop_width = ctx->width;
ctx->crop_height = ctx->height;
}
*out_width = ctx->crop_width;
*out_height = ctx->crop_height;
return ctx;
}
printf("ImageDecoder: Unsupported file format on Linux without libjpeg/libpng linked.\n");
return NULL;
}
bool decode_region(ImageDecoderContext* ctx, int left, int top, int right, int bottom, int sample_size, unsigned char* out_rgba_buffer) {
if (!ctx || !out_rgba_buffer) return false;
int raw_left = left + ctx->crop_left;
int raw_top = top + ctx->crop_top;
int raw_right = right + ctx->crop_left;
int raw_bottom = bottom + ctx->crop_top;
if (ctx->type == TYPE_BMP) {
return decode_bmp_region(ctx->bmp_ctx, raw_left, raw_top, raw_right, raw_bottom, sample_size, out_rgba_buffer);
}
return false;
}
void free_decoder(ImageDecoderContext* ctx) {
if (ctx) {
if (ctx->type == TYPE_BMP) {
free_bmp_decoder(ctx->bmp_ctx);
}
free(ctx);
}
}
#endif
#ifdef __cplusplus
}
#endif