pitpan
|
|
« : 18 de Mayo de 2007, 09:13:38 am » |
|
Well, this is it. A very simple yet functional image converter for TMS9918. At the moment it only supports TGA format, 24bpp uncompressed, up to 512x512 pixels. It converts the image to the best non-dithered solution that verifies the TMS9918 colour restrictions (2 colores for each 8x1 pixel block). The conversion algorithm is quite simple and it requieres a high clocked computer. The output of the program is the following: - file.CHR - uncompressed TMS9918 pattern information - file.CLR - uncompressed TMS9918 colour information - file_TMS.TGA - TGA including the converted image Enjoy. Any suggestions will be welcome. Please note that the RGB values included are very approximated. Better RGB palettes will output better fit images. /* --------------------------------------------------------------- TMSOPT v.0.02 - Eduardo A. Robsy Petrus, 2007 --------------------------------------------------------------- TGA image converter (24 bpp, uncompressed) to TMS9918 format --------------------------------------------------------------- Overview --------------------------------------------------------------- Selects the best local solution for each 8x1 pixel block Optimization uses the following algorithm:
(a) For each pixel, RGB differences are calculated - All RGB channels have the same weight - It uses absolute error instead of squared error
(b) This error value is added for all pixels in each 8x1 block
(c) The CHR-CLR combination with lowest error rate is selected
It is a brute-force method, computationally very expensive. Some patience and a high-clocked CPU computed is recommended. --------------------------------------------------------------- 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! --------------------------------------------------------------- History --------------------------------------------------------------- Ages ago - algorithm created 16/05/2007 - first C version (RAW format) 17/05/2007 - TGA format included, some optimization included --------------------------------------------------------------- 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 author, though. --------------------------------------------------------------- */
// Headers!
#include<stdio.h> #include<time.h>
// Just one function for everything
main(int argc, char **argv) {
// Vars
FILE *file,*CHR,*CLR; int bc,bp,i,j,x,y,c,p,k,MAXX,MAXY; unsigned int score[256][512],n,total=0,done=0,size; char *name; unsigned char image[512][512][3],header[18],palette[16][3]=
// TMS9918 RGB palette - approximated 50Hz PAL values
{{0x00,0x00,0x00}, // 0 Transparent - black (not used) {0x00,0x00,0x00}, // 1 Black {0x12,0xFF,0x00}, // 2 Green {0x76,0xFF,0x6F}, // 3 Light green {0x12,0x00,0xFF}, // 4 Blue {0x69,0x69,0xFF}, // 5 Light blue {0xA1,0x10,0x10}, // 6 Dark red {0x00,0xFF,0xFC}, // 7 Cyan {0xFF,0x00,0x00}, // 8 Red {0xFF,0x80,0x80}, // 9 Light red {0xFF,0xEA,0x00}, // 10 Yellow {0xFF,0xFB,0x80}, // 11 Light yellow {0x1D,0xA8,0x02}, // 12 Dark green {0xFF,0x00,0xFC}, // 13 Magenta {0xCC,0xCC,0xCC}, // 14 Light gray {0xFF,0xFF,0xFF}};// 15 White
// Get time
clock();
// Application prompt
printf("TMSopt v.0.01 - TGA 24bpp to TMS9918 converter\nCoded by Eduardo A. Robsy Petrus, 2007\n"); // Test if only one command-line parameter is available
if (argc==1) { printf("Syntax: TMSopt [file.tga]\n"); return; }
// Open source image (TGA, 24-bit, uncompressed)
if ((file=fopen(argv[1],"rb"))==NULL) { printf("cannot open %s file!\n",argv[1]); return; }
// 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];
if ((n!=2)||(header[2]!=2)||(header[17])||(header[16]!=24)) { printf("Unsupported file format!\n"); return; }
// 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; }
// Load image data
for (y=MAXY-1;y>=0;y--) for (x=0;x<MAXX;x++) for (k=0;k<3;k++) image[x][y][2-k]=fgetc(file);
// Close file
fclose(file);
// Information
printf("Converting %s (%i,%i) to TMS9918 format... ",argv[1],MAXX,MAXY);
// 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");
// Image processing
for (y=0;y<(MAXY+7)>>3;y++) for (x=0;x<(MAXX+7)>>3;x++) for (j=0;j<8;j++)
// Generate alternatives
{ bp=0;bc=0; score[0][0]=-1; for (p=0;p<256;p++) for (c=0x12;c<256;c++) if (c>>4<c&15)
// Score calculation
{ score[p][c]=0; n=p; for (i=0;i<8;i++) { for (k=0;k<3;k++) score[p][c]+=abs(palette[(n&0x80)?c>>4:c&0x0f][k]-image[x<<3|i][y<<3|j][k]); n<<=1; }
// Keep best solution found
if (score[p][c]<score[bp][bc]) { bp=p;bc=c; } total++; if (score[bp][bc]<6) {c=256;p=256;} }
// Save best pattern and colour combination
fputc(bp,CHR); fputc(bc,CLR);
// Replace original image data with TMS result
n=bp; for (i=0;i<8;i++) { for (k=0;k<3;k++) image[x<<3|i][y<<3|j][k]=palette[n&0x80?bc>>4:bc&0x0f][k]; n<<=1; }
// Update status counter if (done*100/size<(done+1)*100/size) printf("\b\b\b%2i%%",100*done/size); done++;
}
// Conversion done
printf("\b\b\bOk \n");
// 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(image[x][y][2-k],file);
// Close file
fclose(file);
// Prompt elapsed time
printf("%.2f million combinations analysed in %.2f seconds",total/1e6,(float)clock()/(float)CLOCKS_PER_SEC);
}
|
|
|
En línea
|
|
|
|
pitpan
|
|
« Respuesta #1 : 18 de Mayo de 2007, 09:18:31 am » |
|
By the way, I've only tested it with TGAs created by Photoshop. I haven't reviewed if other applications would use the same TGA header format . If you do, please let me know the result.
|
|
|
En línea
|
|
|
|
pitpan
|
|
« Respuesta #2 : 18 de Mayo de 2007, 05:46:31 pm » |
|
Check some examples:
|
|
|
En línea
|
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #3 : 18 de Mayo de 2007, 06:19:04 pm » |
|
You can optimize your inner code a lot!!
1) why do you store all the tests situation in matrix score[][] ? you do not need it: simply evalue the current score, if it is better than the previous keep it, otherwise go haed
2) why do you compute all the color/pattern combinations? your program tests all the 105*256=26880 combinations.... you do not need it.
do a outer loop on the 105 color couples like: for (c1=1;c1<15;c1++) for (c2=c1+1;c2<15;c2++) { etc. }
In the inner loop do not scan all the 256 patterns! do this : compute the distance of the color of each point from c1, (costs 8 diffences + 8 products if you use squared errors) compute the distance of the color of each point from c2, (costs 8 diffences + 8 products if you use squared errors)
select the minimum between the two sets of values for each point. (costs 8 comparisons) the sum of the minima is the current score (costs 7 sums) the results of your comparisons forms the current pattern p (the best given c1 and c2)
This cuts the time a lot: instead of doing 256 tests (like you do now) you compute only 8+8 differences, 8 comparisons and 7 sums (+ 16 producs if you use squared errors)
|
|
|
En línea
|
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #4 : 19 de Mayo de 2007, 10:11:40 am » |
|
Now Floyd works!! minor adjustments (and testing) are still required.... /* --------------------------------------------------------------- TMSOPT v.0.04 - Eduardo A. Robsy Petrus & Arturo Ragozini, 2007 --------------------------------------------------------------- TGA image converter (24 bpp, uncompressed) to TMS9918 format --------------------------------------------------------------- Overview --------------------------------------------------------------- Selects the best local solution for each 8x1 pixel block Optimization uses the following algorithm:
(a) For each pixel, RGB differences are calculated - All RGB channels have the same weight - It uses absolute error instead of squared error
(b) This error value is added for all pixels in each 8x1 block
(c) The CHR-CLR combination with lowest error rate is selected
It is a brute-force method, computationally very expensive. Some patience and a high-clocked CPU computed is recommended. --------------------------------------------------------------- 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! --------------------------------------------------------------- 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 --------------------------------------------------------------- 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 author, though. --------------------------------------------------------------- */
// Headers!
#include<stdio.h> #include<time.h> #include<limits.h>
#define scale 32
#define inrange8(t) ((t)<0) ? 0 :(((t)>255) ? 255:(t))
#define clamp(t) ((t)<0) ? 0 :(((t)>255*scale) ? 255*scale : (t))
///////////////// // scanning direction // DIR=1 -> // DIR=0 <- #define DIR 1
///////////////////////////// // switch palette MSX1 MSX2 // (approssimate)
#define MSX1
// Just one function for everything
main(int argc, char **argv) {
// Vars
FILE *file,*CHR,*CLR; int bc,bp,i,j,x,y,c,p,k,MAXX,MAXY; unsigned int n,total=0,done=0,size; char *name; short image[512][512][3],header[18],palette[16][3];
// TMS9918 RGB palette - approximated 50Hz PAL values unsigned int pal[16][3]= { #ifdef MSX1 { 0,0,0}, { 0,0,0}, { 33,200,66}, { 94,220,120}, { 84,85,237}, { 125,118,252}, { 212,82,77}, { 66,235,245}, { 252,85,84}, { 255,121,120}, { 212,193,84}, { 230,206,128}, { 33,176,59}, { 201,91,186}, { 204,204,204}, { 255,255,255} #endif #ifdef MSX2 {0x00,0x00,0x00}, // 0 Transparent - black (not used) {0x00,0x00,0x00}, // 1 Black {0x12,0xFF,0x00}, // 2 Green {0x76,0xFF,0x6F}, // 3 Light green {0x12,0x00,0xFF}, // 4 Blue {0x69,0x69,0xFF}, // 5 Light blue {0xA1,0x10,0x10}, // 6 Dark red {0x00,0xFF,0xFC}, // 7 Cyan {0xFF,0x00,0x00}, // 8 Red {0xFF,0x80,0x80}, // 9 Light red {0xFF,0xEA,0x00}, // 10 Yellow {0xFF,0xFB,0x80}, // 11 Light yellow {0x1D,0xA8,0x02}, // 12 Dark green {0xFF,0x00,0xFC}, // 13 Magenta {0xCC,0xCC,0xCC}, // 14 Light gray {0xFF,0xFF,0xFF} // 15 White #endif }; // 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.04 - TGA 24bpp to TMS9918 converter\nCoded by Eduardo A. Robsy Petrus & Arturo Ragozini, 2007\n"); // Test if only one command-line parameter is available
// Guess the name of the image I used for testing #ifdef DEBUG argc = 2; argv[1] = malloc(20); argv[1][0] = 'e'; argv[1][1] = 'a'; argv[1][2] = 'r'; argv[1][3] = 't'; argv[1][4] = 'h'; argv[1][5] = '.'; argv[1][6] = 't'; argv[1][7] = 'g'; argv[1][8] = 'a'; argv[1][9] = 0; #endif
if (argc==1) { printf("Syntax: TMSopt [file.tga]\n"); return; }
// Open source image (TGA, 24-bit, uncompressed)
if ((file=fopen(argv[1],"rb"))==NULL) { printf("cannot open %s file!\n",argv[1]); return; }
// 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; }
// 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; }
// Load image data
for (y=MAXY-1;y>=0;y--) for (x=0;x<MAXX;x++) for (k=0;k<3;k++) image[x][y][2-k]=((short)fgetc(file))*scale; // Scale image
// Close file
fclose(file);
// Information
printf("Converting %s (%i,%i) to TMS9918 format... ",argv[1],MAXX,MAXY);
// 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");
// Image processing for (y=0;y<(MAXY+7)>>3;y++) for (x=0;x<(MAXX+7)>>3;x++) for (j=0;j<8;j++) { // Generate alternatives unsigned char c1,c2; unsigned int bs = INT_MAX; unsigned char bp = 0, bc = 0; for (c1=1;c1<16;c1++) for (c2=c1+1;c2<16;c2++) { unsigned int cs = 0; unsigned int cp = 0; for (i=0;i<8;i++) { int xx = (x<<3)|i; int yy = (y<<3)|j;
unsigned int mc1=0,mc2=0; xx = (DIR) ? (xx) : (MAXX - 1 - xx); for (k=0;k<3;k++) { int d1 = abs(palette[c1][k]-image[xx][yy][k]); int d2 = abs(palette[c2][k]-image[xx][yy][k]); mc1+=d1*d1; mc2+=d2*d2; } if (mc1>mc2) { cp = (cp<<1) | (0x01); cs += mc2; } else { cp = (cp<<1) | (0x00); cs += mc1; } } if (cs<bs) { bs=cs; bp=cp; bc=c2*16+c1; } } // Here we have the best colors and the best pattern for line j
{ short newpixel[3], quant_error[3]; unsigned char bc1 = bc & 15, bc2 = bc/16; unsigned char cp = 0; for (i=0;i<8;i++) { int xx = (x<<3)|i; int yy = (y<<3)|j;
// Decide again what is the best color between bc1 and bc2 for the current point - now dithered
unsigned int mc1=0,mc2=0; xx = (DIR) ? (xx) : (MAXX - 1 - xx); for (k=0;k<3;k++) { int d1 = abs(palette[bc1][k]-image[xx][yy][k]); int d2 = abs(palette[bc2][k]-image[xx][yy][k]); mc1+=d1*d1; mc2+=d2*d2; } if (mc1>mc2) { cp = (cp<<1) | (0x01); for (k=0;k<3;k++) newpixel[k] = palette[bc2][k]; } else { cp = (cp<<1) | (0x00); for (k=0;k<3;k++) newpixel[k] = palette[bc1][k]; }
// compute the quantization error for (k=0;k<3;k++) quant_error[k] = image[xx][yy][k] - newpixel[k];
// store the current point - now quantized for (k=0;k<3;k++) image[xx][yy][k] = newpixel[k];
// spread the quantization error if (DIR){ if (xx+1<=MAXX-1) for (k=0;k<3;k++) image[xx+1][yy+0][k] = clamp(image[xx+1][yy+0][k]+(7 * quant_error[k])/16); if (yy+1<=MAXY-1) { for (k=0;k<3;k++) image[xx+0][yy+1][k] = clamp(image[xx+0][yy+1][k]+(5 * quant_error[k])/16); if (xx-1 >=0) for (k=0;k<3;k++) image[xx-1][yy+1][k] = clamp(image[xx-1][yy+1][k]+(3 * quant_error[k])/16); if (xx+1 <=MAXX-1) for (k=0;k<3;k++) image[xx+1][yy+1][k] = clamp(image[xx+1][yy+1][k]+(quant_error[k])/16); } } else { if (xx-1>=0) for (k=0;k<3;k++) image[xx-1][yy+0][k] = clamp(image[xx-1][yy+0][k]+(7 * quant_error[k])/16);
if (yy+1<=MAXY-1) {
for (k=0;k<3;k++) image[xx+0][yy+1][k] = clamp(image[xx+0][yy+1][k]+(5 * quant_error[k])/16); if (xx-1 >=0) for (k=0;k<3;k++) image[xx-1][yy+1][k] = clamp(image[xx-1][yy+1][k]+( quant_error[k])/16); if (xx+1 <=MAXX-1) for (k=0;k<3;k++) image[xx+1][yy+1][k] = clamp(image[xx+1][yy+1][k]+(3 * quant_error[k])/16); } } } // Forget the previous pattern not based on dithering bp = cp; } // Save best pattern and colour combination fputc(bp,CHR); fputc(bc,CLR);
// // Replace original image data with TMS result // n=bp; // for (i=0;i<8;i++) // { // for (k=0;k<3;k++) // image[x<<3|i][y<<3|j][k]=palette[n&0x80?bc>>4:bc&0x0f][k]; // n<<=1; // }
// 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");
// 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[x][y][2-k]/scale),file); // Scale to char
// Close file
fclose(file);
// Prompt elapsed time
printf("%.2f million combinations analysed in %.2f seconds",total/1e6,(float)clock()/(float)CLOCKS_PER_SEC);
}
Here the full package for who is interested in some testing http://ragozini.googlepages.com/sc2conv.rar
|
|
« Última modificación: 20 de Mayo de 2007, 12:01:08 am por ARTRAG »
|
En línea
|
|
|
|
WYZ
Visitante
|
|
« Respuesta #5 : 19 de Mayo de 2007, 07:48:24 pm » |
|
Check some examples:
Genial. Fijandose en los detalles como la cara de Roger, o las gafas y los brillos de la calva y la nariz de mortadelo es cuando se da uno cuenta de lo bueno que es. Muy bueno.
|
|
|
En línea
|
|
|
|
Darth_Fistro
|
|
« Respuesta #6 : 22 de Mayo de 2007, 10:44:14 am » |
|
Please, post the executable file! Fantastic!
|
|
|
En línea
|
MSX FOREVER (hasta que saquen un ZX81 con TMS, PSG y 64K de RAM)
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #7 : 22 de Mayo de 2007, 11:59:13 am » |
|
The executable is into the RAR archive Change the extension of the file *.ex_ into *.exe
|
|
|
En línea
|
|
|
|
Darth_Fistro
|
|
« Respuesta #8 : 22 de Mayo de 2007, 12:19:03 pm » |
|
Thanks, ARTRAG!
|
|
|
En línea
|
MSX FOREVER (hasta que saquen un ZX81 con TMS, PSG y 64K de RAM)
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #9 : 22 de Mayo de 2007, 11:56:53 pm » |
|
new package released: http://ragozini.googlepages.com/vdpenc.zipAnyone willing to test it? IMHO Jannone's conversions are still better (sc2 in the package), I do not know where I am wrong. NOTE The CHR and CLR files now can be b-loaded in screen 2 e.g. 10 screen 2: color 15,0,0 20 bload"lenna_.CHR",s 30 bload"lenna_.CLR",s 40 goto 40
|
|
|
En línea
|
|
|
|
jltursan
|
|
« Respuesta #10 : 23 de Mayo de 2007, 09:51:38 am » |
|
Have you tried to contact with Rafael himself?, maybe he can throw some light...
|
|
|
En línea
|
Doom dee doom dee doom
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #11 : 23 de Mayo de 2007, 10:48:09 am » |
|
All problems solved thanks to Rafael Jannone itself: here the full package http://ragozini.googlepages.com/ditheringTest and let me know
|
|
« Última modificación: 24 de Mayo de 2007, 11:57:01 am por ARTRAG »
|
En línea
|
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #12 : 26 de Mayo de 2007, 04:33:17 pm » |
|
New algorithm online with perceptually uniform distances in the color space http://ragozini.googlepages.com/dithering/* --------------------------------------------------------------- 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 selected 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>
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
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; }
// Open source image (TGA, 24-bit, uncompressed)
if ((file=fopen(argv[1],"rb"))==NULL) { printf("cannot open %s file!\n",argv[1]); return; }
// 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; }
// 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; }
// 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");
}
|
|
« Última modificación: 26 de Mayo de 2007, 04:35:00 pm por ARTRAG »
|
En línea
|
|
|
|
ARTRAG
Visitante
|
|
« Respuesta #13 : 29 de Julio de 2007, 09:56:41 am » |
|
|
|
« Última modificación: 29 de Julio de 2007, 11:27:38 am por ARTRAG »
|
En línea
|
|
|
|
|