image: clean up palette semantics, and conversion

This commit is contained in:
Lephe 2022-05-14 15:36:07 +01:00
parent fc6f7d3051
commit 9468a8d725
No known key found for this signature in database
GPG key ID: 1BBA026E13FC0495
12 changed files with 283 additions and 99 deletions

View file

@ -162,6 +162,7 @@ set(SOURCES_CG
src/image/image_alpha.c
src/image/image_clear.c
src/image/image_copy.c
src/image/image_copy_alloc.c
src/image/image_copy_palette.c
src/image/image_create.c
src/image/image_create_vram.c

View file

@ -92,9 +92,11 @@ typedef struct
uint8_t format;
/* Additional flags, a combination of IMAGE_FLAGS_* values */
uint8_t flags;
/* For P8 and P4, number of colors in the palette; this includes alpha for
transparent images, since alpha is always the first entry. For valid P4
images this is always 16, while for P8 it ranges between 1 and 256. */
/* Number of colors in the palette; this includes alpha for transparent
images, as alpha is always the first entry.
RGB16: 0
P8: Ranges between 1 and 256
P4: 16 */
int16_t color_count;
/* Full width and height, in pixels */
uint16_t width;
@ -103,17 +105,12 @@ typedef struct
int stride;
/* Pixel data in row-major order, left to right.
RGB16:
- 2 bytes per entry, each row padded to 4 bytes for alignment
- Each 2-byte value is an RGB565 color
P8:
- 1 byte per entry
- Each byte is a palette index shifted by 128 (to access the color, use
palette[<value>+128])
P4:
- 4 bits per entry, each row padded to a full byte
- Each entry is a palette index (0...15) */
- RGB16: 2 bytes per entry, each row padded to 4 bytes for alignment.
Each 2-byte value is an RGB565 color.
- P8: 1 signed byte per entry. Each byte is a palette index shifted by
128 (to access the color, use palette[<value>+128]).
- P4: 4 bits per entry, each row padded to a full byte. Each entry is a
direct palette index between 0 and 15. */
void *data;
/* For P8 and P4, color palette. The number of entries allocated in the
@ -173,46 +170,67 @@ enum {
/* image_alloc(): Create a new (uninitialized) image
This function allocates a new image of the specified dimensions and format.
It always allocates a new data array, though a palette from another image
can be reused. (If you need to reuse a data array, see image_create() below
or use img_create_sub().)
It always allocates a new data array; if you need to reuse a data array, use
the lower-level image_create() or image_create_sub().
The first parameters [width] and [height] specify the dimensions of the new
image in pixels. The [format] should be one of the IMAGE_* formats, for
example IMAGE_RGB565A or IMAGE_P4_RGB565.
By default, a new palette is allocated for formats with palettes; the new
image owns the palette and frees it when freed. This can be overriden by
setting the [palette] argument to the desired palette; in this case, the new
image does not own the palette and does not free it when freed. For formats
with alpha the first entry of the palette is the alpha color.
Regardless of whether the palette is allocated or specified by hand, for P8
the palette size must be indicated. A value of -1 can be specified to use
the default (256 colors). For all other formats, set a value of -1.
This function does not specify or initialize the palette of the new image;
use image_set_palette(), image_alloc_palette() or image_copy_palette()
after calling this function.
The returned image structure must be freed with image_free() after use.
@width Width of the new image
@height Height of the new image
@format Pixel format; one of the IMAGE_* formats defined above
@palette If not NULL, specifies the palette instead of allocating it
@palette_size For P8, indicates the palette size to use */
image_t *image_alloc(int width, int height, int format,
void *palette, int palette_size);
@format Pixel format; one of the IMAGE_* formats defined above */
image_t *image_alloc(int width, int height, int format);
/* image_set_palette(): Specify an external palette for an image
This function sets the image's palette to the provided address. The number
of entries allocated must be specified in size. It is also the caller's
responsibility to ensure that the palette covers all the indices used in the
image data.
The old palette, if owned by the image, is freed. If [owns=true] the
palette's ownership is given to the image, otherwise it is kept external. */
void image_set_palette(image_t *img, uint16_t *palette, int size, bool owns);
/* image_alloc_palette(): Allocate a new palette for an image
This function allocates a new palette for an image. The number of entries is
specified in size; for P8 it can vary between 1 and 256, for P4 it is
ignored (P4 images always have 16 colors).
The old palette, if owned by the image, is freed. The entries of the new
palette are all initialized to 0. If size is -1, the format's default
palette size is used. Returns true on success. */
bool image_alloc_palette(image_t *img, int size);
/* image_copy_palette(): Copy another image's palette
This function allocates a new palette for an image, and initializes it with
a copy of another image's palette. For P8 the palette can be resized by
specifying a value other than -1 as the size; by default, the source image's
palette size is used (within the limits of the new format). Retuns true on
success. */
bool image_copy_palette(image_t const *src, image_t *dst, int size);
/* image_create(): Create a bare image with no data/palette
This function allocates a new image structure but without data or palette.
The [data] and [palette] members are NULL, and [color_count] is either 0 or
-1 depending on whether the format normally has a palette.
The [data] and [palette] members are NULL, [color_count] and [stride] are 0.
This function is useful to create images that reuse externally-provided
information. It is intended that the user of this function sets the [data],
[stride] and [palette] and [color_count] members themselves. The
IMAGE_FLAGS_DATA_ALLOC and the IMAGE_FLAGS_PALETTE_ALLOC flags can be set on
the image if the user wishes for the image to free its data and palette when
freed.
information. It is intended that the user of this function sets the [data]
and [stride] fields themselves, along with the IMAGE_FLAGS_DATA_ALLOC flag
if the image should own its data.
The [palette] and [color_count] members can be set with image_set_palette(),
image_alloc_palette(), image_copy_palette(), or manually.
The returned image structure must be freed with image_free() after use. */
image_t *image_create(int width, int height, int format);
@ -245,15 +263,6 @@ image_t *image_create_vram(void);
exist, as this could cause the reference's pointers to become dangling. */
void image_free(image_t *img);
/* image_copy_palette(): Duplicate an image's palette
This function duplicates the palette and returns a new one allocated with
malloc(). If the input image is not in a palette format or has no palette
assigned, returns NULL. If the returned pointer is not NULL, free() after
use or set the IMAGE_FLAGS_PALETTE_ALLOC flag on the image holding it so
that free() is automatically called when the image is freed. */
uint16_t *image_copy_palette(image_t const *img);
//---
// Basic image access and information
//---
@ -350,9 +359,19 @@ void image_set_pixel(image_t const *img, int x, int y, int value);
P4 | - Narrow palette Copy |
+-----------+----------------+------------------+
Note that conversions to RGB16 are not lossless because RGB565, P8 and P4
can represent any color; if a color equal to image_alpha(IMAGE_RGB565A) is
found during conversion, this function transforms it slightly to look
similar instead of being transparent.
Formats: RGB16 RGB16, P8 Anything, P4 Anything */
void image_copy(image_t const *src, image_t *dst, bool copy_alpha);
/* image_copy_alloc(): Convert and copy into a new image
This function is similar to image_copy(), but it allocates a target image of
the desired format before copying. */
image_t *image_copy_alloc(image_t const *src, int new_format);
/* image_fill(): Fill an image with a single pixel value */
void image_fill(image_t *img, int value);
@ -403,8 +422,41 @@ image_t *image_sub(image_t const *src, int x, int y, int w, int h,
//---
// Geometric image transforms
//
// All geometric transforms render to position (0,0) of the target image and
// fail if the target image is not large enough to hold the transformed result
// (unlike the rendering functions which render only the visible portion).
//
// To render at position (x,y) of the target image, use img_at(). For instance:
// image_hflip(src, image_at(dst, x, y));
//
// Each transform function has an [_alloc] variant which does the same
// transform but allocates the target image on the fly and returns it. Remember
// that allocation can fail, so you need to check whether the returned image is
// valid.
//
// (You can still pass an invalid image to libimg functions when chaining
// transforms. The invalid image will be ignored or returned unchanged, so you
// can check for it at the end of any large chain.)
//
// Some functions support in-place transforms. This means they can be called
// with the source as destination, and will transform the image without needing
// new memory. For instance, image_hflip(src, src) flips in-place and replaces
// src with a flipped version of itself.
//
// (However, it is not possible to transform in-place if the source and
// destination intersect in non-trivial ways. The result will be incorrect.)
//
// When transforming to a new image, transparent pixels are ignored, so if the
// destination already has some data, it will not be erased automatically. Use
// image_clear() beforehand to achieve that effect. This allows alpha blending
// while transforming, which is especially useful on the VRAM.
//---
/* image_hflip(): Flip horizontally (supports in-place) */
void image_hflip(image_t const *src, image_t *dst);
image_t *image_hflip_alloc(image_t const *src);
/* TODO: Geometric transforms */
//---

View file

@ -2,25 +2,18 @@
#include <stdlib.h>
#include <gint/defs/util.h>
image_t *image_alloc(int width, int height, int format,
void *palette, int palette_size)
image_t *image_alloc(int width, int height, int format)
{
image_t *img = image_create(width, height, format);
if(!img)
return NULL;
if(IMAGE_IS_RGB16(format)) {
if(IMAGE_IS_RGB16(format))
img->stride = ((width + 1) >> 1) * 4;
palette_size = -1;
}
else if(IMAGE_IS_P8(format)) {
else if(IMAGE_IS_P8(format))
img->stride = width;
palette_size = max(0, min(256, palette_size));
}
else if(IMAGE_IS_P4(format)) {
else if(IMAGE_IS_P4(format))
img->stride = ((width + 1) >> 1);
palette_size = 32;
}
void *data = malloc(height * img->stride);
if(!data) {
@ -30,16 +23,5 @@ image_t *image_alloc(int width, int height, int format,
img->data = data;
img->flags |= IMAGE_FLAGS_DATA_ALLOC;
if(!palette && palette_size > 0) {
palette = malloc(palette_size * 2);
if(!palette) {
image_free(img);
return NULL;
}
}
img->palette = palette;
img->color_count = palette_size;
return img;
}

View file

@ -0,0 +1,30 @@
#include <gint/image.h>
#include <gint/defs/util.h>
#include <stdlib.h>
bool image_alloc_palette(image_t *img, int size)
{
if(!img || !IMAGE_IS_INDEXED(img))
return;
if(img->flags & IMAGE_FLAGS_PALETTE_OWN)
free(img->palette);
if(IMAGE_IS_P8(img)) {
size = (size <= 0) ? 256 : min(size, 256);
}
if(IMAGE_IS_P4(img)) {
size = 16;
}
img->palette = calloc(size, 2);
img->color_count = 0;
img->flags &= ~IMAGE_FLAGS_PALETTE_OWN;
if(!img->palette)
return false;
memset(img->palette, 0, 2*size);
img->color_count = size;
img->flags |= IMAGE_FLAGS_PALETTE_OWN;
return true;
}

View file

@ -2,14 +2,15 @@
int image_alpha(int format)
{
switch(format) {
case IMAGE_RGB565A:
return 0x0001;
case IMAGE_P8_RGB565A:
return -128;
case IMAGE_P4_RGB565A:
return 0;
default:
return 0x10000;
}
switch(format) {
case IMAGE_RGB565A:
return 0x0001;
case IMAGE_P8_RGB565A:
return -128;
case IMAGE_P4_RGB565A:
return 0;
default:
/* A value that cannot be found in any pixel of any format */
return 0x10000;
}
}

View file

@ -3,7 +3,7 @@
void image_copy(image_t const *src, image_t *dst, bool copy_alpha)
{
if(!image_target(src, dst, NOT_P4, DATA_RW, SAME_DEPTH))
if(!image_target(src, dst, DATA_RW))
return;
if(!IMAGE_IS_ALPHA(src->format))
copy_alpha = true;
@ -17,19 +17,39 @@ void image_copy(image_t const *src, image_t *dst, bool copy_alpha)
void *src_px = src->data;
void *dst_px = dst->data;
int src_alpha = copy_alpha ? 0x10000 : image_alpha(src->format);
int dst_alpha = copy_alpha ? 0x10000 : image_alpha(dst->format);
if(IMAGE_IS_RGB16(src->format)) {
if(IMAGE_IS_RGB16(src->format) && IMAGE_IS_RGB16(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((uint16_t *)src_px)[x];
if(px != src_alpha)
((uint16_t *)dst_px)[x] = px;
if(px != src_alpha) {
/* Don't copy opaque pixels of value 0x0001 into an RGB565A
array. We can use -= which is faster (subc) without
changing the visuals because dst_alpha != 0. */
((uint16_t *)dst_px)[x] = px - (px == dst_alpha);
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P8(src->format)) {
else if(IMAGE_IS_P8(src->format) && IMAGE_IS_RGB16(dst->format)) {
uint16_t *palette = src->palette + 128;
do {
for(int x = 0; x < w; x++) {
int px = ((int8_t *)src_px)[x];
if(px != src_alpha) {
px = palette[px];
((uint16_t *)dst_px)[x] = px - (px == dst_alpha);
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P8(src->format) && IMAGE_IS_P8(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((int8_t *)src_px)[x];
@ -40,4 +60,63 @@ void image_copy(image_t const *src, image_t *dst, bool copy_alpha)
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P8(src->format) && IMAGE_IS_P4(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((int8_t *)src_px)[x];
if(px != src_alpha) {
uint8_t *cell = dst_px + (x >> 1);
if(x & 1)
*cell = (*cell & 0xf0) | (px & 0x0f);
else
*cell = (*cell & 0x0f) | (px << 4);
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P4(src->format) && IMAGE_IS_P4(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((uint8_t *)src_px)[x >> 1];
px = (x & 1) ? (px & 0x0f) : (px >> 4);
if(px != src_alpha) {
uint8_t *cell = dst_px + (x >> 1);
if(x & 1)
*cell = (*cell & 0xf0) | (px & 0x0f);
else
*cell = (*cell & 0x0f) | (px << 4);
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P4(src->format) && IMAGE_IS_P8(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((uint8_t *)src_px)[x >> 1];
px = (x & 1) ? (px & 0x0f) : (px >> 4);
if(px != src_alpha)
((int8_t *)dst_px)[x] = px;
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
else if(IMAGE_IS_P4(src->format) && IMAGE_IS_RGB16(dst->format)) {
do {
for(int x = 0; x < w; x++) {
int px = ((uint8_t *)src_px)[x >> 1];
px = (x & 1) ? (px & 0x0f) : (px >> 4);
if(px != src_alpha) {
px = src->palette[px];
((uint16_t *)dst_px)[x] = px - (px == dst_alpha);
}
}
src_px += src->stride;
dst_px += dst->stride;
} while(--h > 0);
}
}

View file

@ -0,0 +1,20 @@
#include <gint/image.h>
#include <gint/defs/util.h>
#include <string.h>
image_t *image_copy_alloc(image_t const *src, int new_format)
{
if(!image_valid(src))
return NULL;
image_t *dst = image_alloc(src->width, src->height, new_format);
if(!dst)
return NULL;
if(!image_copy_palette(src, dst, -1)) {
image_free(dst);
return NULL;
}
image_copy(src, dst, true);
return dst;
}

View file

@ -1,16 +1,18 @@
#include <gint/image.h>
#include <stdlib.h>
#include <gint/defs/util.h>
#include <string.h>
uint16_t *image_copy_palette(image_t const *img)
bool image_copy_palette(image_t const *src, image_t *dst, int size)
{
int size = image_palette_size(img);
if(size < 0 || !img->palette)
return NULL;
if(!image_valid(src) || !dst)
return false;
void *palette = malloc(size);
if(!palette)
return NULL;
if(size < 0)
size = src->color_count;
if(!image_alloc_palette(dst, size))
return false;
return memcpy(palette, img->palette, size);
int N = min(src->color_count, dst->color_count);
memcpy(dst->palette, src->palette, 2*N);
return true;
}

View file

@ -14,13 +14,11 @@ image_t *image_create(int width, int height, int format)
img->format = format;
img->flags = 0;
img->color_count = 0;
img->width = width;
img->height = height;
img->stride = 0;
img->data = NULL;
img->color_count = IMAGE_IS_INDEXED(format) ? 0 : -1;
img->palette = NULL;
return img;

View file

@ -0,0 +1,18 @@
#include <gint/image.h>
#include <stdlib.h>
void image_set_palette(image_t *img, uint16_t *palette, int size, bool owns)
{
if(!img || !IMAGE_IS_INDEXED(img))
return;
if(img->flags & IMAGE_FLAGS_PALETTE_OWN)
free(img->palette);
img->palette = palette;
img->color_count = size;
if(owns)
img->flags |= IMAGE_FLAGS_PALETTE_OWN;
else
img->flags &= ~IMAGE_FLAGS_PALETTE_OWN;
}

View file

@ -24,16 +24,16 @@ image_t *image_sub(image_t const *src, int left, int top, int w, int h,
int const ro_flags = IMAGE_FLAGS_DATA_RO | IMAGE_FLAGS_PALETTE_RO;
dst->format = src->format;
dst->flags = src->flags & ro_flags;
dst->stride = src->stride;
dst->color_count = src->color_count;
dst->width = box.w;
dst->height = box.h;
dst->stride = src->stride;
if(IMAGE_IS_RGB16(src->format))
dst->data = src->data + box.top * src->stride + 2 * box.left;
else if(IMAGE_IS_P8(src->format))
dst->data = src->data + box.top * src->stride + box.left;
dst->color_count = src->color_count;
dst->palette = src->palette;
return dst;
}

View file

@ -9,7 +9,8 @@ bool image_valid(image_t const *img)
return (img->data != NULL);
}
if(IMAGE_IS_P8(img->format) || IMAGE_IS_P4(img->format)) {
return (img->data != NULL) && (img->palette != NULL);
return (img->data != NULL) && (img->palette != NULL) &&
(img->color_count != 0);
}
/* Invalid format */