diff --git a/CMakeLists.txt b/CMakeLists.txt index ff71315..ad37da7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/include/gint/image.h b/include/gint/image.h index ae17acc..42519bb 100644 --- a/include/gint/image.h +++ b/include/gint/image.h @@ -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[+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[+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 */ //--- diff --git a/src/image/image_alloc.c b/src/image/image_alloc.c index 5856f81..010aa44 100644 --- a/src/image/image_alloc.c +++ b/src/image/image_alloc.c @@ -2,25 +2,18 @@ #include #include -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; } diff --git a/src/image/image_alloc_palette.c b/src/image/image_alloc_palette.c new file mode 100644 index 0000000..1afe5a2 --- /dev/null +++ b/src/image/image_alloc_palette.c @@ -0,0 +1,30 @@ +#include +#include +#include + +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; +} diff --git a/src/image/image_alpha.c b/src/image/image_alpha.c index baa45e6..d96c25b 100644 --- a/src/image/image_alpha.c +++ b/src/image/image_alpha.c @@ -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; + } } diff --git a/src/image/image_copy.c b/src/image/image_copy.c index 8825d28..eef220f 100644 --- a/src/image/image_copy.c +++ b/src/image/image_copy.c @@ -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); + } } diff --git a/src/image/image_copy_alloc.c b/src/image/image_copy_alloc.c new file mode 100644 index 0000000..40a0920 --- /dev/null +++ b/src/image/image_copy_alloc.c @@ -0,0 +1,20 @@ +#include +#include +#include + +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; +} diff --git a/src/image/image_copy_palette.c b/src/image/image_copy_palette.c index d2a709e..f7b4b3b 100644 --- a/src/image/image_copy_palette.c +++ b/src/image/image_copy_palette.c @@ -1,16 +1,18 @@ #include -#include +#include #include -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; } diff --git a/src/image/image_create.c b/src/image/image_create.c index f36c49f..d7a1a58 100644 --- a/src/image/image_create.c +++ b/src/image/image_create.c @@ -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; diff --git a/src/image/image_set_palette.c b/src/image/image_set_palette.c new file mode 100644 index 0000000..021b823 --- /dev/null +++ b/src/image/image_set_palette.c @@ -0,0 +1,18 @@ +#include +#include + +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; +} diff --git a/src/image/image_sub.c b/src/image/image_sub.c index 7e55646..0cf4cb4 100644 --- a/src/image/image_sub.c +++ b/src/image/image_sub.c @@ -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; } diff --git a/src/image/image_valid.c b/src/image/image_valid.c index 861df87..4533161 100644 --- a/src/image/image_valid.c +++ b/src/image/image_valid.c @@ -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 */