diff options
Diffstat (limited to 'src/image.cpp')
| -rw-r--r-- | src/image.cpp | 1324 |
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; + } +} + + + +} |
