#include "image.h"
#include "image.inl"
#include "imgutil.inl"
#include "dctx.h"
#include "draw.h"
#include "draw.inl"
#include "palette.h"
#include "pixbuf.h"
#include <geom2d/t2d.h>
#include <core/buffer.h>
#include <core/heap.h>
#include <core/hfile.h>
#include <core/respackh.h>
#include <core/stream.h>
#include <core/strings.h>
#include <sewer/bmem.h>
#include <sewer/cassert.h>
#include <sewer/ptr.h>
struct _image_t
{
uint32_t num_instances;
uint32_t num_frames;
real32_t *frame_length;
codec_t codec;
OSImage *osimage;
void *data;
FPtr_destroy func_destroy_data;
};
static Image *i_create_image(const uint32_t num_instances, const uint32_t num_frames, real32_t **frame_length, const codec_t codec, OSImage **osimage)
{
Image *image = heap_new(Image);
image->num_instances = num_instances;
image->num_frames = num_frames;
image->frame_length = ptr_dget(frame_length, real32_t);
image->codec = codec;
image->osimage = ptr_dget_no_null(osimage, OSImage);
image->data = NULL;
image->func_destroy_data = NULL;
return image;
}
void image_destroy(Image **image)
{
cassert_no_null(image);
cassert_no_null(*image);
if ((*image)->num_instances == 1)
{
(*image)->num_instances = 0;
if ((*image)->frame_length != NULL)
heap_free(dcast(&(*image)->frame_length, byte_t), (*image)->num_frames * sizeof32(real32_t), "ImageFrames");
if ((*image)->data != NULL)
{
if ((*image)->func_destroy_data != NULL)
(*image)->func_destroy_data(&(*image)->data);
}
osimage_destroy(&(*image)->osimage);
heap_delete(image, Image);
}
else
{
cassert((*image)->num_instances > 0);
(*image)->num_instances -= 1;
*image = NULL;
}
}
static ___INLINE bool_t i_with_alpha(const color_t *palette, const uint32_t palsize)
{
uint32_t i = 0;
for (i = 0; i < palsize; ++i)
{
if ((byte_t)(palette[i] >> 24) != 255)
return TRUE;
}
return FALSE;
}
static ___INLINE bool_t i_gray_palette(const color_t *palette, const uint32_t palsize)
{
uint32_t i = 0;
for (i = 0; i < palsize; ++i)
{
if (((byte_t)palette[i] != (byte_t)(palette[i] >> 8)) || ((byte_t)palette[i] != (byte_t)(palette[i] >> 16)))
return FALSE;
}
return TRUE;
}
Image *image_from_pixels(const uint32_t width, const uint32_t height, const pixformat_t format, const byte_t *data, const color_t *palette, const uint32_t palsize)
{
OSImage *osimage = NULL;
real32_t *frame_length = NULL;
Pixbuf *rgb_pixels = NULL;
pixformat_t nformat = format;
switch (format)
{
case ekINDEX1:
case ekINDEX2:
case ekINDEX4:
case ekINDEX8:
{
Palette *defpal = NULL;
bool_t alpha_palette = FALSE;
bool_t gray_palette = FALSE;
const color_t *cpalette = palette;
uint32_t bpp = pixbuf_format_bpp(format);
if (cpalette == NULL)
{
defpal = _imgutil_def_palette(format);
cpalette = palette_ccolors(defpal);
}
else
{
alpha_palette = i_with_alpha(palette, palsize);
if (alpha_palette == FALSE)
gray_palette = i_gray_palette(palette, palsize);
}
if (alpha_palette == TRUE)
rgb_pixels = _imgutil_indexed_to_rgba(width, height, data, 0, bpp, cpalette);
else if (gray_palette == TRUE)
rgb_pixels = _imgutil_indexed_to_gray(width, height, data, 0, bpp, cpalette);
else
rgb_pixels = _imgutil_indexed_to_rgb(width, height, data, 0, bpp, cpalette);
ptr_destopt(palette_destroy, &defpal, Palette);
break;
}
case ekGRAY8:
case ekRGB24:
case ekRGBA32:
break;
case ekFIMAGE:
cassert_default();
}
if (rgb_pixels != NULL)
{
nformat = pixbuf_format(rgb_pixels);
cassert(nformat == ekRGB24 || nformat == ekRGBA32);
osimage = osimage_create_from_pixels(width, height, nformat, pixbuf_data(rgb_pixels));
pixbuf_destroy(&rgb_pixels);
}
else
{
osimage = osimage_create_from_pixels(width, height, format, data);
}
return i_create_image(1, PARAM(num_frames, 0), &frame_length, nformat == ekRGB24 ? ekJPG : ekPNG, &osimage);
}
Image *image_from_pixbuf(const Pixbuf *pixbuf, const Palette *palette)
{
return image_from_pixels(
pixbuf_width(pixbuf),
pixbuf_height(pixbuf),
pixbuf_format(pixbuf),
pixbuf_cdata(pixbuf),
palette ? palette_ccolors(palette) : NULL,
palette ? palette_size(palette) : 0);
}
static codec_t i_codec(const byte_t first)
{
if (first == 0x89)
{
return ekPNG;
}
else if (first == 0xFF)
{
return ekJPG;
}
else if (first == 'G')
{
return ekGIF;
}
else if (first == 'B' || first == 'C' || first == 'I' || first == 'P')
{
return ekBMP;
}
else
{
cassert_msg(FALSE, "Unknown image encoding");
return ENUM_MAX(codec_t);
}
}
Image *image_from_file(const char_t *pathname, ferror_t *error)
{
Image *img = NULL;
Buffer *buffer = hfile_buffer(pathname, error);
if (buffer != NULL)
{
const byte_t *data = buffer_data(buffer);
uint32_t size = buffer_size(buffer);
img = image_from_data(data, size);
buffer_destroy(&buffer);
}
return img;
}
Image *image_from_data(const byte_t *data, const uint32_t size)
{
codec_t codec = i_codec(data[0]);
if (codec != ENUM_MAX(codec_t))
{
OSImage *osimage = NULL;
real32_t *frame_length = NULL;
osimage = osimage_create_from_data(data, size);
return i_create_image(1, PARAM(num_frames, 0), &frame_length, codec, &osimage);
}
return NULL;
}
const Image *image_from_resource(const ResPack *pack, const ResId id)
{
return respack_object(pack, id, image_from_data, image_destroy, Image);
}
Image *image_copy(const Image *image)
{
cassert_no_null(image);
cast(image, Image)->num_instances += 1;
return cast(image, Image);
}
Image *image_trim(const Image *image, const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height)
{
T2Df t2d;
DCtx *ctx = dctx_bitmap(width, height, ekRGB24);
t2d_movef(&t2d, kT2D_IDENTf, -(real32_t)x, -(real32_t)y);
draw_matrixf(ctx, &t2d);
draw_image(ctx, image, 0, 0);
return dctx_image(&ctx);
}
static void i_rotated_image_size(const T2Df *t2d, const uint32_t width, const uint32_t height, real32_t *fx, real32_t *fy, uint32_t *fwidth, uint32_t *fheight)
{
real32_t minx = 1e8f;
real32_t maxx = -1e8f;
real32_t miny = 1e8f;
real32_t maxy = -1e8f;
uint32_t i = 0;
V2Df corners[4];
corners[0].x = 0;
corners[0].y = 0;
corners[1].x = (real32_t)width;
corners[1].y = 0;
corners[2].x = (real32_t)width;
corners[2].y = (real32_t)height;
corners[3].x = 0;
corners[3].y = (real32_t)height;
for (i = 0; i < 4; ++i)
{
t2d_vmultf(corners + i, t2d, corners + i);
if (corners[i].x < minx)
minx = corners[i].x;
if (corners[i].x > maxx)
maxx = corners[i].x;
if (corners[i].y < miny)
miny = corners[i].y;
if (corners[i].y > maxy)
maxy = corners[i].y;
}
*fx = minx;
*fy = miny;
*fwidth = (uint32_t)(maxx - minx) + 1;
*fheight = (uint32_t)(maxy - miny) + 1;
}
Image *image_rotate(const Image *image, const real32_t angle, const bool_t nsize, const color_t background, T2Df *t2dc)
{
uint32_t width, height;
uint32_t fwidth, fheight;
real32_t cx, cy;
real32_t fx, fy;
T2Df t2d;
DCtx *ctx = NULL;
cassert_no_null(image);
osimage_info(image->osimage, &width, &height, NULL, NULL);
cx = width / 2.f;
cy = height / 2.f;
t2d_movef(&t2d, kT2D_IDENTf, cx, cy);
t2d_rotatef(&t2d, &t2d, angle);
t2d_movef(&t2d, &t2d, -cx, -cy);
if (nsize == TRUE)
{
i_rotated_image_size(&t2d, width, height, &fx, &fy, &fwidth, &fheight);
}
else
{
fx = 0;
fy = 0;
fwidth = width;
fheight = height;
}
ctx = dctx_bitmap(fwidth, fheight, ekRGB24);
draw_clear(ctx, background);
t2d_movef(&t2d, kT2D_IDENTf, -fx + cx, -fy + cy);
t2d_rotatef(&t2d, &t2d, angle);
t2d_movef(&t2d, &t2d, -cx, -cy);
draw_matrixf(ctx, &t2d);
if (t2dc != NULL)
*t2dc = t2d;
draw_image(ctx, image, 0, 0);
return dctx_image(&ctx);
}
Image *image_scale(const Image *image, const uint32_t nwidth, const uint32_t nheight)
{
uint32_t width = nwidth, height = nheight;
uint32_t current_width, current_height;
cassert_no_null(image);
if (width == UINT32_MAX && height == UINT32_MAX)
{
cast(image, Image)->num_instances += 1;
return cast(image, Image);
}
osimage_info(image->osimage, ¤t_width, ¤t_height, NULL, NULL);
if (width == UINT32_MAX)
{
width = (current_width * nheight) / current_height;
}
else if (height == UINT32_MAX)
{
height = (current_height * nwidth) / current_width;
}
if (current_width != width || current_height != height)
{
OSImage *osimage = NULL;
real32_t *frame_length = NULL;
cassert_no_null(image);
osimage = osimage_create_scaled(image->osimage, width, height);
return i_create_image(1, PARAM(num_frames, 0), &frame_length, image->codec, &osimage);
}
else
{
cast(image, Image)->num_instances += 1;
return cast(image, Image);
}
}
Image *image_read(Stream *stm)
{
if (stm_is_memory(stm) == TRUE)
{
const byte_t *data = stm_buffer(stm);
uint64_t st = stm_bytes_readed(stm);
if (_imgutil_parse(stm, NULL) == TRUE)
{
uint64_t ed = stm_bytes_readed(stm);
return image_from_data(data, (uint32_t)(ed - st));
}
else
{
return NULL;
}
}
else
{
Image *image = NULL;
Stream *stm_out = stm_memory(4096);
if (_imgutil_parse(stm, stm_out) == TRUE)
{
const byte_t *data = stm_buffer(stm_out);
uint32_t size = stm_buffer_size(stm_out);
image = image_from_data(data, size);
}
stm_close(&stm_out);
return image;
}
}
bool_t image_to_file(const Image *image, const char_t *pathname, ferror_t *error)
{
Stream *stm = stm_to_file(pathname, error);
if (stm)
{
image_write(stm, image);
stm_close(&stm);
return TRUE;
}
return FALSE;
}
void image_write(Stream *stream, const Image *image)
{
cassert_no_null(image);
cassert(osimage_available_codec(image->osimage, image->codec) == TRUE);
osimage_write(image->osimage, image->codec, stream);
}
pixformat_t image_format(const Image *image)
{
pixformat_t format = ENUM_MAX(pixformat_t);
cassert_no_null(image);
osimage_info(image->osimage, NULL, NULL, &format, NULL);
return format;
}
uint32_t image_width(const Image *image)
{
uint32_t w;
cassert_no_null(image);
osimage_info(image->osimage, &w, NULL, NULL, NULL);
return w;
}
uint32_t image_height(const Image *image)
{
uint32_t h;
cassert_no_null(image);
osimage_info(image->osimage, NULL, &h, NULL, NULL);
return h;
}
Pixbuf *image_pixels(const Image *image, const pixformat_t format)
{
Pixbuf *pixels = NULL;
cassert_no_null(image);
cassert(format == ekRGB24 || format == ekRGBA32 || format == ekGRAY8 || format == ekFIMAGE);
osimage_info(image->osimage, NULL, NULL, NULL, &pixels);
if (pixels && format != ekFIMAGE && pixbuf_format(pixels) != format)
{
Pixbuf *npixels = pixbuf_convert(pixels, NULL, format);
pixbuf_destroy(&pixels);
pixels = npixels;
}
return pixels;
}
bool_t image_codec(const Image *image, const codec_t codec)
{
cassert_no_null(image);
if (osimage_available_codec(image->osimage, codec) == TRUE)
{
cast(image, Image)->codec = codec;
return TRUE;
}
return FALSE;
}
codec_t image_get_codec(const Image *image)
{
cassert_no_null(image);
return image->codec;
}
static void i_frames(Image *image)
{
cassert_no_null(image);
cassert(image->num_frames == 0);
cassert(image->frame_length == NULL);
osimage_frames(image->osimage, &image->num_frames, NULL);
cassert(image->num_frames > 0);
if (image->num_frames > 1)
{
uint32_t i = 0;
image->frame_length = cast(heap_malloc(image->num_frames * sizeof32(real32_t), "ImageFrames"), real32_t);
for (i = 0; i < image->num_frames; ++i)
osimage_frame(image->osimage, i, image->frame_length + i);
}
}
uint32_t image_num_frames(const Image *image)
{
cassert_no_null(image);
if (image->num_frames == 0)
i_frames(cast(image, Image));
return image->num_frames;
}
real32_t image_frame_length(const Image *image, const uint32_t findex)
{
cassert_no_null(image);
if (image->num_frames == 0)
i_frames(cast(image, Image));
cassert(findex < image->num_frames);
return image->frame_length[findex];
}
void image_data_imp(Image *image, void **data, FPtr_destroy func_destroy_data)
{
cassert_no_null(image);
cassert(image->data == NULL);
cassert(image->func_destroy_data == NULL);
image->data = ptr_dget_no_null(data, void);
image->func_destroy_data = func_destroy_data;
}
void *image_get_data_imp(const Image *image)
{
cassert_no_null(image);
return image->data;
}
const OSImage *osimage_from_image(const Image *image)
{
cassert_no_null(image);
return image->osimage;
}
const void *image_native(const Image *image)
{
cassert_no_null(image);
return osimage_native(image->osimage);
}
Image *dctx_image(DCtx **ctx)
{
OSImage *osimage = NULL;
real32_t *frame_length = NULL;
osimage = osimage_from_context(ctx);
return i_create_image(1, PARAM(num_frames, 0), &frame_length, ekPNG, &osimage);
}
void draw_image(DCtx *ctx, const Image *image, const real32_t x, const real32_t y)
{
cassert_no_null(image);
_draw_imgimp(ctx, image->osimage, UINT32_MAX, x, y, FALSE);
}
void draw_image_frame(DCtx *ctx, const Image *image, const uint32_t frame, const real32_t x, const real32_t y)
{
cassert_no_null(image);
_draw_imgimp(ctx, image->osimage, frame, x, y, FALSE);
}