Add image compressor

This commit is contained in:
2026-06-12 03:47:52 +07:00
parent b09ecf06ab
commit 099307e098
188 changed files with 4758 additions and 4923 deletions
+69 -13
View File
@@ -11,36 +11,79 @@ set(FFMPEG_INCLUDE_DIR
set(FFMPEG_LIB_DIR
${FFMPEG_ROOT}/lib)
# Output dari setup_image_compressor_android_*.sh:
# cpp/third_party/android-image-compres/<ABI>/include
# cpp/third_party/android-image-compres/<ABI>/lib
set(IMAGE_COMPRESS_ROOT
${CMAKE_SOURCE_DIR}/third_party/android-image-compres/${ANDROID_ABI})
set(IMAGE_COMPRESS_INCLUDE_DIR
${IMAGE_COMPRESS_ROOT}/include)
set(IMAGE_COMPRESS_LIB_DIR
${IMAGE_COMPRESS_ROOT}/lib)
add_library(kcompressor SHARED
native_compressor.cpp)
native_compressor.cpp
compressor_common.cpp
video_compressor.cpp
image_compressor.cpp)
target_link_options(kcompressor PRIVATE
-Wl,--allow-multiple-definition
-Wl,--exclude-libs,libimagequant.a
-Wl,--exclude-libs,liboxipng_ffi.a)
target_include_directories(kcompressor PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}
${FFMPEG_INCLUDE_DIR}
${FFMPEG_INCLUDE_DIR}/x264
${FFMPEG_INCLUDE_DIR}/x265)
${FFMPEG_INCLUDE_DIR}/x265
${IMAGE_COMPRESS_INCLUDE_DIR})
function(import_static_required target_name lib_dir lib_name)
set(full_path ${lib_dir}/${lib_name})
if (NOT EXISTS ${full_path})
message(FATAL_ERROR "Missing static library: ${full_path}")
endif()
function(import_ffmpeg_static target_name lib_name)
add_library(${target_name} STATIC IMPORTED)
set_target_properties(${target_name} PROPERTIES
IMPORTED_LOCATION ${FFMPEG_LIB_DIR}/${lib_name})
IMPORTED_LOCATION ${full_path})
endfunction()
import_ffmpeg_static(avcodec libavcodec.a)
import_ffmpeg_static(avfilter libavfilter.a)
import_ffmpeg_static(avformat libavformat.a)
import_ffmpeg_static(avutil libavutil.a)
import_ffmpeg_static(swresample libswresample.a)
import_ffmpeg_static(swscale libswscale.a)
import_ffmpeg_static(x264 libx264.a)
import_ffmpeg_static(x265 libx265.a)
import_static_required(avcodec ${FFMPEG_LIB_DIR} libavcodec.a)
import_static_required(avfilter ${FFMPEG_LIB_DIR} libavfilter.a)
import_static_required(avformat ${FFMPEG_LIB_DIR} libavformat.a)
import_static_required(avutil ${FFMPEG_LIB_DIR} libavutil.a)
import_static_required(swresample ${FFMPEG_LIB_DIR} libswresample.a)
import_static_required(swscale ${FFMPEG_LIB_DIR} libswscale.a)
import_static_required(x264 ${FFMPEG_LIB_DIR} libx264.a)
import_static_required(x265 ${FFMPEG_LIB_DIR} libx265.a)
import_static_required(mozjpeg_jpeg ${IMAGE_COMPRESS_LIB_DIR} libjpeg.a)
import_static_required(mozjpeg_turbojpeg ${IMAGE_COMPRESS_LIB_DIR} libturbojpeg.a)
import_static_required(webp ${IMAGE_COMPRESS_LIB_DIR} libwebp.a)
import_static_required(sharpyuv ${IMAGE_COMPRESS_LIB_DIR} libsharpyuv.a)
import_static_required(avif ${IMAGE_COMPRESS_LIB_DIR} libavif.a)
import_static_required(aom ${IMAGE_COMPRESS_LIB_DIR} libaom.a)
import_static_required(yuv ${IMAGE_COMPRESS_LIB_DIR} libyuv.a)
import_static_required(imagequant ${IMAGE_COMPRESS_LIB_DIR} libimagequant.a)
import_static_required(oxipng_ffi ${IMAGE_COMPRESS_LIB_DIR} liboxipng_ffi.a)
import_static_required(kimage_png ${IMAGE_COMPRESS_LIB_DIR} libkimage_png.a)
target_compile_features(kcompressor PRIVATE cxx_std_17)
target_compile_options(kcompressor PRIVATE
-Wall
-Wextra
-Wno-unused-parameter)
-Wno-unused-parameter
-Wno-deprecated-declarations)
# --start-group penting karena semua dependency berupa static archive.
target_link_libraries(kcompressor
-Wl,--start-group
avfilter
avformat
avcodec
@@ -49,6 +92,18 @@ target_link_libraries(kcompressor
avutil
x264
x265
mozjpeg_turbojpeg
mozjpeg_jpeg
webp
sharpyuv
avif
aom
yuv
imagequant
oxipng_ffi
kimage_png
-Wl,--end-group
android
@@ -56,4 +111,5 @@ target_link_libraries(kcompressor
z
m
dl
atomic
c++_static)
+38
View File
@@ -0,0 +1,38 @@
#include "compressor_common.h"
#include <atomic>
static std::atomic_bool g_cancel_requested(false);
void set_cancel_requested(bool value) {
g_cancel_requested.store(value, std::memory_order_release);
}
void reset_cancel_requested() {
set_cancel_requested(false);
}
bool is_cancel_requested() {
return g_cancel_requested.load(std::memory_order_acquire);
}
void callback_progress(JNIEnv *env, jobject callback, int percent, const std::string &message) {
if (!env || !callback) return;
jclass cls = env->GetObjectClass(callback);
if (!cls) return;
jmethodID mid = env->GetMethodID(cls, "onProgress", "(ILjava/lang/String;)V");
if (!mid) {
env->DeleteLocalRef(cls);
return;
}
jstring jmsg = env->NewStringUTF(message.c_str());
if (jmsg) {
env->CallVoidMethod(callback, mid, static_cast<jint>(percent), jmsg);
env->DeleteLocalRef(jmsg);
}
env->DeleteLocalRef(cls);
}
+10
View File
@@ -0,0 +1,10 @@
#pragma once
#include <jni.h>
#include <string>
void set_cancel_requested(bool value);
void reset_cancel_requested();
bool is_cancel_requested();
void callback_progress(JNIEnv *env, jobject callback, int percent, const std::string &message);
+735
View File
@@ -0,0 +1,735 @@
#include "image_compressor.h"
#include "compressor_common.h"
#include <algorithm>
#include <cerrno>
#include <cmath>
#include <cstdint>
#include <cstring>
#include <string>
#include <vector>
#include <android/log.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <turbojpeg.h>
#include <webp/decode.h>
#include <webp/encode.h>
#include <avif/avif.h>
extern "C" {
#include <libimagequant.h>
#include <oxipng_ffi.h>
#include <kimage_png_codec.h>
}
#include <lodepng.h>
#define LOG_TAG "KCompressorImage"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)
struct RgbaImage {
int width = 0;
int height = 0;
std::vector<uint8_t> pixels;
};
static bool read_all_fd(int fd, std::vector<uint8_t> &out) {
if (fd < 0) return false;
int owned_fd = dup(fd);
if (owned_fd < 0) return false;
if (lseek64(owned_fd, 0, SEEK_SET) < 0) {
LOGD("input image fd is not seekable at start: %s", strerror(errno));
}
struct stat st {};
if (fstat(owned_fd, &st) == 0 && st.st_size > 0 && st.st_size < 1024LL * 1024LL * 1024LL) {
out.reserve(static_cast<size_t>(st.st_size));
}
uint8_t buffer[64 * 1024];
while (true) {
if (is_cancel_requested()) {
close(owned_fd);
return false;
}
ssize_t ret = read(owned_fd, buffer, sizeof(buffer));
if (ret == 0) break;
if (ret < 0) {
if (errno == EINTR) continue;
close(owned_fd);
return false;
}
out.insert(out.end(), buffer, buffer + ret);
}
close(owned_fd);
return !out.empty();
}
static bool write_all_fd(int fd, const std::vector<uint8_t> &data) {
if (fd < 0 || data.empty()) return false;
int owned_fd = dup(fd);
if (owned_fd < 0) return false;
if (lseek64(owned_fd, 0, SEEK_SET) < 0) {
LOGD("output image fd is not seekable at start: %s", strerror(errno));
}
if (ftruncate(owned_fd, 0) < 0) {
LOGD("ftruncate image output failed or unsupported: %s", strerror(errno));
}
size_t total = 0;
while (total < data.size()) {
if (is_cancel_requested()) {
close(owned_fd);
return false;
}
ssize_t ret = write(owned_fd, data.data() + total, data.size() - total);
if (ret < 0) {
if (errno == EINTR) continue;
close(owned_fd);
return false;
}
if (ret == 0) {
close(owned_fd);
return false;
}
total += static_cast<size_t>(ret);
}
fsync(owned_fd);
close(owned_fd);
return true;
}
static int clamp_int(int value, int min_value, int max_value) {
return std::max(min_value, std::min(max_value, value));
}
static bool has_prefix(const std::vector<uint8_t> &data, const uint8_t *magic, size_t magic_len) {
return data.size() >= magic_len && memcmp(data.data(), magic, magic_len) == 0;
}
static bool looks_like_jpeg(const std::vector<uint8_t> &data) {
static const uint8_t magic[] = {0xFF, 0xD8};
return has_prefix(data, magic, sizeof(magic));
}
static bool looks_like_png(const std::vector<uint8_t> &data) {
static const uint8_t magic[] = {0x89, 'P', 'N', 'G', 0x0D, 0x0A, 0x1A, 0x0A};
return has_prefix(data, magic, sizeof(magic));
}
static bool looks_like_webp(const std::vector<uint8_t> &data) {
return data.size() >= 12 &&
memcmp(data.data(), "RIFF", 4) == 0 &&
memcmp(data.data() + 8, "WEBP", 4) == 0;
}
static bool looks_like_avif(const std::vector<uint8_t> &data) {
if (data.size() < 16) return false;
const size_t max_scan = std::min<size_t>(data.size(), 64);
for (size_t i = 0; i + 8 <= max_scan; ++i) {
if (memcmp(data.data() + i, "ftyp", 4) == 0) {
const char *brand = reinterpret_cast<const char *>(data.data() + i + 4);
return memcmp(brand, "avif", 4) == 0 ||
memcmp(brand, "avis", 4) == 0 ||
memcmp(brand, "mif1", 4) == 0 ||
memcmp(brand, "msf1", 4) == 0;
}
}
return false;
}
static bool decode_jpeg_rgba(const std::vector<uint8_t> &input, RgbaImage &out) {
tjhandle handle = tjInitDecompress();
if (!handle) return false;
int width = 0;
int height = 0;
int subsamp = 0;
int colorspace = 0;
int ret = tjDecompressHeader3(
handle,
const_cast<unsigned char *>(input.data()),
static_cast<unsigned long>(input.size()),
&width,
&height,
&subsamp,
&colorspace
);
if (ret != 0 || width <= 0 || height <= 0) {
tjDestroy(handle);
return false;
}
out.width = width;
out.height = height;
out.pixels.assign(static_cast<size_t>(width) * height * 4, 255);
ret = tjDecompress2(
handle,
const_cast<unsigned char *>(input.data()),
static_cast<unsigned long>(input.size()),
out.pixels.data(),
width,
width * 4,
height,
TJPF_RGBA,
TJFLAG_FASTDCT
);
tjDestroy(handle);
return ret == 0;
}
static bool decode_png_rgba(const std::vector<uint8_t> &input, RgbaImage &out) {
uint8_t *rgba = nullptr;
unsigned width = 0;
unsigned height = 0;
int ret = kimg_png_decode_rgba(
input.data(),
input.size(),
&rgba,
&width,
&height
);
if (ret != 0 || !rgba || width == 0 || height == 0) {
if (rgba) kimg_free(rgba);
return false;
}
out.width = static_cast<int>(width);
out.height = static_cast<int>(height);
out.pixels.assign(rgba, rgba + static_cast<size_t>(width) * height * 4);
kimg_free(rgba);
return true;
}
static bool decode_webp_rgba(const std::vector<uint8_t> &input, RgbaImage &out) {
int width = 0;
int height = 0;
if (!WebPGetInfo(input.data(), input.size(), &width, &height) || width <= 0 || height <= 0) {
return false;
}
uint8_t *rgba = WebPDecodeRGBA(input.data(), input.size(), &width, &height);
if (!rgba) return false;
out.width = width;
out.height = height;
out.pixels.assign(rgba, rgba + static_cast<size_t>(width) * height * 4);
WebPFree(rgba);
return true;
}
static bool decode_avif_rgba(const std::vector<uint8_t> &input, RgbaImage &out) {
avifDecoder *decoder = avifDecoderCreate();
if (!decoder) return false;
avifImage *image = avifImageCreateEmpty();
if (!image) {
avifDecoderDestroy(decoder);
return false;
}
avifResult result = avifDecoderReadMemory(decoder, image, input.data(), input.size());
if (result != AVIF_RESULT_OK || image->width == 0 || image->height == 0) {
avifImageDestroy(image);
avifDecoderDestroy(decoder);
return false;
}
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, image);
rgb.format = AVIF_RGB_FORMAT_RGBA;
rgb.depth = 8;
if (avifRGBImageAllocatePixels(&rgb) != AVIF_RESULT_OK) {
avifImageDestroy(image);
avifDecoderDestroy(decoder);
return false;
}
result = avifImageYUVToRGB(image, &rgb);
if (result != AVIF_RESULT_OK) {
avifRGBImageFreePixels(&rgb);
avifImageDestroy(image);
avifDecoderDestroy(decoder);
return false;
}
out.width = static_cast<int>(image->width);
out.height = static_cast<int>(image->height);
out.pixels.assign(rgb.pixels, rgb.pixels + static_cast<size_t>(rgb.rowBytes) * image->height);
// Pastikan buffer contiguous width * height * 4, karena rowBytes bisa lebih besar.
if (rgb.rowBytes != image->width * 4) {
std::vector<uint8_t> compact(static_cast<size_t>(image->width) * image->height * 4);
for (uint32_t y = 0; y < image->height; ++y) {
memcpy(compact.data() + static_cast<size_t>(y) * image->width * 4,
rgb.pixels + static_cast<size_t>(y) * rgb.rowBytes,
static_cast<size_t>(image->width) * 4);
}
out.pixels.swap(compact);
}
avifRGBImageFreePixels(&rgb);
avifImageDestroy(image);
avifDecoderDestroy(decoder);
return true;
}
static bool decode_input_rgba(const std::vector<uint8_t> &input,
const std::string &input_mime,
RgbaImage &out) {
std::string mime = input_mime;
std::transform(mime.begin(), mime.end(), mime.begin(), ::tolower);
if (mime.find("jpeg") != std::string::npos || mime.find("jpg") != std::string::npos || looks_like_jpeg(input)) {
return decode_jpeg_rgba(input, out);
}
if (mime.find("png") != std::string::npos || looks_like_png(input)) {
return decode_png_rgba(input, out);
}
if (mime.find("webp") != std::string::npos || looks_like_webp(input)) {
return decode_webp_rgba(input, out);
}
if (mime.find("avif") != std::string::npos || looks_like_avif(input)) {
return decode_avif_rgba(input, out);
}
// Fallback by magic.
if (looks_like_jpeg(input)) return decode_jpeg_rgba(input, out);
if (looks_like_png(input)) return decode_png_rgba(input, out);
if (looks_like_webp(input)) return decode_webp_rgba(input, out);
if (looks_like_avif(input)) return decode_avif_rgba(input, out);
return false;
}
static RgbaImage resize_rgba_if_needed(const RgbaImage &src, int max_long_side) {
if (src.width <= 0 || src.height <= 0 || src.pixels.empty()) {
return src;
}
int long_side = std::max(src.width, src.height);
if (max_long_side <= 0 || max_long_side >= long_side) {
return src;
}
double scale = static_cast<double>(max_long_side) / static_cast<double>(long_side);
int dst_w = std::max(1, static_cast<int>(std::llround(src.width * scale)));
int dst_h = std::max(1, static_cast<int>(std::llround(src.height * scale)));
RgbaImage dst;
dst.width = dst_w;
dst.height = dst_h;
dst.pixels.assign(static_cast<size_t>(dst_w) * dst_h * 4, 0);
const double inv_x = static_cast<double>(src.width) / static_cast<double>(dst_w);
const double inv_y = static_cast<double>(src.height) / static_cast<double>(dst_h);
for (int y = 0; y < dst_h; ++y) {
int sy = std::min(src.height - 1, static_cast<int>(y * inv_y));
for (int x = 0; x < dst_w; ++x) {
int sx = std::min(src.width - 1, static_cast<int>(x * inv_x));
const uint8_t *sp = src.pixels.data() + (static_cast<size_t>(sy) * src.width + sx) * 4;
uint8_t *dp = dst.pixels.data() + (static_cast<size_t>(y) * dst_w + x) * 4;
dp[0] = sp[0];
dp[1] = sp[1];
dp[2] = sp[2];
dp[3] = sp[3];
}
}
return dst;
}
static bool encode_jpeg_rgba(const RgbaImage &image, int quality, std::vector<uint8_t> &out) {
tjhandle handle = tjInitCompress();
if (!handle) return false;
unsigned char *jpeg_buf = nullptr;
unsigned long jpeg_size = 0;
int ret = tjCompress2(
handle,
const_cast<uint8_t *>(image.pixels.data()),
image.width,
image.width * 4,
image.height,
TJPF_RGBA,
&jpeg_buf,
&jpeg_size,
TJSAMP_420,
clamp_int(quality, 1, 100),
TJFLAG_FASTDCT
);
if (ret != 0 || !jpeg_buf || jpeg_size == 0) {
if (jpeg_buf) tjFree(jpeg_buf);
tjDestroy(handle);
return false;
}
out.assign(jpeg_buf, jpeg_buf + jpeg_size);
tjFree(jpeg_buf);
tjDestroy(handle);
return true;
}
static bool oxipng_optimize_vector(const std::vector<uint8_t> &png, std::vector<uint8_t> &out, uint8_t level) {
uint8_t *optimized = nullptr;
size_t optimized_len = 0;
int ret = oxipng_optimize_memory(
png.data(),
png.size(),
level,
1,
0,
&optimized,
&optimized_len
);
if (ret == 0 && optimized && optimized_len > 0) {
out.assign(optimized, optimized + optimized_len);
oxipng_free(optimized, optimized_len);
return true;
}
if (optimized) {
oxipng_free(optimized, optimized_len);
}
out = png;
return true;
}
static bool encode_png_lossless_rgba(const RgbaImage &image, std::vector<uint8_t> &out) {
uint8_t *png = nullptr;
size_t png_len = 0;
int ret = kimg_png_encode_rgba(
image.pixels.data(),
static_cast<unsigned>(image.width),
static_cast<unsigned>(image.height),
&png,
&png_len
);
if (ret != 0 || !png || png_len == 0) {
if (png) kimg_free(png);
return false;
}
std::vector<uint8_t> encoded(png, png + png_len);
kimg_free(png);
return oxipng_optimize_vector(encoded, out, 3);
}
static bool encode_png_lossy_quantized(const RgbaImage &image, int quality, std::vector<uint8_t> &out) {
liq_attr *attr = liq_attr_create();
if (!attr) return false;
const int max_quality = clamp_int(quality, 1, 100);
const int min_quality = std::max(0, max_quality - 25);
liq_set_max_colors(attr, 256);
liq_set_quality(attr, min_quality, max_quality);
liq_set_speed(attr, 4);
liq_image *liq_img = liq_image_create_rgba(
attr,
const_cast<uint8_t *>(image.pixels.data()),
image.width,
image.height,
0.0
);
if (!liq_img) {
liq_attr_destroy(attr);
return false;
}
liq_result *result = nullptr;
liq_error err = liq_image_quantize(liq_img, attr, &result);
if (err != LIQ_OK || !result) {
liq_image_destroy(liq_img);
liq_attr_destroy(attr);
return false;
}
liq_set_dithering_level(result, 0.7f);
std::vector<uint8_t> indexed(static_cast<size_t>(image.width) * image.height);
err = liq_write_remapped_image(result, liq_img, indexed.data(), indexed.size());
if (err != LIQ_OK) {
liq_result_destroy(result);
liq_image_destroy(liq_img);
liq_attr_destroy(attr);
return false;
}
const liq_palette *palette = liq_get_palette(result);
if (!palette || palette->count <= 0) {
liq_result_destroy(result);
liq_image_destroy(liq_img);
liq_attr_destroy(attr);
return false;
}
lodepng::State state;
state.encoder.auto_convert = 0;
state.info_raw.colortype = LCT_PALETTE;
state.info_raw.bitdepth = 8;
state.info_png.color.colortype = LCT_PALETTE;
state.info_png.color.bitdepth = 8;
for (unsigned int i = 0; i < palette->count; ++i) {
const liq_color &c = palette->entries[i];
lodepng_palette_add(&state.info_raw, c.r, c.g, c.b, c.a);
lodepng_palette_add(&state.info_png.color, c.r, c.g, c.b, c.a);
}
std::vector<unsigned char> png;
unsigned lode_err = lodepng::encode(
png,
indexed,
static_cast<unsigned>(image.width),
static_cast<unsigned>(image.height),
state
);
liq_result_destroy(result);
liq_image_destroy(liq_img);
liq_attr_destroy(attr);
if (lode_err != 0 || png.empty()) {
return false;
}
std::vector<uint8_t> encoded(png.begin(), png.end());
return oxipng_optimize_vector(encoded, out, 4);
}
static bool encode_webp_rgba(const RgbaImage &image, int quality, bool lossless, std::vector<uint8_t> &out) {
uint8_t *webp = nullptr;
size_t webp_size = 0;
if (lossless) {
webp_size = WebPEncodeLosslessRGBA(
image.pixels.data(),
image.width,
image.height,
image.width * 4,
&webp
);
} else {
webp_size = WebPEncodeRGBA(
image.pixels.data(),
image.width,
image.height,
image.width * 4,
static_cast<float>(clamp_int(quality, 1, 100)),
&webp
);
}
if (!webp || webp_size == 0) {
if (webp) WebPFree(webp);
return false;
}
out.assign(webp, webp + webp_size);
WebPFree(webp);
return true;
}
static bool encode_avif_rgba(const RgbaImage &image, int quality, std::vector<uint8_t> &out) {
avifImage *avif = avifImageCreate(
static_cast<uint32_t>(image.width),
static_cast<uint32_t>(image.height),
8,
AVIF_PIXEL_FORMAT_YUV444
);
if (!avif) return false;
avifRGBImage rgb;
avifRGBImageSetDefaults(&rgb, avif);
rgb.format = AVIF_RGB_FORMAT_RGBA;
rgb.depth = 8;
rgb.pixels = const_cast<uint8_t *>(image.pixels.data());
rgb.rowBytes = static_cast<uint32_t>(image.width * 4);
avifResult result = avifImageRGBToYUV(avif, &rgb);
if (result != AVIF_RESULT_OK) {
avifImageDestroy(avif);
return false;
}
avifEncoder *encoder = avifEncoderCreate();
if (!encoder) {
avifImageDestroy(avif);
return false;
}
encoder->maxThreads = 0;
encoder->speed = 6;
encoder->quality = clamp_int(quality, 1, 100);
encoder->qualityAlpha = clamp_int(quality, 1, 100);
avifRWData encoded = AVIF_DATA_EMPTY;
result = avifEncoderWrite(encoder, avif, &encoded);
if (result == AVIF_RESULT_OK && encoded.data && encoded.size > 0) {
out.assign(encoded.data, encoded.data + encoded.size);
avifRWDataFree(&encoded);
avifEncoderDestroy(encoder);
avifImageDestroy(avif);
return true;
}
avifRWDataFree(&encoded);
avifEncoderDestroy(encoder);
avifImageDestroy(avif);
return false;
}
static bool encode_output(const RgbaImage &image,
const std::string &output_format,
int quality,
std::vector<uint8_t> &out) {
std::string fmt = output_format;
std::transform(fmt.begin(), fmt.end(), fmt.begin(), ::tolower);
if (fmt == "jpeg" || fmt == "jpg") {
return encode_jpeg_rgba(image, quality, out);
}
if (fmt == "png_lossy") {
if (encode_png_lossy_quantized(image, quality, out)) {
return true;
}
LOGD("PNG lossy quantize failed, fallback to PNG lossless");
return encode_png_lossless_rgba(image, out);
}
if (fmt == "png" || fmt == "png_lossless") {
return encode_png_lossless_rgba(image, out);
}
if (fmt == "webp_lossless") {
return encode_webp_rgba(image, quality, true, out);
}
if (fmt == "webp") {
return encode_webp_rgba(image, quality, false, out);
}
if (fmt == "avif") {
return encode_avif_rgba(image, quality, out);
}
return false;
}
int compress_image_fd_impl(JNIEnv *env,
int input_fd,
int output_fd,
const std::string &input_mime,
const std::string &output_format,
int quality,
int max_long_side,
jobject callback) {
if (input_fd < 0 || output_fd < 0) {
callback_progress(env, callback, 0, "FD gambar tidak valid");
return -1;
}
quality = clamp_int(quality, 1, 100);
if (max_long_side < 0) max_long_side = 0;
callback_progress(env, callback, 0, "Membaca gambar...");
std::vector<uint8_t> input;
if (!read_all_fd(input_fd, input)) {
callback_progress(env, callback, 0, is_cancel_requested() ? "Canceled" : "Gagal membaca gambar");
return is_cancel_requested() ? -125 : -2;
}
if (is_cancel_requested()) {
callback_progress(env, callback, 0, "Canceled");
return -125;
}
callback_progress(env, callback, 15, "Decode gambar...");
RgbaImage decoded;
if (!decode_input_rgba(input, input_mime, decoded)) {
callback_progress(env, callback, 0, "Format gambar tidak didukung");
return -3;
}
if (is_cancel_requested()) {
callback_progress(env, callback, 0, "Canceled");
return -125;
}
callback_progress(env, callback, 35, "Resize gambar...");
RgbaImage work = resize_rgba_if_needed(decoded, max_long_side);
if (is_cancel_requested()) {
callback_progress(env, callback, 0, "Canceled");
return -125;
}
callback_progress(env, callback, 60, "Encode gambar...");
std::vector<uint8_t> encoded;
if (!encode_output(work, output_format, quality, encoded)) {
callback_progress(env, callback, 0, "Gagal encode gambar");
return -4;
}
if (is_cancel_requested()) {
callback_progress(env, callback, 0, "Canceled");
return -125;
}
callback_progress(env, callback, 92, "Menyimpan gambar...");
if (!write_all_fd(output_fd, encoded)) {
callback_progress(env, callback, 0, "Gagal menulis output gambar");
return -5;
}
callback_progress(env, callback, 100, "Done");
return 0;
}
+13
View File
@@ -0,0 +1,13 @@
#pragma once
#include <jni.h>
#include <string>
int compress_image_fd_impl(JNIEnv *env,
int input_fd,
int output_fd,
const std::string &input_mime,
const std::string &output_format,
int quality,
int max_long_side,
jobject callback);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16
View File
@@ -0,0 +1,16 @@
#pragma once
#include <jni.h>
#include <string>
int compress_video_fd_impl(JNIEnv *env,
int input_fd,
int output_fd,
int target_short_side,
int input_rotation_degrees,
int crf,
const std::string &encoder_name,
const std::string &preset,
const std::string &audio_mode,
int audio_bitrate,
jobject callback);