mirror of
https://github.com/obsproject/obs-studio.git
synced 2026-08-28 17:42:28 +08:00
Merge pull request #11178 from RytoEX/really-update-libnsgif
libobs/graphics: Update libnsgif to 1.0.0 libobs/graphics: Hide GIF decoding data from exported header file libobs/graphics: Break image file API to prevent ABI issues Co-authored-by: Norihiro Kamae <norihiro@nagater.net> Co-authored-by: Lain <lain@obsproject.com>
This commit is contained in:
@@ -190,8 +190,10 @@ target_sources(
|
||||
graphics/image-file.c
|
||||
graphics/image-file.h
|
||||
graphics/input.h
|
||||
graphics/libnsgif/libnsgif.c
|
||||
graphics/libnsgif/libnsgif.h
|
||||
graphics/libnsgif/gif.c
|
||||
graphics/libnsgif/lzw.c
|
||||
graphics/libnsgif/lzw.h
|
||||
graphics/libnsgif/nsgif.h
|
||||
graphics/math-defs.h
|
||||
graphics/math-extra.c
|
||||
graphics/math-extra.h
|
||||
@@ -282,7 +284,7 @@ set(
|
||||
graphics/graphics.h
|
||||
graphics/image-file.h
|
||||
graphics/input.h
|
||||
graphics/libnsgif/libnsgif.h
|
||||
graphics/libnsgif/nsgif.h
|
||||
graphics/math-defs.h
|
||||
graphics/math-extra.h
|
||||
graphics/matrix3.h
|
||||
|
||||
@@ -67,6 +67,14 @@ set_source_files_properties(
|
||||
PROPERTIES COMPILE_DEFINITIONS OBS_VERSION="${OBS_VERSION_CANONICAL}"
|
||||
)
|
||||
|
||||
set_source_files_properties(
|
||||
graphics/libnsgif/gif.c
|
||||
graphics/libnsgif/lzw.c
|
||||
graphics/libnsgif/lzw.h
|
||||
graphics/libnsgif/nsgif.h
|
||||
PROPERTIES COMPILE_OPTIONS "/wd4244;/wd4267"
|
||||
)
|
||||
|
||||
target_link_libraries(
|
||||
libobs
|
||||
PRIVATE Avrt Dwmapi Dxgi winmm Rpcrt4 OBS::obfuscate OBS::winhandle OBS::COMutils
|
||||
|
||||
+118
-129
@@ -20,9 +20,23 @@
|
||||
#include "../util/platform.h"
|
||||
#include "../util/dstr.h"
|
||||
#include "vec4.h"
|
||||
#include "libnsgif/nsgif.h"
|
||||
|
||||
#define blog(level, format, ...) blog(level, "%s: " format, __FUNCTION__, __VA_ARGS__)
|
||||
|
||||
struct gs_image_file_internal {
|
||||
nsgif_t *gif;
|
||||
const nsgif_info_t *gif_info;
|
||||
const uint8_t *gif_frame_image;
|
||||
nsgif_bitmap_cb_vt bitmap_callbacks;
|
||||
|
||||
uint8_t *gif_data;
|
||||
uint8_t **animation_frame_cache;
|
||||
uint8_t *animation_frame_data;
|
||||
|
||||
int last_decoded_frame;
|
||||
};
|
||||
|
||||
static void *bi_def_bitmap_create(int width, int height)
|
||||
{
|
||||
return bmalloc((size_t)4 * width * height);
|
||||
@@ -55,37 +69,40 @@ static void bi_def_bitmap_modified(void *bitmap)
|
||||
UNUSED_PARAMETER(bitmap);
|
||||
}
|
||||
|
||||
static inline int get_full_decoded_gif_size(gs_image_file_t *image)
|
||||
static inline int get_full_decoded_gif_size(gs_image_file_ex_t *image)
|
||||
{
|
||||
return image->gif.width * image->gif.height * 4 * image->gif.frame_count;
|
||||
return image->internal->gif_info->width * image->internal->gif_info->height * 4 *
|
||||
image->internal->gif_info->frame_count;
|
||||
}
|
||||
|
||||
static inline void *alloc_mem(gs_image_file_t *image, uint64_t *mem_usage, size_t size)
|
||||
static inline void *alloc_mem(gs_image_file_ex_t *image, uint64_t *mem_usage, size_t size)
|
||||
{
|
||||
UNUSED_PARAMETER(image);
|
||||
|
||||
if (mem_usage)
|
||||
*mem_usage += size;
|
||||
|
||||
return bzalloc(size);
|
||||
}
|
||||
|
||||
static bool init_animated_gif(gs_image_file_t *image, const char *path, uint64_t *mem_usage,
|
||||
static bool init_animated_gif(gs_image_file_ex_t *image, const char *path, uint64_t *mem_usage,
|
||||
enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
bool is_animated_gif = true;
|
||||
gif_result result;
|
||||
nsgif_error result;
|
||||
uint64_t max_size;
|
||||
size_t size, size_read;
|
||||
FILE *file;
|
||||
nsgif_bitmap_t *bitmap;
|
||||
|
||||
image->bitmap_callbacks.bitmap_create = bi_def_bitmap_create;
|
||||
image->bitmap_callbacks.bitmap_destroy = bi_def_bitmap_destroy;
|
||||
image->bitmap_callbacks.bitmap_get_buffer = bi_def_bitmap_get_buffer;
|
||||
image->bitmap_callbacks.bitmap_modified = bi_def_bitmap_modified;
|
||||
image->bitmap_callbacks.bitmap_set_opaque = bi_def_bitmap_set_opaque;
|
||||
image->bitmap_callbacks.bitmap_test_opaque = bi_def_bitmap_test_opaque;
|
||||
image->internal->bitmap_callbacks.create = bi_def_bitmap_create;
|
||||
image->internal->bitmap_callbacks.destroy = bi_def_bitmap_destroy;
|
||||
image->internal->bitmap_callbacks.get_buffer = bi_def_bitmap_get_buffer;
|
||||
image->internal->bitmap_callbacks.modified = bi_def_bitmap_modified;
|
||||
image->internal->bitmap_callbacks.set_opaque = bi_def_bitmap_set_opaque;
|
||||
image->internal->bitmap_callbacks.test_opaque = bi_def_bitmap_test_opaque;
|
||||
|
||||
gif_create(&image->gif, &image->bitmap_callbacks);
|
||||
nsgif_create(&image->internal->bitmap_callbacks, NSGIF_BITMAP_FMT_R8G8B8A8, &image->internal->gif);
|
||||
|
||||
file = os_fopen(path, "rb");
|
||||
if (!file) {
|
||||
@@ -97,15 +114,15 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path, uint64_t
|
||||
size = (size_t)os_ftelli64(file);
|
||||
fseek(file, 0, SEEK_SET);
|
||||
|
||||
image->gif_data = bmalloc(size);
|
||||
size_read = fread(image->gif_data, 1, size, file);
|
||||
image->internal->gif_data = bmalloc(size);
|
||||
size_read = fread(image->internal->gif_data, 1, size, file);
|
||||
if (size_read != size) {
|
||||
blog(LOG_WARNING, "Failed to fully read gif file '%s'.", path);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
do {
|
||||
result = gif_initialise(&image->gif, size, image->gif_data);
|
||||
result = nsgif_data_scan(image->internal->gif, size, image->internal->gif_data);
|
||||
if (result < 0) {
|
||||
blog(LOG_WARNING,
|
||||
"Failed to initialize gif '%s', "
|
||||
@@ -113,39 +130,45 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path, uint64_t
|
||||
path);
|
||||
goto fail;
|
||||
}
|
||||
} while (result != GIF_OK);
|
||||
} while (result != NSGIF_OK);
|
||||
|
||||
if (image->gif.width > 4096 || image->gif.height > 4096) {
|
||||
blog(LOG_WARNING, "Bad texture dimensions (%dx%d) in '%s'", image->gif.width, image->gif.height, path);
|
||||
nsgif_data_complete(image->internal->gif);
|
||||
|
||||
image->internal->gif_info = nsgif_get_info(image->internal->gif);
|
||||
|
||||
if (image->internal->gif_info->width > 4096 || image->internal->gif_info->height > 4096) {
|
||||
blog(LOG_WARNING, "Bad texture dimensions (%dx%d) in '%s'", image->internal->gif_info->width,
|
||||
image->internal->gif_info->height, path);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
max_size = (uint64_t)image->gif.width * (uint64_t)image->gif.height * (uint64_t)image->gif.frame_count * 4LLU;
|
||||
max_size = (uint64_t)image->internal->gif_info->width * (uint64_t)image->internal->gif_info->height *
|
||||
(uint64_t)image->internal->gif_info->frame_count * 4LLU;
|
||||
|
||||
if ((uint64_t)get_full_decoded_gif_size(image) != max_size) {
|
||||
blog(LOG_WARNING, "Gif '%s' overflowed maximum pointer size", path);
|
||||
goto fail;
|
||||
}
|
||||
|
||||
image->is_animated_gif = (image->gif.frame_count > 1 && result >= 0);
|
||||
image->is_animated_gif = (image->internal->gif_info->frame_count > 1 && result >= 0);
|
||||
if (image->is_animated_gif) {
|
||||
gif_decode_frame(&image->gif, 0);
|
||||
nsgif_frame_decode(image->internal->gif, 0, &bitmap);
|
||||
|
||||
image->animation_frame_cache = alloc_mem(image, mem_usage, image->gif.frame_count * sizeof(uint8_t *));
|
||||
image->animation_frame_data = alloc_mem(image, mem_usage, get_full_decoded_gif_size(image));
|
||||
image->internal->animation_frame_cache =
|
||||
alloc_mem(image, mem_usage, image->internal->gif_info->frame_count * sizeof(uint8_t *));
|
||||
image->internal->animation_frame_data = alloc_mem(image, mem_usage, get_full_decoded_gif_size(image));
|
||||
|
||||
for (unsigned int i = 0; i < image->gif.frame_count; i++) {
|
||||
if (gif_decode_frame(&image->gif, i) != GIF_OK)
|
||||
blog(LOG_WARNING,
|
||||
"Couldn't decode frame %u "
|
||||
"of '%s'",
|
||||
i, path);
|
||||
for (unsigned int i = 0; i < image->internal->gif_info->frame_count; i++) {
|
||||
if (nsgif_frame_decode(image->internal->gif, i, &bitmap) != NSGIF_OK)
|
||||
blog(LOG_WARNING, "Couldn't decode frame %u of '%s'", i, path);
|
||||
}
|
||||
|
||||
gif_decode_frame(&image->gif, 0);
|
||||
nsgif_frame_decode(image->internal->gif, 0, &bitmap);
|
||||
|
||||
image->cx = (uint32_t)image->gif.width;
|
||||
image->cy = (uint32_t)image->gif.height;
|
||||
image->internal->gif_frame_image = (const uint8_t *)bitmap;
|
||||
|
||||
image->cx = (uint32_t)image->internal->gif_info->width;
|
||||
image->cy = (uint32_t)image->internal->gif_info->height;
|
||||
image->format = GS_RGBA;
|
||||
|
||||
if (mem_usage) {
|
||||
@@ -154,14 +177,14 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path, uint64_t
|
||||
}
|
||||
|
||||
if (alpha_mode == GS_IMAGE_ALPHA_PREMULTIPLY_SRGB) {
|
||||
gs_premultiply_xyza_srgb_loop(image->gif.frame_image, (size_t)image->cx * image->cy);
|
||||
gs_premultiply_xyza_srgb_loop((uint8_t *)bitmap, (size_t)image->cx * image->cy);
|
||||
} else if (alpha_mode == GS_IMAGE_ALPHA_PREMULTIPLY) {
|
||||
gs_premultiply_xyza_loop(image->gif.frame_image, (size_t)image->cx * image->cy);
|
||||
gs_premultiply_xyza_loop((uint8_t *)bitmap, (size_t)image->cx * image->cy);
|
||||
}
|
||||
} else {
|
||||
gif_finalise(&image->gif);
|
||||
bfree(image->gif_data);
|
||||
image->gif_data = NULL;
|
||||
nsgif_destroy(image->internal->gif);
|
||||
bfree(image->internal->gif_data);
|
||||
image->internal->gif_data = NULL;
|
||||
is_animated_gif = false;
|
||||
goto not_animated;
|
||||
}
|
||||
@@ -170,7 +193,8 @@ static bool init_animated_gif(gs_image_file_t *image, const char *path, uint64_t
|
||||
|
||||
fail:
|
||||
if (!image->loaded)
|
||||
gs_image_file_free(image);
|
||||
gs_image_file_ex_free(image);
|
||||
|
||||
not_animated:
|
||||
if (file)
|
||||
fclose(file);
|
||||
@@ -178,8 +202,8 @@ not_animated:
|
||||
return is_animated_gif;
|
||||
}
|
||||
|
||||
static void gs_image_file_init_internal(gs_image_file_t *image, const char *file, uint64_t *mem_usage,
|
||||
enum gs_color_space *space, enum gs_image_alpha_mode alpha_mode)
|
||||
static void gs_image_file_ex_init_internal(gs_image_file_ex_t *image, const char *file, uint64_t *mem_usage,
|
||||
enum gs_color_space *space, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
size_t len;
|
||||
|
||||
@@ -191,6 +215,8 @@ static void gs_image_file_init_internal(gs_image_file_t *image, const char *file
|
||||
if (!file)
|
||||
return;
|
||||
|
||||
image->internal = bzalloc(sizeof(struct gs_image_file_internal));
|
||||
|
||||
len = strlen(file);
|
||||
|
||||
if (len > 4 && astrcmpi(file + len - 4, ".gif") == 0) {
|
||||
@@ -209,64 +235,50 @@ static void gs_image_file_init_internal(gs_image_file_t *image, const char *file
|
||||
image->loaded = !!image->texture_data;
|
||||
if (!image->loaded) {
|
||||
blog(LOG_WARNING, "Failed to load file '%s'", file);
|
||||
gs_image_file_free(image);
|
||||
gs_image_file_ex_free(image);
|
||||
}
|
||||
}
|
||||
|
||||
void gs_image_file_init(gs_image_file_t *image, const char *file)
|
||||
void gs_image_file_ex_init(gs_image_file_ex_t *image, const char *file, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
enum gs_color_space unused;
|
||||
gs_image_file_init_internal(image, file, NULL, &unused, GS_IMAGE_ALPHA_STRAIGHT);
|
||||
gs_image_file_ex_init_internal(image, file, NULL, &unused, alpha_mode);
|
||||
}
|
||||
|
||||
void gs_image_file_free(gs_image_file_t *image)
|
||||
void gs_image_file_ex_free(gs_image_file_ex_t *image)
|
||||
{
|
||||
if (!image)
|
||||
return;
|
||||
|
||||
if (image->loaded) {
|
||||
if (image->is_animated_gif) {
|
||||
gif_finalise(&image->gif);
|
||||
bfree(image->animation_frame_cache);
|
||||
bfree(image->animation_frame_data);
|
||||
if (image->is_animated_gif && image->internal) {
|
||||
nsgif_destroy(image->internal->gif);
|
||||
bfree(image->internal->animation_frame_cache);
|
||||
bfree(image->internal->animation_frame_data);
|
||||
}
|
||||
|
||||
gs_texture_destroy(image->texture);
|
||||
}
|
||||
|
||||
bfree(image->texture_data);
|
||||
bfree(image->gif_data);
|
||||
if (image->internal) {
|
||||
bfree(image->internal->gif_data);
|
||||
bfree(image->internal);
|
||||
}
|
||||
memset(image, 0, sizeof(*image));
|
||||
}
|
||||
|
||||
void gs_image_file2_init(gs_image_file2_t *if2, const char *file)
|
||||
{
|
||||
enum gs_color_space unused;
|
||||
gs_image_file_init_internal(&if2->image, file, &if2->mem_usage, &unused, GS_IMAGE_ALPHA_STRAIGHT);
|
||||
}
|
||||
|
||||
void gs_image_file3_init(gs_image_file3_t *if3, const char *file, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
enum gs_color_space unused;
|
||||
gs_image_file_init_internal(&if3->image2.image, file, &if3->image2.mem_usage, &unused, alpha_mode);
|
||||
if3->alpha_mode = alpha_mode;
|
||||
}
|
||||
|
||||
void gs_image_file4_init(gs_image_file4_t *if4, const char *file, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
gs_image_file_init_internal(&if4->image3.image2.image, file, &if4->image3.image2.mem_usage, &if4->space,
|
||||
alpha_mode);
|
||||
if4->image3.alpha_mode = alpha_mode;
|
||||
}
|
||||
|
||||
void gs_image_file_init_texture(gs_image_file_t *image)
|
||||
void gs_image_file_ex_init_texture(gs_image_file_ex_t *image)
|
||||
{
|
||||
if (!image->loaded)
|
||||
return;
|
||||
|
||||
if (!image->internal)
|
||||
image->internal = bzalloc(sizeof(struct gs_image_file_internal));
|
||||
|
||||
if (image->is_animated_gif) {
|
||||
image->texture = gs_texture_create(image->cx, image->cy, image->format, 1,
|
||||
(const uint8_t **)&image->gif.frame_image, GS_DYNAMIC);
|
||||
(const uint8_t **)&image->internal->gif_frame_image, GS_DYNAMIC);
|
||||
|
||||
} else {
|
||||
image->texture = gs_texture_create(image->cx, image->cy, image->format, 1,
|
||||
@@ -276,15 +288,17 @@ void gs_image_file_init_texture(gs_image_file_t *image)
|
||||
}
|
||||
}
|
||||
|
||||
static inline uint64_t get_time(gs_image_file_t *image, int i)
|
||||
static inline uint64_t get_time(gs_image_file_ex_t *image, int i)
|
||||
{
|
||||
uint64_t val = (uint64_t)image->gif.frames[i].frame_delay * 10000000ULL;
|
||||
const nsgif_frame_info_t *gif_frame_info = nsgif_get_frame_info(image->internal->gif, i);
|
||||
uint64_t val = (uint64_t)gif_frame_info->delay * 10000000ULL;
|
||||
if (!val)
|
||||
val = 100000000;
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static inline int calculate_new_frame(gs_image_file_t *image, uint64_t elapsed_time_ns, int loops)
|
||||
static inline int calculate_new_frame(gs_image_file_ex_t *image, uint64_t elapsed_time_ns, int loops)
|
||||
{
|
||||
int new_frame = image->cur_frame;
|
||||
|
||||
@@ -295,7 +309,7 @@ static inline int calculate_new_frame(gs_image_file_t *image, uint64_t elapsed_t
|
||||
break;
|
||||
|
||||
image->cur_time -= t;
|
||||
if ((unsigned int)++new_frame == image->gif.frame_count) {
|
||||
if ((unsigned int)++new_frame == image->internal->gif_info->frame_count) {
|
||||
if (!loops || ++image->cur_loop < loops) {
|
||||
new_frame = 0;
|
||||
} else if (image->cur_loop == loops) {
|
||||
@@ -308,50 +322,55 @@ static inline int calculate_new_frame(gs_image_file_t *image, uint64_t elapsed_t
|
||||
return new_frame;
|
||||
}
|
||||
|
||||
static void decode_new_frame(gs_image_file_t *image, int new_frame, enum gs_image_alpha_mode alpha_mode)
|
||||
static void decode_new_frame(gs_image_file_ex_t *image, int new_frame, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
if (!image->animation_frame_cache[new_frame]) {
|
||||
if (!image->internal->animation_frame_cache[new_frame]) {
|
||||
int last_frame;
|
||||
nsgif_bitmap_t *bitmap;
|
||||
|
||||
/* if looped, decode frame 0 */
|
||||
last_frame = (new_frame < image->last_decoded_frame) ? 0 : image->last_decoded_frame + 1;
|
||||
last_frame =
|
||||
(new_frame < image->internal->last_decoded_frame) ? 0 : image->internal->last_decoded_frame + 1;
|
||||
|
||||
/* decode missed frames */
|
||||
for (int i = last_frame; i < new_frame; i++) {
|
||||
if (gif_decode_frame(&image->gif, i) != GIF_OK)
|
||||
if (nsgif_frame_decode(image->internal->gif, i, &bitmap) != NSGIF_OK)
|
||||
return;
|
||||
}
|
||||
|
||||
/* decode actual desired frame */
|
||||
if (gif_decode_frame(&image->gif, new_frame) == GIF_OK) {
|
||||
const size_t area = (size_t)image->gif.width * image->gif.height;
|
||||
if (nsgif_frame_decode(image->internal->gif, new_frame, &bitmap) == NSGIF_OK) {
|
||||
const size_t area =
|
||||
(size_t)image->internal->gif_info->width * image->internal->gif_info->height;
|
||||
size_t pos = new_frame * area * 4;
|
||||
image->animation_frame_cache[new_frame] = image->animation_frame_data + pos;
|
||||
image->internal->animation_frame_cache[new_frame] = image->internal->animation_frame_data + pos;
|
||||
|
||||
if (alpha_mode == GS_IMAGE_ALPHA_PREMULTIPLY_SRGB) {
|
||||
gs_premultiply_xyza_srgb_loop(image->gif.frame_image, area);
|
||||
gs_premultiply_xyza_srgb_loop((uint8_t *)bitmap, area);
|
||||
} else if (alpha_mode == GS_IMAGE_ALPHA_PREMULTIPLY) {
|
||||
gs_premultiply_xyza_loop(image->gif.frame_image, area);
|
||||
gs_premultiply_xyza_loop((uint8_t *)bitmap, area);
|
||||
}
|
||||
|
||||
memcpy(image->animation_frame_cache[new_frame], image->gif.frame_image, area * 4);
|
||||
image->internal->gif_frame_image = (const uint8_t *)bitmap;
|
||||
|
||||
image->last_decoded_frame = new_frame;
|
||||
memcpy(image->internal->animation_frame_cache[new_frame], (const uint8_t *)bitmap, area * 4);
|
||||
|
||||
image->internal->last_decoded_frame = new_frame;
|
||||
}
|
||||
}
|
||||
|
||||
image->cur_frame = new_frame;
|
||||
}
|
||||
|
||||
static bool gs_image_file_tick_internal(gs_image_file_t *image, uint64_t elapsed_time_ns,
|
||||
enum gs_image_alpha_mode alpha_mode)
|
||||
static bool gs_image_file_ex_tick_internal(gs_image_file_ex_t *image, uint64_t elapsed_time_ns,
|
||||
enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
int loops;
|
||||
|
||||
if (!image->is_animated_gif || !image->loaded)
|
||||
if (!image->is_animated_gif || !image->loaded || !image->internal)
|
||||
return false;
|
||||
|
||||
loops = image->gif.loop_count;
|
||||
loops = image->internal->gif_info->loop_max;
|
||||
if (loops >= 0xFFFF)
|
||||
loops = 0;
|
||||
|
||||
@@ -367,54 +386,24 @@ static bool gs_image_file_tick_internal(gs_image_file_t *image, uint64_t elapsed
|
||||
return false;
|
||||
}
|
||||
|
||||
bool gs_image_file_tick(gs_image_file_t *image, uint64_t elapsed_time_ns)
|
||||
bool gs_image_file_ex_tick(gs_image_file_ex_t *image, uint64_t elapsed_time_ns)
|
||||
{
|
||||
return gs_image_file_tick_internal(image, elapsed_time_ns, false);
|
||||
return gs_image_file_ex_tick_internal(image, elapsed_time_ns, false);
|
||||
}
|
||||
|
||||
bool gs_image_file2_tick(gs_image_file2_t *if2, uint64_t elapsed_time_ns)
|
||||
static void gs_image_file_ex_update_texture_internal(gs_image_file_ex_t *image, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
return gs_image_file_tick_internal(&if2->image, elapsed_time_ns, false);
|
||||
}
|
||||
|
||||
bool gs_image_file3_tick(gs_image_file3_t *if3, uint64_t elapsed_time_ns)
|
||||
{
|
||||
return gs_image_file_tick_internal(&if3->image2.image, elapsed_time_ns, if3->alpha_mode);
|
||||
}
|
||||
|
||||
bool gs_image_file4_tick(gs_image_file4_t *if4, uint64_t elapsed_time_ns)
|
||||
{
|
||||
return gs_image_file_tick_internal(&if4->image3.image2.image, elapsed_time_ns, if4->image3.alpha_mode);
|
||||
}
|
||||
|
||||
static void gs_image_file_update_texture_internal(gs_image_file_t *image, enum gs_image_alpha_mode alpha_mode)
|
||||
{
|
||||
if (!image->is_animated_gif || !image->loaded)
|
||||
if (!image->is_animated_gif || !image->loaded || !image->internal)
|
||||
return;
|
||||
|
||||
if (!image->animation_frame_cache[image->cur_frame])
|
||||
if (!image->internal->animation_frame_cache[image->cur_frame])
|
||||
decode_new_frame(image, image->cur_frame, alpha_mode);
|
||||
|
||||
gs_texture_set_image(image->texture, image->animation_frame_cache[image->cur_frame], image->gif.width * 4,
|
||||
false);
|
||||
gs_texture_set_image(image->texture, image->internal->animation_frame_cache[image->cur_frame],
|
||||
image->internal->gif_info->width * 4, false);
|
||||
}
|
||||
|
||||
void gs_image_file_update_texture(gs_image_file_t *image)
|
||||
void gs_image_file_ex_update_texture(gs_image_file_ex_t *image)
|
||||
{
|
||||
gs_image_file_update_texture_internal(image, false);
|
||||
}
|
||||
|
||||
void gs_image_file2_update_texture(gs_image_file2_t *if2)
|
||||
{
|
||||
gs_image_file_update_texture_internal(&if2->image, false);
|
||||
}
|
||||
|
||||
void gs_image_file3_update_texture(gs_image_file3_t *if3)
|
||||
{
|
||||
gs_image_file_update_texture_internal(&if3->image2.image, if3->alpha_mode);
|
||||
}
|
||||
|
||||
void gs_image_file4_update_texture(gs_image_file4_t *if4)
|
||||
{
|
||||
gs_image_file_update_texture_internal(&if4->image3.image2.image, if4->image3.alpha_mode);
|
||||
gs_image_file_ex_update_texture_internal(image, false);
|
||||
}
|
||||
|
||||
@@ -18,13 +18,12 @@
|
||||
#pragma once
|
||||
|
||||
#include "graphics.h"
|
||||
#include "libnsgif/libnsgif.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct gs_image_file {
|
||||
struct gs_image_file_ex {
|
||||
gs_texture_t *texture;
|
||||
enum gs_color_format format;
|
||||
uint32_t cx;
|
||||
@@ -33,91 +32,26 @@ struct gs_image_file {
|
||||
bool frame_updated;
|
||||
bool loaded;
|
||||
|
||||
gif_animation gif;
|
||||
uint8_t *gif_data;
|
||||
uint8_t **animation_frame_cache;
|
||||
uint8_t *animation_frame_data;
|
||||
struct gs_image_file_internal *internal;
|
||||
|
||||
uint64_t cur_time;
|
||||
int cur_frame;
|
||||
int cur_loop;
|
||||
int last_decoded_frame;
|
||||
|
||||
uint8_t *texture_data;
|
||||
gif_bitmap_callback_vt bitmap_callbacks;
|
||||
};
|
||||
|
||||
struct gs_image_file2 {
|
||||
struct gs_image_file image;
|
||||
uint64_t mem_usage;
|
||||
};
|
||||
|
||||
struct gs_image_file3 {
|
||||
struct gs_image_file2 image2;
|
||||
enum gs_image_alpha_mode alpha_mode;
|
||||
};
|
||||
|
||||
struct gs_image_file4 {
|
||||
struct gs_image_file3 image3;
|
||||
enum gs_color_space space;
|
||||
};
|
||||
|
||||
typedef struct gs_image_file gs_image_file_t;
|
||||
typedef struct gs_image_file2 gs_image_file2_t;
|
||||
typedef struct gs_image_file3 gs_image_file3_t;
|
||||
typedef struct gs_image_file4 gs_image_file4_t;
|
||||
typedef struct gs_image_file_ex gs_image_file_ex_t;
|
||||
|
||||
EXPORT void gs_image_file_init(gs_image_file_t *image, const char *file);
|
||||
EXPORT void gs_image_file_free(gs_image_file_t *image);
|
||||
EXPORT void gs_image_file_ex_init(gs_image_file_ex_t *image, const char *file, enum gs_image_alpha_mode alpha_mode);
|
||||
EXPORT void gs_image_file_ex_free(gs_image_file_ex_t *image);
|
||||
|
||||
EXPORT void gs_image_file_init_texture(gs_image_file_t *image);
|
||||
EXPORT bool gs_image_file_tick(gs_image_file_t *image, uint64_t elapsed_time_ns);
|
||||
EXPORT void gs_image_file_update_texture(gs_image_file_t *image);
|
||||
|
||||
EXPORT void gs_image_file2_init(gs_image_file2_t *if2, const char *file);
|
||||
|
||||
EXPORT bool gs_image_file2_tick(gs_image_file2_t *if2, uint64_t elapsed_time_ns);
|
||||
EXPORT void gs_image_file2_update_texture(gs_image_file2_t *if2);
|
||||
|
||||
EXPORT void gs_image_file3_init(gs_image_file3_t *if3, const char *file, enum gs_image_alpha_mode alpha_mode);
|
||||
|
||||
EXPORT bool gs_image_file3_tick(gs_image_file3_t *if3, uint64_t elapsed_time_ns);
|
||||
EXPORT void gs_image_file3_update_texture(gs_image_file3_t *if3);
|
||||
|
||||
EXPORT void gs_image_file4_init(gs_image_file4_t *if4, const char *file, enum gs_image_alpha_mode alpha_mode);
|
||||
|
||||
EXPORT bool gs_image_file4_tick(gs_image_file4_t *if4, uint64_t elapsed_time_ns);
|
||||
EXPORT void gs_image_file4_update_texture(gs_image_file4_t *if4);
|
||||
|
||||
static inline void gs_image_file2_free(gs_image_file2_t *if2)
|
||||
{
|
||||
gs_image_file_free(&if2->image);
|
||||
if2->mem_usage = 0;
|
||||
}
|
||||
|
||||
static inline void gs_image_file2_init_texture(gs_image_file2_t *if2)
|
||||
{
|
||||
gs_image_file_init_texture(&if2->image);
|
||||
}
|
||||
|
||||
static inline void gs_image_file3_free(gs_image_file3_t *if3)
|
||||
{
|
||||
gs_image_file2_free(&if3->image2);
|
||||
}
|
||||
|
||||
static inline void gs_image_file3_init_texture(gs_image_file3_t *if3)
|
||||
{
|
||||
gs_image_file2_init_texture(&if3->image2);
|
||||
}
|
||||
|
||||
static inline void gs_image_file4_free(gs_image_file4_t *if4)
|
||||
{
|
||||
gs_image_file3_free(&if4->image3);
|
||||
}
|
||||
|
||||
static inline void gs_image_file4_init_texture(gs_image_file4_t *if4)
|
||||
{
|
||||
gs_image_file3_init_texture(&if4->image3);
|
||||
}
|
||||
EXPORT void gs_image_file_ex_init_texture(gs_image_file_ex_t *image);
|
||||
EXPORT bool gs_image_file_ex_tick(gs_image_file_ex_t *image, uint64_t elapsed_time_ns);
|
||||
EXPORT void gs_image_file_ex_update_texture(gs_image_file_ex_t *image);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,142 +0,0 @@
|
||||
/*
|
||||
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/** \file
|
||||
* Progressive animated GIF file decoding (interface).
|
||||
*/
|
||||
|
||||
#ifndef _LIBNSGIF_H_
|
||||
#define _LIBNSGIF_H_
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
/* Error return values
|
||||
*/
|
||||
typedef enum {
|
||||
GIF_WORKING = 1,
|
||||
GIF_OK = 0,
|
||||
GIF_INSUFFICIENT_FRAME_DATA = -1,
|
||||
GIF_FRAME_DATA_ERROR = -2,
|
||||
GIF_INSUFFICIENT_DATA = -3,
|
||||
GIF_DATA_ERROR = -4,
|
||||
GIF_INSUFFICIENT_MEMORY = -5,
|
||||
GIF_FRAME_NO_DISPLAY = -6,
|
||||
GIF_END_OF_FRAME = -7
|
||||
} gif_result;
|
||||
|
||||
/* Maximum LZW bits available
|
||||
*/
|
||||
#define GIF_MAX_LZW 12
|
||||
|
||||
/* The GIF frame data
|
||||
*/
|
||||
typedef struct gif_frame {
|
||||
bool display; /**< whether the frame should be displayed/animated */
|
||||
unsigned int frame_delay; /**< delay (in 100th second intervals) before animating the frame */
|
||||
/** Internal members are listed below
|
||||
*/
|
||||
unsigned int frame_pointer; /**< offset (in bytes) to the GIF frame data */
|
||||
bool virgin; /**< whether the frame has previously been used */
|
||||
bool opaque; /**< whether the frame is totally opaque */
|
||||
bool redraw_required; /**< whether a forcable screen redraw is required */
|
||||
unsigned char disposal_method; /**< how the previous frame should be disposed; affects plotting */
|
||||
bool transparency; /**< whether we acknowledge transparency */
|
||||
unsigned char transparency_index; /**< the index designating a transparent pixel */
|
||||
unsigned int redraw_x; /**< x co-ordinate of redraw rectangle */
|
||||
unsigned int redraw_y; /**< y co-ordinate of redraw rectangle */
|
||||
unsigned int redraw_width; /**< width of redraw rectangle */
|
||||
unsigned int redraw_height; /**< height of redraw rectangle */
|
||||
} gif_frame;
|
||||
|
||||
/* API for Bitmap callbacks
|
||||
*/
|
||||
typedef void* (*gif_bitmap_cb_create)(int width, int height);
|
||||
typedef void (*gif_bitmap_cb_destroy)(void *bitmap);
|
||||
typedef unsigned char* (*gif_bitmap_cb_get_buffer)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_set_opaque)(void *bitmap, bool opaque);
|
||||
typedef bool (*gif_bitmap_cb_test_opaque)(void *bitmap);
|
||||
typedef void (*gif_bitmap_cb_modified)(void *bitmap);
|
||||
|
||||
/* The Bitmap callbacks function table
|
||||
*/
|
||||
typedef struct gif_bitmap_callback_vt {
|
||||
gif_bitmap_cb_create bitmap_create; /**< Create a bitmap. */
|
||||
gif_bitmap_cb_destroy bitmap_destroy; /**< Free a bitmap. */
|
||||
gif_bitmap_cb_get_buffer bitmap_get_buffer; /**< Return a pointer to the pixel data in a bitmap. */
|
||||
/** Members below are optional
|
||||
*/
|
||||
gif_bitmap_cb_set_opaque bitmap_set_opaque; /**< Sets whether a bitmap should be plotted opaque. */
|
||||
gif_bitmap_cb_test_opaque bitmap_test_opaque; /**< Tests whether a bitmap has an opaque alpha channel. */
|
||||
gif_bitmap_cb_modified bitmap_modified; /**< The bitmap image has changed, so flush any persistent cache. */
|
||||
} gif_bitmap_callback_vt;
|
||||
|
||||
/* The GIF animation data
|
||||
*/
|
||||
typedef struct gif_animation {
|
||||
gif_bitmap_callback_vt bitmap_callbacks; /**< callbacks for bitmap functions */
|
||||
unsigned char *gif_data; /**< pointer to GIF data */
|
||||
unsigned int width; /**< width of GIF (may increase during decoding) */
|
||||
unsigned int height; /**< height of GIF (may increase during decoding) */
|
||||
unsigned int frame_count; /**< number of frames decoded */
|
||||
unsigned int frame_count_partial; /**< number of frames partially decoded */
|
||||
gif_frame *frames; /**< decoded frames */
|
||||
int decoded_frame; /**< current frame decoded to bitmap */
|
||||
void *frame_image; /**< currently decoded image; stored as bitmap from bitmap_create callback */
|
||||
int loop_count; /**< number of times to loop animation */
|
||||
gif_result current_error; /**< current error type, or 0 for none*/
|
||||
/** Internal members are listed below
|
||||
*/
|
||||
unsigned int buffer_position; /**< current index into GIF data */
|
||||
unsigned int buffer_size; /**< total number of bytes of GIF data available */
|
||||
unsigned int frame_holders; /**< current number of frame holders */
|
||||
unsigned int background_index; /**< index in the colour table for the background colour */
|
||||
unsigned int aspect_ratio; /**< image aspect ratio (ignored) */
|
||||
unsigned int colour_table_size; /**< size of colour table (in entries) */
|
||||
bool global_colours; /**< whether the GIF has a global colour table */
|
||||
unsigned int *global_colour_table; /**< global colour table */
|
||||
unsigned int *local_colour_table; /**< local colour table */
|
||||
|
||||
|
||||
/* General LZW values. They are NO LONGER shared for all GIFs being decoded BECAUSE
|
||||
THAT IS A TERRIBLE IDEA TO SAVE 10Kb or so per GIF.
|
||||
*/
|
||||
unsigned char buf[4];
|
||||
unsigned char *direct;
|
||||
|
||||
int table[2][(1 << GIF_MAX_LZW)];
|
||||
unsigned char stack[(1 << GIF_MAX_LZW) * 2];
|
||||
unsigned char *stack_pointer;
|
||||
int code_size, set_code_size;
|
||||
int max_code, max_code_size;
|
||||
int clear_code, end_code;
|
||||
int curbit, lastbit, last_byte;
|
||||
int firstcode, oldcode;
|
||||
bool zero_data_block;
|
||||
bool get_done;
|
||||
|
||||
/* Whether to clear the decoded image rather than plot
|
||||
*/
|
||||
bool clear_image;
|
||||
} gif_animation;
|
||||
|
||||
void gif_create(gif_animation *gif, gif_bitmap_callback_vt *bitmap_callbacks);
|
||||
gif_result gif_initialise(gif_animation *gif, size_t size, unsigned char *data);
|
||||
gif_result gif_decode_frame(gif_animation *gif, unsigned int frame);
|
||||
void gif_finalise(gif_animation *gif);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
};
|
||||
#endif
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,613 @@
|
||||
/*
|
||||
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
|
||||
* Licensed under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
|
||||
* Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*/
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include "lzw.h"
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief LZW decompression (implementation)
|
||||
*
|
||||
* Decoder for GIF LZW data.
|
||||
*/
|
||||
|
||||
/** Maximum number of lzw table entries. */
|
||||
#define LZW_TABLE_ENTRY_MAX (1u << LZW_CODE_MAX)
|
||||
|
||||
/**
|
||||
* Context for reading LZW data.
|
||||
*
|
||||
* LZW data is split over multiple sub-blocks. Each sub-block has a
|
||||
* byte at the start, which says the sub-block size, and then the data.
|
||||
* Zero-size sub-blocks have no data, and the biggest sub-block size is
|
||||
* 255, which means there are 255 bytes of data following the sub-block
|
||||
* size entry.
|
||||
*
|
||||
* Note that an individual LZW code can be split over up to three sub-blocks.
|
||||
*/
|
||||
struct lzw_read_ctx {
|
||||
const uint8_t *restrict data; /**< Pointer to start of input data */
|
||||
size_t data_len; /**< Input data length */
|
||||
size_t data_sb_next; /**< Offset to sub-block size */
|
||||
|
||||
const uint8_t *sb_data; /**< Pointer to current sub-block in data */
|
||||
size_t sb_bit; /**< Current bit offset in sub-block */
|
||||
uint32_t sb_bit_count; /**< Bit count in sub-block */
|
||||
};
|
||||
|
||||
/**
|
||||
* LZW table entry.
|
||||
*
|
||||
* Records in the table are composed of 1 or more entries.
|
||||
* Entries refer to the entry they extend which can be followed to compose
|
||||
* the complete record. To compose the record in reverse order, take
|
||||
* the `value` from each entry, and move to the entry it extends.
|
||||
* If the extended entries index is < the current clear_code, then it
|
||||
* is the last entry in the record.
|
||||
*/
|
||||
struct lzw_table_entry {
|
||||
uint8_t value; /**< Last value for record ending at entry. */
|
||||
uint8_t first; /**< First value in entry's entire record. */
|
||||
uint16_t count; /**< Count of values in this entry's record. */
|
||||
uint16_t extends; /**< Offset in table to previous entry. */
|
||||
};
|
||||
|
||||
/**
|
||||
* LZW decompression context.
|
||||
*/
|
||||
struct lzw_ctx {
|
||||
struct lzw_read_ctx input; /**< Input reading context */
|
||||
|
||||
uint16_t prev_code; /**< Code read from input previously. */
|
||||
uint16_t prev_code_first; /**< First value of previous code. */
|
||||
uint16_t prev_code_count; /**< Total values for previous code. */
|
||||
|
||||
uint8_t initial_code_size; /**< Starting LZW code size. */
|
||||
|
||||
uint8_t code_size; /**< Current LZW code size. */
|
||||
uint16_t code_max; /**< Max code value for current code size. */
|
||||
|
||||
uint16_t clear_code; /**< Special Clear code value */
|
||||
uint16_t eoi_code; /**< Special End of Information code value */
|
||||
|
||||
uint16_t table_size; /**< Next position in table to fill. */
|
||||
|
||||
uint16_t output_code; /**< Code that has been partially output. */
|
||||
uint16_t output_left; /**< Number of values left for output_code. */
|
||||
|
||||
bool has_transparency; /**< Whether the image is opaque. */
|
||||
uint8_t transparency_idx; /**< Index representing transparency. */
|
||||
const uint32_t *restrict colour_map; /**< Index to colour mapping. */
|
||||
|
||||
/** LZW code table. Generated during decode. */
|
||||
struct lzw_table_entry table[LZW_TABLE_ENTRY_MAX];
|
||||
|
||||
/** Output value stack. */
|
||||
uint8_t stack_base[LZW_TABLE_ENTRY_MAX];
|
||||
};
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_context_create(struct lzw_ctx **ctx)
|
||||
{
|
||||
struct lzw_ctx *c = malloc(sizeof(*c));
|
||||
if (c == NULL) {
|
||||
return LZW_NO_MEM;
|
||||
}
|
||||
|
||||
*ctx = c;
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
void lzw_context_destroy(struct lzw_ctx *ctx)
|
||||
{
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Advance the context to the next sub-block in the input data.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated on success.
|
||||
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
|
||||
*/
|
||||
static lzw_result lzw__block_advance(struct lzw_read_ctx *restrict ctx)
|
||||
{
|
||||
size_t block_size;
|
||||
size_t next_block_pos = ctx->data_sb_next;
|
||||
const uint8_t *data_next = ctx->data + next_block_pos;
|
||||
|
||||
if (next_block_pos >= ctx->data_len) {
|
||||
return LZW_NO_DATA;
|
||||
}
|
||||
|
||||
block_size = *data_next;
|
||||
|
||||
if ((next_block_pos + block_size) >= ctx->data_len) {
|
||||
return LZW_NO_DATA;
|
||||
}
|
||||
|
||||
ctx->sb_bit = 0;
|
||||
ctx->sb_bit_count = block_size * 8;
|
||||
|
||||
if (block_size == 0) {
|
||||
ctx->data_sb_next += 1;
|
||||
return LZW_OK_EOD;
|
||||
}
|
||||
|
||||
ctx->sb_data = data_next + 1;
|
||||
ctx->data_sb_next += block_size + 1;
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the next LZW code of given size from the raw input data.
|
||||
*
|
||||
* Reads codes from the input data stream coping with GIF data sub-blocks.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] code_size Size of LZW code to get from data.
|
||||
* \param[out] code_out Returns an LZW code on success.
|
||||
* \return LZW_OK or LZW_OK_EOD on success, appropriate error otherwise.
|
||||
*/
|
||||
static inline lzw_result lzw__read_code(
|
||||
struct lzw_read_ctx *restrict ctx,
|
||||
uint16_t code_size,
|
||||
uint16_t *restrict code_out)
|
||||
{
|
||||
uint32_t code = 0;
|
||||
uint32_t current_bit = ctx->sb_bit & 0x7;
|
||||
|
||||
if (ctx->sb_bit + 24 <= ctx->sb_bit_count) {
|
||||
/* Fast path: read three bytes from this sub-block */
|
||||
const uint8_t *data = ctx->sb_data + (ctx->sb_bit >> 3);
|
||||
code |= *data++ << 0;
|
||||
code |= *data++ << 8;
|
||||
code |= *data << 16;
|
||||
ctx->sb_bit += code_size;
|
||||
} else {
|
||||
/* Slow path: code spans sub-blocks */
|
||||
uint8_t byte_advance = (current_bit + code_size) >> 3;
|
||||
uint8_t byte = 0;
|
||||
uint8_t bits_remaining_0 = (code_size < (8u - current_bit)) ?
|
||||
code_size : (8u - current_bit);
|
||||
uint8_t bits_remaining_1 = code_size - bits_remaining_0;
|
||||
uint8_t bits_used[3] = {
|
||||
[0] = bits_remaining_0,
|
||||
[1] = bits_remaining_1 < 8 ? bits_remaining_1 : 8,
|
||||
[2] = bits_remaining_1 - 8,
|
||||
};
|
||||
|
||||
assert(byte_advance <= 2);
|
||||
|
||||
while (true) {
|
||||
const uint8_t *data = ctx->sb_data;
|
||||
lzw_result res;
|
||||
|
||||
/* Get any data from end of this sub-block */
|
||||
while (byte <= byte_advance &&
|
||||
ctx->sb_bit < ctx->sb_bit_count) {
|
||||
code |= data[ctx->sb_bit >> 3] << (byte << 3);
|
||||
ctx->sb_bit += bits_used[byte];
|
||||
byte++;
|
||||
}
|
||||
|
||||
/* Check if we have all we need */
|
||||
if (byte > byte_advance) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* Move to next sub-block */
|
||||
res = lzw__block_advance(ctx);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*code_out = (code >> current_bit) & ((1 << code_size) - 1);
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle clear code.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[out] code_out Returns next code after a clear code.
|
||||
* \return LZW_OK or error code.
|
||||
*/
|
||||
static inline lzw_result lzw__handle_clear(
|
||||
struct lzw_ctx *ctx,
|
||||
uint16_t *code_out)
|
||||
{
|
||||
uint16_t code;
|
||||
|
||||
/* Reset table building context */
|
||||
ctx->code_size = ctx->initial_code_size;
|
||||
ctx->code_max = (1 << ctx->initial_code_size) - 1;
|
||||
ctx->table_size = ctx->eoi_code + 1;
|
||||
|
||||
/* There might be a sequence of clear codes, so process them all */
|
||||
do {
|
||||
lzw_result res = lzw__read_code(&ctx->input,
|
||||
ctx->code_size, &code);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
} while (code == ctx->clear_code);
|
||||
|
||||
/* The initial code must be from the initial table. */
|
||||
if (code > ctx->clear_code) {
|
||||
return LZW_BAD_ICODE;
|
||||
}
|
||||
|
||||
*code_out = code;
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode_init(
|
||||
struct lzw_ctx *ctx,
|
||||
uint8_t minimum_code_size,
|
||||
const uint8_t *input_data,
|
||||
size_t input_length,
|
||||
size_t input_pos)
|
||||
{
|
||||
struct lzw_table_entry *table = ctx->table;
|
||||
lzw_result res;
|
||||
uint16_t code;
|
||||
|
||||
if (minimum_code_size >= LZW_CODE_MAX) {
|
||||
return LZW_BAD_ICODE;
|
||||
}
|
||||
|
||||
/* Initialise the input reading context */
|
||||
ctx->input.data = input_data;
|
||||
ctx->input.data_len = input_length;
|
||||
ctx->input.data_sb_next = input_pos;
|
||||
|
||||
ctx->input.sb_bit = 0;
|
||||
ctx->input.sb_bit_count = 0;
|
||||
|
||||
/* Initialise the table building context */
|
||||
ctx->initial_code_size = minimum_code_size + 1;
|
||||
|
||||
ctx->clear_code = (1 << minimum_code_size) + 0;
|
||||
ctx->eoi_code = (1 << minimum_code_size) + 1;
|
||||
|
||||
ctx->output_left = 0;
|
||||
|
||||
/* Initialise the standard table entries */
|
||||
for (uint16_t i = 0; i < ctx->clear_code; i++) {
|
||||
table[i].first = i;
|
||||
table[i].value = i;
|
||||
table[i].count = 1;
|
||||
}
|
||||
|
||||
res = lzw__handle_clear(ctx, &code);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Store details of this code as "previous code" to the context. */
|
||||
ctx->prev_code_first = ctx->table[code].first;
|
||||
ctx->prev_code_count = ctx->table[code].count;
|
||||
ctx->prev_code = code;
|
||||
|
||||
/* Add code to context for immediate output. */
|
||||
ctx->output_code = code;
|
||||
ctx->output_left = 1;
|
||||
|
||||
ctx->has_transparency = false;
|
||||
ctx->transparency_idx = 0;
|
||||
ctx->colour_map = NULL;
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode_init_map(
|
||||
struct lzw_ctx *ctx,
|
||||
uint8_t minimum_code_size,
|
||||
uint32_t transparency_idx,
|
||||
const uint32_t *colour_table,
|
||||
const uint8_t *input_data,
|
||||
size_t input_length,
|
||||
size_t input_pos)
|
||||
{
|
||||
lzw_result res;
|
||||
|
||||
if (colour_table == NULL) {
|
||||
return LZW_BAD_PARAM;
|
||||
}
|
||||
|
||||
res = lzw_decode_init(ctx, minimum_code_size,
|
||||
input_data, input_length, input_pos);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
ctx->has_transparency = (transparency_idx <= 0xFF);
|
||||
ctx->transparency_idx = transparency_idx;
|
||||
ctx->colour_map = colour_table;
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new table entry.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] code Last value code for new table entry.
|
||||
*/
|
||||
static inline void lzw__table_add_entry(
|
||||
struct lzw_ctx *ctx,
|
||||
uint16_t code)
|
||||
{
|
||||
struct lzw_table_entry *entry = &ctx->table[ctx->table_size];
|
||||
|
||||
entry->value = code;
|
||||
entry->first = ctx->prev_code_first;
|
||||
entry->count = ctx->prev_code_count + 1;
|
||||
entry->extends = ctx->prev_code;
|
||||
|
||||
ctx->table_size++;
|
||||
}
|
||||
|
||||
typedef uint32_t (*lzw_writer_fn)(
|
||||
struct lzw_ctx *ctx,
|
||||
void *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t output_pos,
|
||||
uint16_t code,
|
||||
uint16_t left);
|
||||
|
||||
/**
|
||||
* Get the next LZW code and write its value(s) to output buffer.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] write_fn Function for writing pixels to output.
|
||||
* \param[in] output_data Array to write output values into.
|
||||
* \param[in] output_length Size of output array.
|
||||
* \param[in,out] output_written Number of values written. Updated on exit.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
static inline lzw_result lzw__decode(
|
||||
struct lzw_ctx *ctx,
|
||||
lzw_writer_fn write_fn,
|
||||
void *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t *restrict output_written)
|
||||
{
|
||||
lzw_result res;
|
||||
uint16_t code;
|
||||
|
||||
/* Get a new code from the input */
|
||||
res = lzw__read_code(&ctx->input, ctx->code_size, &code);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Handle the new code */
|
||||
if (code == ctx->eoi_code) {
|
||||
/* Got End of Information code */
|
||||
return LZW_EOI_CODE;
|
||||
|
||||
} else if (code > ctx->table_size) {
|
||||
/* Code is invalid */
|
||||
return LZW_BAD_CODE;
|
||||
|
||||
} else if (code == ctx->clear_code) {
|
||||
res = lzw__handle_clear(ctx, &code);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
|
||||
} else if (ctx->table_size < LZW_TABLE_ENTRY_MAX) {
|
||||
uint16_t size = ctx->table_size;
|
||||
lzw__table_add_entry(ctx, (code < size) ?
|
||||
ctx->table[code].first :
|
||||
ctx->prev_code_first);
|
||||
|
||||
/* Ensure code size is increased, if needed. */
|
||||
if (size == ctx->code_max && ctx->code_size < LZW_CODE_MAX) {
|
||||
ctx->code_size++;
|
||||
ctx->code_max = (1 << ctx->code_size) - 1;
|
||||
}
|
||||
}
|
||||
|
||||
*output_written += write_fn(ctx,
|
||||
output_data, output_length, *output_written,
|
||||
code, ctx->table[code].count);
|
||||
|
||||
/* Store details of this code as "previous code" to the context. */
|
||||
ctx->prev_code_first = ctx->table[code].first;
|
||||
ctx->prev_code_count = ctx->table[code].count;
|
||||
ctx->prev_code = code;
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write values for this code to the output stack.
|
||||
*
|
||||
* If there isn't enough space in the output stack, this function will write
|
||||
* the as many as it can into the output. If `ctx->output_left > 0` after
|
||||
* this call, then there is more data for this code left to output. The code
|
||||
* is stored to the context as `ctx->output_code`.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] output_data Array to write output values into.
|
||||
* \param[in] output_length length Size of output array.
|
||||
* \param[in] output_used Current position in output array.
|
||||
* \param[in] code LZW code to output values for.
|
||||
* \param[in] left Number of values remaining to output for this code.
|
||||
* \return Number of pixel values written.
|
||||
*/
|
||||
static inline uint32_t lzw__write_fn(struct lzw_ctx *ctx,
|
||||
void *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t output_used,
|
||||
uint16_t code,
|
||||
uint16_t left)
|
||||
{
|
||||
uint8_t *restrict output_pos = (uint8_t *)output_data + output_used;
|
||||
const struct lzw_table_entry * const table = ctx->table;
|
||||
uint32_t space = output_length - output_used;
|
||||
uint16_t count = left;
|
||||
|
||||
if (count > space) {
|
||||
left = count - space;
|
||||
count = space;
|
||||
} else {
|
||||
left = 0;
|
||||
}
|
||||
|
||||
ctx->output_code = code;
|
||||
ctx->output_left = left;
|
||||
|
||||
/* Skip over any values we don't have space for. */
|
||||
for (unsigned i = left; i != 0; i--) {
|
||||
const struct lzw_table_entry *entry = table + code;
|
||||
code = entry->extends;
|
||||
}
|
||||
|
||||
output_pos += count;
|
||||
for (unsigned i = count; i != 0; i--) {
|
||||
const struct lzw_table_entry *entry = table + code;
|
||||
*--output_pos = entry->value;
|
||||
code = entry->extends;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode(struct lzw_ctx *ctx,
|
||||
const uint8_t *restrict *const restrict output_data,
|
||||
uint32_t *restrict output_written)
|
||||
{
|
||||
const uint32_t output_length = sizeof(ctx->stack_base);
|
||||
|
||||
*output_written = 0;
|
||||
*output_data = ctx->stack_base;
|
||||
|
||||
if (ctx->output_left != 0) {
|
||||
*output_written += lzw__write_fn(ctx,
|
||||
ctx->stack_base, output_length, *output_written,
|
||||
ctx->output_code, ctx->output_left);
|
||||
}
|
||||
|
||||
while (*output_written != output_length) {
|
||||
lzw_result res = lzw__decode(ctx, lzw__write_fn,
|
||||
ctx->stack_base, output_length, output_written);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Write colour mapped values for this code to the output.
|
||||
*
|
||||
* If there isn't enough space in the output stack, this function will write
|
||||
* the as many as it can into the output. If `ctx->output_left > 0` after
|
||||
* this call, then there is more data for this code left to output. The code
|
||||
* is stored to the context as `ctx->output_code`.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] output_data Array to write output values into.
|
||||
* \param[in] output_length Size of output array.
|
||||
* \param[in] output_used Current position in output array.
|
||||
* \param[in] code LZW code to output values for.
|
||||
* \param[in] left Number of values remaining to output for code.
|
||||
* \return Number of pixel values written.
|
||||
*/
|
||||
static inline uint32_t lzw__map_write_fn(struct lzw_ctx *ctx,
|
||||
void *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t output_used,
|
||||
uint16_t code,
|
||||
uint16_t left)
|
||||
{
|
||||
uint32_t *restrict output_pos = (uint32_t *)output_data + output_used;
|
||||
const struct lzw_table_entry * const table = ctx->table;
|
||||
uint32_t space = output_length - output_used;
|
||||
uint16_t count = left;
|
||||
|
||||
if (count > space) {
|
||||
left = count - space;
|
||||
count = space;
|
||||
} else {
|
||||
left = 0;
|
||||
}
|
||||
|
||||
ctx->output_code = code;
|
||||
ctx->output_left = left;
|
||||
|
||||
for (unsigned i = left; i != 0; i--) {
|
||||
const struct lzw_table_entry *entry = table + code;
|
||||
code = entry->extends;
|
||||
}
|
||||
|
||||
output_pos += count;
|
||||
if (ctx->has_transparency) {
|
||||
for (unsigned i = count; i != 0; i--) {
|
||||
const struct lzw_table_entry *entry = table + code;
|
||||
--output_pos;
|
||||
if (entry->value != ctx->transparency_idx) {
|
||||
*output_pos = ctx->colour_map[entry->value];
|
||||
}
|
||||
code = entry->extends;
|
||||
}
|
||||
} else {
|
||||
for (unsigned i = count; i != 0; i--) {
|
||||
const struct lzw_table_entry *entry = table + code;
|
||||
*--output_pos = ctx->colour_map[entry->value];
|
||||
code = entry->extends;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/* Exported function, documented in lzw.h */
|
||||
lzw_result lzw_decode_map(struct lzw_ctx *ctx,
|
||||
uint32_t *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t *restrict output_written)
|
||||
{
|
||||
*output_written = 0;
|
||||
|
||||
if (ctx->colour_map == NULL) {
|
||||
return LZW_NO_COLOUR;
|
||||
}
|
||||
|
||||
if (ctx->output_left != 0) {
|
||||
*output_written += lzw__map_write_fn(ctx,
|
||||
output_data, output_length, *output_written,
|
||||
ctx->output_code, ctx->output_left);
|
||||
}
|
||||
|
||||
while (*output_written != output_length) {
|
||||
lzw_result res = lzw__decode(ctx, lzw__map_write_fn,
|
||||
output_data, output_length, output_written);
|
||||
if (res != LZW_OK) {
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
return LZW_OK;
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* This file is part of NetSurf's LibNSGIF, http://www.netsurf-browser.org/
|
||||
* Licensed under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*
|
||||
* Copyright 2017 Michael Drake <michael.drake@codethink.co.uk>
|
||||
* Copyright 2021 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*/
|
||||
|
||||
#ifndef LZW_H_
|
||||
#define LZW_H_
|
||||
|
||||
/**
|
||||
* \file
|
||||
* \brief LZW decompression (interface)
|
||||
*
|
||||
* Decoder for GIF LZW data.
|
||||
*/
|
||||
|
||||
/** Maximum LZW code size in bits */
|
||||
#define LZW_CODE_MAX 12
|
||||
|
||||
/* Declare lzw internal context structure */
|
||||
struct lzw_ctx;
|
||||
|
||||
/** LZW decoding response codes */
|
||||
typedef enum lzw_result {
|
||||
LZW_OK, /**< Success */
|
||||
LZW_OK_EOD, /**< Success; reached zero-length sub-block */
|
||||
LZW_NO_MEM, /**< Error: Out of memory */
|
||||
LZW_NO_DATA, /**< Error: Out of data */
|
||||
LZW_EOI_CODE, /**< Error: End of Information code */
|
||||
LZW_NO_COLOUR, /**< Error: No colour map provided. */
|
||||
LZW_BAD_ICODE, /**< Error: Bad initial LZW code */
|
||||
LZW_BAD_PARAM, /**< Error: Bad function parameter. */
|
||||
LZW_BAD_CODE, /**< Error: Bad LZW code */
|
||||
} lzw_result;
|
||||
|
||||
/**
|
||||
* Create an LZW decompression context.
|
||||
*
|
||||
* \param[out] ctx Returns an LZW decompression context. Caller owned,
|
||||
* free with lzw_context_destroy().
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_context_create(
|
||||
struct lzw_ctx **ctx);
|
||||
|
||||
/**
|
||||
* Destroy an LZW decompression context.
|
||||
*
|
||||
* \param[in] ctx The LZW decompression context to destroy.
|
||||
*/
|
||||
void lzw_context_destroy(
|
||||
struct lzw_ctx *ctx);
|
||||
|
||||
/**
|
||||
* Initialise an LZW decompression context for decoding.
|
||||
*
|
||||
* \param[in] ctx The LZW decompression context to initialise.
|
||||
* \param[in] minimum_code_size The LZW Minimum Code Size.
|
||||
* \param[in] input_data The compressed data.
|
||||
* \param[in] input_length Byte length of compressed data.
|
||||
* \param[in] input_pos Start position in data. Must be position
|
||||
* of a size byte at sub-block start.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode_init(
|
||||
struct lzw_ctx *ctx,
|
||||
uint8_t minimum_code_size,
|
||||
const uint8_t *input_data,
|
||||
size_t input_length,
|
||||
size_t input_pos);
|
||||
|
||||
/**
|
||||
* Read input codes until end of LZW context owned output buffer.
|
||||
*
|
||||
* Ensure anything in output is used before calling this, as anything
|
||||
* there before this call will be trampled.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[out] output_data Returns pointer to array of output values.
|
||||
* \param[out] output_written Returns the number of values written to data.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode(struct lzw_ctx *ctx,
|
||||
const uint8_t *restrict *const restrict output_data,
|
||||
uint32_t *restrict output_written);
|
||||
|
||||
/**
|
||||
* Initialise an LZW decompression context for decoding to colour map values.
|
||||
*
|
||||
* For transparency to work correctly, the given client buffer must have
|
||||
* the values from the previous frame. The transparency_idx should be a value
|
||||
* of 256 or above, if the frame does not have transparency.
|
||||
*
|
||||
* \param[in] ctx The LZW decompression context to initialise.
|
||||
* \param[in] minimum_code_size The LZW Minimum Code Size.
|
||||
* \param[in] transparency_idx Index representing transparency.
|
||||
* \param[in] colour_table Index to pixel colour mapping.
|
||||
* \param[in] input_data The compressed data.
|
||||
* \param[in] input_length Byte length of compressed data.
|
||||
* \param[in] input_pos Start position in data. Must be position
|
||||
* of a size byte at sub-block start.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode_init_map(
|
||||
struct lzw_ctx *ctx,
|
||||
uint8_t minimum_code_size,
|
||||
uint32_t transparency_idx,
|
||||
const uint32_t *colour_table,
|
||||
const uint8_t *input_data,
|
||||
size_t input_length,
|
||||
size_t input_pos);
|
||||
|
||||
/**
|
||||
* Read LZW codes into client buffer, mapping output to colours.
|
||||
*
|
||||
* The context must have been initialised using \ref lzw_decode_init_map
|
||||
* before calling this function, in order to provide the colour mapping table
|
||||
* and any transparency index.
|
||||
*
|
||||
* Ensure anything in output is used before calling this, as anything
|
||||
* there before this call will be trampled.
|
||||
*
|
||||
* \param[in] ctx LZW reading context, updated.
|
||||
* \param[in] output_data Client buffer to fill with colour mapped values.
|
||||
* \param[in] output_length Size of output array.
|
||||
* \param[out] output_written Returns the number of values written to data.
|
||||
* \return LZW_OK on success, or appropriate error code otherwise.
|
||||
*/
|
||||
lzw_result lzw_decode_map(struct lzw_ctx *ctx,
|
||||
uint32_t *restrict output_data,
|
||||
uint32_t output_length,
|
||||
uint32_t *restrict output_written);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,527 @@
|
||||
/*
|
||||
* Copyright 2004 Richard Wilson <richard.wilson@netsurf-browser.org>
|
||||
* Copyright 2008 Sean Fox <dyntryx@gmail.com>
|
||||
* Copyright 2013-2022 Michael Drake <tlsa@netsurf-browser.org>
|
||||
*
|
||||
* This file is part of NetSurf's libnsgif, http://www.netsurf-browser.org/
|
||||
* Licenced under the MIT License,
|
||||
* http://www.opensource.org/licenses/mit-license.php
|
||||
*/
|
||||
|
||||
/**
|
||||
* \file
|
||||
* Interface to progressive animated GIF file decoding.
|
||||
*/
|
||||
|
||||
#ifndef NSNSGIF_H
|
||||
#define NSNSGIF_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
/** Representation of infinity. */
|
||||
#define NSGIF_INFINITE (UINT32_MAX)
|
||||
|
||||
/** Maximum colour table size */
|
||||
#define NSGIF_MAX_COLOURS 256
|
||||
|
||||
/**
|
||||
* Opaque type used by LibNSGIF to represent a GIF object in memory.
|
||||
*/
|
||||
typedef struct nsgif nsgif_t;
|
||||
|
||||
/**
|
||||
* LibNSGIF rectangle structure.
|
||||
*
|
||||
* * Top left coordinate is `(x0, y0)`.
|
||||
* * Width is `x1 - x0`.
|
||||
* * Height is `y1 - y0`.
|
||||
* * Units are pixels.
|
||||
*/
|
||||
typedef struct nsgif_rect {
|
||||
/** x co-ordinate of redraw rectangle, left */
|
||||
uint32_t x0;
|
||||
/** y co-ordinate of redraw rectangle, top */
|
||||
uint32_t y0;
|
||||
/** x co-ordinate of redraw rectangle, right */
|
||||
uint32_t x1;
|
||||
/** y co-ordinate of redraw rectangle, bottom */
|
||||
uint32_t y1;
|
||||
} nsgif_rect_t;
|
||||
|
||||
/**
|
||||
* LibNSGIF return codes.
|
||||
*/
|
||||
typedef enum {
|
||||
/**
|
||||
* Success.
|
||||
*/
|
||||
NSGIF_OK,
|
||||
|
||||
/**
|
||||
* Out of memory error.
|
||||
*/
|
||||
NSGIF_ERR_OOM,
|
||||
|
||||
/**
|
||||
* GIF source data is invalid, and no frames are recoverable.
|
||||
*/
|
||||
NSGIF_ERR_DATA,
|
||||
|
||||
/**
|
||||
* Frame number is not valid.
|
||||
*/
|
||||
NSGIF_ERR_BAD_FRAME,
|
||||
|
||||
/**
|
||||
* GIF source data contained an error in a frame.
|
||||
*/
|
||||
NSGIF_ERR_DATA_FRAME,
|
||||
|
||||
/**
|
||||
* Unexpected end of GIF source data.
|
||||
*/
|
||||
NSGIF_ERR_END_OF_DATA,
|
||||
|
||||
/**
|
||||
* Can't supply more data after calling \ref nsgif_data_complete.
|
||||
*/
|
||||
NSGIF_ERR_DATA_COMPLETE,
|
||||
|
||||
/**
|
||||
* The current frame cannot be displayed.
|
||||
*/
|
||||
NSGIF_ERR_FRAME_DISPLAY,
|
||||
|
||||
/**
|
||||
* Indicates an animation is complete, and \ref nsgif_reset must be
|
||||
* called to restart the animation from the beginning.
|
||||
*/
|
||||
NSGIF_ERR_ANIMATION_END,
|
||||
} nsgif_error;
|
||||
|
||||
/**
|
||||
* NSGIF \ref nsgif_bitmap_t pixel format.
|
||||
*
|
||||
* All pixel formats are 32 bits per pixel (bpp). The different formats
|
||||
* allow control over the ordering of the colour channels. All colour
|
||||
* channels are 8 bits wide.
|
||||
*
|
||||
* Note that the GIF file format only supports an on/off mask, so the
|
||||
* alpha (A) component (opacity) will always have a value of `0` (fully
|
||||
* transparent) or `255` (fully opaque).
|
||||
*/
|
||||
typedef enum nsgif_bitmap_fmt {
|
||||
/** Bite-wise RGBA: Byte order: 0xRR, 0xGG, 0xBB, 0xAA. */
|
||||
NSGIF_BITMAP_FMT_R8G8B8A8,
|
||||
|
||||
/** Bite-wise BGRA: Byte order: 0xBB, 0xGG, 0xRR, 0xAA. */
|
||||
NSGIF_BITMAP_FMT_B8G8R8A8,
|
||||
|
||||
/** Bite-wise ARGB: Byte order: 0xAA, 0xRR, 0xGG, 0xBB. */
|
||||
NSGIF_BITMAP_FMT_A8R8G8B8,
|
||||
|
||||
/** Bite-wise ABGR: Byte order: 0xAA, 0xBB, 0xGG, 0xRR. */
|
||||
NSGIF_BITMAP_FMT_A8B8G8R8,
|
||||
|
||||
/**
|
||||
* 32-bit RGBA (0xRRGGBBAA).
|
||||
*
|
||||
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_A8B8G8R8.
|
||||
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_R8G8B8A8.
|
||||
*/
|
||||
NSGIF_BITMAP_FMT_RGBA8888,
|
||||
|
||||
/**
|
||||
* 32-bit BGRA (0xBBGGRRAA).
|
||||
*
|
||||
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_A8R8G8B8.
|
||||
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_B8G8R8A8.
|
||||
*/
|
||||
NSGIF_BITMAP_FMT_BGRA8888,
|
||||
|
||||
/**
|
||||
* 32-bit ARGB (0xAARRGGBB).
|
||||
*
|
||||
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_B8G8R8A8.
|
||||
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_A8R8G8B8.
|
||||
*/
|
||||
NSGIF_BITMAP_FMT_ARGB8888,
|
||||
|
||||
/**
|
||||
* 32-bit BGRA (0xAABBGGRR).
|
||||
*
|
||||
* * On little endian host, same as \ref NSGIF_BITMAP_FMT_R8G8B8A8.
|
||||
* * On big endian host, same as \ref NSGIF_BITMAP_FMT_A8B8G8R8.
|
||||
*/
|
||||
NSGIF_BITMAP_FMT_ABGR8888,
|
||||
} nsgif_bitmap_fmt_t;
|
||||
|
||||
/**
|
||||
* Client bitmap type.
|
||||
*
|
||||
* These are client-created and destroyed, via the \ref nsgif_bitmap_cb_vt
|
||||
* callbacks, but they are owned by a \ref nsgif_t.
|
||||
*
|
||||
* See \ref nsgif_bitmap_fmt for pixel format information.
|
||||
*
|
||||
* The bitmap may have a row_span greater than the bitmap width, but the
|
||||
* difference between row span and width must be a whole number of pixels
|
||||
* (a multiple of four bytes).
|
||||
*/
|
||||
typedef void nsgif_bitmap_t;
|
||||
|
||||
/** Bitmap callbacks function table */
|
||||
typedef struct nsgif_bitmap_cb_vt {
|
||||
/**
|
||||
* Callback to create a bitmap with the given dimensions.
|
||||
*
|
||||
* \param[in] width Required bitmap width in pixels.
|
||||
* \param[in] height Required bitmap height in pixels.
|
||||
* \return pointer to client's bitmap structure or NULL on error.
|
||||
*/
|
||||
nsgif_bitmap_t* (*create)(int width, int height);
|
||||
|
||||
/**
|
||||
* Callback to free a bitmap.
|
||||
*
|
||||
* \param[in] bitmap The bitmap to destroy.
|
||||
*/
|
||||
void (*destroy)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/**
|
||||
* Get pointer to pixel buffer in a bitmap.
|
||||
*
|
||||
* The pixel buffer must be `(width + N) * height * sizeof(uint32_t)`.
|
||||
* Where `N` is any number greater than or equal to 0.
|
||||
* Note that the returned pointer to uint8_t must be 4-byte aligned.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \return pointer to bitmap's pixel buffer.
|
||||
*/
|
||||
uint8_t* (*get_buffer)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/* The following functions are optional. */
|
||||
|
||||
/**
|
||||
* Set whether a bitmap can be plotted opaque.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \param[in] opaque Whether the current frame is opaque.
|
||||
*/
|
||||
void (*set_opaque)(nsgif_bitmap_t *bitmap, bool opaque);
|
||||
|
||||
/**
|
||||
* Tests whether a bitmap has an opaque alpha channel.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
* \return true if the bitmap is opaque, false otherwise.
|
||||
*/
|
||||
bool (*test_opaque)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/**
|
||||
* Bitmap modified notification.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
*/
|
||||
void (*modified)(nsgif_bitmap_t *bitmap);
|
||||
|
||||
/**
|
||||
* Get row span in pixels.
|
||||
*
|
||||
* If this callback is not provided, LibNSGIF will use the width.
|
||||
*
|
||||
* If row span is greater than width, this callback must be provided.
|
||||
*
|
||||
* \param[in] bitmap The bitmap.
|
||||
*/
|
||||
uint32_t (*get_rowspan)(nsgif_bitmap_t *bitmap);
|
||||
} nsgif_bitmap_cb_vt;
|
||||
|
||||
/**
|
||||
* Convert an error code to a string.
|
||||
*
|
||||
* \param[in] err The error code to convert.
|
||||
* \return String representation of given error code.
|
||||
*/
|
||||
const char *nsgif_strerror(nsgif_error err);
|
||||
|
||||
/**
|
||||
* Create the NSGIF object.
|
||||
*
|
||||
* \param[in] bitmap_vt Bitmap operation functions v-table.
|
||||
* \param[in] bitmap_fmt Bitmap pixel format specification.
|
||||
* \param[out] gif_out Return \ref nsgif_t object on success.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_create(
|
||||
const nsgif_bitmap_cb_vt *bitmap_vt,
|
||||
nsgif_bitmap_fmt_t bitmap_fmt,
|
||||
nsgif_t **gif_out);
|
||||
|
||||
/**
|
||||
* Free a NSGIF object.
|
||||
*
|
||||
* \param[in] gif The NSGIF to free.
|
||||
*/
|
||||
void nsgif_destroy(nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Scan the source image data.
|
||||
*
|
||||
* This is used to feed the source data into LibNSGIF. This must be called
|
||||
* before calling \ref nsgif_frame_decode.
|
||||
*
|
||||
* It can be called multiple times with, with increasing sizes. If it is called
|
||||
* several times, as more data is available (e.g. slow network fetch) the data
|
||||
* already given to \ref nsgif_data_scan must be provided each time.
|
||||
*
|
||||
* Once all the data has been provided, call \ref nsgif_data_complete.
|
||||
*
|
||||
* For example, if you call \ref nsgif_data_scan with 25 bytes of data, and then
|
||||
* fetch another 10 bytes, you would need to call \ref nsgif_data_scan with a
|
||||
* size of 35 bytes, and the whole 35 bytes must be contiguous memory. It is
|
||||
* safe to `realloc` the source buffer between calls to \ref nsgif_data_scan.
|
||||
* (The actual data pointer is allowed to be different.)
|
||||
*
|
||||
* If an error occurs, all previously scanned frames are retained.
|
||||
*
|
||||
* Note that an error returned from this function is purely informational.
|
||||
* So long as at least one frame is available, you can display frames.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[in] size Number of bytes in data.
|
||||
* \param[in] data Raw source GIF data.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_data_scan(
|
||||
nsgif_t *gif,
|
||||
size_t size,
|
||||
const uint8_t *data);
|
||||
|
||||
/**
|
||||
* Tell libnsgif that all the gif data has been provided.
|
||||
*
|
||||
* Call this after calling \ref nsgif_data_scan with the the entire GIF
|
||||
* source data. You can call \ref nsgif_data_scan multiple times up until
|
||||
* this is called, and after this is called, \ref nsgif_data_scan will
|
||||
* return an error.
|
||||
*
|
||||
* You can decode a GIF before this is called, however, it will fail to
|
||||
* decode any truncated final frame data and will not perform loops when
|
||||
* driven via \ref nsgif_frame_prepare (because it doesn't know if there
|
||||
* will be more frames supplied in future data).
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
*/
|
||||
void nsgif_data_complete(
|
||||
nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Prepare to show a frame.
|
||||
*
|
||||
* If this is the last frame of an animation with a finite loop count, the
|
||||
* returned `delay_cs` will be \ref NSGIF_INFINITE, indicating that the frame
|
||||
* should be shown forever.
|
||||
*
|
||||
* Note that if \ref nsgif_data_complete has not been called on this gif,
|
||||
* animated GIFs will not loop back to the start. Instead it will return
|
||||
* \ref NSGIF_ERR_END_OF_DATA.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[out] area The area in pixels that must be redrawn.
|
||||
* \param[out] delay_cs Time to wait after frame_new before next frame in cs.
|
||||
* \param[out] frame_new The frame to decode.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_frame_prepare(
|
||||
nsgif_t *gif,
|
||||
nsgif_rect_t *area,
|
||||
uint32_t *delay_cs,
|
||||
uint32_t *frame_new);
|
||||
|
||||
/**
|
||||
* Decodes a GIF frame.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[in] frame The frame number to decode.
|
||||
* \param[out] bitmap On success, returns pointer to the client-allocated,
|
||||
* nsgif-owned client bitmap structure.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_frame_decode(
|
||||
nsgif_t *gif,
|
||||
uint32_t frame,
|
||||
nsgif_bitmap_t **bitmap);
|
||||
|
||||
/**
|
||||
* Reset a GIF animation.
|
||||
*
|
||||
* Some animations are only meant to loop N times, and then show the
|
||||
* final frame forever. This function resets the loop and frame counters,
|
||||
* so that the animation can be replayed without the overhead of recreating
|
||||
* the \ref nsgif_t object and rescanning the raw data.
|
||||
*
|
||||
* \param[in] gif A \ref nsgif_t object.
|
||||
*
|
||||
* \return NSGIF_OK on success, or appropriate error otherwise.
|
||||
*/
|
||||
nsgif_error nsgif_reset(
|
||||
nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Information about a GIF.
|
||||
*/
|
||||
typedef struct nsgif_info {
|
||||
/** width of GIF (may increase during decoding) */
|
||||
uint32_t width;
|
||||
/** height of GIF (may increase during decoding) */
|
||||
uint32_t height;
|
||||
/** number of frames decoded */
|
||||
uint32_t frame_count;
|
||||
/** number of times to play animation (zero means loop forever) */
|
||||
int loop_max;
|
||||
/** background colour in same pixel format as \ref nsgif_bitmap_t. */
|
||||
uint32_t background;
|
||||
/** whether the GIF has a global colour table */
|
||||
bool global_palette;
|
||||
} nsgif_info_t;
|
||||
|
||||
/**
|
||||
* Frame disposal method.
|
||||
*
|
||||
* Clients do not need to know about this, it is provided purely for dumping
|
||||
* raw information about GIF frames.
|
||||
*/
|
||||
enum nsgif_disposal {
|
||||
NSGIF_DISPOSAL_UNSPECIFIED, /**< No disposal method specified. */
|
||||
NSGIF_DISPOSAL_NONE, /**< Frame remains. */
|
||||
NSGIF_DISPOSAL_RESTORE_BG, /**< Clear frame to background colour. */
|
||||
NSGIF_DISPOSAL_RESTORE_PREV, /**< Restore previous frame. */
|
||||
NSGIF_DISPOSAL_RESTORE_QUIRK, /**< Alias for NSGIF_DISPOSAL_RESTORE_PREV. */
|
||||
};
|
||||
|
||||
/**
|
||||
* Convert a disposal method to a string.
|
||||
*
|
||||
* \param[in] disposal The disposal method to convert.
|
||||
* \return String representation of given disposal method.
|
||||
*/
|
||||
const char *nsgif_str_disposal(enum nsgif_disposal disposal);
|
||||
|
||||
/**
|
||||
* Information about a GIF frame.
|
||||
*/
|
||||
typedef struct nsgif_frame_info {
|
||||
/** whether the frame should be displayed/animated */
|
||||
bool display;
|
||||
/** whether the frame may have transparency */
|
||||
bool transparency;
|
||||
/** whether the frame has a local colour table */
|
||||
bool local_palette;
|
||||
/** whether the frame is interlaced */
|
||||
bool interlaced;
|
||||
/** Disposal method for previous frame; affects plotting */
|
||||
uint8_t disposal;
|
||||
/** delay (in cs) before animating the frame */
|
||||
uint32_t delay;
|
||||
|
||||
/** Frame's redraw rectangle. */
|
||||
nsgif_rect_t rect;
|
||||
} nsgif_frame_info_t;
|
||||
|
||||
/**
|
||||
* Get information about a GIF from an \ref nsgif_t object.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object to get info for.
|
||||
*
|
||||
* \return The gif info, or NULL on error.
|
||||
*/
|
||||
const nsgif_info_t *nsgif_get_info(const nsgif_t *gif);
|
||||
|
||||
/**
|
||||
* Get information about a GIF from an \ref nsgif_t object.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object to get frame info for.
|
||||
* \param[in] frame The frame number to get info for.
|
||||
*
|
||||
* \return The gif frame info, or NULL on error.
|
||||
*/
|
||||
const nsgif_frame_info_t *nsgif_get_frame_info(
|
||||
const nsgif_t *gif,
|
||||
uint32_t frame);
|
||||
|
||||
/**
|
||||
* Get the global colour palette.
|
||||
*
|
||||
* If the GIF has no global colour table, this will return the default
|
||||
* colour palette.
|
||||
*
|
||||
* Colours in same pixel format as \ref nsgif_bitmap_t.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[out] table Client buffer to hold the colour table.
|
||||
* \param[out] entries The number of used entries in the colour table.
|
||||
*/
|
||||
void nsgif_global_palette(
|
||||
const nsgif_t *gif,
|
||||
uint32_t table[NSGIF_MAX_COLOURS],
|
||||
size_t *entries);
|
||||
|
||||
/**
|
||||
* Get the local colour palette for a frame.
|
||||
*
|
||||
* Frames may have no local palette. In this case they use the global palette.
|
||||
* This function returns false if the frame has no local palette.
|
||||
*
|
||||
* Colours in same pixel format as \ref nsgif_bitmap_t.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object.
|
||||
* \param[in] frame The frame to get the palette for.
|
||||
* \param[out] table Client buffer to hold the colour table.
|
||||
* \param[out] entries The number of used entries in the colour table.
|
||||
* \return true if a palette is returned, false otherwise.
|
||||
*/
|
||||
bool nsgif_local_palette(
|
||||
const nsgif_t *gif,
|
||||
uint32_t frame,
|
||||
uint32_t table[NSGIF_MAX_COLOURS],
|
||||
size_t *entries);
|
||||
|
||||
/**
|
||||
* Configure handling of small frame delays.
|
||||
*
|
||||
* Historically people created GIFs with a tiny frame delay, however the slow
|
||||
* hardware of the time meant they actually played much slower. As computers
|
||||
* sped up, to prevent animations playing faster than intended, decoders came
|
||||
* to ignore overly small frame delays.
|
||||
*
|
||||
* By default a \ref nsgif_frame_prepare() managed animation will override
|
||||
* frame delays of less than 2 centiseconds with a default frame delay of
|
||||
* 10 centiseconds. This matches the behaviour of web browsers and other
|
||||
* renderers.
|
||||
*
|
||||
* Both the minimum and the default values can be overridden for a given GIF
|
||||
* by the client. To get frame delays exactly as specified by the GIF file, set
|
||||
* `delay_min` to zero.
|
||||
*
|
||||
* Note that this does not affect the frame delay in the frame info
|
||||
* (\ref nsgif_frame_info_t) structure, which will always contain values
|
||||
* specified by the GIF.
|
||||
*
|
||||
* \param[in] gif The \ref nsgif_t object to configure.
|
||||
* \param[in] delay_min The minimum frame delay in centiseconds.
|
||||
* \param[in] delay_default The delay to use if a frame delay is less than
|
||||
* `delay_min`.
|
||||
*/
|
||||
void nsgif_set_frame_delay_behaviour(
|
||||
nsgif_t *gif,
|
||||
uint16_t delay_min,
|
||||
uint16_t delay_default);
|
||||
|
||||
#endif
|
||||
@@ -27,7 +27,7 @@ struct image_source {
|
||||
volatile bool file_decoded;
|
||||
volatile bool texture_loaded;
|
||||
|
||||
gs_image_file4_t if4;
|
||||
gs_image_file_ex_t image;
|
||||
};
|
||||
|
||||
static time_t get_modified_timestamp(const char *filename)
|
||||
@@ -35,6 +35,7 @@ static time_t get_modified_timestamp(const char *filename)
|
||||
struct stat stats;
|
||||
if (os_stat(filename, &stats) != 0)
|
||||
return -1;
|
||||
|
||||
return stats.st_mtime;
|
||||
}
|
||||
|
||||
@@ -51,8 +52,8 @@ void image_source_preload_image(void *data)
|
||||
return;
|
||||
|
||||
context->file_timestamp = get_modified_timestamp(context->file);
|
||||
gs_image_file4_init(&context->if4, context->file,
|
||||
context->linear_alpha ? GS_IMAGE_ALPHA_PREMULTIPLY_SRGB : GS_IMAGE_ALPHA_PREMULTIPLY);
|
||||
gs_image_file_ex_init(&context->image, context->file,
|
||||
context->linear_alpha ? GS_IMAGE_ALPHA_PREMULTIPLY_SRGB : GS_IMAGE_ALPHA_PREMULTIPLY);
|
||||
os_atomic_set_bool(&context->file_decoded, true);
|
||||
}
|
||||
|
||||
@@ -65,11 +66,12 @@ static void image_source_load_texture(void *data)
|
||||
debug("loading texture '%s'", context->file);
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file4_init_texture(&context->if4);
|
||||
gs_image_file_ex_init_texture(&context->image);
|
||||
obs_leave_graphics();
|
||||
|
||||
if (!context->if4.image3.image2.image.loaded)
|
||||
if (!context->image.loaded)
|
||||
warn("failed to load texture '%s'", context->file);
|
||||
|
||||
context->update_time_elapsed = 0;
|
||||
os_atomic_set_bool(&context->texture_loaded, true);
|
||||
}
|
||||
@@ -81,7 +83,7 @@ static void image_source_unload(void *data)
|
||||
os_atomic_set_bool(&context->texture_loaded, false);
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file4_free(&context->if4);
|
||||
gs_image_file_ex_free(&context->image);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
@@ -105,6 +107,7 @@ static void image_source_update(void *data, obs_data_t *settings)
|
||||
|
||||
if (context->file)
|
||||
bfree(context->file);
|
||||
|
||||
context->file = bstrdup(file);
|
||||
context->persistent = !unload;
|
||||
context->linear_alpha = linear_alpha;
|
||||
@@ -146,13 +149,13 @@ static void restart_gif(void *data)
|
||||
{
|
||||
struct image_source *context = data;
|
||||
|
||||
if (context->if4.image3.image2.image.is_animated_gif) {
|
||||
context->if4.image3.image2.image.cur_frame = 0;
|
||||
context->if4.image3.image2.image.cur_loop = 0;
|
||||
context->if4.image3.image2.image.cur_time = 0;
|
||||
if (context->image.is_animated_gif) {
|
||||
context->image.cur_frame = 0;
|
||||
context->image.cur_loop = 0;
|
||||
context->image.cur_time = 0;
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file4_update_texture(&context->if4);
|
||||
gs_image_file_ex_update_texture(&context->image);
|
||||
obs_leave_graphics();
|
||||
|
||||
context->restart_gif = false;
|
||||
@@ -182,19 +185,20 @@ static void image_source_destroy(void *data)
|
||||
|
||||
if (context->file)
|
||||
bfree(context->file);
|
||||
|
||||
bfree(context);
|
||||
}
|
||||
|
||||
static uint32_t image_source_getwidth(void *data)
|
||||
{
|
||||
struct image_source *context = data;
|
||||
return context->if4.image3.image2.image.cx;
|
||||
return context->image.cx;
|
||||
}
|
||||
|
||||
static uint32_t image_source_getheight(void *data)
|
||||
{
|
||||
struct image_source *context = data;
|
||||
return context->if4.image3.image2.image.cy;
|
||||
return context->image.cy;
|
||||
}
|
||||
|
||||
static void image_source_render(void *data, gs_effect_t *effect)
|
||||
@@ -203,7 +207,7 @@ static void image_source_render(void *data, gs_effect_t *effect)
|
||||
if (!os_atomic_load_bool(&context->texture_loaded))
|
||||
return;
|
||||
|
||||
struct gs_image_file *const image = &context->if4.image3.image2.image;
|
||||
gs_image_file_ex_t *const image = &context->image;
|
||||
gs_texture_t *const texture = image->texture;
|
||||
if (!texture)
|
||||
return;
|
||||
@@ -251,8 +255,9 @@ static void image_source_tick(void *data, float seconds)
|
||||
|
||||
if (obs_source_showing(context->source)) {
|
||||
if (!context->active) {
|
||||
if (context->if4.image3.image2.image.is_animated_gif)
|
||||
if (context->image.is_animated_gif)
|
||||
context->last_time = frame_time;
|
||||
|
||||
context->active = true;
|
||||
}
|
||||
|
||||
@@ -268,13 +273,13 @@ static void image_source_tick(void *data, float seconds)
|
||||
return;
|
||||
}
|
||||
|
||||
if (context->last_time && context->if4.image3.image2.image.is_animated_gif) {
|
||||
if (context->last_time && context->image.is_animated_gif) {
|
||||
uint64_t elapsed = frame_time - context->last_time;
|
||||
bool updated = gs_image_file4_tick(&context->if4, elapsed);
|
||||
bool updated = gs_image_file_ex_tick(&context->image, elapsed);
|
||||
|
||||
if (updated) {
|
||||
obs_enter_graphics();
|
||||
gs_image_file4_update_texture(&context->if4);
|
||||
gs_image_file_ex_update_texture(&context->image);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
}
|
||||
@@ -316,7 +321,7 @@ static obs_properties_t *image_source_properties(void *data)
|
||||
uint64_t image_source_get_memory_usage(void *data)
|
||||
{
|
||||
struct image_source *s = data;
|
||||
return s->if4.image3.image2.mem_usage;
|
||||
return s->image.mem_usage;
|
||||
}
|
||||
|
||||
static void missing_file_callback(void *src, const char *new_path, void *data)
|
||||
@@ -356,8 +361,8 @@ static enum gs_color_space image_source_get_color_space(void *data, size_t count
|
||||
UNUSED_PARAMETER(preferred_spaces);
|
||||
|
||||
struct image_source *const s = data;
|
||||
gs_image_file4_t *const if4 = &s->if4;
|
||||
return if4->image3.image2.image.texture ? if4->space : GS_CS_SRGB;
|
||||
gs_image_file_ex_t *const image = &s->image;
|
||||
return image->texture ? image->space : GS_CS_SRGB;
|
||||
}
|
||||
|
||||
static struct obs_source_info image_source_info = {
|
||||
|
||||
@@ -30,7 +30,7 @@ struct lut_filter_data {
|
||||
gs_effect_t *effect;
|
||||
gs_texture_t *target;
|
||||
|
||||
gs_image_file_t image;
|
||||
gs_image_file_ex_t image;
|
||||
|
||||
uint32_t cube_width;
|
||||
void *cube_data;
|
||||
@@ -243,7 +243,7 @@ static void color_grade_filter_update(void *data, obs_data_t *settings)
|
||||
filter->cube_data = NULL;
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file_free(&filter->image);
|
||||
gs_image_file_ex_free(&filter->image);
|
||||
gs_voltexture_destroy(filter->target);
|
||||
filter->target = NULL;
|
||||
obs_leave_graphics();
|
||||
@@ -261,7 +261,7 @@ static void color_grade_filter_update(void *data, obs_data_t *settings)
|
||||
filter->cube_data = load_cube_file(path, &filter->cube_width, &filter->domain_min,
|
||||
&filter->domain_max, &clut_dim);
|
||||
} else {
|
||||
gs_image_file_init(&filter->image, path);
|
||||
gs_image_file_ex_init(&filter->image, path, GS_IMAGE_ALPHA_STRAIGHT);
|
||||
filter->cube_width = LUT_WIDTH;
|
||||
}
|
||||
|
||||
@@ -384,7 +384,7 @@ static void color_grade_filter_destroy(void *data)
|
||||
obs_enter_graphics();
|
||||
gs_effect_destroy(filter->effect);
|
||||
gs_voltexture_destroy(filter->target);
|
||||
gs_image_file_free(&filter->image);
|
||||
gs_image_file_ex_free(&filter->image);
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(filter->cube_data);
|
||||
|
||||
@@ -37,7 +37,7 @@ struct mask_filter_data {
|
||||
float update_time_elapsed;
|
||||
|
||||
gs_texture_t *target;
|
||||
gs_image_file_t image;
|
||||
gs_image_file_ex_t image;
|
||||
struct vec4 color;
|
||||
bool lock_aspect;
|
||||
};
|
||||
@@ -47,6 +47,7 @@ static time_t get_modified_timestamp(const char *filename)
|
||||
struct stat stats;
|
||||
if (os_stat(filename, &stats) != 0)
|
||||
return -1;
|
||||
|
||||
return stats.st_mtime;
|
||||
}
|
||||
|
||||
@@ -59,7 +60,7 @@ static const char *mask_filter_get_name(void *unused)
|
||||
static void mask_filter_image_unload(struct mask_filter_data *filter)
|
||||
{
|
||||
obs_enter_graphics();
|
||||
gs_image_file_free(&filter->image);
|
||||
gs_image_file_ex_free(&filter->image);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
@@ -71,11 +72,11 @@ static void mask_filter_image_load(struct mask_filter_data *filter)
|
||||
|
||||
if (path && *path) {
|
||||
filter->image_file_timestamp = get_modified_timestamp(path);
|
||||
gs_image_file_init(&filter->image, path);
|
||||
gs_image_file_ex_init(&filter->image, path, GS_IMAGE_ALPHA_STRAIGHT);
|
||||
filter->update_time_elapsed = 0;
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file_init_texture(&filter->image);
|
||||
gs_image_file_ex_init_texture(&filter->image);
|
||||
obs_leave_graphics();
|
||||
}
|
||||
|
||||
@@ -93,12 +94,14 @@ static void mask_filter_update_internal(void *data, obs_data_t *settings, float
|
||||
|
||||
if (filter->image_file)
|
||||
bfree(filter->image_file);
|
||||
|
||||
filter->image_file = bstrdup(path);
|
||||
|
||||
if (srgb)
|
||||
vec4_from_rgba_srgb(&filter->color, color);
|
||||
else
|
||||
vec4_from_rgba(&filter->color, color);
|
||||
|
||||
filter->color.w = opacity;
|
||||
|
||||
mask_filter_image_load(filter);
|
||||
@@ -209,7 +212,7 @@ static void mask_filter_destroy(void *data)
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_effect_destroy(filter->effect);
|
||||
gs_image_file_free(&filter->image);
|
||||
gs_image_file_ex_free(&filter->image);
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(filter);
|
||||
@@ -235,9 +238,9 @@ static void mask_filter_tick(void *data, float seconds)
|
||||
if (!filter->last_time)
|
||||
filter->last_time = cur_time;
|
||||
|
||||
gs_image_file_tick(&filter->image, cur_time - filter->last_time);
|
||||
gs_image_file_ex_tick(&filter->image, cur_time - filter->last_time);
|
||||
obs_enter_graphics();
|
||||
gs_image_file_update_texture(&filter->image);
|
||||
gs_image_file_ex_update_texture(&filter->image);
|
||||
obs_leave_graphics();
|
||||
|
||||
filter->last_time = cur_time;
|
||||
|
||||
@@ -25,7 +25,7 @@ struct luma_wipe_info {
|
||||
gs_eparam_t *ep_invert;
|
||||
gs_eparam_t *ep_softness;
|
||||
|
||||
gs_image_file_t luma_image;
|
||||
gs_image_file_ex_t luma_image;
|
||||
bool invert_luma;
|
||||
float softness;
|
||||
obs_data_t *wipes_list;
|
||||
@@ -53,13 +53,13 @@ static void luma_wipe_update(void *data, obs_data_t *settings)
|
||||
char *file = obs_module_file(path.array);
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file_free(&lwipe->luma_image);
|
||||
gs_image_file_ex_free(&lwipe->luma_image);
|
||||
obs_leave_graphics();
|
||||
|
||||
gs_image_file_init(&lwipe->luma_image, file);
|
||||
gs_image_file_ex_init(&lwipe->luma_image, file, GS_IMAGE_ALPHA_STRAIGHT);
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file_init_texture(&lwipe->luma_image);
|
||||
gs_image_file_ex_init_texture(&lwipe->luma_image);
|
||||
obs_leave_graphics();
|
||||
|
||||
bfree(file);
|
||||
@@ -117,7 +117,7 @@ static void luma_wipe_destroy(void *data)
|
||||
struct luma_wipe_info *lwipe = data;
|
||||
|
||||
obs_enter_graphics();
|
||||
gs_image_file_free(&lwipe->luma_image);
|
||||
gs_image_file_ex_free(&lwipe->luma_image);
|
||||
obs_leave_graphics();
|
||||
|
||||
obs_data_release(lwipe->wipes_list);
|
||||
|
||||
Reference in New Issue
Block a user