Util: More palette support

This commit is contained in:
Vicki Pfau 2023-04-17 04:36:05 -07:00
parent 618a51cabb
commit 9fa607b30f
6 changed files with 163 additions and 51 deletions

View File

@ -118,11 +118,15 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y);
void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color);
void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color);
void mImageSetPaletteSize(struct mImage* image, unsigned count);
void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color);
void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y);
void mImageComposite(struct mImage* image, const struct mImage* source, int x, int y);
void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source, int x, int y, float alpha);
uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat to);
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to);
#ifndef PYCPARSE
static inline unsigned mColorFormatBytes(enum mColorFormat format) {

View File

@ -28,10 +28,9 @@ enum {
png_structp PNGWriteOpen(struct VFile* source);
png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum mColorFormat);
png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height);
bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries);
png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries);
bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels, enum mColorFormat);
bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels);
bool PNGWriteCustomChunk(png_structp png, const char* name, size_t size, void* data);
void PNGWriteClose(png_structp png, png_infop info);

View File

@ -24,8 +24,6 @@ class PNG:
self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_XBGR8)
if self.mode == MODE_RGBA:
self._info = lib.PNGWriteHeader(self._png, image.width, image.height, lib.mCOLOR_ABGR8)
if self.mode == MODE_INDEX:
self._info = lib.PNGWriteHeader8(self._png, image.width, image.height)
return self._info != ffi.NULL
def write_pixels(self, image):
@ -33,8 +31,6 @@ class PNG:
return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_XBGR8)
if self.mode == MODE_RGBA:
return lib.PNGWritePixels(self._png, image.width, image.height, image.stride, image.buffer, lib.mCOLOR_ABGR8)
if self.mode == MODE_INDEX:
return lib.PNGWritePixels8(self._png, image.width, image.height, image.stride, image.buffer)
return False
def write_close(self):

View File

@ -64,6 +64,15 @@ struct mImage* mImageCreateWithStride(unsigned width, unsigned height, unsigned
free(image);
return NULL;
}
if (format == mCOLOR_PAL8) {
image->palette = malloc(1024);
if (!image->palette) {
free(image->data);
free(image);
return NULL;
}
image->palSize = 1;
}
return image;
}
@ -224,27 +233,14 @@ struct mImage* mImageConvertToFormat(const struct mImage* image, enum mColorForm
// TODO: Implement more specializations, e.g. alpha narrowing/widening, channel swapping
size_t x, y;
if (image->format == mCOLOR_PAL8) {
for (y = 0; y < newImage->height; ++y) {
uintptr_t src = (uintptr_t) ROW(image, y);
uintptr_t dst = (uintptr_t) ROW(newImage, y);
for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) {
uint32_t color;
GET_PIXEL(color, src, image->depth);
color = image->palette[color];
PUT_PIXEL(color, dst, newImage->depth);
}
}
} else {
for (y = 0; y < newImage->height; ++y) {
uintptr_t src = (uintptr_t) ROW(image, y);
uintptr_t dst = (uintptr_t) ROW(newImage, y);
for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) {
uint32_t color;
GET_PIXEL(color, src, image->depth);
color = mColorConvert(color, image->format, format);
PUT_PIXEL(color, dst, newImage->depth);
}
for (y = 0; y < newImage->height; ++y) {
uintptr_t src = (uintptr_t) ROW(image, y);
uintptr_t dst = (uintptr_t) ROW(newImage, y);
for (x = 0; x < newImage->width; ++x, src += image->depth, dst += newImage->depth) {
uint32_t color;
GET_PIXEL(color, src, image->depth);
color = mImageColorConvert(color, image, format);
PUT_PIXEL(color, dst, newImage->depth);
}
}
return newImage;
@ -280,9 +276,16 @@ bool mImageSavePNG(const struct mImage* image, struct VFile* vf) {
png_infop info = NULL;
bool ok = false;
if (png) {
info = PNGWriteHeader(png, image->width, image->height, image->format);
if (info) {
ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format);
if (image->format == mCOLOR_PAL8) {
info = PNGWriteHeaderPalette(png, image->width, image->height, image->palette, image->palSize);
if (info) {
ok = PNGWritePixelsPalette(png, image->width, image->height, image->stride, image->data);
}
} else {
info = PNGWriteHeader(png, image->width, image->height, image->format);
if (info) {
ok = PNGWritePixels(png, image->width, image->height, image->stride, image->data, image->format);
}
}
PNGWriteClose(png, info);
}
@ -334,12 +337,7 @@ uint32_t mImageGetPixelRaw(const struct mImage* image, unsigned x, unsigned y) {
}
uint32_t mImageGetPixel(const struct mImage* image, unsigned x, unsigned y) {
uint32_t raw = mImageGetPixelRaw(image, x, y);
if (image->format == mCOLOR_PAL8) {
return image->palette[raw];
} else {
return mColorConvert(raw, image->format, mCOLOR_ARGB8);
}
return mImageColorConvert(mImageGetPixelRaw(image, x, y), image, mCOLOR_ARGB8);
}
void mImageSetPixelRaw(struct mImage* image, unsigned x, unsigned y, uint32_t color) {
@ -375,6 +373,26 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color
mImageSetPixelRaw(image, x, y, mColorConvert(color, mCOLOR_ARGB8, image->format));
}
void mImageSetPaletteSize(struct mImage* image, unsigned count) {
if (image->format != mCOLOR_PAL8) {
return;
}
if (count > 256) {
count = 256;
}
image->palSize = count;
}
void mImageSetPaletteEntry(struct mImage* image, unsigned index, uint32_t color) {
if (image->format != mCOLOR_PAL8) {
return;
}
if (index > 256) {
return;
}
image->palette[index] = color;
}
#define COMPOSITE_BOUNDS_INIT \
struct mRectangle dstRect = { \
.x = 0, \
@ -411,6 +429,11 @@ void mImageSetPixel(struct mImage* image, unsigned x, unsigned y, uint32_t color
}
void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y) {
if (image->format == mCOLOR_PAL8) {
// Can't blit to paletted image
return;
}
COMPOSITE_BOUNDS_INIT;
for (y = 0; y < srcRect.height; ++y) {
@ -419,7 +442,7 @@ void mImageBlit(struct mImage* image, const struct mImage* source, int x, int y)
for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) {
uint32_t color;
GET_PIXEL(color, srcPixel, source->depth);
color = mColorConvert(color, source->format, image->format);
color = mImageColorConvert(color, source, image->format);
PUT_PIXEL(color, dstPixel, image->depth);
}
}
@ -431,6 +454,11 @@ void mImageComposite(struct mImage* image, const struct mImage* source, int x, i
return;
}
if (image->format == mCOLOR_PAL8) {
// Can't blit to paletted image
return;
}
COMPOSITE_BOUNDS_INIT;
for (y = 0; y < srcRect.height; ++y) {
@ -439,7 +467,7 @@ void mImageComposite(struct mImage* image, const struct mImage* source, int x, i
for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) {
uint32_t color, colorB;
GET_PIXEL(color, srcPixel, source->depth);
color = mColorConvert(color, source->format, mCOLOR_ARGB8);
color = mImageColorConvert(color, source, mCOLOR_ARGB8);
if (color < 0xFF000000) {
GET_PIXEL(colorB, dstPixel, image->depth);
colorB = mColorConvert(colorB, image->format, mCOLOR_ARGB8);
@ -456,6 +484,10 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source,
mImageComposite(image, source, x, y);
return;
}
if (image->format == mCOLOR_PAL8) {
// Can't blit to paletted image
return;
}
if (alpha <= 0) {
return;
}
@ -474,7 +506,7 @@ void mImageCompositeWithAlpha(struct mImage* image, const struct mImage* source,
for (x = 0; x < srcRect.width; ++x, srcPixel += source->depth, dstPixel += image->depth) {
uint32_t color, colorB;
GET_PIXEL(color, srcPixel, source->depth);
color = mColorConvert(color, source->format, mCOLOR_ARGB8);
color = mImageColorConvert(color, source, mCOLOR_ARGB8);
uint32_t alpha = (color >> 24) * fixedAlpha;
alpha >>= 9;
if (alpha > 0xFF) {
@ -686,3 +718,13 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat
return color;
}
uint32_t mImageColorConvert(uint32_t color, const struct mImage* from, enum mColorFormat to) {
if (from->format != mCOLOR_PAL8) {
return mColorConvert(color, from->format, to);
}
if (color < from->palSize) {
color = from->palette[color];
}
return mColorConvert(color, mCOLOR_ARGB8, to);
}

View File

@ -9,6 +9,8 @@
#include <mgba-util/vfs.h>
static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries);
static void _pngWrite(png_structp png, png_bytep buffer, png_size_t size) {
struct VFile* vf = png_get_io_ptr(png);
size_t written = vf->write(vf, buffer, size);
@ -38,15 +40,23 @@ png_structp PNGWriteOpen(struct VFile* source) {
return png;
}
static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, int type) {
static png_infop _pngWriteHeader(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries, int type) {
png_infop info = png_create_info_struct(png);
if (!info) {
return 0;
return NULL;
}
if (setjmp(png_jmpbuf(png))) {
return 0;
return NULL;
}
png_set_IHDR(png, info, width, height, 8, type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);
if (type == PNG_COLOR_TYPE_PALETTE) {
if (!palette) {
return NULL;
}
if (!PNGWritePalette(png, info, palette, entries)) {
return NULL;
}
}
png_write_info(png, info);
return info;
}
@ -80,15 +90,18 @@ png_infop PNGWriteHeader(png_structp png, unsigned width, unsigned height, enum
case mCOLOR_L8:
type = PNG_COLOR_TYPE_GRAY;
break;
case mCOLOR_PAL8:
type = PNG_COLOR_TYPE_PALETTE;
break;
}
return _pngWriteHeader(png, width, height, type);
return _pngWriteHeader(png, width, height, NULL, 0, type);
}
png_infop PNGWriteHeader8(png_structp png, unsigned width, unsigned height) {
return _pngWriteHeader(png, width, height, PNG_COLOR_TYPE_PALETTE);
png_infop PNGWriteHeaderPalette(png_structp png, unsigned width, unsigned height, const uint32_t* palette, unsigned entries) {
return _pngWriteHeader(png, width, height, palette, entries, PNG_COLOR_TYPE_PALETTE);
}
bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) {
static bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, unsigned entries) {
if (!palette || !entries) {
return false;
}
@ -99,14 +112,13 @@ bool PNGWritePalette(png_structp png, png_infop info, const uint32_t* palette, u
png_byte trans[256];
unsigned i;
for (i = 0; i < entries && i < 256; ++i) {
colors[i].red = palette[i];
colors[i].red = palette[i] >> 16;
colors[i].green = palette[i] >> 8;
colors[i].blue = palette[i] >> 16;
colors[i].blue = palette[i];
trans[i] = palette[i] >> 24;
}
png_set_PLTE(png, info, colors, entries);
png_set_tRNS(png, info, trans, entries, NULL);
png_write_info(png, info);
return true;
}
@ -430,6 +442,7 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s
_convertRowRGB8(row, pixelRow, width);
break;
case mCOLOR_L8:
case mCOLOR_PAL8:
memcpy(row, pixelRow, width);
break;
case mCOLOR_ANY:
@ -442,7 +455,7 @@ bool PNGWritePixels(png_structp png, unsigned width, unsigned height, unsigned s
return true;
}
bool PNGWritePixels8(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) {
bool PNGWritePixelsPalette(png_structp png, unsigned width, unsigned height, unsigned stride, const void* pixels) {
UNUSED(width);
const png_byte* pixelData = pixels;
if (setjmp(png_jmpbuf(png))) {

View File

@ -453,6 +453,22 @@ M_TEST_DEFINE(oobWrite) {
assert_memory_equal(buffer, (&(uint8_t[8]) { 0xFF, 0xFF, 0xFF, 0xFF }), sizeof(buffer));
}
M_TEST_DEFINE(paletteAccess) {
struct mImage* image = mImageCreate(1, 1, mCOLOR_PAL8);
mImageSetPaletteSize(image, 1);
mImageSetPaletteEntry(image, 0, 0xFF00FF00);
mImageSetPixelRaw(image, 0, 0, 0);
assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0);
assert_int_equal(mImageGetPixel(image, 0, 0), 0xFF00FF00);
mImageSetPaletteEntry(image, 0, 0x01234567);
assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0);
assert_int_equal(mImageGetPixel(image, 0, 0), 0x01234567);
mImageDestroy(image);
}
#ifdef USE_PNG
M_TEST_DEFINE(loadPng24) {
const uint8_t data[] = {
@ -653,6 +669,46 @@ M_TEST_DEFINE(savePngL8) {
assert_int_equal(mImageGetPixel(image, 1, 1), 0xFFFFFFFF);
mImageDestroy(image);
}
M_TEST_DEFINE(savePngPal8) {
struct mImage* image = mImageCreate(2, 2, mCOLOR_PAL8);
mImageSetPaletteSize(image, 4);
mImageSetPaletteEntry(image, 0, 0x00000000);
mImageSetPaletteEntry(image, 1, 0x40FF0000);
mImageSetPaletteEntry(image, 2, 0x8000FF00);
mImageSetPaletteEntry(image, 3, 0xC00000FF);
mImageSetPixelRaw(image, 0, 0, 0);
mImageSetPixelRaw(image, 1, 0, 1);
mImageSetPixelRaw(image, 0, 1, 2);
mImageSetPixelRaw(image, 1, 1, 3);
assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000);
assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000);
assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00);
assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF);
struct VFile* vf = VFileMemChunk(NULL, 0);
assert_true(mImageSaveVF(image, vf, "png"));
mImageDestroy(image);
assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0);
assert_true(isPNG(vf));
assert_int_equal(vf->seek(vf, 0, SEEK_SET), 0);
image = mImageLoadVF(vf);
vf->close(vf);
assert_non_null(image);
assert_int_equal(image->format, mCOLOR_PAL8);
assert_int_equal(mImageGetPixelRaw(image, 0, 0), 0);
assert_int_equal(mImageGetPixelRaw(image, 1, 0), 1);
assert_int_equal(mImageGetPixelRaw(image, 0, 1), 2);
assert_int_equal(mImageGetPixelRaw(image, 1, 1), 3);
assert_int_equal(mImageGetPixel(image, 0, 0), 0x00000000);
assert_int_equal(mImageGetPixel(image, 1, 0), 0x40FF0000);
assert_int_equal(mImageGetPixel(image, 0, 1), 0x8000FF00);
assert_int_equal(mImageGetPixel(image, 1, 1), 0xC00000FF);
mImageDestroy(image);
}
#endif
M_TEST_DEFINE(convert1x1) {
@ -1094,6 +1150,7 @@ M_TEST_SUITE_DEFINE(Image,
cmocka_unit_test(pitchWrite),
cmocka_unit_test(strideWrite),
cmocka_unit_test(oobWrite),
cmocka_unit_test(paletteAccess),
#ifdef USE_PNG
cmocka_unit_test(loadPng24),
cmocka_unit_test(loadPng32),
@ -1102,6 +1159,7 @@ M_TEST_SUITE_DEFINE(Image,
cmocka_unit_test(savePngNonNative),
cmocka_unit_test(savePngRoundTrip),
cmocka_unit_test(savePngL8),
cmocka_unit_test(savePngPal8),
#endif
cmocka_unit_test(convert1x1),
cmocka_unit_test(convert2x1),