#include "draw2d_gtk.ixx"
#include "osimage.inl"
#include "../image.inl"
#include "../imgutil.inl"
#include "../dctxh.h"
#include "../pixbuf.h"
#include <core/buffer.h>
#include <core/heap.h>
#include <core/strings.h>
#include <core/stream.h>
#include <sewer/bmem.h>
#include <sewer/cassert.h>
#include <sewer/ptr.h>
#include <sewer/unicode.h>
#if (GDK_PIXBUF_MAJOR < 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR < 28))
#include <osbs/bfile.h>
#include <core/hfile.h>
#endif
#include <sewer/nowarn.hxx>
#include <gdk/gdk.h>
#include <sewer/warn.hxx>
#if !defined(__GTK3__)
#error This file is only for GTK Toolkit
#endif
struct _osimage_t
{
GdkPixbuf *pixbuf;
GdkPixbufAnimation *animation;
uint32_t num_frames;
};
void osimage_alloc_globals(void)
{
}
void osimage_dealloc_globals(void)
{
}
static ___INLINE OSImage *i_osimage(GdkPixbuf *pixbuf)
{
OSImage *image = heap_new(OSImage);
image->pixbuf = pixbuf;
image->animation = NULL;
image->num_frames = 1;
return image;
}
static ___INLINE OSImage *i_osanimation(GdkPixbufAnimation *animation, const uint32_t num_frames)
{
OSImage *image = heap_new(OSImage);
image->pixbuf = NULL;
image->animation = animation;
image->num_frames = num_frames;
return image;
}
static void i_destroy_pixbuf_data(guchar *pixels, gpointer data)
{
uint32_t size = (uint32_t)(intptr_t)data;
heap_delete_n(&pixels, size, guchar);
}
OSImage *osimage_create_from_pixels(const uint32_t width, const uint32_t height, const pixformat_t format, const byte_t *pixel_data)
{
OSImage *image = NULL;
GdkPixbuf *pixbuf = NULL;
GdkColorspace colorspace = GDK_COLORSPACE_RGB;
gboolean has_alpha = FALSE;
int bits_per_sample = 8;
int rowstride = 0;
int offset = 0;
uint32_t size = 0;
guchar *data = NULL;
switch (format)
{
case ekRGBA32:
colorspace = GDK_COLORSPACE_RGB;
has_alpha = TRUE;
rowstride = width * 4;
size = width * height * 4;
data = heap_new_n(size, guchar);
bmem_copy(data, pixel_data, size);
break;
case ekRGB24:
{
uint32_t i, j;
guchar *dest = NULL;
colorspace = GDK_COLORSPACE_RGB;
has_alpha = FALSE;
rowstride = width * 3;
offset = (rowstride % 4) == 0 ? 0 : 4 - (rowstride % 4);
rowstride += offset;
size = rowstride * height;
data = heap_new_n(size, guchar);
dest = data;
bmem_copy(data, pixel_data, size);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i, dest += 3)
{
dest[0] = pixel_data[0];
dest[1] = pixel_data[1];
dest[2] = pixel_data[2];
pixel_data += 3;
}
dest += offset;
}
break;
}
case ekGRAY8:
{
uint32_t i, j;
guchar *dest = NULL;
colorspace = GDK_COLORSPACE_RGB;
has_alpha = FALSE;
rowstride = width * 3;
offset = (rowstride % 4) == 0 ? 0 : 4 - (rowstride % 4);
rowstride += offset;
size = rowstride * height;
data = heap_new_n(size, guchar);
dest = data;
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i, dest += 3)
{
dest[0] = *pixel_data;
dest[1] = *pixel_data;
dest[2] = *pixel_data;
pixel_data += 1;
}
dest += offset;
}
break;
}
cassert_default();
}
pixbuf = gdk_pixbuf_new_from_data((const guchar *)data, colorspace, has_alpha, bits_per_sample, (int)width, (int)height, rowstride, i_destroy_pixbuf_data, (gpointer)(intptr_t)size);
image = i_osimage(pixbuf);
return image;
}
static ___INLINE bool_t i_is_gif_buffer(const byte_t *data, const uint32_t size)
{
if (size >= 6)
{
const char_t *str = cast_const(data, char_t);
if (str_equ_cn(str, "GIF87a", 6) == TRUE || str_equ_cn(str, "GIF89a", 6) == TRUE)
return TRUE;
}
return FALSE;
}
static GdkPixbuf *i_pixbuf_from_data(const byte_t *data, const uint32_t size)
{
#if (GDK_PIXBUF_MAJOR > 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR >= 14))
GInputStream *stream = g_memory_input_stream_new_from_data(cast_const(data, void), (gssize)size, NULL);
GdkPixbuf *pixbuf = gdk_pixbuf_new_from_stream(stream, NULL, NULL);
gboolean ok = g_input_stream_close(stream, NULL, NULL);
cassert_unref(ok == TRUE, ok);
return pixbuf;
#else
const char_t *tempfile = "/tmp/___imgdata2303temp___.bin";
GdkPixbuf *pixbuf = NULL;
if (hfile_from_data(tempfile, data, size, NULL) == TRUE)
{
pixbuf = gdk_pixbuf_new_from_file((const char *)tempfile, NULL);
bfile_delete(tempfile, NULL);
}
return pixbuf;
#endif
}
static GdkPixbufAnimation *i_animation_from_data(const byte_t *data, const uint32_t size)
{
#if (GDK_PIXBUF_MAJOR > 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR >= 28))
GInputStream *stream = g_memory_input_stream_new_from_data(cast_const(data, void), (gssize)size, NULL);
GdkPixbufAnimation *animation = gdk_pixbuf_animation_new_from_stream(stream, NULL, NULL);
gboolean ok = g_input_stream_close(stream, NULL, NULL);
cassert_unref(ok == TRUE, ok);
return animation;
#else
const char_t *tempfile = "/tmp/___imgdata2303temp___.bin";
GdkPixbufAnimation *animation = NULL;
if (hfile_from_data(tempfile, data, size, NULL) == TRUE)
{
animation = gdk_pixbuf_animation_new_from_file((const char *)tempfile, NULL);
bfile_delete(tempfile, NULL);
}
return animation;
#endif
}
#if (GDK_PIXBUF_MAJOR > 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR >= 4))
static gboolean i_encode(const gchar *data, gsize size, GError **error, gpointer stream)
{
stm_write(cast(stream, Stream), cast_const(data, byte_t), (uint32_t)size);
unref(error);
return TRUE;
}
#endif
static void i_pixbuf_save(GdkPixbuf *pixbuf, const char *type, Stream *stm)
{
gboolean ok = FALSE;
#if (GDK_PIXBUF_MAJOR > 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR >= 4))
ok = gdk_pixbuf_save_to_callback(pixbuf, i_encode, (gpointer)stm, type, NULL, NULL);
cassert_unref(ok == TRUE, ok);
#else
const char_t *tempfile = "/tmp/___imgdata2303temp___.bin";
Buffer *buffer = NULL;
ok = gdk_pixbuf_save(pixbuf, tempfile, type, NULL, NULL);
cassert_unref(ok == TRUE, ok);
buffer = hfile_buffer(tempfile, NULL);
if (buffer != NULL)
{
const byte_t *data = buffer_data(buffer);
uint32_t size = buffer_size(buffer);
stm_write(stm, data, size);
buffer_destroy(&buffer);
}
#endif
}
static OSImage *i_single_osimage_from_data(const byte_t *data, const uint32_t size)
{
GdkPixbuf *pixbuf = i_pixbuf_from_data(data, size);
return i_osimage(pixbuf);
}
static OSImage *i_multiple_osimage_from_data(const byte_t *data, const uint32_t size, const uint32_t num_frames)
{
GdkPixbufAnimation *animation = i_animation_from_data(data, size);
return i_osanimation(animation, num_frames);
}
OSImage *osimage_create_from_data(const byte_t *data, const uint32_t size)
{
OSImage *image = NULL;
cassert_no_null(data);
cassert(size > 0);
if (i_is_gif_buffer(data, size) == FALSE)
{
image = i_single_osimage_from_data(data, size);
}
else
{
uint32_t num_frames = _imgutil_num_frames(data, size);
if (num_frames == 1)
image = i_single_osimage_from_data(data, size);
else
image = i_multiple_osimage_from_data(data, size, num_frames);
}
return image;
}
OSImage *osimage_create_from_type(const char_t *file_type)
{
unref(file_type);
cassert(FALSE);
return NULL;
}
OSImage *osimage_create_scaled(const OSImage *image, const uint32_t new_width, const uint32_t new_height)
{
cassert_no_null(image);
if (image->pixbuf != NULL)
{
GdkPixbuf *cpixbuf = gdk_pixbuf_scale_simple(image->pixbuf, (int)new_width, (int)new_height, GDK_INTERP_BILINEAR);
return i_osimage(cpixbuf);
}
else
{
GdkPixbufAnimation *canimation = NULL;
cassert(image->animation != NULL);
canimation = g_object_ref(image->animation);
return i_osanimation(canimation, image->num_frames);
}
}
OSImage *osimage_from_context(DCtx **ctx)
{
gint w, h;
GdkPixbuf *pixbuf = NULL;
cassert_no_null(ctx);
cassert_no_null(*ctx);
w = cairo_image_surface_get_width((*ctx)->surface);
h = cairo_image_surface_get_height((*ctx)->surface);
pixbuf = gdk_pixbuf_get_from_surface((*ctx)->surface, 0, 0, w, h);
if ((*ctx)->format == ekGRAY8)
{
guchar *pixels = gdk_pixbuf_get_pixels(pixbuf);
gboolean alpha = gdk_pixbuf_get_has_alpha(pixbuf);
uint32_t i, j, offset = alpha ? 4 : 3;
int stride = gdk_pixbuf_get_rowstride(pixbuf) - (offset * w);
for (j = 0; j < (uint32_t)h; ++j)
{
for (i = 0; i < (uint32_t)w; ++i)
{
guchar gray = (guchar)((77 * pixels[0] + 148 * pixels[1] + 30 * pixels[2]) / 255);
pixels[0] = gray;
pixels[1] = gray;
pixels[2] = gray;
pixels += offset;
}
pixels += stride;
}
}
dctx_destroy(ctx);
return i_osimage(pixbuf);
}
void osimage_destroy(OSImage **image)
{
cassert_no_null(image);
cassert_no_null(*image);
if ((*image)->pixbuf != NULL)
{
g_object_unref((*image)->pixbuf);
}
else
{
cassert_no_null((*image)->animation);
g_object_unref((*image)->animation);
}
heap_delete(image, OSImage);
}
static bool_t i_gray_image(const byte_t *data, const uint32_t width, const uint32_t height, const uint32_t bpp, const uint32_t stride)
{
uint32_t i = 0, j = 0;
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
if (data[0] != data[1] || data[0] != data[2])
return FALSE;
data += bpp;
}
data += stride;
}
return TRUE;
}
static bool_t i_has_alpha(const byte_t *data, const uint32_t width, const uint32_t height, const uint32_t bpp, const uint32_t stride)
{
uint32_t i = 0, j = 0;
cassert(bpp == 4);
cassert(stride == 0);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
if (data[3] != 255)
return TRUE;
data += bpp;
}
data += stride;
}
return FALSE;
}
static Pixbuf *i_bitmap_pixels(const byte_t *data, const uint32_t width, const uint32_t height, const uint32_t bpp, const uint32_t stride, const pixformat_t format)
{
Pixbuf *pixels = pixbuf_create(width, height, format);
byte_t *pdata = pixbuf_data(pixels);
uint32_t n = width * height, i = 0, j = 0;
if (format == ekRGBA32)
{
cassert(bpp == 4);
cassert(stride == 0);
bmem_copy(pdata, data, n * 4);
}
else if (format == ekRGB24)
{
cassert(bpp == 3 || bpp == 4);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
pdata[0] = data[0];
pdata[1] = data[1];
pdata[2] = data[2];
pdata += 3;
data += bpp;
}
data += stride;
}
}
else
{
cassert(bpp == 1 || bpp == 3 || bpp == 4);
cassert(format == ekGRAY8);
for (j = 0; j < height; ++j)
{
for (i = 0; i < width; ++i)
{
pdata[0] = data[0];
pdata += 1;
data += bpp;
}
data += stride;
}
}
return pixels;
}
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
static ___INLINE GTimeVal i_time_init(void)
{
GTimeVal tval;
tval.tv_sec = 0;
tval.tv_usec = 0;
return tval;
}
static void i_time_add_ms(GTimeVal *tval, const uint32_t msecs)
{
cassert_no_null(tval);
tval->tv_usec += msecs * 1000;
tval->tv_sec += tval->tv_usec / 1000000;
tval->tv_usec = tval->tv_usec % 1000000;
}
#pragma GCC diagnostic pop
static int i_animation_delay(GdkPixbufAnimation *animation, const uint32_t frame)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
GTimeVal tval = i_time_init();
#pragma GCC diagnostic pop
GdkPixbufAnimationIter *iter = gdk_pixbuf_animation_get_iter(animation, &tval);
int delay = gdk_pixbuf_animation_iter_get_delay_time(iter);
uint32_t i = 0;
for (i = 0; i < frame; ++i)
{
gboolean next_frame = FALSE;
#if defined(__ASSERTS__)
i_time_add_ms(&tval, delay - 1);
cassert(gdk_pixbuf_animation_iter_advance(iter, &tval) == FALSE);
i_time_add_ms(&tval, 1);
#else
i_time_add_ms(&tval, delay);
#endif
next_frame = gdk_pixbuf_animation_iter_advance(iter, &tval);
cassert_unref(next_frame == TRUE, next_frame);
delay = gdk_pixbuf_animation_iter_get_delay_time(iter);
}
g_object_unref(iter);
return delay;
}
static const GdkPixbuf *i_animation_pixbuf(GdkPixbufAnimation *animation, const uint32_t frame)
{
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
GTimeVal tval = i_time_init();
#pragma GCC diagnostic pop
GdkPixbufAnimationIter *iter = gdk_pixbuf_animation_get_iter(animation, &tval);
GdkPixbuf *pixbuf = NULL;
uint32_t i = 0;
for (i = 0; i < frame; ++i)
{
int delay = gdk_pixbuf_animation_iter_get_delay_time(iter);
gboolean next_frame = FALSE;
#if defined(__ASSERTS__)
i_time_add_ms(&tval, delay - 1);
cassert(gdk_pixbuf_animation_iter_advance(iter, &tval) == FALSE);
i_time_add_ms(&tval, 1);
#else
i_time_add_ms(&tval, delay);
#endif
next_frame = gdk_pixbuf_animation_iter_advance(iter, &tval);
cassert_unref(next_frame == TRUE, next_frame);
}
pixbuf = gdk_pixbuf_animation_iter_get_pixbuf(iter);
g_object_unref(iter);
return pixbuf;
}
static const GdkPixbuf *i_image_pixbuf(const OSImage *image)
{
cassert_no_null(image);
if (image->pixbuf != NULL)
{
return image->pixbuf;
}
else
{
cassert_no_null(image->animation);
return i_animation_pixbuf(image->animation, 0);
}
}
static ___INLINE uint32_t i_width(const OSImage *image)
{
cassert_no_null(image);
if (image->pixbuf != NULL)
{
return gdk_pixbuf_get_width(image->pixbuf);
}
else
{
cassert_no_null(image->animation);
return gdk_pixbuf_animation_get_width(image->animation);
}
}
static ___INLINE uint32_t i_height(const OSImage *image)
{
cassert_no_null(image);
if (image->pixbuf != NULL)
{
return gdk_pixbuf_get_height(image->pixbuf);
}
else
{
cassert_no_null(image->animation);
return gdk_pixbuf_animation_get_height(image->animation);
}
}
void osimage_info(const OSImage *image, uint32_t *width, uint32_t *height, pixformat_t *format, Pixbuf **pixels)
{
cassert_no_null(image);
if (width != NULL)
*width = i_width(image);
if (height != NULL)
*height = i_height(image);
if (format != NULL || pixels != NULL)
{
pixformat_t lformat = ENUM_MAX(pixformat_t);
const GdkPixbuf *pixbuf = i_image_pixbuf(image);
const byte_t *buffer = cast_const(gdk_pixbuf_get_pixels(pixbuf), byte_t);
uint32_t w = gdk_pixbuf_get_width(pixbuf);
uint32_t h = gdk_pixbuf_get_height(pixbuf);
uint32_t bits_per_pixel = gdk_pixbuf_get_n_channels(pixbuf) * 8;
uint32_t offset = bits_per_pixel / 8;
uint32_t stride = gdk_pixbuf_get_rowstride(pixbuf) - (offset * w);
cassert(bits_per_pixel == 24 || bits_per_pixel == 32);
cassert(w == i_width(image));
cassert(h == i_height(image));
if (bits_per_pixel == 24 && gdk_pixbuf_get_has_alpha(pixbuf) == TRUE)
{
bits_per_pixel = 32;
offset = 4;
}
if (bits_per_pixel == 8)
{
lformat = ekGRAY8;
}
else if (bits_per_pixel == 24)
{
if (i_gray_image(buffer, w, h, 3, stride) == TRUE)
lformat = ekGRAY8;
else
lformat = ekRGB24;
}
else if (bits_per_pixel == 32)
{
if (i_has_alpha(buffer, w, h, 4, stride) == TRUE)
lformat = ekRGBA32;
else if (i_gray_image(buffer, w, h, 4, stride) == TRUE)
lformat = ekGRAY8;
else
lformat = ekRGB24;
}
ptr_assign(format, lformat);
if (pixels != NULL)
{
if (lformat != ENUM_MAX(pixformat_t))
*pixels = i_bitmap_pixels(buffer, w, h, (uint32_t)(bits_per_pixel / 8), stride, lformat);
else
*pixels = NULL;
}
}
}
static ___INLINE const gchar *i_codec(const codec_t codec)
{
switch (codec)
{
case ekJPG:
return "jpeg";
case ekPNG:
return "png";
case ekBMP:
return "bmp";
case ekGIF:
return "gif";
cassert_default();
}
return "";
}
bool_t osimage_available_codec(const OSImage *image, const codec_t codec)
{
GSList *slist0 = gdk_pixbuf_get_formats();
GSList *slist = NULL;
const gchar *scodec = i_codec(codec);
bool_t ok = FALSE;
unref(image);
for (slist = slist0; slist; slist = g_slist_next(slist))
{
GdkPixbufFormat *format = slist->data;
#if (GDK_PIXBUF_MAJOR > 2 || (GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR >= 2))
const gchar *name = gdk_pixbuf_format_get_name(format);
gboolean writable = gdk_pixbuf_format_is_writable(format);
#else
const gchar *name = format->name;
gboolean writable = (format->flags & GDK_PIXBUF_FORMAT_WRITABLE) != 0;
#endif
if (str_equ_c(name, scodec) == TRUE)
{
ok = writable;
break;
}
}
g_slist_free(slist0);
return ok;
}
void osimage_write(const OSImage *image, const codec_t codec, Stream *stream)
{
const char *type = i_codec(codec);
cassert_no_null(image);
if (image->pixbuf != NULL)
{
i_pixbuf_save(image->pixbuf, type, stream);
}
else
{
GdkPixbuf *pixbuf = NULL;
cassert_no_null(image->animation);
pixbuf = gdk_pixbuf_animation_get_static_image(image->animation);
i_pixbuf_save(pixbuf, type, stream);
}
}
void osimage_frames(const OSImage *image, uint32_t *num_frames, uint32_t *num_loops)
{
cassert_no_null(image);
unref(num_loops);
if (num_frames != NULL)
*num_frames = image->num_frames;
}
void osimage_frame(const OSImage *image, const uint32_t frame_index, real32_t *frame_length)
{
cassert_no_null(image);
cassert_no_null(frame_length);
cassert_no_null(image->animation);
cassert(frame_index < image->num_frames);
*frame_length = .001f * i_animation_delay(image->animation, frame_index);
}
const void *osimage_native(const OSImage *image)
{
return cast_const(_osimage_pixbuf(image, 0), void);
}
const GdkPixbuf *_osimage_pixbuf(const OSImage *image, const uint32_t frame_index)
{
cassert_no_null(image);
if (image->pixbuf != NULL)
{
return image->pixbuf;
}
else
{
cassert_no_null(image->animation);
return i_animation_pixbuf(image->animation, frame_index);
}
}