#include #include #include "plat.h" #include "str.h" #include "vid_enums.h" typedef struct { float r, g, b; } vec3; typedef struct { unsigned char r, g, b, a; } Colour; typedef struct { Colour* pixels; int w, h; } Image; typedef struct { vec3 start, end; unsigned char indices[4 * 4]; } Block; typedef struct { float start, end; unsigned char indices[4 * 4]; } Block_BC4; const char* format_names[] = { #define x(n) #n, texture_format_xmacro() #undef x }; unsigned encode_endpoint(const vec3* v) { return ((unsigned)(v->r * 31.9999f) << 11) | ((unsigned)(v->g * 63.9999f) << 5) | ((unsigned)(v->b * 31.9999f)); } unsigned encode_endpoint_bc4(float v) { return (unsigned)(v * 255.0f); } void get_block(Block* block, Colour* pixels, int stride) { int x, y, i; vec3 cols[4 * 4]; vec3 palette[4]; vec3 s, e; unsigned es, ee; float cd; for (y = 0; y < 4; y++) { for (x = 0; x < 4; x++) { Colour pixel = pixels[x + y * stride]; vec3 vec; vec.r = (float)pixel.r / 255.0f; vec.g = (float)pixel.g / 255.0f; vec.b = (float)pixel.b / 255.0f; cols[x + y * 4] = vec; } } cd = -1.0f; for (i = 0; i < 4 * 4; i++) { int j; vec3* a = &cols[i]; for (j = i + 1; j < 4 * 4; j++) { vec3* b = &cols[j]; float d = (b->r - a->r) * (b->r - a->r) + (b->g - a->g) * (b->g - a->g) + (b->b - a->b) * (b->b - a->b); if (d > cd) { cd = d; s = *a; e = *b; } } } es = encode_endpoint(&s); ee = encode_endpoint(&e); if (es == ee) { block->start = s; block->end.r = 0.0f; block->end.g = 0.0f; block->end.b = 0.0f; for (i = 0; i < 16; i++) { block->indices[i] = 0; } return; } if (es < ee) { /* todo alpha */ vec3 t = s; s = e; e = t; } palette[0] = s; palette[1] = e; palette[2].r = 0.666666f*palette[0].r + 0.333333f*palette[1].r; palette[2].g = 0.666666f*palette[0].g + 0.333333f*palette[1].g; palette[2].b = 0.666666f*palette[0].b + 0.333333f*palette[1].b; palette[3].r = 0.333333f*palette[0].r + 0.666666f*palette[1].r; palette[3].g = 0.333333f*palette[0].g + 0.666666f*palette[1].g; palette[3].b = 0.333333f*palette[0].b + 0.666666f*palette[1].b; for (i = 0; i < 4 * 4; i++) { vec3* col = &cols[i]; int closest = 0, j; float cd = 99999.0f; for (j = 0; j < 4; j++) { vec3* p = &palette[j]; float d = (p->r - col->r) * (p->r - col->r) + (p->g - col->g) * (p->g - col->g) + (p->b - col->b) * (p->b - col->b); if (d < cd) { cd = d; closest = j; } } block->indices[i] = closest; } block->start = s; block->end = e; } void compress_block(const Block* block, FILE* f) { int i; unsigned start = encode_endpoint(&block->start); unsigned end = encode_endpoint(&block->end); unsigned indices = 0; fwrite(&start, 1, 2, f); fwrite(&end, 1, 2, f); for (i = 0; i < 4 * 4; i++) { indices |= block->indices[i] << (i * 2); } fwrite(&indices, 1, 4, f); } void get_block_bc4( Block_BC4* block, Colour* pixels, int stride, int channel ) { int x, y; float palette[8]; palette[0] = 0.08f; palette[1] = 0.92f; float biggest = -1.0f; float smallest = 1.0f; for (y = 0; y < 4; y++) { for (x = 0; x < 4; x++) { uint8_t* pixel = (uint8_t*)&pixels[x + y * stride]; float v = (float)pixel[channel] / 255.0f; if (v > biggest) biggest = v; if (v < smallest) smallest = v; if (v > palette[0]) palette[0] = v; if (v < palette[1]) palette[1] = v; } } if (biggest == smallest) { int i, idx = 1; block->end = biggest; if (palette[0] > 0.9f) { idx = 0; block->start = smallest; block->end = 0.0f; } else block->start = 1.0f; assert(block->start > block->end); for (i = 0; i < 16; i++) block->indices[i] = idx; } if (biggest > 0.92f && smallest < 0.08f) { float t = palette[1]; palette[1] = palette[0]; palette[0] = t; palette[2] = (4.0f * palette[0] + 1.0f * palette[1]) / 5.0f; palette[3] = (3.0f * palette[0] + 2.0f * palette[1]) / 5.0f; palette[4] = (2.0f * palette[0] + 3.0f * palette[1]) / 5.0f; palette[5] = (1.0f * palette[0] + 4.0f * palette[1]) / 5.0f; palette[6] = 0.0f; palette[7] = 1.0f; } else { palette[2] = (6.0f * palette[0] + 1.0f * palette[1]) / 7.0f; palette[3] = (5.0f * palette[0] + 2.0f * palette[1]) / 7.0f; palette[4] = (4.0f * palette[0] + 3.0f * palette[1]) / 7.0f; palette[5] = (3.0f * palette[0] + 4.0f * palette[1]) / 7.0f; palette[6] = (2.0f * palette[0] + 5.0f * palette[1]) / 7.0f; palette[7] = (1.0f * palette[0] + 6.0f * palette[1]) / 7.0f; } block->start = palette[0]; block->end = palette[1]; for (y = 0; y < 4; y++) { for (x = 0; x < 4; x++) { uint8_t* pixel = (uint8_t*)&pixels[x + y * stride]; float v = (float)pixel[channel] / 255.0f; float d = 100.0f; int i; for (i = 0; i < 8; i++) { float d2 = (v - palette[i]); d2 = d2 < 0.0f? -d2: d2; if (d2 < d) { d = d2; block->indices[x + y * 4] = i; } } } } } void compress_block_bc4(const Block_BC4* block, FILE* f) { int i; unsigned start = encode_endpoint_bc4(block->start); unsigned end = encode_endpoint_bc4(block->end); uint64_t indices = 0; fwrite(&start, 1, 1, f); fwrite(&end, 1, 1, f); for (i = 0; i < 4 * 4; i++) { indices |= ((uint64_t)block->indices[i]) << (i * 3); } /* 48 bits of indices */ fwrite(&indices, 1, 6, f); } void compress_bc1(Colour* pixels, int w, int h, FILE* f) { int x, y, cw, ch; Block block; cw = w / 4; ch = h / 4; for (y = 0; y < ch; y++) { for (x = 0; x < cw; x++) { get_block(&block, &pixels[x * 4 + y * 4 * w], w); compress_block(&block, f); } } } void compress_bc4(Colour* pixels, int w, int h, FILE* f) { int x, y, cw = w / 4, ch = h / 4; Block_BC4 block; for (y = 0; y < ch; y++) { for (x = 0; x < cw; x++) { get_block_bc4(&block, &pixels[x * 4 + y * 4 * w], w, 0); compress_block_bc4(&block, f); } } } void compress_bc5(Colour* pixels, int w, int h, FILE* f) { int x, y, cw = w / 4, ch = h / 4; Block_BC4 a, b; for (y = 0; y < ch; y++) { for (x = 0; x < cw; x++) { Colour* p = &pixels[x * 4 + y * 4 * w]; get_block_bc4(&a, p, w, 0); get_block_bc4(&b, p, w, 1); compress_block_bc4(&a, f); compress_block_bc4(&b, f); } } } void convert(Image* image, Texture_Format target, FILE* f) { fwrite("TXTR", 1, 4, f); fwrite(&image->w, 1, 4, f); fwrite(&image->h, 1, 4, f); fwrite(&target, 1, 4, f); switch (target) { case texture_format_bc1: compress_bc1(image->pixels, image->w, image->h, f); break; case texture_format_bc4: compress_bc4(image->pixels, image->w, image->h, f); break; case texture_format_bc5: compress_bc5(image->pixels, image->w, image->h, f); break; default: print_err("Unsupported target format.\n"); pbreak(40); } } Texture_Format texture_format_from_string(const char* s) { int i, e = sizeof format_names / sizeof *format_names; for (i = 0; i < e; i++) if (string_equal(s, format_names[i])) return (Texture_Format)i; print_err("Invalid texture format %s\n", s); pbreak(10); return (Texture_Format)0; } int main(int argc, const char** argv) { FILE* outfile, * infile; char bmp_magic[2]; unsigned bmp_offset; int bmp_w, bmp_h, s, x, y; unsigned short bmp_bits; Colour* buffer; Image img; Colour pixel; Texture_Format target; if (argc < 4) { print_err("Usage: %s infile outfile format.\n", argv[0]); return 1; } infile = fopen(argv[1], "rb"); if (!infile) { print_err("Failed to open %s.\n", argv[1]); return 2; } target = texture_format_from_string(argv[3]); fread(bmp_magic, 2, 1, infile); if (bmp_magic[0] != 'B' || bmp_magic[1] != 'M') { print_err("Not a valid bitmap file.\n"); return 3; } fseek(infile, 10, SEEK_SET); fread(&bmp_offset, 4, 1, infile); fseek(infile, 18, SEEK_SET); fread(&bmp_w, 4, 1, infile); fread(&bmp_h, 4, 1, infile); fseek(infile, 28, SEEK_SET); fread(&bmp_bits, 2, 1, infile); if (bmp_bits != 32) { print_err("Bitmap must have 32 bit pixels. Instead has %d bit pixels.\n", bmp_bits); return 4; } if (bmp_w % 4 != 0 || bmp_h % 4 != 0) { print_err("Bitmap must have a size divisible by four.\n"); return 5; } fseek(infile, bmp_offset, SEEK_SET); s = bmp_w * bmp_h; buffer = malloc(s * 4); /* Flip & read. */ for (y = 0; y < bmp_h; y++) { for (x = 0; x < bmp_w; x++) { unsigned char t; fread(&pixel, 1, 4, infile); t = pixel.r; pixel.r = pixel.b; pixel.b = t; if (pixel.a == 0) { pixel.r = 0; pixel.g = 0; pixel.b = 0; } buffer[x + (bmp_h - y - 1) * bmp_w] = pixel; } } outfile = fopen(argv[2], "wb"); if (!outfile) { print_err("Failed to open %s.\n", argv[2]); return 6; } img.pixels = buffer; img.w = bmp_w; img.h = bmp_h; convert(&img, target, outfile); fclose(outfile); return 0; }