Evas: add 'qoi' image loader and saver
This commit is contained in:
parent
60a7c31dd0
commit
216c5a15a0
|
@ -68,6 +68,8 @@ static const struct ext_loader_s loaders[] =
|
|||
|
||||
MATCHING(".jxl", "jxl"),
|
||||
|
||||
MATCHING(".qoi", "qoi"),
|
||||
|
||||
MATCHING(".avif", "avif"),
|
||||
MATCHING(".avifs", "avif"),
|
||||
|
||||
|
@ -195,8 +197,8 @@ static const struct ext_loader_s loaders[] =
|
|||
static const char *loaders_name[] =
|
||||
{ /* in order of most likely needed */
|
||||
"png", "jpeg", "eet", "xpm", "tiff", "gif", "svg", "webp", "pmaps",
|
||||
"bmp", "tga", "wbmp", "ico", "psd", "jp2k", "dds", "jxl", "avif", "heif",
|
||||
"generic"
|
||||
"bmp", "tga", "wbmp", "ico", "psd", "jp2k", "dds", "jxl", "qoi", "avif",
|
||||
"heif", "generic"
|
||||
};
|
||||
|
||||
struct evas_image_foreach_loader_data
|
||||
|
|
|
@ -37,6 +37,8 @@ evas_common_save_image_to_file(RGBA_Image *im, const char *file, const char *key
|
|||
saver = "avif";
|
||||
if (!strcasecmp(p, "jxl"))
|
||||
saver = "jxl";
|
||||
if (!strcasecmp(p, "qoi"))
|
||||
saver = "qoi";
|
||||
}
|
||||
|
||||
if (saver)
|
||||
|
|
|
@ -210,6 +210,7 @@ EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, jxl);
|
|||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, pmaps);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, png);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, psd);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, qoi);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, svg);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, tga);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_loader, tiff);
|
||||
|
@ -230,6 +231,7 @@ EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, eet);
|
|||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, jpeg);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, jxl);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, png);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, qoi);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, tiff);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, webp);
|
||||
EVAS_EINA_STATIC_MODULE_DEFINE(image_saver, tgv);
|
||||
|
@ -334,6 +336,9 @@ static const struct {
|
|||
#ifdef EVAS_STATIC_BUILD_PSD
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_loader, psd),
|
||||
#endif
|
||||
#ifdef EVAS_STATIC_BUILD_QOI
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_loader, qoi),
|
||||
#endif
|
||||
#ifdef EVAS_STATIC_BUILD_SVG
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_loader, svg),
|
||||
#endif
|
||||
|
@ -380,6 +385,9 @@ static const struct {
|
|||
#ifdef EVAS_STATIC_BUILD_PNG
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_saver, png),
|
||||
#endif
|
||||
#ifdef EVAS_STATIC_BUILD_QOI
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_saver, qoi),
|
||||
#endif
|
||||
#ifdef EVAS_STATIC_BUILD_TIFF
|
||||
EVAS_EINA_STATIC_MODULE_USE(image_saver, tiff),
|
||||
#endif
|
||||
|
|
|
@ -29,6 +29,7 @@ evas_image_loaders_file = [
|
|||
['pmaps', 'shared', []],
|
||||
['png', 'static', [png]],
|
||||
['psd', 'shared', []],
|
||||
['qoi', 'shared', []],
|
||||
['tga', 'shared', []],
|
||||
['tgv', 'shared', [rg_etc, lz4]],
|
||||
['tiff', 'shared', [tiff]],
|
||||
|
@ -43,6 +44,7 @@ evas_image_savers_file = [
|
|||
['jpeg', 'static', [jpeg]],
|
||||
['jxl' , 'shared', [libjxl, libjxl_threads]],
|
||||
['png', 'static', [png]],
|
||||
['qoi', 'shared', []],
|
||||
['tgv', 'shared', [rg_etc, lz4]],
|
||||
['tiff', 'shared', [tiff]],
|
||||
['webp', 'shared', [webp]],
|
||||
|
|
|
@ -0,0 +1,416 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "Evas_Loader.h"
|
||||
#include "evas_common_private.h"
|
||||
|
||||
/*
|
||||
* code based on original qoi.h code (MIT license):
|
||||
* https://github.com/phoboslab/qoi/blob/master/qoi.h
|
||||
* date: 2023 march the 14th
|
||||
*/
|
||||
|
||||
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
|
||||
|
||||
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
|
||||
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
|
||||
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
|
||||
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
|
||||
#define QOI_OP_RGB 0xfe /* 11111110 */
|
||||
#define QOI_OP_RGBA 0xff /* 11111111 */
|
||||
|
||||
#define QOI_MASK_2 0xc0 /* 11000000 */
|
||||
|
||||
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
|
||||
|
||||
#define QOI_MAGIC \
|
||||
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
|
||||
((unsigned int)'i') << 8 | ((unsigned int)'f'))
|
||||
|
||||
#define QOI_HEADER_SIZE 14
|
||||
|
||||
#define QOI_PIXELS_MAX ((unsigned int)400000000)
|
||||
|
||||
#ifdef ERR
|
||||
# undef ERR
|
||||
#endif
|
||||
#define ERR(...) EINA_LOG_DOM_ERR(_evas_loader_qoi_log_dom, __VA_ARGS__)
|
||||
|
||||
#ifdef WRN
|
||||
# undef WRN
|
||||
#endif
|
||||
#define WRN(...) EINA_LOG_DOM_WARN(_evas_loader_qoi_log_dom, __VA_ARGS__)
|
||||
|
||||
#ifdef INF
|
||||
# undef INF
|
||||
#endif
|
||||
#define INF(...) EINA_LOG_DOM_INFO(_evas_loader_qoi_log_dom, __VA_ARGS__)
|
||||
|
||||
typedef struct _Evas_Loader_Internal Evas_Loader_Internal;
|
||||
struct _Evas_Loader_Internal
|
||||
{
|
||||
Eina_File *f;
|
||||
Evas_Image_Load_Opts *opts;
|
||||
Evas_Image_Animated *animated;
|
||||
};
|
||||
|
||||
typedef union {
|
||||
struct { unsigned char r, g, b, a; } rgba;
|
||||
unsigned int v;
|
||||
} qoi_rgba_t;
|
||||
|
||||
static int _evas_loader_qoi_log_dom = -1;
|
||||
|
||||
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
|
||||
|
||||
static unsigned int read_32(const unsigned char *data, int *p)
|
||||
{
|
||||
unsigned int a = data[(*p)++];
|
||||
unsigned int b = data[(*p)++];
|
||||
unsigned int c = data[(*p)++];
|
||||
unsigned int d = data[(*p)++];
|
||||
return a << 24 | b << 16 | c << 8 | d;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
evas_image_load_file_head_qoi_internal(Evas_Loader_Internal *loader EINA_UNUSED,
|
||||
Emile_Image_Property *prop,
|
||||
void *map, size_t length,
|
||||
int *error)
|
||||
{
|
||||
const unsigned char *bytes;
|
||||
unsigned int magic;
|
||||
unsigned char channels;
|
||||
unsigned char colorspace;
|
||||
int p = 0;
|
||||
Eina_Bool ret;
|
||||
|
||||
ret = EINA_FALSE;
|
||||
prop->w = 0;
|
||||
prop->h = 0;
|
||||
prop->alpha = EINA_FALSE;
|
||||
|
||||
if (length < QOI_HEADER_SIZE + sizeof(qoi_padding))
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bytes = (const unsigned char *)map;
|
||||
|
||||
magic = read_32(bytes, &p);
|
||||
if (magic != QOI_MAGIC)
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
prop->w = read_32(bytes, &p);
|
||||
prop->h = read_32(bytes, &p);
|
||||
if ((prop->w < 1) ||
|
||||
(prop->h < 1) ||
|
||||
(prop->h >= QOI_PIXELS_MAX / prop->w) ||
|
||||
(prop->w > IMG_MAX_SIZE) ||
|
||||
(prop->h > IMG_MAX_SIZE) ||
|
||||
IMG_TOO_BIG(prop->w, prop->h))
|
||||
{
|
||||
*error= EVAS_LOAD_ERROR_GENERIC;
|
||||
return ret;
|
||||
}
|
||||
|
||||
channels = bytes[p++];
|
||||
colorspace = bytes[p++];
|
||||
|
||||
if ((channels < 3) ||
|
||||
(channels > 4) ||
|
||||
(colorspace > 1))
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
prop->alpha = channels == 4;
|
||||
|
||||
*error = EVAS_LOAD_ERROR_NONE;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
evas_image_load_file_data_qoi_internal(Evas_Loader_Internal *loader EINA_UNUSED,
|
||||
Emile_Image_Property *prop,
|
||||
void *pixels,
|
||||
void *map, size_t length,
|
||||
int *error)
|
||||
{
|
||||
qoi_rgba_t index[64];
|
||||
const unsigned char *bytes;
|
||||
qoi_rgba_t px;
|
||||
unsigned int *iter;
|
||||
unsigned int magic;
|
||||
unsigned char channels;
|
||||
unsigned char colorspace;
|
||||
int p = 0;
|
||||
int run = 0;
|
||||
int chunks_len;
|
||||
size_t px_len;
|
||||
size_t px_pos;
|
||||
Eina_Bool ret;
|
||||
|
||||
ret = EINA_FALSE;
|
||||
prop->w = 0;
|
||||
prop->h = 0;
|
||||
prop->alpha = EINA_FALSE;
|
||||
|
||||
if (length < QOI_HEADER_SIZE + sizeof(qoi_padding))
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
bytes = (const unsigned char *)map;
|
||||
|
||||
magic = read_32(bytes, &p);
|
||||
if (magic != QOI_MAGIC)
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_UNKNOWN_FORMAT;
|
||||
return ret;
|
||||
}
|
||||
|
||||
prop->w = read_32(bytes, &p);
|
||||
prop->h = read_32(bytes, &p);
|
||||
if ((prop->w < 1) ||
|
||||
(prop->h < 1) ||
|
||||
(prop->h >= QOI_PIXELS_MAX / prop->w) ||
|
||||
(prop->w > IMG_MAX_SIZE) ||
|
||||
(prop->h > IMG_MAX_SIZE) ||
|
||||
IMG_TOO_BIG(prop->w, prop->h))
|
||||
{
|
||||
*error= EVAS_LOAD_ERROR_GENERIC;
|
||||
return ret;
|
||||
}
|
||||
|
||||
channels = bytes[p++];
|
||||
colorspace = bytes[p++];
|
||||
|
||||
if ((channels < 3) ||
|
||||
(channels > 4) ||
|
||||
(colorspace > 1))
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_CORRUPT_FILE;
|
||||
return ret;
|
||||
}
|
||||
|
||||
prop->alpha = channels == 4;
|
||||
|
||||
px_len = prop->w * prop->h * channels;
|
||||
|
||||
QOI_ZEROARR(index);
|
||||
px.rgba.r = 0;
|
||||
px.rgba.g = 0;
|
||||
px.rgba.b = 0;
|
||||
px.rgba.a = 255;
|
||||
|
||||
iter = pixels;
|
||||
chunks_len = length - (int)sizeof(qoi_padding);
|
||||
for (px_pos = 0; px_pos < px_len; px_pos += channels, iter++)
|
||||
{
|
||||
if (run > 0)
|
||||
{
|
||||
run--;
|
||||
}
|
||||
else if (p < chunks_len)
|
||||
{
|
||||
int b1 = bytes[p++];
|
||||
|
||||
if (b1 == QOI_OP_RGB)
|
||||
{
|
||||
px.rgba.r = bytes[p++];
|
||||
px.rgba.g = bytes[p++];
|
||||
px.rgba.b = bytes[p++];
|
||||
}
|
||||
else if (b1 == QOI_OP_RGBA)
|
||||
{
|
||||
px.rgba.r = bytes[p++];
|
||||
px.rgba.g = bytes[p++];
|
||||
px.rgba.b = bytes[p++];
|
||||
px.rgba.a = bytes[p++];
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_INDEX)
|
||||
{
|
||||
px = index[b1];
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_DIFF)
|
||||
{
|
||||
px.rgba.r += ((b1 >> 4) & 0x03) - 2;
|
||||
px.rgba.g += ((b1 >> 2) & 0x03) - 2;
|
||||
px.rgba.b += ( b1 & 0x03) - 2;
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_LUMA)
|
||||
{
|
||||
int b2 = bytes[p++];
|
||||
int vg = (b1 & 0x3f) - 32;
|
||||
px.rgba.r += vg - 8 + ((b2 >> 4) & 0x0f);
|
||||
px.rgba.g += vg;
|
||||
px.rgba.b += vg - 8 + (b2 & 0x0f);
|
||||
}
|
||||
else if ((b1 & QOI_MASK_2) == QOI_OP_RUN)
|
||||
{
|
||||
run = (b1 & 0x3f);
|
||||
}
|
||||
|
||||
index[QOI_COLOR_HASH(px) % 64] = px;
|
||||
}
|
||||
|
||||
*iter = ((prop->alpha ? px.rgba.a : 255) << 24) | (px.rgba.r << 16) | (px.rgba.g << 8) | px.rgba.b;
|
||||
}
|
||||
|
||||
*error = EVAS_LOAD_ERROR_NONE;
|
||||
return EINA_TRUE;
|
||||
}
|
||||
|
||||
static void *
|
||||
evas_image_load_file_open_qoi(Eina_File *f, Eina_Stringshare *key EINA_UNUSED,
|
||||
Evas_Image_Load_Opts *opts,
|
||||
Evas_Image_Animated *animated EINA_UNUSED,
|
||||
int *error)
|
||||
{
|
||||
Evas_Loader_Internal *loader;
|
||||
|
||||
loader = calloc(1, sizeof (Evas_Loader_Internal));
|
||||
if (!loader)
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_RESOURCE_ALLOCATION_FAILED;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
loader->f = f;
|
||||
loader->opts = opts;
|
||||
|
||||
return loader;
|
||||
}
|
||||
|
||||
static void
|
||||
evas_image_load_file_close_qoi(void *loader_data)
|
||||
{
|
||||
free(loader_data);
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
evas_image_load_file_head_qoi(void *loader_data,
|
||||
Evas_Image_Property *prop,
|
||||
int *error)
|
||||
{
|
||||
Evas_Loader_Internal *loader = loader_data;
|
||||
Eina_File *f;
|
||||
void *map;
|
||||
Eina_Bool val;
|
||||
|
||||
f = loader->f;
|
||||
|
||||
map = eina_file_map_all(f, EINA_FILE_RANDOM);
|
||||
if (!map)
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_DOES_NOT_EXIST;
|
||||
return EINA_FALSE;
|
||||
}
|
||||
|
||||
val = evas_image_load_file_head_qoi_internal(loader,
|
||||
(Emile_Image_Property *)prop,
|
||||
map, eina_file_size_get(f),
|
||||
error);
|
||||
|
||||
eina_file_map_free(f, map);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
static Eina_Bool
|
||||
evas_image_load_file_data_qoi(void *loader_data,
|
||||
Evas_Image_Property *prop,
|
||||
void *pixels,
|
||||
int *error)
|
||||
{
|
||||
Evas_Loader_Internal *loader;
|
||||
Eina_File *f;
|
||||
void *map;
|
||||
Eina_Bool val = EINA_FALSE;
|
||||
|
||||
loader = (Evas_Loader_Internal *)loader_data;
|
||||
f = loader->f;
|
||||
|
||||
map = eina_file_map_all(f, EINA_FILE_WILLNEED);
|
||||
if (!map)
|
||||
{
|
||||
*error = EVAS_LOAD_ERROR_DOES_NOT_EXIST;
|
||||
goto on_error;
|
||||
}
|
||||
|
||||
val = evas_image_load_file_data_qoi_internal(loader,
|
||||
(Emile_Image_Property *)prop,
|
||||
pixels,
|
||||
map, eina_file_size_get(f),
|
||||
error);
|
||||
|
||||
eina_file_map_free(f, map);
|
||||
|
||||
on_error:
|
||||
return val;
|
||||
}
|
||||
|
||||
static Evas_Image_Load_Func evas_image_load_qoi_func =
|
||||
{
|
||||
EVAS_IMAGE_LOAD_VERSION,
|
||||
evas_image_load_file_open_qoi,
|
||||
evas_image_load_file_close_qoi,
|
||||
evas_image_load_file_head_qoi,
|
||||
NULL,
|
||||
evas_image_load_file_data_qoi,
|
||||
NULL,
|
||||
EINA_TRUE,
|
||||
EINA_FALSE
|
||||
};
|
||||
|
||||
static int
|
||||
module_open(Evas_Module *em)
|
||||
{
|
||||
if (!em) return 0;
|
||||
|
||||
_evas_loader_qoi_log_dom = eina_log_domain_register("evas-qoi", EINA_COLOR_BLUE);
|
||||
if (_evas_loader_qoi_log_dom < 0)
|
||||
{
|
||||
EINA_LOG_ERR("Can not create a module log domain.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
em->functions = (void *)(&evas_image_load_qoi_func);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
module_close(Evas_Module *em EINA_UNUSED)
|
||||
{
|
||||
if (_evas_loader_qoi_log_dom >= 0)
|
||||
{
|
||||
eina_log_domain_unregister(_evas_loader_qoi_log_dom);
|
||||
_evas_loader_qoi_log_dom = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static Evas_Module_Api evas_modapi =
|
||||
{
|
||||
EVAS_MODULE_API_VERSION,
|
||||
"qoi",
|
||||
"none",
|
||||
{
|
||||
module_open,
|
||||
module_close
|
||||
}
|
||||
};
|
||||
|
||||
EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_LOADER, image_loader, qoi);
|
||||
|
||||
#ifndef EVAS_STATIC_BUILD_QOI
|
||||
EVAS_EINA_MODULE_DEFINE(image_loader, qoi);
|
||||
#endif
|
|
@ -0,0 +1,264 @@
|
|||
#ifdef HAVE_CONFIG_H
|
||||
# include <config.h>
|
||||
#endif
|
||||
|
||||
#include "evas_common_private.h"
|
||||
#include "evas_private.h"
|
||||
|
||||
/*
|
||||
* code based on original qoi.h code (MIT license):
|
||||
* https://github.com/phoboslab/qoi/blob/master/qoi.h
|
||||
* date: 2023 march the 14th
|
||||
*/
|
||||
|
||||
#define QOI_SRGB 0
|
||||
|
||||
#define QOI_ZEROARR(a) memset((a),0,sizeof(a))
|
||||
|
||||
#define QOI_OP_INDEX 0x00 /* 00xxxxxx */
|
||||
#define QOI_OP_DIFF 0x40 /* 01xxxxxx */
|
||||
#define QOI_OP_LUMA 0x80 /* 10xxxxxx */
|
||||
#define QOI_OP_RUN 0xc0 /* 11xxxxxx */
|
||||
#define QOI_OP_RGB 0xfe /* 11111110 */
|
||||
#define QOI_OP_RGBA 0xff /* 11111111 */
|
||||
|
||||
#define QOI_COLOR_HASH(C) (C.rgba.r*3 + C.rgba.g*5 + C.rgba.b*7 + C.rgba.a*11)
|
||||
|
||||
#define QOI_MAGIC \
|
||||
(((unsigned int)'q') << 24 | ((unsigned int)'o') << 16 | \
|
||||
((unsigned int)'i') << 8 | ((unsigned int)'f'))
|
||||
|
||||
#define QOI_HEADER_SIZE 14
|
||||
|
||||
#define QOI_PIXELS_MAX ((unsigned int)400000000)
|
||||
|
||||
typedef union {
|
||||
struct { unsigned char r, g, b, a; } rgba;
|
||||
unsigned int v;
|
||||
} qoi_rgba_t;
|
||||
|
||||
static const unsigned char qoi_padding[8] = {0,0,0,0,0,0,0,1};
|
||||
|
||||
static int
|
||||
save_image_qoi(RGBA_Image *im, const char *file, int quality EINA_UNUSED)
|
||||
{
|
||||
qoi_rgba_t index[64];
|
||||
qoi_rgba_t px;
|
||||
qoi_rgba_t px_prev;
|
||||
FILE *f;
|
||||
unsigned char *iter;
|
||||
unsigned int channels;
|
||||
unsigned int colorspace;
|
||||
unsigned int v;
|
||||
int run;
|
||||
int px_len;
|
||||
int px_end;
|
||||
int px_pos;
|
||||
int i;
|
||||
int ret = 0;
|
||||
|
||||
if (!im || !im->image.data || !file || !*file)
|
||||
return ret;
|
||||
|
||||
if (im->cache_entry.h >= QOI_PIXELS_MAX / im->cache_entry.w)
|
||||
return ret;
|
||||
|
||||
f = fopen(file, "wb");
|
||||
if (!f)
|
||||
return ret;
|
||||
|
||||
v = (0xff000000 & QOI_MAGIC) >> 24;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x00ff0000 & QOI_MAGIC) >> 16;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x0000ff00 & QOI_MAGIC) >> 8;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x000000ff & QOI_MAGIC);
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
v = (0xff000000 & im->cache_entry.w) >> 24;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x00ff0000 & im->cache_entry.w) >> 16;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x0000ff00 & im->cache_entry.w) >> 8;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x000000ff & im->cache_entry.w);
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
v = (0xff000000 & im->cache_entry.h) >> 24;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x00ff0000 & im->cache_entry.h) >> 16;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x0000ff00 & im->cache_entry.h) >> 8;
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
v = (0x000000ff & im->cache_entry.h);
|
||||
if (fwrite(&v, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
channels = 4;
|
||||
if (fwrite(&channels, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
colorspace = QOI_SRGB;
|
||||
if (fwrite(&colorspace, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
QOI_ZEROARR(index);
|
||||
|
||||
run = 0;
|
||||
px_prev.rgba.r = 0;
|
||||
px_prev.rgba.g = 0;
|
||||
px_prev.rgba.b = 0;
|
||||
px_prev.rgba.a = 255;
|
||||
px = px_prev;
|
||||
|
||||
px_len = im->cache_entry.w * im->cache_entry.h * channels;
|
||||
px_end = px_len - channels;
|
||||
|
||||
iter = (unsigned char *)im->image.data;
|
||||
for (px_pos = 0; px_pos < px_len; px_pos += channels, iter +=4)
|
||||
{
|
||||
px.rgba.b = *(iter + 0);
|
||||
px.rgba.g = *(iter + 1);
|
||||
px.rgba.r = *(iter + 2);
|
||||
px.rgba.a = *(iter + 3);
|
||||
|
||||
if (px.v == px_prev.v)
|
||||
{
|
||||
run++;
|
||||
if (run == 62 || px_pos == px_end)
|
||||
{
|
||||
unsigned char val = QOI_OP_RUN | (run - 1);
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
run = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int index_pos;
|
||||
|
||||
if (run > 0)
|
||||
{
|
||||
unsigned char val = QOI_OP_RUN | (run - 1);
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
run = 0;
|
||||
}
|
||||
|
||||
index_pos = QOI_COLOR_HASH(px) % 64;
|
||||
|
||||
if (index[index_pos].v == px.v)
|
||||
{
|
||||
unsigned char val = QOI_OP_INDEX | index_pos;
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
else
|
||||
{
|
||||
index[index_pos] = px;
|
||||
|
||||
if (px.rgba.a == px_prev.rgba.a)
|
||||
{
|
||||
signed char vr = px.rgba.r - px_prev.rgba.r;
|
||||
signed char vg = px.rgba.g - px_prev.rgba.g;
|
||||
signed char vb = px.rgba.b - px_prev.rgba.b;
|
||||
|
||||
signed char vg_r = vr - vg;
|
||||
signed char vg_b = vb - vg;
|
||||
|
||||
if (vr > -3 && vr < 2 &&
|
||||
vg > -3 && vg < 2 &&
|
||||
vb > -3 && vb < 2)
|
||||
{
|
||||
unsigned char val = QOI_OP_DIFF | (vr + 2) << 4 | (vg + 2) << 2 | (vb + 2);
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
else if (vg_r > -9 && vg_r < 8 &&
|
||||
vg > -33 && vg < 32 &&
|
||||
vg_b > -9 && vg_b < 8)
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
val = QOI_OP_LUMA | (vg + 32);
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
|
||||
val = (vg_r + 8) << 4 | (vg_b + 8);
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
val = QOI_OP_RGB;
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.r, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.g, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.b, 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned char val;
|
||||
|
||||
val = QOI_OP_RGBA;
|
||||
if (fwrite(&val, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.r, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.g, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.b, 1, 1, f) != 1) goto close_f;
|
||||
if (fwrite(&px.rgba.a, 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
}
|
||||
}
|
||||
px_prev = px;
|
||||
}
|
||||
|
||||
for (i = 0; i < (int)sizeof(qoi_padding); i++)
|
||||
{
|
||||
if (fwrite(&qoi_padding[i], 1, 1, f) != 1) goto close_f;
|
||||
}
|
||||
|
||||
ret = 1;
|
||||
|
||||
close_f:
|
||||
fclose(f);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static int evas_image_save_file_qoi(RGBA_Image *im, const char *file, const char *key EINA_UNUSED,
|
||||
int quality, int compress EINA_UNUSED, const char *encoding EINA_UNUSED)
|
||||
{
|
||||
return save_image_qoi(im, file, quality);
|
||||
}
|
||||
|
||||
|
||||
static Evas_Image_Save_Func evas_image_save_qoi_func =
|
||||
{
|
||||
evas_image_save_file_qoi
|
||||
};
|
||||
|
||||
static int
|
||||
module_open(Evas_Module *em)
|
||||
{
|
||||
if (!em) return 0;
|
||||
em->functions = (void *)(&evas_image_save_qoi_func);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
module_close(Evas_Module *em EINA_UNUSED)
|
||||
{
|
||||
}
|
||||
|
||||
static Evas_Module_Api evas_modapi =
|
||||
{
|
||||
EVAS_MODULE_API_VERSION,
|
||||
"qoi",
|
||||
"none",
|
||||
{
|
||||
module_open,
|
||||
module_close
|
||||
}
|
||||
};
|
||||
|
||||
EVAS_MODULE_DEFINE(EVAS_MODULE_TYPE_IMAGE_SAVER, image_saver, qoi);
|
||||
|
||||
#ifndef EVAS_STATIC_BUILD_QOI
|
||||
EVAS_EINA_MODULE_DEFINE(image_saver, qoi);
|
||||
#endif
|
Loading…
Reference in New Issue