mirror of https://github.com/mgba-emu/mgba.git
Util: More palette support
This commit is contained in:
parent
618a51cabb
commit
9fa607b30f
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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):
|
||||
|
|
108
src/util/image.c
108
src/util/image.c
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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))) {
|
||||
|
|
|
@ -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),
|
||||
|
|
Loading…
Reference in New Issue