summaryrefslogtreecommitdiff
path: root/src/image.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/image.cpp')
-rw-r--r--src/image.cpp1324
1 files changed, 1324 insertions, 0 deletions
diff --git a/src/image.cpp b/src/image.cpp
new file mode 100644
index 0000000..23877b8
--- /dev/null
+++ b/src/image.cpp
@@ -0,0 +1,1324 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+
+#include "rwbase.h"
+#include "rwerror.h"
+#include "rwplg.h"
+#include "rwpipeline.h"
+#include "rwobjects.h"
+#include "rwengine.h"
+#include "ps2/rwps2.h"
+#include "d3d/rwd3d.h"
+#include "d3d/rwxbox.h"
+#include "d3d/rwd3d8.h"
+#include "d3d/rwd3d9.h"
+
+#define PLUGIN_ID ID_IMAGE
+
+namespace rw {
+
+int32 Image::numAllocated;
+
+struct FileAssociation
+{
+ char *extension;
+ Image *(*read)(const char *afilename);
+ void (*write)(Image *image, const char *filename);
+};
+
+struct ImageGlobals
+{
+ char *searchPaths;
+ int numSearchPaths;
+ FileAssociation fileFormats[10];
+ int numFileFormats;
+};
+int32 imageModuleOffset;
+
+#define IMAGEGLOBAL(v) (PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset)->v)
+
+// Image formats are as follows:
+// 32 bit has 4 bytes: 8888 RGBA
+// 24 bit has 3 bytes: 888 RGB
+// 16 bit has 2 bytes: 1555 ARGB stored in platform native order (TODO?)
+// palettes always have 4 bytes: r, g, b, a
+// 8 bit has 1 byte: x
+// 4 bit has 1 byte per two pixels: 0xLR, where L and R are the left and right pixel resp.
+
+Image*
+Image::create(int32 width, int32 height, int32 depth)
+{
+ Image *img = (Image*)rwMalloc(sizeof(Image), MEMDUR_EVENT | ID_IMAGE);
+ if(img == nil){
+ RWERROR((ERR_ALLOC, sizeof(Image)));
+ return nil;
+ }
+ numAllocated++;
+ img->flags = 0;
+ img->width = width;
+ img->height = height;
+ img->depth = depth;
+ img->bpp = depth < 8 ? 1 : depth/8;
+ img->stride = 0;
+ img->pixels = nil;
+ img->palette = nil;
+ return img;
+}
+
+void
+Image::destroy(void)
+{
+ this->free();
+ rwFree(this);
+ numAllocated--;
+}
+
+void
+Image::allocate(void)
+{
+ if(this->pixels == nil){
+ this->stride = this->width*this->bpp;
+ this->pixels = rwNewT(uint8, this->stride*this->height, MEMDUR_EVENT | ID_IMAGE);
+ this->flags |= 1;
+ }
+ if(this->palette == nil){
+ if(this->depth == 4 || this->depth == 8)
+ this->palette = rwNewT(uint8, (1 << this->depth)*4, MEMDUR_EVENT | ID_IMAGE);
+ this->flags |= 2;
+ }
+}
+
+void
+Image::free(void)
+{
+ if(this->flags&1){
+ rwFree(this->pixels);
+ this->pixels = nil;
+ }
+ if(this->flags&2){
+ rwFree(this->palette);
+ this->palette = nil;
+ }
+ this->flags = 0;
+}
+
+void
+Image::setPixels(uint8 *pixels)
+{
+ this->pixels = pixels;
+ this->flags |= 1;
+}
+
+void
+decompressDXT1(uint8 *adst, int32 w, int32 h, uint8 *src)
+{
+ /* j loops through old texels
+ * x and y loop through new texels */
+ int32 x = 0, y = 0;
+ uint32 c[4][4];
+ uint8 idx[16];
+ uint8 (*dst)[4] = (uint8(*)[4])adst;
+ for(int32 j = 0; j < w*h/2; j += 8){
+ /* calculate colors */
+ uint32 col0 = *((uint16*)&src[j+0]);
+ uint32 col1 = *((uint16*)&src[j+2]);
+ c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F;
+ c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F;
+ c[0][2] = ( col0 & 0x1F)*0xFF/0x1F;
+ c[0][3] = 0xFF;
+
+ c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F;
+ c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F;
+ c[1][2] = ( col1 & 0x1F)*0xFF/0x1F;
+ c[1][3] = 0xFF;
+ if(col0 > col1){
+ c[2][0] = (2*c[0][0] + 1*c[1][0])/3;
+ c[2][1] = (2*c[0][1] + 1*c[1][1])/3;
+ c[2][2] = (2*c[0][2] + 1*c[1][2])/3;
+ c[2][3] = 0xFF;
+
+ c[3][0] = (1*c[0][0] + 2*c[1][0])/3;
+ c[3][1] = (1*c[0][1] + 2*c[1][1])/3;
+ c[3][2] = (1*c[0][2] + 2*c[1][2])/3;
+ c[3][3] = 0xFF;
+ }else{
+ c[2][0] = (c[0][0] + c[1][0])/2;
+ c[2][1] = (c[0][1] + c[1][1])/2;
+ c[2][2] = (c[0][2] + c[1][2])/2;
+ c[2][3] = 0xFF;
+
+ c[3][0] = 0x00;
+ c[3][1] = 0x00;
+ c[3][2] = 0x00;
+ c[3][3] = 0x00;
+ }
+
+ /* make index list */
+ uint32 indices = *((uint32*)&src[j+4]);
+ for(int32 k = 0; k < 16; k++){
+ idx[k] = indices & 0x3;
+ indices >>= 2;
+ }
+
+ /* write bytes */
+ for(uint32 l = 0; l < 4; l++)
+ for(uint32 k = 0; k < 4; k++){
+ dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0];
+ dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1];
+ dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2];
+ dst[(y+l)*w + x+k][3] = c[idx[l*4+k]][3];
+ }
+ x += 4;
+ if(x >= w){
+ y += 4;
+ x = 0;
+ }
+ }
+}
+
+void
+decompressDXT3(uint8 *adst, int32 w, int32 h, uint8 *src)
+{
+ /* j loops through old texels
+ * x and y loop through new texels */
+ int32 x = 0, y = 0;
+ uint32 c[4][4];
+ uint8 idx[16];
+ uint8 a[16];
+ uint8 (*dst)[4] = (uint8(*)[4])adst;
+ for(int32 j = 0; j < w*h; j += 16){
+ /* calculate colors */
+ uint32 col0 = *((uint16*)&src[j+8]);
+ uint32 col1 = *((uint16*)&src[j+10]);
+ c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F;
+ c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F;
+ c[0][2] = ( col0 & 0x1F)*0xFF/0x1F;
+
+ c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F;
+ c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F;
+ c[1][2] = ( col1 & 0x1F)*0xFF/0x1F;
+
+ c[2][0] = (2*c[0][0] + 1*c[1][0])/3;
+ c[2][1] = (2*c[0][1] + 1*c[1][1])/3;
+ c[2][2] = (2*c[0][2] + 1*c[1][2])/3;
+
+ c[3][0] = (1*c[0][0] + 2*c[1][0])/3;
+ c[3][1] = (1*c[0][1] + 2*c[1][1])/3;
+ c[3][2] = (1*c[0][2] + 2*c[1][2])/3;
+
+ /* make index list */
+ uint32 indices = *((uint32*)&src[j+12]);
+ for(int32 k = 0; k < 16; k++){
+ idx[k] = indices & 0x3;
+ indices >>= 2;
+ }
+ uint64 alphas = *((uint64*)&src[j+0]);
+ for(int32 k = 0; k < 16; k++){
+ a[k] = (alphas & 0xF)*17;
+ alphas >>= 4;
+ }
+
+ /* write bytes */
+ for(uint32 l = 0; l < 4; l++)
+ for(uint32 k = 0; k < 4; k++){
+ dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0];
+ dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1];
+ dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2];
+ dst[(y+l)*w + x+k][3] = a[l*4+k];
+ }
+ x += 4;
+ if(x >= w){
+ y += 4;
+ x = 0;
+ }
+ }
+}
+
+void
+decompressDXT5(uint8 *adst, int32 w, int32 h, uint8 *src)
+{
+ /* j loops through old texels
+ * x and y loop through new texels */
+ int32 x = 0, y = 0;
+ uint32 c[4][4];
+ uint32 a[8];
+ uint8 idx[16];
+ uint8 aidx[16];
+ uint8 (*dst)[4] = (uint8(*)[4])adst;
+ for(int32 j = 0; j < w*h; j += 16){
+ /* calculate colors */
+ uint32 col0 = *((uint16*)&src[j+8]);
+ uint32 col1 = *((uint16*)&src[j+10]);
+ c[0][0] = ((col0>>11) & 0x1F)*0xFF/0x1F;
+ c[0][1] = ((col0>> 5) & 0x3F)*0xFF/0x3F;
+ c[0][2] = ( col0 & 0x1F)*0xFF/0x1F;
+
+ c[1][0] = ((col1>>11) & 0x1F)*0xFF/0x1F;
+ c[1][1] = ((col1>> 5) & 0x3F)*0xFF/0x3F;
+ c[1][2] = ( col1 & 0x1F)*0xFF/0x1F;
+ if(col0 > col1){
+ c[2][0] = (2*c[0][0] + 1*c[1][0])/3;
+ c[2][1] = (2*c[0][1] + 1*c[1][1])/3;
+ c[2][2] = (2*c[0][2] + 1*c[1][2])/3;
+
+ c[3][0] = (1*c[0][0] + 2*c[1][0])/3;
+ c[3][1] = (1*c[0][1] + 2*c[1][1])/3;
+ c[3][2] = (1*c[0][2] + 2*c[1][2])/3;
+ }else{
+ c[2][0] = (c[0][0] + c[1][0])/2;
+ c[2][1] = (c[0][1] + c[1][1])/2;
+ c[2][2] = (c[0][2] + c[1][2])/2;
+
+ c[3][0] = 0x00;
+ c[3][1] = 0x00;
+ c[3][2] = 0x00;
+ }
+
+ a[0] = src[j+0];
+ a[1] = src[j+1];
+ if(a[0] > a[1]){
+ a[2] = (6*a[0] + 1*a[1])/7;
+ a[3] = (5*a[0] + 2*a[1])/7;
+ a[4] = (4*a[0] + 3*a[1])/7;
+ a[5] = (3*a[0] + 4*a[1])/7;
+ a[6] = (2*a[0] + 5*a[1])/7;
+ a[7] = (1*a[0] + 6*a[1])/7;
+ }else{
+ a[2] = (4*a[0] + 1*a[1])/5;
+ a[3] = (3*a[0] + 2*a[1])/5;
+ a[4] = (2*a[0] + 3*a[1])/5;
+ a[5] = (1*a[0] + 4*a[1])/5;
+ a[6] = 0;
+ a[7] = 0xFF;
+ }
+
+ /* make index list */
+ uint32 indices = *((uint32*)&src[j+12]);
+ for(int32 k = 0; k < 16; k++){
+ idx[k] = indices & 0x3;
+ indices >>= 2;
+ }
+ // only 6 indices
+ uint64 alphas = *((uint64*)&src[j+2]);
+ for(int32 k = 0; k < 16; k++){
+ aidx[k] = alphas & 0x7;
+ alphas >>= 3;
+ }
+
+ /* write bytes */
+ for(uint32 l = 0; l < 4; l++)
+ for(uint32 k = 0; k < 4; k++){
+ dst[(y+l)*w + x+k][0] = c[idx[l*4+k]][0];
+ dst[(y+l)*w + x+k][1] = c[idx[l*4+k]][1];
+ dst[(y+l)*w + x+k][2] = c[idx[l*4+k]][2];
+ dst[(y+l)*w + x+k][3] = a[aidx[l*4+k]];
+ }
+ x += 4;
+ if(x >= w){
+ y += 4;
+ x = 0;
+ }
+ }
+}
+
+// not strictly image but related
+
+// flip a DXT 2-bit block
+static void
+flipBlock(uint8 *dst, uint8 *src)
+{
+ // color
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ // bits
+ dst[4] = src[7];
+ dst[5] = src[6];
+ dst[6] = src[5];
+ dst[7] = src[4];
+}
+
+// flip top 2 rows of a DXT 2-bit block
+static void
+flipBlock_half(uint8 *dst, uint8 *src)
+{
+ // color
+ dst[0] = src[0];
+ dst[1] = src[1];
+ dst[2] = src[2];
+ dst[3] = src[3];
+ // bits
+ dst[4] = src[5];
+ dst[5] = src[4];
+ dst[6] = src[6];
+ dst[7] = src[7];
+}
+
+// flip a DXT3 4-bit alpha block
+static void
+flipAlphaBlock3(uint8 *dst, uint8 *src)
+{
+ dst[6] = src[0];
+ dst[7] = src[1];
+ dst[4] = src[2];
+ dst[5] = src[3];
+ dst[2] = src[4];
+ dst[3] = src[5];
+ dst[0] = src[6];
+ dst[1] = src[7];
+}
+
+// flip top 2 rows of a DXT3 4-bit alpha block
+static void
+flipAlphaBlock3_half(uint8 *dst, uint8 *src)
+{
+ dst[0] = src[2];
+ dst[1] = src[3];
+ dst[2] = src[0];
+ dst[3] = src[1];
+ dst[4] = src[4];
+ dst[5] = src[5];
+ dst[6] = src[6];
+ dst[7] = src[7];
+}
+
+// flip a DXT5 3-bit alpha block
+static void
+flipAlphaBlock5(uint8 *dst, uint8 *src)
+{
+ // color
+ dst[0] = src[0];
+ dst[1] = src[1];
+ // bits
+ uint64 bits = *(uint64*)&src[2];
+ uint64 flipbits = 0;
+ for(int i = 0; i < 4; i++){
+ flipbits <<= 12;
+ flipbits |= bits & 0xFFF;
+ bits >>= 12;
+ }
+ memcpy(dst+2, &flipbits, 6);
+}
+
+// flip top 2 rows of a DXT5 3-bit alpha block
+static void
+flipAlphaBlock5_half(uint8 *dst, uint8 *src)
+{
+ // color
+ dst[0] = src[0];
+ dst[1] = src[1];
+ // bits
+ uint64 bits = *(uint64*)&src[2];
+ uint64 flipbits = bits & 0xFFFFFF000000;
+ flipbits |= (bits>>12) & 0xFFF;
+ flipbits |= (bits<<12) & 0xFFF000;
+ memcpy(dst+2, &flipbits, 6);
+}
+
+void
+flipDXT1(uint8 *dst, uint8 *src, uint32 width, uint32 height)
+{
+ int x, y;
+ int bw = (width+3)/4;
+ int bh = (height+3)/4;
+ if(height < 4){
+ // used pixels are always at the top
+ // so don't swap the full 4 rows
+ if(height == 2){
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipBlock_half(dst, src);
+ s += 8;
+ d += 8;
+ }
+ }else
+ memcpy(dst, src, 8*bw);
+ return;
+ }
+ dst += 8*bw*bh;
+ for(y = 0; y < bh; y++){
+ dst -= 8*bw;
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipBlock(d, s);
+ s += 8;
+ d += 8;
+ }
+ src += 8*bw;
+ }
+}
+
+void
+flipDXT3(uint8 *dst, uint8 *src, uint32 width, uint32 height)
+{
+ int x, y;
+ int bw = (width+3)/4;
+ int bh = (height+3)/4;
+ if(height < 4){
+ // used pixels are always at the top
+ // so don't swap the full 4 rows
+ if(height == 2){
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipAlphaBlock3_half(d, s);
+ flipBlock_half(d+8, s+8);
+ s += 16;
+ d += 16;
+ }
+ }else
+ memcpy(dst, src, 16*bw);
+ return;
+ }
+ dst += 16*bw*bh;
+ for(y = 0; y < bh; y++){
+ dst -= 16*bw;
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipAlphaBlock3(d, s);
+ flipBlock(d+8, s+8);
+ s += 16;
+ d += 16;
+ }
+ src += 16*bw;
+ }
+}
+
+void
+flipDXT5(uint8 *dst, uint8 *src, uint32 width, uint32 height)
+{
+ int x, y;
+ int bw = (width+3)/4;
+ int bh = (height+3)/4;
+ if(height < 4){
+ // used pixels are always at the top
+ // so don't swap the full 4 rows
+ if(height == 2){
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipAlphaBlock5_half(d, s);
+ flipBlock_half(d+8, s+8);
+ s += 16;
+ d += 16;
+ }
+ }else
+ memcpy(dst, src, 16*bw);
+ return;
+ }
+ dst += 16*bw*bh;
+ for(y = 0; y < bh; y++){
+ dst -= 16*bw;
+ uint8 *s = src;
+ uint8 *d = dst;
+ for(x = 0; x < bw; x++){
+ flipAlphaBlock5(d, s);
+ flipBlock(d+8, s+8);
+ s += 16;
+ d += 16;
+ }
+ src += 16*bw;
+ }
+}
+
+void
+flipDXT(int32 type, uint8 *dst, uint8 *src, uint32 width, uint32 height)
+{
+ switch(type){
+ case 1:
+ flipDXT1(dst, src, width, height);
+ break;
+ case 3:
+ flipDXT3(dst, src, width, height);
+ break;
+ case 5:
+ flipDXT5(dst, src, width, height);
+ break;
+ }
+}
+
+void
+Image::setPixelsDXT(int32 type, uint8 *pixels)
+{
+ switch(type){
+ case 1:
+ decompressDXT1(this->pixels, this->width, this->height, pixels);
+ break;
+ case 3:
+ decompressDXT3(this->pixels, this->width, this->height, pixels);
+ break;
+ case 5:
+ decompressDXT5(this->pixels, this->width, this->height, pixels);
+ break;
+ }
+}
+
+void
+Image::setPalette(uint8 *palette)
+{
+ this->palette = palette;
+ this->flags |= 2;
+}
+
+void
+Image::compressPalette(void)
+{
+ if(this->depth != 8)
+ return;
+ uint8 *pixels = this->pixels;
+ for(int y = 0; y < this->height; y++){
+ uint8 *line = pixels;
+ for(int x = 0; x < this->width; x++){
+ if(*line > 0xF) return;
+ line += this->bpp;
+ }
+ pixels += this->stride;
+ }
+ this->depth = 4;
+}
+
+bool32
+Image::hasAlpha(void)
+{
+ uint8 ret = 0xFF;
+ uint8 *pixels = this->pixels;
+ if(this->depth == 32){
+ for(int y = 0; y < this->height; y++){
+ uint8 *line = pixels;
+ for(int x = 0; x < this->width; x++){
+ ret &= line[3];
+ line += this->bpp;
+ }
+ pixels += this->stride;
+ }
+ }else if(this->depth == 24){
+ return 0;
+ }else if(this->depth == 16){
+ for(int y = 0; y < this->height; y++){
+ uint8 *line = pixels;
+ for(int x = 0; x < this->width; x++){
+ ret &= line[1] & 0x80;
+ line += this->bpp;
+ }
+ pixels += this->stride;
+ }
+ return ret != 0x80;
+ }else if(this->depth <= 8){
+ for(int y = 0; y < this->height; y++){
+ uint8 *line = pixels;
+ for(int x = 0; x < this->width; x++){
+ ret &= this->palette[*line*4+3];
+ line += this->bpp;
+ }
+ pixels += this->stride;
+ }
+ }
+ return ret != 0xFF;
+}
+
+void
+Image::convertTo32(void)
+{
+ assert(this->pixels);
+ uint8 *pixels = this->pixels;
+ int32 newstride = this->width*4;
+ uint8 *newpixels;
+
+ void (*fun)(uint8 *out, uint8 *in) = nil;
+ switch(this->depth){
+ case 4:
+ case 8:
+ assert(this->palette);
+ this->unpalettize(true);
+ return;
+ case 16:
+ fun = conv_RGBA8888_from_ARGB1555;
+ break;
+ case 24:
+ fun = conv_RGBA8888_from_RGB888;
+ break;
+ default:
+ return;
+ }
+
+ newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE);
+ uint8 *pixels32 = newpixels;
+ for(int y = 0; y < this->height; y++){
+ uint8 *line = pixels;
+ uint8 *newline = newpixels;
+ for(int x = 0; x < this->width; x++){
+ fun(newline, line);
+ line += this->bpp;
+ newline += 4;
+ }
+ pixels += this->stride;
+ newpixels += newstride;
+ }
+
+ this->free();
+ this->depth = 32;
+ this->bpp = 4;
+ this->stride = newstride;
+ this->pixels = nil;
+ this->palette = nil;
+ this->setPixels(pixels32);
+}
+
+void
+Image::palettize(int32 depth)
+{
+ RGBA colors[256];
+ ColorQuant quant;
+ uint8 *newpixels;
+ uint32 newstride;
+
+ quant.init();
+ quant.addImage(this);
+ assert(depth <= 8);
+ quant.makePalette(1<<depth, colors);
+
+ newstride = this->width;
+ newpixels = rwNewT(uint8, newstride*this->height, MEMDUR_EVENT | ID_IMAGE);
+ // TODO: maybe do floyd-steinberg dithering?
+ quant.matchImage(newpixels, newstride, this);
+
+ this->free();
+ this->depth = depth;
+ this->bpp = depth < 8 ? 1 : depth/8;
+ this->stride = newstride;
+ this->pixels = nil;
+ this->palette = nil;
+ this->setPixels(newpixels);
+ this->allocate();
+ memcpy(this->palette, colors, 4*(1<<depth));
+
+ quant.destroy();
+}
+
+void
+Image::unpalettize(bool forceAlpha)
+{
+ if(this->depth > 8)
+ return;
+ assert(this->pixels);
+ assert(this->palette);
+
+ int32 ndepth = (forceAlpha || this->hasAlpha()) ? 32 : 24;
+ int32 nstride = this->width*ndepth/8;
+ uint8 *npixels = rwNewT(uint8, nstride*this->height, MEMDUR_EVENT | ID_IMAGE);
+
+ uint8 *line = this->pixels;
+ uint8 *nline = npixels;
+ uint8 *p, *np;
+ for(int32 y = 0; y < this->height; y++){
+ p = line;
+ np = nline;
+ for(int32 x = 0; x < this->width; x++){
+ np[0] = this->palette[*p*4+0];
+ np[1] = this->palette[*p*4+1];
+ np[2] = this->palette[*p*4+2];
+ np += 3;
+ if(ndepth == 32)
+ *np++ = this->palette[*p*4+3];
+ p++;
+ }
+ line += this->stride;
+ nline += nstride;
+ }
+ this->free();
+ this->depth = ndepth;
+ this->bpp = ndepth < 8 ? 1 : ndepth/8;
+ this->stride = nstride;
+ this->setPixels(npixels);
+}
+
+// Copy the biggest channel value to alpha
+void
+Image::makeMask(void)
+{
+ int32 maxcol;
+ switch(this->depth){
+ case 4:
+ case 8: {
+ assert(this->palette);
+ int32 pallen = 1 << this->depth;
+ for(int32 i = 0; i < pallen; i++){
+ maxcol = this->palette[i*4+0];
+ if(this->palette[i*4+1] > maxcol) maxcol = this->palette[i*4+1];
+ if(this->palette[i*4+2] > maxcol) maxcol = this->palette[i*4+2];
+ this->palette[i*4+3] = maxcol;
+ }
+ break;
+ }
+
+ case 16:
+ case 24:
+ this->convertTo32();
+ // fallthrough
+
+ case 32: {
+ assert(this->pixels);
+ uint8 *line = this->pixels;
+ uint8 *p;
+ for(int32 y = 0; y < this->height; y++){
+ p = line;
+ for(int32 x = 0; x < this->width; x++){
+ maxcol = p[0];
+ if(p[1] > maxcol) maxcol = p[1];
+ if(p[2] > maxcol) maxcol = p[2];
+ p[3] = maxcol;
+ p += this->bpp;
+ }
+ line += this->stride;
+ }
+ break;
+ }
+ }
+}
+
+void
+Image::applyMask(Image *mask)
+{
+ if(this->width != mask->width || this->height != mask->height)
+ return; // TODO: set an error
+ // we could use alpha with 16 bits but what's the point?
+ if(mask->depth == 16 || mask->depth == 24)
+ return;
+
+ this->convertTo32();
+ assert(this->depth == 32);
+
+ uint8 *line = this->pixels;
+ uint8 *mline = mask->pixels;
+ uint8 *p, *m;
+ for(int32 y = 0; y < this->height; y++){
+ p = line;
+ m = mline;
+ for(int32 x = 0; x < this->width; x++){
+ if(mask->depth == 32)
+ p[3] = m[3];
+ else if(mask->depth <= 8)
+ p[3] = mask->palette[m[0]*4+3];
+ p += this->bpp;
+ m += mask->bpp;
+ }
+ line += this->stride;
+ mline += mask->stride;
+ }
+}
+
+void
+Image::removeMask(void)
+{
+ if(this->depth <= 8){
+ assert(this->palette);
+ int32 pallen = 4*(1 << this->depth);
+ for(int32 i = 0; i < pallen; i += 4)
+ this->palette[i+3] = 0xFF;
+ return;
+ }
+ if(this->depth == 24)
+ return;
+ assert(this->pixels);
+ uint8 *line = this->pixels;
+ uint8 *p;
+ for(int32 y = 0; y < this->height; y++){
+ p = line;
+ for(int32 x = 0; x < this->width; x++){
+ switch(this->depth){
+ case 16:
+ p[1] |= 0x80;
+ p += 2;
+ break;
+ case 32:
+ p[3] = 0xFF;
+ p += 4;
+ break;
+ }
+ }
+ line += this->stride;
+ }
+}
+
+Image*
+Image::extractMask(void)
+{
+ Image *img = Image::create(this->width, this->height, 8);
+ img->allocate();
+
+ // use an 8bit palette to store all shades of grey
+ for(int32 i = 0; i < 256; i++){
+ img->palette[i*4+0] = i;
+ img->palette[i*4+1] = i;
+ img->palette[i*4+2] = i;
+ img->palette[i*4+3] = 0xFF;
+ }
+
+ // Then use the alpha value as palette index
+ uint8 *line = this->pixels;
+ uint8 *nline = img->pixels;
+ uint8 *p, *np;
+ for(int32 y = 0; y < this->height; y++){
+ p = line;
+ np = nline;
+ for(int32 x = 0; x < this->width; x++){
+ switch(this->depth){
+ case 4:
+ case 8:
+ *np++ = this->palette[*p*4+3];
+ p++;
+ break;
+ case 16:
+ *np++ = 0xFF*!!(p[1]&0x80);
+ p += 2;
+ break;
+ case 24:
+ *np++ = 0xFF;
+ p += 3;
+ break;
+ case 32:
+ *np++ = p[3];
+ p += 4;
+ break;
+ }
+ }
+ line += this->stride;
+ nline += img->stride;
+ }
+ return img;
+}
+
+void
+Image::setSearchPath(const char *path)
+{
+ char *p, *end;
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ rwFree(g->searchPaths);
+ g->numSearchPaths = 0;
+ if(path)
+ g->searchPaths = p = rwStrdup(path, MEMDUR_EVENT);
+ else{
+ g->searchPaths = nil;
+ return;
+ }
+ while(p && *p){
+ end = strchr(p, ';');
+ if(end)
+ *end++ = '\0';
+ g->numSearchPaths++;
+ p = end;
+ }
+}
+
+void
+Image::printSearchPath(void)
+{
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ char *p = g->searchPaths;
+ for(int i = 0; i < g->numSearchPaths; i++){
+ printf("%s\n", p);
+ p += strlen(p) + 1;
+ }
+}
+
+char*
+Image::getFilename(const char *name)
+{
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ void *f;
+ char *s, *p = g->searchPaths;
+ size_t len = strlen(name)+1;
+ if(g->numSearchPaths == 0){
+ s = rwStrdup(name, MEMDUR_EVENT);
+ makePath(s);
+ f = engine->filefuncs.rwfopen(s, "rb");
+ if(f){
+ engine->filefuncs.rwfclose(f);
+// printf("found %s\n", s);
+ return s;
+ }
+ rwFree(s);
+ return nil;
+ }else
+ for(int i = 0; i < g->numSearchPaths; i++){
+ s = (char*)rwMalloc(strlen(p)+len, MEMDUR_EVENT | ID_IMAGE);
+ if(s == nil){
+ RWERROR((ERR_ALLOC, strlen(p)+len));
+ return nil;
+ }
+ strcpy(s, p);
+ strcat(s, name);
+ makePath(s);
+ f = engine->filefuncs.rwfopen(s, "r");
+ if(f){
+ engine->filefuncs.rwfclose(f);
+// printf("found %s\n", name);
+ return s;
+ }
+ rwFree(s);
+ p += strlen(p) + 1;
+ }
+ return nil;
+}
+
+Image*
+Image::readMasked(const char *imageName, const char *maskName)
+{
+ Image *img, *mask;
+
+ img = read(imageName);
+ if(img == nil)
+ return nil;
+ if(maskName && maskName[0]){
+ mask = read(maskName);
+ if(mask == nil)
+ return img;
+ mask->makeMask();
+ int32 origDepth = img->depth;
+ img->applyMask(mask);
+ mask->destroy();
+ if(origDepth <= 8 && img->depth != origDepth)
+ img->palettize(origDepth);
+ }
+ return img;
+}
+
+Image*
+Image::read(const char *imageName)
+{
+ int i;
+ char *filename, *ext, *found;
+ Image *img;
+
+ filename = rwNewT(char, strlen(imageName) + 20, MEMDUR_FUNCTION | ID_IMAGE);
+ strcpy(filename, imageName);
+ ext = filename + strlen(filename);
+ *ext++ = '.';
+ // Try all supported extensions
+ for(i = 0; i < IMAGEGLOBAL(numFileFormats); i++){
+ if(IMAGEGLOBAL(fileFormats)[i].read == nil)
+ continue;
+ strncpy(ext, IMAGEGLOBAL(fileFormats)[i].extension, 19);
+ found = getFilename(filename);
+ // Found a file
+ if(found){
+ img = IMAGEGLOBAL(fileFormats)[i].read(found);
+ rwFree(found);
+ // It was a valid image of that format
+ if(img){
+ rwFree(filename);
+ return img;
+ }
+ }
+ }
+ rwFree(filename);
+ return nil;
+}
+
+bool32
+Image::registerFileFormat(const char *ext, fileRead read, fileWrite write)
+{
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ if(g->numFileFormats >= (int)nelem(g->fileFormats))
+ return 0;
+ g->fileFormats[g->numFileFormats].extension = rwStrdup(ext, MEMDUR_EVENT);
+ g->fileFormats[g->numFileFormats].read = read;
+ g->fileFormats[g->numFileFormats].write = write;
+ g->numFileFormats++;
+ return 1;
+}
+
+static void*
+imageOpen(void *object, int32 offset, int32 size)
+{
+ imageModuleOffset = offset;
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ g->searchPaths = nil;
+ g->numSearchPaths = 0;
+ g->numFileFormats = 0;
+ return object;
+}
+
+static void*
+imageClose(void *object, int32 offset, int32 size)
+{
+ ImageGlobals *g = PLUGINOFFSET(ImageGlobals, engine, imageModuleOffset);
+ int i;
+ rwFree(g->searchPaths);
+ g->searchPaths = nil;
+ g->numSearchPaths = 0;
+ for(i = 0; i < g->numFileFormats; i++)
+ rwFree(g->fileFormats[i].extension);
+ g->numFileFormats = 0;
+ return object;
+}
+
+void
+Image::registerModule(void)
+{
+ Engine::registerPlugin(sizeof(ImageGlobals), ID_IMAGEMODULE, imageOpen, imageClose);
+}
+
+
+
+/*
+ * Color Quantization
+ */
+
+// An address for a single level is 4 bits.
+// Since we have 8 bpp that is 32 bits to address any tree node.
+// The lower bits address the higher level tree nodes.
+// This is essentially a bit reverse and swizzle.
+static uint32
+makeTreeAddr(RGBA color)
+{
+ int32 i;
+ uint32 addr = 0;
+ uint32 r = 1;
+ uint32 g = 2;
+ uint32 b = 4;
+ uint32 a = 8;
+ for(i = 0; i < 8; i++){
+ uint32 mask = 0x80>>i;
+ if(color.red & mask) addr |= r;
+ if(color.green & mask) addr |= g;
+ if(color.blue & mask) addr |= b;
+ if(color.alpha & mask) addr |= a;
+ r <<= 4;
+ g <<= 4;
+ b <<= 4;
+ a <<= 4;
+ }
+ return addr;
+}
+
+void
+ColorQuant::Node::destroy(void)
+{
+ int i;
+ for(i = 0; i < 16; i++)
+ if(this->children[i])
+ this->children[i]->destroy();
+ if(this->link.next)
+ this->link.remove();
+ rwFree(this);
+}
+
+ColorQuant::Node*
+ColorQuant::createNode(int32 level)
+{
+ int i;
+ ColorQuant::Node *node = rwNewT(ColorQuant::Node, 1, MEMDUR_EVENT | ID_IMAGE);
+ node->parent = nil;
+ for(i = 0; i < 16; i++)
+ node->children[i] = nil;
+ node->r = 0;
+ node->g = 0;
+ node->b = 0;
+ node->a = 0;
+ node->numPixels = 0;
+ node->link.init();
+
+ if(level == 0)
+ this->leaves.append(&node->link);
+
+ return node;
+}
+
+ColorQuant::Node*
+ColorQuant::getNode(ColorQuant::Node *root, uint32 addr, int32 level)
+{
+ if(level == 0)
+ return root;
+
+ uint32 a = addr & 0xF;
+ if(root->children[a] == nil){
+ root->children[a] = this->createNode(level-1);
+ root->children[a]->parent = root;
+ }
+
+ return this->getNode(root->children[a], addr>>4, level-1);
+}
+
+ColorQuant::Node*
+ColorQuant::findNode(ColorQuant::Node *root, uint32 addr, int32 level)
+{
+ if(level == 0)
+ return root;
+
+ uint32 a = addr & 0xF;
+ if(root->children[a] == nil)
+ return root;
+
+ return this->findNode(root->children[a], addr>>4, level-1);
+}
+
+void
+ColorQuant::reduceNode(Node *node)
+{
+ int i;
+ assert(node->numPixels == 0);
+ for(i = 0; i < 16; i++)
+ if(node->children[i]){
+ node->r += node->children[i]->r;
+ node->g += node->children[i]->g;
+ node->b += node->children[i]->b;
+ node->a += node->children[i]->a;
+ node->numPixels += node->children[i]->numPixels;
+ node->children[i]->destroy();
+ node->children[i] = nil;
+ }
+ assert(node->link.next == nil);
+ assert(node->link.prev == nil);
+ this->leaves.append(&node->link);
+}
+
+void
+ColorQuant::Node::addColor(RGBA color)
+{
+ this->r += color.red;
+ this->g += color.green;
+ this->b += color.blue;
+ this->a += color.alpha;
+ this->numPixels++;
+}
+
+void
+ColorQuant::init(void)
+{
+ this->leaves.init();
+ this->root = this->createNode(QUANTDEPTH);
+}
+
+void
+ColorQuant::destroy(void)
+{
+ this->root->destroy();
+}
+
+void
+ColorQuant::addColor(RGBA color)
+{
+ uint32 addr = makeTreeAddr(color);
+ ColorQuant::Node *node = this->getNode(root, addr, QUANTDEPTH);
+ node->addColor(color);
+}
+
+uint8
+ColorQuant::findColor(RGBA color)
+{
+ uint32 addr = makeTreeAddr(color);
+ ColorQuant::Node *node = this->findNode(root, addr, QUANTDEPTH);
+ return node->numPixels;
+}
+
+void
+ColorQuant::addImage(Image *img)
+{
+ RGBA col;
+ uint8 rgba[4];
+ uint8 *pixels = img->pixels;
+ for(int y = 0; y < img->height; y++){
+ uint8 *line = pixels;
+ for(int x = 0; x < img->width; x++){
+ uint8 *p = line;
+ switch(img->depth){
+ case 4: case 8:
+ conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]);
+ break;
+ case 32:
+ conv_RGBA8888_from_RGBA8888(rgba, p);
+ break;
+ case 24:
+ conv_RGBA8888_from_RGB888(rgba, p);
+ break;
+ case 16:
+ conv_RGBA8888_from_ARGB1555(rgba, p);
+ break;
+ default: assert(0 && "invalid depth");
+ }
+ col.red = rgba[0];
+ col.green = rgba[1];
+ col.blue = rgba[2];
+ col.alpha = rgba[3];
+ this->addColor(col);
+ line += img->bpp;
+ }
+ pixels += img->stride;
+ }
+}
+
+void
+ColorQuant::makePalette(int32 numColors, RGBA *colors)
+{
+ while(this->leaves.count() > numColors){
+ Node *n = LLLinkGetData(this->leaves.link.next, Node, link);
+ this->reduceNode(n->parent);
+ }
+
+ int i = 0;
+ FORLIST(lnk, this->leaves){
+ Node *n = LLLinkGetData(lnk, Node, link);
+ n->r /= n->numPixels;
+ n->g /= n->numPixels;
+ n->b /= n->numPixels;
+ n->a /= n->numPixels;
+ colors[i].red = n->r;
+ colors[i].green = n->g;
+ colors[i].blue = n->b;
+ colors[i].alpha = n->a;
+ n->numPixels = i++;
+ }
+}
+
+void
+ColorQuant::matchImage(uint8 *dstPixels, uint32 dstStride, Image *img)
+{
+ RGBA col;
+ uint8 rgba[4];
+ uint8 *pixels = img->pixels;
+ for(int y = 0; y < img->height; y++){
+ uint8 *line = pixels;
+ uint8 *dline = dstPixels;
+ for(int x = 0; x < img->width; x++){
+ uint8 *p = line;
+ uint8 *d = dline;
+ switch(img->depth){
+ case 4: case 8:
+ conv_RGBA8888_from_RGBA8888(rgba, &img->palette[p[0]*4]);
+ break;
+ case 32:
+ conv_RGBA8888_from_RGBA8888(rgba, p);
+ break;
+ case 24:
+ conv_RGBA8888_from_RGB888(rgba, p);
+ break;
+ case 16:
+ conv_RGBA8888_from_ARGB1555(rgba, p);
+ break;
+ default: assert(0 && "invalid depth");
+ }
+
+ col.red = rgba[0];
+ col.green = rgba[1];
+ col.blue = rgba[2];
+ col.alpha = rgba[3];
+ *d = this->findColor(col);
+
+ line += img->bpp;
+ dline++;
+ }
+ pixels += img->stride;
+ dstPixels += dstStride;
+ }
+}
+
+
+
+}