// ------------------------------------------------------------------------------- // Image Enhancer Christophe Kohler 2005-2012 // consolecoder@hotmail.com // V1.1 09Aug12 // // This algorithm can be used on indexed color pictures. It find dither patterns // and replace them by blended colors, in order to remove dither. // // Filter search dither patterns in pictures and replace them by blended colors // Mainly done for C64 pictures (16 colors) // Algorithm is very fast, can be used in emulator, like Vice for commodore machines. // // // // ------------------------------------------------------------------------------- #include "ColorEnhancer.h" void cnh_BuildPatternToBlendTable(); //class point struct color { unsigned char R,G,B,A; }; // -- Algorithm Coefs #define COEFHIGH 60 #define COEFMID 50 #define COEFLOW 40 #define COEFVERYLOW 30 #define COEFALIAS 18 #define TRESHOLD 196*3 // 128*3 // All color with more than this difference will not be treaten #define PATTERNREMOVE // Use this to add list of patterns for pattern detection and replace (core of algorithm) #define ANTIALIASING // Use this to add list of patterns for antialising (need some improvments) static char gTableBuild=0; // Pattern and Coeficient (in a readable form) // Each pattern will be rotated and flipped, to have all possibilities (then stored into an optimised format) // See after "PatternToBlend" // Each patterns will be converted into a 9bit number (512 possible value). short Patterns[][10]= { #ifdef PATTERNREMOVE // -------- Full Pattern { 1,0,1, 0,1,0, 1,0,1, COEFMID }, // -------- Around Full pattern, with no color // Up-Center Full Pattern (with no color) { 0,0,0, 1,0,1, 0,1,0, COEFLOW }, { 0,0,0, 0,1,0, 1,0,1, COEFLOW }, // Up-Right Full Pattern (with no color) { 0,0,0, 0,1,0, 1,0,0, COEFLOW }, { 0,0,0, 1,0,0, 0,1,0, COEFLOW }, // -------- Around Full pattern, with color // Up-Center Full Pattern (with color) { 1,1,1, 1,0,1, 0,1,0, COEFHIGH }, { 1,1,1, 0,1,0, 1,0,1, COEFHIGH }, // Up-Right Full Pattern (with color) { 1,1,1, 0,1,1, 1,0,1, COEFHIGH }, { 1,1,1, 1,0,1, 0,1,1, COEFHIGH }, // Full Pattern + Full corner (Up Right) { 1,1,1, 1,1,0, 1,0,1, COEFHIGH }, // Full Pattern + Full corner (Up) /* // Already exists { 0,0,0, 0,1,0, 1,0,1, COEFLOW }, */ { 1,1,1, 1,0,1, 0,1,0, COEFHIGH }, // Full Pattern + Corner Missing (Up Right) { 0,0,1, 0,1,0, 1,0,1, COEFHIGH }, // Full Pattern + 1 corner { 1,1,0, 1,0,1, 0,1,0, COEFLOW }, // Simple pattern { 0,1,0, 0,0,0, 0,1,0, COEFLOW }, { 0,0,1, 0,0,0, 0,0,1, COEFVERYLOW }, #endif #ifdef ANTIALIASING // Anti Aliasing (line) { 0,1,0, 1,0,0, 0,0,0, COEFALIAS }, // Anti Aliasing (Surface) { 1,1,0, 1,0,0, 0,0,0, COEFALIAS }, // Anti Aliasing { 1,1,1, 1,0,1, 0,0,0, COEFALIAS }, // Anti Aliasing { 0,1,1, 1,0,1, 0,0,0, COEFALIAS }, // Anti Aliasing (Corners) { 1,1,1, 1,0,0, 1,0,0, COEFALIAS }, // Anti Aliasing (Corners) { 1,1,0, 1,0,0, 1,0,0, COEFALIAS }, // Anti Aliasing (Corners) { 1,1,0, 1,0,0, 0,0,0, COEFALIAS }, // Anti Aliasing (Line) { 0,1,0, 1,0,0, 1,0,0, COEFALIAS }, { 1,0,1, 0,0,1, 0,1,1, COEFALIAS }, #endif // Last element { 0,0,0, 0,0,0, 0,0,0, -1 // (do not change). This will tell that this is last element of the list. }, }; // Pattern Blending coef blend (optimized form) // Each pattern is converted into a 9bit number (1 pixel = 1 bit). // That means we can store all combinations intro a 512 value array. // So in fact, we only store blending coefficient into a 512 array value // Some index will be unused. short PatternToBlend[512]; // For each pattern, associate a blending coefficient #define MAXCOLOR 256 struct color gColortable[256]; int gNbcolor=0; // -------------------------------------------------------- // Image is stored in index (0 to 16) // So when blinding, we need to know which color is behind each index // -------------------------------------------------------- void cnh_DefineColorTable(int* pColorTable, int nbcolors) { int i; gNbcolor=0; if ( nbcolors>MAXCOLOR ) return; // Error, too much colors gNbcolor=nbcolors; for (i=0; i>16)&0x000000FF); G=((color>>8)&0x000000FF); B=((color>>0)&0x000000FF); gColortable[i].R=R; gColortable[i].G=G; gColortable[i].B=B; gColortable[i].A=255; } } // -------------------------------------------------------- // Name: computeoffset // From pattern, compute binary offset (9 bit value) // And store associated blend coefficient into the final table "PatternToBlend" // -------------------------------------------------------- void computeoffset(int i) { // Compute offset // Pixels are this way (1 point = 0 or 1) (optimized form to parse faster a whole line) // 1 4 7 // 2 5 8 // 3 6 9 // And that gives a binary number like this : // 123456789 // unsigned short binaryvalue; // Compute offset in table binaryvalue= (Patterns[i][0]<<8)+(Patterns[i][3]<<7)+(Patterns[i][6]<<6) +(Patterns[i][1]<<5)+(Patterns[i][4]<<4)+(Patterns[i][7]<<3) +(Patterns[i][2]<<2)+(Patterns[i][5]<<1)+ Patterns[i][8]; // Put blending coeficient PatternToBlend[binaryvalue]=Patterns[i][9]; // -- Add negative pattern Patterns[i][0] = !Patterns[i][0]; Patterns[i][1] = !Patterns[i][1]; Patterns[i][2] = !Patterns[i][2]; Patterns[i][3] = !Patterns[i][3]; Patterns[i][4] = !Patterns[i][4]; Patterns[i][5] = !Patterns[i][5]; Patterns[i][6] = !Patterns[i][6]; Patterns[i][7] = !Patterns[i][7]; Patterns[i][8] = !Patterns[i][8]; if ( Patterns[i][9] != 0 ) Patterns[i][9] = 100-Patterns[i][9]; else Patterns[i][9] = Patterns[i][9]; // Keep zero // Compute offset in table binaryvalue= (Patterns[i][0]<<8)+(Patterns[i][3]<<7)+(Patterns[i][6]<<6) +(Patterns[i][1]<<5)+(Patterns[i][4]<<4)+(Patterns[i][7]<<3) +(Patterns[i][2]<<2)+(Patterns[i][5]<<1)+ Patterns[i][8]; // Put blending coefficient PatternToBlend[binaryvalue]=Patterns[i][9]; // -- Back to normal Patterns[i][0] = !Patterns[i][0]; Patterns[i][1] = !Patterns[i][1]; Patterns[i][2] = !Patterns[i][2]; Patterns[i][3] = !Patterns[i][3]; Patterns[i][4] = !Patterns[i][4]; Patterns[i][5] = !Patterns[i][5]; Patterns[i][6] = !Patterns[i][6]; Patterns[i][7] = !Patterns[i][7]; Patterns[i][8] = !Patterns[i][8]; if ( Patterns[i][9] != 0 ) Patterns[i][9] = 100-Patterns[i][9]; else Patterns[i][9] = Patterns[i][9]; // Keep zero } // -------------------------------------------------------- // Name: rotateclockwise // Rotate a pattern // -------------------------------------------------------- void rotateclockwise(int i) { // Pattern is entered in array like this // 0 1 2 // 3 4 5 // 6 7 8 // After rotation is is like this : // 6 3 0 // 7 4 1 // 8 5 2 short temp; temp=Patterns[i][0]; Patterns[i][0]=Patterns[i][6]; Patterns[i][6]=Patterns[i][8]; Patterns[i][8]=Patterns[i][2]; Patterns[i][2]=temp; temp=Patterns[i][1]; Patterns[i][1]=Patterns[i][3]; Patterns[i][3]=Patterns[i][7]; Patterns[i][7]=Patterns[i][5]; Patterns[i][5]=temp; } // -------------------------------------------------------- // Name: Flip a pattern // -------------------------------------------------------- void fliphoriz(int i) { // Pattern is entered in array like this // 0 1 2 // 3 4 5 // 6 7 8 // After flip, there are like this // 2 1 0 // 5 4 3 // 8 7 6 short temp; #define SWITCH(x,y) temp=Patterns[i][x];Patterns[i][x]=Patterns[i][y];Patterns[i][y]=temp; SWITCH(0,2); SWITCH(3,5); SWITCH(6,8); } // -------------------------------------------------------------------------- // Name: BuildPatternToBlendTable // From patterns, build optimized values for patterns. // // -------------------------------------------------------------------------- void cnh_BuildPatternToBlendTable() { int i; if (gTableBuild==1) return; gTableBuild=1; for (i=0; i<512; i++) { PatternToBlend[i]=-1; // -1 = no blending } i=0; // Parse input patterns, compute their offset in table and add blending coefficient while( Patterns[i][9]!=-1 ) // Parse all patterns until last elements (coef=-1 for last element). { computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); // Pattern is back to its initial state fliphoriz(i); computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); computeoffset(i); rotateclockwise(i); fliphoriz(i); // Pattern is back to its initial state i++; // Next pattern } } // -------------------------------------------------------------------------- // Name: Process // Process the input pixel // Return resulting color // // nextpixeloffset should be 2 for original C64 resolution (each pixel is two pixel large). 320x200 // nextpixeloffset can be one, if original picture is 160x200 // -------------------------------------------------------------------------- unsigned int cnh_ProcessPixel(unsigned char* src, int pitch, unsigned char nextpixeloffset) { int x,y; struct color newpoint; unsigned short binaryvalue[256]; int i,j; int nbvalidcoef; float amount=0; int nonnulindex[4]; // Store colors that are found into current pattern int index; unsigned int resultcolor; cnh_BuildPatternToBlendTable(); // Will do nothing if already initialized index=*src; newpoint.R=gColortable[index].R; newpoint.G=gColortable[index].G; newpoint.B=gColortable[index].B; newpoint.A=gColortable[index].A; // Debug, return pixel read from source (so should display normal screen) // if ( gFlagCK1 ) // { // resultcolor=0; // resultcolor=(newpoint.R<<16)|(newpoint.G<<8)|(newpoint.B); // return resultcolor; // } //newpoint.G=0; // Test non nul value (blank points) for (i=0; i TRESHOLD ) // { // nbvalidcoef=0; // } // } if ( (nbvalidcoef == 1) && (amount==0) ) { newpoint = gColortable[nonnulindex[0]]; nbvalidcoef=0; } // Blend all colors if (nbvalidcoef > 0) { newpoint.R=0; newpoint.G=0; newpoint.B=0; newpoint.A=0; // If amount is less than 100 (perfect negative matching not found) // Then we complete the blend with initial color if ( amount < 100 ) { int initialcolorcoef; initialcolorcoef=100-amount; amount=100; newpoint.R = (unsigned char)( ( gColortable[*src].R * initialcolorcoef )/amount); newpoint.G = (unsigned char)( ( gColortable[*src].G * initialcolorcoef )/amount); newpoint.B = (unsigned char)( ( gColortable[*src].B * initialcolorcoef )/amount); } for (i=0; i100) // blendvalue=100; // } // if ( gFlagCK4 ) // { // blendvalue = blendvalue/2; // } newpoint.R += (unsigned char)( ( gColortable[index].R * blendvalue )/amount); newpoint.G += (unsigned char)( ( gColortable[index].G * blendvalue )/amount); newpoint.B += (unsigned char)( ( gColortable[index].B * blendvalue )/amount); } } } // Compute result color resultcolor=(newpoint.R<<16)+(newpoint.G<<8)+(newpoint.B); return resultcolor; } // -------------------------------------------------------- // // -------------------------------------------------------- // Debug purpose only #define COEF2_HIGH 60 #define COEF2_MID 50 #define COEF2_LOW 40 #define COEF2_VERYLOW 30 #define COEF2_ALIAS 0 #define COEF3_HIGH 60 #define COEF3_MID 50 #define COEF3_LOW 40 #define COEF3_VERYLOW 25 #define COEF3_ALIAS 20 #define COEF4_HIGH 60 #define COEF4_MID 50 #define COEF4_LOW 40 #define COEF4_VERYLOW 25 #define COEF4_ALIAS 30 #define COEF5_HIGH 60 #define COEF5_MID 50 #define COEF5_LOW 40 #define COEF5_VERYLOW 25 #define COEF5_ALIAS 20 unsigned char gFlagCK1=0; unsigned char gFlagCK2=0; unsigned char gFlagCK3=0; unsigned char gFlagCK4=0; unsigned char gFlagCK5=0; // -------------------------------------------------------- // Call back function, called from UI of Vice // -------------------------------------------------------- void cnh_ChangeParam1() { int i; gFlagCK1=1; gFlagCK2=0; gFlagCK3=0; gFlagCK4=0; gFlagCK5=0; Patterns[0][9]=COEFMID; for (i=1; i<1+4; i++) Patterns[i][9]=COEFLOW; for (i=5; i<5+7; i++) Patterns[i][9]=COEFHIGH; Patterns[12][9]=COEFLOW; Patterns[13][9]=COEFLOW; Patterns[14][9]=COEFVERYLOW; // Aliasing for (i=15; i<15+9; i++) Patterns[i][9]=COEFALIAS; gTableBuild=0; cnh_BuildPatternToBlendTable(); // Rebuild table } // -------------------------------------------------------- // // -------------------------------------------------------- void cnh_ChangeParam2() { int i; gFlagCK1=0; gFlagCK2=1; gFlagCK3=0; gFlagCK4=0; gFlagCK5=0; Patterns[0][9]=COEF2_MID; for (i=1; i<1+4; i++) Patterns[i][9]=COEF2_LOW; for (i=5; i<5+7; i++) Patterns[i][9]=COEF2_HIGH; Patterns[12][9]=COEF2_LOW; Patterns[13][9]=COEF2_LOW; Patterns[14][9]=COEF2_VERYLOW; // Aliasing for (i=15; i<15+9; i++) Patterns[i][9]=COEF2_ALIAS; gTableBuild=0; cnh_BuildPatternToBlendTable(); // Rebuild table } // -------------------------------------------------------- // // -------------------------------------------------------- void cnh_ChangeParam3() { int i; gFlagCK1=0; gFlagCK2=0; gFlagCK3=1; gFlagCK4=0; gFlagCK5=0; Patterns[0][9]=COEF3_MID; for (i=1; i<1+4; i++) Patterns[i][9]=COEF3_LOW; for (i=5; i<5+7; i++) Patterns[i][9]=COEF3_HIGH; Patterns[12][9]=COEF3_LOW; Patterns[13][9]=COEF3_LOW; Patterns[14][9]=COEF3_VERYLOW; // Aliasing for (i=15; i<15+9; i++) Patterns[i][9]=COEF3_ALIAS; gTableBuild=0; cnh_BuildPatternToBlendTable(); // Rebuild table } // -------------------------------------------------------- // // -------------------------------------------------------- void cnh_ChangeParam4() { int i; gFlagCK1=0; gFlagCK2=0; gFlagCK3=0; gFlagCK4=1; gFlagCK5=0; Patterns[0][9]=COEF4_MID; for (i=1; i<1+4; i++) Patterns[i][9]=COEF4_LOW; for (i=5; i<5+7; i++) Patterns[i][9]=COEF4_HIGH; Patterns[12][9]=COEF4_LOW; Patterns[13][9]=COEF4_LOW; Patterns[14][9]=COEF4_VERYLOW; // Aliasing for (i=15; i<15+9; i++) Patterns[i][9]=COEF4_ALIAS; gTableBuild=0; cnh_BuildPatternToBlendTable(); // Rebuild table } // -------------------------------------------------------- // // -------------------------------------------------------- void cnh_ChangeParam5() { int i; gFlagCK1=0; gFlagCK2=0; gFlagCK3=0; gFlagCK4=0; gFlagCK5=1; Patterns[0][9]=COEF5_MID; for (i=1; i<1+4; i++) Patterns[i][9]=COEF5_LOW; for (i=5; i<5+7; i++) Patterns[i][9]=COEF5_HIGH; Patterns[12][9]=COEF5_LOW; Patterns[13][9]=COEF5_LOW; Patterns[14][9]=COEF5_VERYLOW; // Aliasing for (i=15; i<15+9; i++) Patterns[i][9]=COEF5_ALIAS; gTableBuild=0; cnh_BuildPatternToBlendTable(); // Rebuild table }