mirror of
https://github.com/sehugg/8bitworkshop.git
synced 2024-12-01 13:50:30 +00:00
477 lines
13 KiB
C
477 lines
13 KiB
C
|
|
/*
|
|
---------------------------------------------------------------
|
|
TMSOPT v.0.1 - Eduardo A. Robsy Petrus & Arturo Ragozini 2007
|
|
Credits to Rafael Jannone for his Floyd-Steinberg implementation
|
|
---------------------------------------------------------------
|
|
TGA image converter (24 bpp, uncompressed) to TMS9918 format
|
|
---------------------------------------------------------------
|
|
Overview
|
|
---------------------------------------------------------------
|
|
Selects the best solution for each 8x1 pixel block
|
|
Optimization uses the following algorithm:
|
|
|
|
(a) Select one 1x8 block, select a couple of colors, apply
|
|
Floyd-Steinberg within the block, compute the squared error,
|
|
repeat for all 105 color combinations, keep the best couple
|
|
of colors.
|
|
|
|
(b) Apply Floyd-Steinberg to the current 1x8 block with the best
|
|
two colors seleted before and spread the errors to the
|
|
adjacent blocks.
|
|
|
|
(c) repeat (a) and (b) on the next 1x8 block, scan all lines.
|
|
|
|
(d) Convert the image in pattern and color definitions (CHR & CLR)
|
|
|
|
To load in MSX basic use something like this:
|
|
|
|
10 screen 2: color 15,0,0
|
|
20 bload"FILE.CHR",s
|
|
30 bload"FILE.CLR",s
|
|
40 goto 40
|
|
|
|
---------------------------------------------------------------
|
|
Compilation instructions
|
|
---------------------------------------------------------------
|
|
Tested with GCC/Win32 [mingw]:
|
|
|
|
GCC TMSopt.c -oTMSopt.exe -O3 -s
|
|
|
|
It is standard C, so there is a fair chance of being portable!
|
|
NOTE
|
|
In the current release the name of the C file has become scr2floyd.c
|
|
---------------------------------------------------------------
|
|
History
|
|
---------------------------------------------------------------
|
|
Ages ago - algorithm created
|
|
16/05/2007 - first C version (RAW format)
|
|
17/05/2007 - TGA format included, some optimization included
|
|
18/05/2007 - Big optimization (200 times faster), support for
|
|
square errors
|
|
19/05/2007 - Floyd-Stenberg added, scaling for better rounding
|
|
24/05/2007 - Floyd-Stenberg included in the color optimization.
|
|
---------------------------------------------------------------
|
|
Legal disclaimer
|
|
---------------------------------------------------------------
|
|
Do whatever you want to do with this code/program.
|
|
Use at your own risk, all responsability would be declined.
|
|
It would be nice if you credit the authors, though.
|
|
---------------------------------------------------------------
|
|
*/
|
|
|
|
// Headers!
|
|
|
|
#include<stdio.h>
|
|
#include<time.h>
|
|
#include<limits.h>
|
|
#include<stdlib.h>
|
|
|
|
typedef unsigned int uint;
|
|
typedef unsigned char uchar;
|
|
typedef unsigned short ushort;
|
|
typedef unsigned long ulong;
|
|
|
|
//#define DEBUG
|
|
|
|
#define scale 16
|
|
#define inrange8(t) ((t)<0) ? 0 :(((t)>255) ? 255:(t))
|
|
#define clamp(t) ((t)<0) ? 0 :(((t)>255*scale) ? 255*scale : (t))
|
|
|
|
|
|
typedef struct {
|
|
float r, g, b;
|
|
} RGB;
|
|
|
|
float ColourDistance(RGB e1, RGB e2)
|
|
{
|
|
float r,g,b;
|
|
float rmean;
|
|
|
|
e1.r/=scale;
|
|
e1.g/=scale;
|
|
e1.b/=scale;
|
|
|
|
e2.r/=scale;
|
|
e2.g/=scale;
|
|
e2.b/=scale;
|
|
|
|
rmean = ( (int)e1.r + (int)e2.r ) / 2 ;
|
|
r = ((int)e1.r - (int)e2.r);
|
|
g = ((int)e1.g - (int)e2.g);
|
|
b = ((int)e1.b - (int)e2.b);
|
|
// return r*r+g*g+b*b;
|
|
return ((((512+rmean)*r*r)/256) + 4*g*g + (((767-rmean)*b*b)/256));
|
|
}
|
|
|
|
// Just one function for everything
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
|
|
// Vars
|
|
|
|
FILE *file,*CHR,*CLR;
|
|
int bc,bp,i,j,x,y,c,p,k,MAXX,MAXY;
|
|
uint n,total=0,done=0,size;
|
|
char *name;
|
|
short image[512+2][512+2][3],header[18],palette[16][3];
|
|
|
|
// TMS9918 RGB palette - approximated 50Hz PAL values
|
|
uint pal[16][3]= {
|
|
{ 0,0,0}, // 0 Transparent
|
|
{ 0,0,0}, // 1 Black 0 0 0
|
|
{ 33,200,66}, // 2 Medium green 33 200 66
|
|
{ 94,220,120}, // 3 Light green 94 220 120
|
|
{ 84,85,237}, // 4 Dark blue 84 85 237
|
|
{ 125,118,252}, // 5 Light blue 125 118 252
|
|
{ 212,82,77}, // 6 Dark red 212 82 77
|
|
{ 66,235,245}, // 7 Cyan 66 235 245
|
|
{ 252,85,84}, // 8 Medium red 252 85 84
|
|
{ 255,121,120}, // 9 Light red 255 121 120
|
|
{ 212,193,84}, // A Dark yellow 212 193 84
|
|
{ 230,206,128}, // B Light yellow 230 206 128
|
|
{ 33,176,59}, // C Dark green 33 176 59
|
|
{ 201,91,186}, // D Magenta 201 91 186
|
|
{ 204,204,204}, // E Gray 204 204 204
|
|
{ 255,255,255} // F White 255 255 255
|
|
};
|
|
// Scale palette
|
|
|
|
for (i=0;i<16;i++)
|
|
for (k=0;k<3;k++)
|
|
palette[i][k] = scale*pal[i][k];
|
|
|
|
// Get time
|
|
|
|
clock();
|
|
|
|
// Application prompt
|
|
|
|
printf("TMSopt v.0.1 - TGA 24bpp to TMS9918 converter.\nCoded by Eduardo A. Robsy Petrus & Arturo Ragozini 2007.\n\n");
|
|
printf("Credits to Rafael Jannone for his Floyd-Steinberg implementation.\n \n");
|
|
|
|
|
|
// Guess the name of the image I used for testing
|
|
#ifdef DEBUG
|
|
argc = 2;
|
|
argv[1] = malloc(20);
|
|
argv[1][0] = 'l';
|
|
argv[1][1] = 'e';
|
|
argv[1][2] = 'n';
|
|
argv[1][3] = 'n';
|
|
argv[1][4] = 'a';
|
|
argv[1][5] = '_';
|
|
argv[1][6] = '.';
|
|
argv[1][7] = 't';
|
|
argv[1][8] = 'g';
|
|
argv[1][9] = 'a';
|
|
argv[1][10] = 0;
|
|
#endif
|
|
|
|
// Test if only one command-line parameter is available
|
|
|
|
if (argc==1)
|
|
{
|
|
printf("Syntax: TMSopt [file.tga]\n");
|
|
return 1;
|
|
}
|
|
|
|
// Open source image (TGA, 24-bit, uncompressed)
|
|
|
|
if ((file=fopen(argv[1],"rb"))==NULL)
|
|
{
|
|
printf("cannot open %s file!\n",argv[1]);
|
|
return 2;
|
|
}
|
|
|
|
// Read TGA header
|
|
|
|
for (i=0;i<18;i++) header[i]=fgetc(file);
|
|
|
|
// Check header info
|
|
|
|
for (i=0,n=0;i<12;i++) n+=header[i];
|
|
|
|
// I deleted the check on n, was it important ?
|
|
if ((header[2]!=2)||(header[17])||(header[16]!=24))
|
|
{
|
|
printf("Unsupported file format!\n");
|
|
return 3;
|
|
}
|
|
|
|
// Calculate size
|
|
|
|
MAXX=header[12]|header[13]<<8;
|
|
MAXY=header[14]|header[15]<<8;
|
|
|
|
size=((MAXX+7)>>3)*MAXY;
|
|
|
|
// Check size limits
|
|
|
|
if ((!MAXX)||(MAXX>512)||(!MAXY)||(MAXY>512))
|
|
{
|
|
printf("Unsupported size!");
|
|
return 4;
|
|
}
|
|
|
|
// Load image data
|
|
|
|
for (y=MAXY-1;y>=0;y--)
|
|
for (x=0;x<MAXX;x++)
|
|
for (k=0;k<3;k++)
|
|
image[x+1][y+1][2-k]=((short)fgetc(file))*scale; // Scale image
|
|
|
|
for (x=0;x<MAXX;x++)
|
|
for (k=0;k<3;k++)
|
|
image[x][0][k] = image[x][1][k];
|
|
|
|
for (y=0;y<MAXY;y++)
|
|
for (k=0;k<3;k++)
|
|
image[0][y][k] = image[1][0][k];
|
|
|
|
|
|
// Close file
|
|
|
|
fclose(file);
|
|
|
|
// Information
|
|
|
|
printf("Converting %s (%i,%i) to TMS9918 format ",argv[1],MAXX,MAXY);
|
|
printf("in (%i,%i) screen 2 tiles... ",((MAXX+7)>>3),((MAXY+7)>>3));
|
|
|
|
|
|
// Image processing
|
|
|
|
for (y=0;y<((MAXY+7)>>3);y++)
|
|
for (j=0;j<8;j++)
|
|
for (x=0;x<((MAXX+7)>>3);x++)
|
|
{
|
|
// Generate alternatives
|
|
uchar c1, c2;
|
|
uchar bc1, bc2;
|
|
uint bv;
|
|
uint bs = INT_MAX;
|
|
|
|
uint yy = 1+((y<<3)|j);
|
|
|
|
for (c1=1;c1<16;c1++)
|
|
{
|
|
RGB cp1 = {palette[c1][0],palette[c1][1],palette[c1][2]};
|
|
|
|
for (c2=c1+1;c2<16;c2++)
|
|
{
|
|
RGB cp2 = {palette[c2][0],palette[c2][1],palette[c2][2]};
|
|
|
|
uint xx = 1+(x<<3);
|
|
|
|
RGB ppp = {clamp(image[xx][yy][0]),clamp(image[xx][yy][1]),clamp(image[xx][yy][2])};
|
|
|
|
uint cs = 0;
|
|
uint cv = 0;
|
|
|
|
for (i=0;i<8;i++)
|
|
{
|
|
short e10 = (ppp.r-cp1.r);
|
|
short e11 = (ppp.g-cp1.g);
|
|
short e12 = (ppp.b-cp1.b);
|
|
long mc1 = ColourDistance(cp1,ppp);
|
|
|
|
short e20 = (ppp.r-cp2.r);
|
|
short e21 = (ppp.g-cp2.g);
|
|
short e22 = (ppp.b-cp2.b);
|
|
long mc2 = ColourDistance(cp2,ppp);
|
|
|
|
cs += (mc1>mc2) ? mc2 : mc1;
|
|
|
|
if (cs>bs) break;
|
|
|
|
cv |= ((mc1>mc2)<<i);
|
|
|
|
xx++;
|
|
if (mc1>mc2)
|
|
{
|
|
ppp.r = clamp(image[xx][yy][0]) + 7*e20/16;
|
|
ppp.g = clamp(image[xx][yy][1]) + 7*e21/16;
|
|
ppp.b = clamp(image[xx][yy][2]) + 7*e22/16;
|
|
}
|
|
else
|
|
{
|
|
ppp.r = clamp(image[xx][yy][0]) + 7*e10/16;
|
|
ppp.g = clamp(image[xx][yy][1]) + 7*e11/16;
|
|
ppp.b = clamp(image[xx][yy][2]) + 7*e12/16;
|
|
}
|
|
}
|
|
if (cs<bs)
|
|
{
|
|
bs = cs;
|
|
bv = cv;
|
|
bc1 = c1;
|
|
bc2 = c2;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Here we have the best colors and the best pattern for line j
|
|
|
|
short quant_error;
|
|
|
|
uint xx = 1+((x<<3));
|
|
|
|
for (i=0;i<8;i++,xx++)
|
|
for (k=0;k<3;k++)
|
|
{
|
|
// Compute the quantization error
|
|
|
|
if (bv&(1<<i))
|
|
{
|
|
quant_error = (clamp(image[xx][yy][k]) - palette[bc2][k])/16;
|
|
image[xx][yy][k] = palette[bc2][k];
|
|
}
|
|
else
|
|
{
|
|
quant_error = (clamp(image[xx][yy][k]) - palette[bc1][k])/16;
|
|
image[xx][yy][k] = palette[bc1][k];
|
|
}
|
|
|
|
// Spread the quantization error
|
|
|
|
short q2 = quant_error<<1;
|
|
image[xx+1][yy+1][k] = clamp(image[xx+1][yy+1][k])+ quant_error; // 1 *
|
|
quant_error += q2 ;
|
|
image[xx-1][yy+1][k] = clamp(image[xx-1][yy+1][k])+ quant_error; // 3 *
|
|
quant_error += q2 ;
|
|
image[xx+0][yy+1][k] = clamp(image[xx+0][yy+1][k])+ quant_error; // 5 *
|
|
quant_error += q2 ;
|
|
image[xx+1][yy+0][k] = clamp(image[xx+1][yy+0][k])+ quant_error; // 7 *
|
|
}
|
|
|
|
|
|
// Update status counter
|
|
|
|
if (done*100/size<(done+1)*100/size)
|
|
printf("\b\b\b%2i%%",100*done/size);
|
|
done++;
|
|
total++;
|
|
}
|
|
|
|
|
|
// Conversion done
|
|
|
|
printf("\b\b\bOk \n");
|
|
|
|
|
|
// Create TMS output files (CHR, CLR)
|
|
|
|
argv[1][strlen(argv[1])-3]='C';
|
|
argv[1][strlen(argv[1])-2]='H';
|
|
argv[1][strlen(argv[1])-1]='R';
|
|
CHR=fopen(argv[1],"wb");
|
|
|
|
argv[1][strlen(argv[1])-2]='L';
|
|
CLR=fopen(argv[1],"wb");
|
|
|
|
fputc(0xFE,CLR); // Binary data
|
|
fputc(0x00,CLR); // Start at 2000h
|
|
fputc(0x20,CLR);
|
|
fputc(0xFF,CLR); // Stop at 37FFh
|
|
fputc(0x37,CLR);
|
|
fputc(0x00,CLR); // Run
|
|
fputc(0x00,CLR);
|
|
|
|
|
|
fputc(0xFE,CHR); // Binary data
|
|
fputc(0x00,CHR); // Start at 0000h
|
|
fputc(0x00,CHR);
|
|
fputc(0xFF,CHR); // Stop at 17FFh
|
|
fputc(0x17,CHR);
|
|
fputc(0x00,CHR); // Run
|
|
fputc(0x00,CHR);
|
|
|
|
// Save best pattern and colour combination
|
|
// NOTE1:
|
|
// THIS PART CAN BE LARGELY CUTTED AND OPTIMIZED REUSING
|
|
// RESULTS FROM THE PREVIOUS LOOP, BUT WHO CARES?
|
|
// NOTE2:
|
|
// This code can be used for conversion without dithering
|
|
|
|
for (y=0;y<((MAXY+7)>>3);y++)
|
|
for (x=0;(x<(MAXX+7)>>3);x++)
|
|
for (j=0;j<8;j++)
|
|
{
|
|
uchar c1,c2;
|
|
uint bs = INT_MAX;
|
|
uchar bp = 0, bc = 0;
|
|
|
|
uint yy = 1+((y<<3)|j);
|
|
|
|
for (c1=1;c1<16;c1++)
|
|
{
|
|
RGB cp1 = {palette[c1][0],palette[c1][1],palette[c1][2]};
|
|
for (c2=c1+1;c2<16;c2++)
|
|
{
|
|
RGB cp2 = {palette[c2][0],palette[c2][1],palette[c2][2]};
|
|
uint cs = 0;
|
|
uint cp = 0;
|
|
for (i=0;i<8;i++)
|
|
{
|
|
uint xx = 1+((x<<3)|i);
|
|
RGB ppp = {clamp(image[xx][yy][0]),clamp(image[xx][yy][1]),clamp(image[xx][yy][2])};
|
|
|
|
long mc1 = ColourDistance(cp1,ppp);
|
|
long mc2 = ColourDistance(cp2,ppp);
|
|
|
|
cp = (cp<<1) | (mc1>mc2);
|
|
cs += (mc1>mc2) ? mc2 : mc1;
|
|
if (cs>bs) break;
|
|
}
|
|
if (cs<bs)
|
|
{
|
|
bs=cs;
|
|
bp=cp;
|
|
bc=c2*16+c1;
|
|
}
|
|
}
|
|
}
|
|
|
|
fputc(bc,CLR);
|
|
fputc(bp,CHR);
|
|
}
|
|
|
|
|
|
fclose(CHR);
|
|
fclose(CLR);
|
|
|
|
// Generate new name
|
|
|
|
name = malloc(0x100);
|
|
argv[1][strlen(argv[1])-4]=0;
|
|
strcpy(name,argv[1]);
|
|
strcat(name,"_tms.tga");
|
|
|
|
// Save file header
|
|
|
|
file=fopen(name,"wb");
|
|
|
|
for (i=0;i<18;i++) fputc(header[i],file);
|
|
|
|
// Save image data
|
|
|
|
for (y=MAXY-1;y>=0;y--)
|
|
for (x=0;x<MAXX;x++)
|
|
for (k=0;k<3;k++)
|
|
fputc(inrange8(image[1+x][1+y][2-k]/scale),file); // Scale to char
|
|
|
|
// Close file
|
|
|
|
fclose(file);
|
|
|
|
// Prompt elapsed time
|
|
|
|
printf("%.2f million combinations analysed in %.2f seconds.\n",total/1e6,(float)clock()/(float)CLOCKS_PER_SEC);
|
|
printf("Note: the .CLR and .CHR files have correct headers only for 256x192 images. \n");
|
|
|
|
return 0;
|
|
}
|
|
|