#include #include #include #define stop() __debugbreak() #include #define int64 __int64 #pragma warning(disable:4127) #define STBIR__WEIGHT_TABLES #define STBIR_PROFILE #define STB_IMAGE_RESIZE_IMPLEMENTATION #include "stb_image_resize2.h" static int * file_read( char const * filename ) { size_t s; int * m; FILE * f = fopen( filename, "rb" ); if ( f == 0 ) return 0; fseek( f, 0, SEEK_END); s = ftell( f ); fseek( f, 0, SEEK_SET); m = malloc( s + 4 ); m[0] = (int)s; fread( m+1, 1, s, f); fclose(f); return( m ); } typedef struct fileinfo { int * timings; int timing_count; int dimensionx, dimensiony; int numtypes; int * types; int * effective; int cpu; int simd; int numinputrects; int * inputrects; int outputscalex, outputscaley; int milliseconds; int64 cycles; double scale_time; int bitmapx, bitmapy; char const * filename; } fileinfo; int numfileinfo; fileinfo fi[256]; unsigned char * bitmap; int bitmapw, bitmaph, bitmapp; static int use_timing_file( char const * filename, int index ) { int * base = file_read( filename ); int * file = base; if ( base == 0 ) return 0; ++file; // skip file image size; if ( *file++ != 'VFT1' ) return 0; fi[index].cpu = *file++; fi[index].simd = *file++; fi[index].dimensionx = *file++; fi[index].dimensiony = *file++; fi[index].numtypes = *file++; fi[index].types = file; file += fi[index].numtypes; fi[index].effective = file; file += fi[index].numtypes; fi[index].numinputrects = *file++; fi[index].inputrects = file; file += fi[index].numinputrects * 2; fi[index].outputscalex = *file++; fi[index].outputscaley = *file++; fi[index].milliseconds = *file++; fi[index].cycles = ((int64*)file)[0]; file += 2; fi[index].filename = filename; fi[index].timings = file; fi[index].timing_count = (int) ( ( base[0] - ( ((char*)file - (char*)base - sizeof(int) ) ) ) / (sizeof(int)*2) ); fi[index].scale_time = (double)fi[index].milliseconds / (double)fi[index].cycles; return 1; } static int vert_first( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int ox, int oy, int ix, int iy, int filter, STBIR__V_FIRST_INFO * v_info ) { float h_scale=(float)ox/(float)(ix); float v_scale=(float)oy/(float)(iy); stbir__support_callback * support = stbir__builtin_supports[filter]; int vertical_filter_width = stbir__get_filter_pixel_width(support,v_scale,0); int vertical_gather = ( v_scale >= ( 1.0f - stbir__small_float ) ) || ( vertical_filter_width <= STBIR_FORCE_GATHER_FILTER_SCANLINES_AMOUNT ); return stbir__should_do_vertical_first( weights_table, stbir__get_filter_pixel_width(support,h_scale,0), h_scale, ox, vertical_filter_width, v_scale, oy, vertical_gather, v_info ); } #define STB_IMAGE_WRITE_IMPLEMENTATION #include "stb_image_write.h" static void alloc_bitmap() { int findex; int x = 0, y = 0; int w = 0, h = 0; for( findex = 0 ; findex < numfileinfo ; findex++ ) { int nx, ny; int thisw, thish; thisw = ( fi[findex].dimensionx * fi[findex].numtypes ) + ( fi[findex].numtypes - 1 ); thish = ( fi[findex].dimensiony * fi[findex].numinputrects ) + ( fi[findex].numinputrects - 1 ); for(;;) { nx = x + ((x)?4:0) + thisw; ny = y + ((y)?4:0) + thish; if ( ( nx <= 3600 ) || ( x == 0 ) ) { fi[findex].bitmapx = x + ((x)?4:0); fi[findex].bitmapy = y + ((y)?4:0); x = nx; if ( x > w ) w = x; if ( ny > h ) h = ny; break; } else { x = 0; y = h; } } } w = (w+3) & ~3; bitmapw = w; bitmaph = h; bitmapp = w * 3; // RGB bitmap = malloc( bitmapp * bitmaph ); memset( bitmap, 0, bitmapp * bitmaph ); } static void build_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index, int findex ) { static int colors[STBIR_RESIZE_CLASSIFICATIONS]; STBIR__V_FIRST_INFO v_info = {0}; int * ts; int ir; unsigned char * bitm = bitmap + ( fi[findex].bitmapx*3 ) + ( fi[findex].bitmapy*bitmapp) ; for( ir = 0; ir < STBIR_RESIZE_CLASSIFICATIONS ; ir++ ) colors[ ir ] = 127*ir/STBIR_RESIZE_CLASSIFICATIONS+128; ts = fi[findex].timings; for( ir = 0 ; ir < fi[findex].numinputrects ; ir++ ) { int ix, iy, chanind; ix = fi[findex].inputrects[ir*2]; iy = fi[findex].inputrects[ir*2+1]; for( chanind = 0 ; chanind < fi[findex].numtypes ; chanind++ ) { int ofs, h, hh; // just do the type that we're on if ( chanind != do_channel_count_index ) { ts += 2 * fi[findex].dimensionx * fi[findex].dimensiony; continue; } // bitmap offset ofs=chanind*(fi[findex].dimensionx+1)*3+ir*(fi[findex].dimensiony+1)*bitmapp; h = 1; for( hh = 0 ; hh < fi[findex].dimensiony; hh++ ) { int ww, w = 1; for( ww = 0 ; ww < fi[findex].dimensionx; ww++ ) { int good, v_first, VF, HF; VF = ts[0]; HF = ts[1]; v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first))); if ( good ) { bitm[ofs+2] = 0; bitm[ofs+1] = (unsigned char)colors[v_info.v_resize_classification]; } else { double r; if ( HF < VF ) r = (double)(VF-HF)/(double)HF; else r = (double)(HF-VF)/(double)VF; if ( r > 0.4f) r = 0.4; r *= 1.0f/0.4f; bitm[ofs+2] = (char)(255.0f*r); bitm[ofs+1] = (char)(((float)colors[v_info.v_resize_classification])*(1.0f-r)); } bitm[ofs] = 0; ofs += 3; ts += 2; w += fi[findex].outputscalex; } ofs += bitmapp - fi[findex].dimensionx*3; h += fi[findex].outputscaley; } } } } static void build_comp_bitmap( float weights[STBIR_RESIZE_CLASSIFICATIONS][4], int do_channel_count_index ) { int * ts0; int * ts1; int ir; unsigned char * bitm = bitmap + ( fi[0].bitmapx*3 ) + ( fi[0].bitmapy*bitmapp) ; ts0 = fi[0].timings; ts1 = fi[1].timings; for( ir = 0 ; ir < fi[0].numinputrects ; ir++ ) { int ix, iy, chanind; ix = fi[0].inputrects[ir*2]; iy = fi[0].inputrects[ir*2+1]; for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) { int ofs, h, hh; // just do the type that we're on if ( chanind != do_channel_count_index ) { ts0 += 2 * fi[0].dimensionx * fi[0].dimensiony; ts1 += 2 * fi[0].dimensionx * fi[0].dimensiony; continue; } // bitmap offset ofs=chanind*(fi[0].dimensionx+1)*3+ir*(fi[0].dimensiony+1)*bitmapp; h = 1; for( hh = 0 ; hh < fi[0].dimensiony; hh++ ) { int ww, w = 1; for( ww = 0 ; ww < fi[0].dimensionx; ww++ ) { int v_first, time0, time1; v_first = vert_first( weights, w, h, ix, iy, STBIR_FILTER_MITCHELL, 0 ); time0 = ( v_first ) ? ts0[0] : ts0[1]; time1 = ( v_first ) ? ts1[0] : ts1[1]; if ( time0 < time1 ) { double r = (double)(time1-time0)/(double)time0; if ( r > 0.4f) r = 0.4; r *= 1.0f/0.4f; bitm[ofs+2] = 0; bitm[ofs+1] = (char)(255.0f*r); bitm[ofs] = (char)(64.0f*(1.0f-r)); } else { double r = (double)(time0-time1)/(double)time1; if ( r > 0.4f) r = 0.4; r *= 1.0f/0.4f; bitm[ofs+2] = (char)(255.0f*r); bitm[ofs+1] = 0; bitm[ofs] = (char)(64.0f*(1.0f-r)); } ofs += 3; ts0 += 2; ts1 += 2; w += fi[0].outputscalex; } ofs += bitmapp - fi[0].dimensionx*3; h += fi[0].outputscaley; } } } } static void write_bitmap() { stbi_write_png( "results.png", bitmapp / 3, bitmaph, 3|STB_IMAGE_BGR, bitmap, bitmapp ); } static void calc_errors( float weights_table[STBIR_RESIZE_CLASSIFICATIONS][4], int * curtot, double * curerr, int do_channel_count_index ) { int th, findex; STBIR__V_FIRST_INFO v_info = {0}; for(th=0;th 200 ) { int findex; do_bitmap: lasttick = t; newbest = 0; for( findex = 0 ; findex < numfileinfo ; findex++ ) build_bitmap( best_output_weights, channel_count_index, findex ); lasttick = GetTickCount(); } } } windowranges[ channel_count_index ] = range; // advance all the weights and loop --range; } while( ( range >= 0 ) && ( !windowstatus ) ); // if we hit here, then we tried all weights for this opt, so save them } static void print_struct( float weight[5][STBIR_RESIZE_CLASSIFICATIONS][4], char const * name ) { printf("\n\nstatic float %s[5][STBIR_RESIZE_CLASSIFICATIONS][4]=\n{", name ); { int i; for(i=0;i<5;i++) { int th; for(th=0;th 60000) sprintf( time, "%dm %ds",ms/60000, (ms/1000)%60 ); else sprintf( time, "%ds",ms/1000 ); return time; } static BITMAPINFOHEADER bmiHeader; static DWORD extrawindoww, extrawindowh; static HINSTANCE instance; static int curzoom = 1; static LRESULT WINAPI WindowProc( HWND window, UINT message, WPARAM wparam, LPARAM lparam ) { switch( message ) { case WM_CHAR: if ( wparam != 27 ) break; // falls through case WM_CLOSE: { int i; int max = 0; for( i = 0 ; i < fi[0].numtypes ; i++ ) if( windowranges[i] > max ) max = windowranges[i]; if ( ( max == 0 ) || ( MessageBox( window, "Cancel before training is finished?", "Vertical First Training", MB_OKCANCEL|MB_ICONSTOP ) == IDOK ) ) { for( i = 0 ; i < fi[0].numtypes ; i++ ) if( windowranges[i] > max ) max = windowranges[i]; if ( max ) windowstatus = 1; DestroyWindow( window ); } } return 0; case WM_PAINT: { PAINTSTRUCT ps; HDC dc; dc = BeginPaint( window, &ps ); StretchDIBits( dc, 0, 0, bitmapw*curzoom, bitmaph*curzoom, 0, 0, bitmapw, bitmaph, bitmap, (BITMAPINFO*)&bmiHeader, DIB_RGB_COLORS, SRCCOPY ); PatBlt( dc, bitmapw*curzoom, 0, 4096, 4096, WHITENESS ); PatBlt( dc, 0, bitmaph*curzoom, 4096, 4096, WHITENESS ); SetTextColor( dc, RGB(0,0,0) ); SetBkColor( dc, RGB(255,255,255) ); SetBkMode( dc, OPAQUE ); { int i, l = 0, max = 0; char buf[1024]; RECT rc; POINT p; for( i = 0 ; i < fi[0].numtypes ; i++ ) { l += sprintf( buf + l, "channels: %d %s\n", fi[0].effective[i], windowranges[i] ? expand_to_string( windowranges[i] ) : "Done." ); if ( windowranges[i] > max ) max = windowranges[i]; } rc.left = 32; rc.top = bitmaph*curzoom+10; rc.right = 512; rc.bottom = rc.top + 512; DrawText( dc, buf, -1, &rc, DT_TOP ); l = 0; if ( max == 0 ) { static DWORD traindone = 0; if ( traindone == 0 ) traindone = GetTickCount(); l = sprintf( buf, "Finished in %s.", gettime( traindone - trainstart ) ); } else if ( max != MAXRANGE ) l = sprintf( buf, "Done in %s...", gettime( (int) ( ( ( (int64)max * ( (int64)GetTickCount() - (int64)trainstart ) ) ) / (int64) ( MAXRANGE - max ) ) ) ); GetCursorPos( &p ); ScreenToClient( window, &p ); if ( ( p.x >= 0 ) && ( p.y >= 0 ) && ( p.x < (bitmapw*curzoom) ) && ( p.y < (bitmaph*curzoom) ) ) { int findex; int x, y, w, h, sx, sy, ix, iy, ox, oy; int ir, chanind; int * ts; char badstr[64]; STBIR__V_FIRST_INFO v_info={0}; p.x /= curzoom; p.y /= curzoom; for( findex = 0 ; findex < numfileinfo ; findex++ ) { x = fi[findex].bitmapx; y = fi[findex].bitmapy; w = x + ( fi[findex].dimensionx + 1 ) * fi[findex].numtypes; h = y + ( fi[findex].dimensiony + 1 ) * fi[findex].numinputrects; if ( ( p.x >= x ) && ( p.y >= y ) && ( p.x < w ) && ( p.y < h ) ) goto found; } goto nope; found: ir = ( p.y - y ) / ( fi[findex].dimensiony + 1 ); sy = ( p.y - y ) % ( fi[findex].dimensiony + 1 ); if ( sy >= fi[findex].dimensiony ) goto nope; chanind = ( p.x - x ) / ( fi[findex].dimensionx + 1 ); sx = ( p.x - x ) % ( fi[findex].dimensionx + 1 ); if ( sx >= fi[findex].dimensionx ) goto nope; ix = fi[findex].inputrects[ir*2]; iy = fi[findex].inputrects[ir*2+1]; ts = fi[findex].timings + ( ( fi[findex].dimensionx * fi[findex].dimensiony * fi[findex].numtypes * ir ) + ( fi[findex].dimensionx * fi[findex].dimensiony * chanind ) + ( fi[findex].dimensionx * sy ) + sx ) * 2; ox = 1+fi[findex].outputscalex*sx; oy = 1+fi[findex].outputscaley*sy; if ( windowstatus != 2 ) { int VF, HF, v_first, good; VF = ts[0]; HF = ts[1]; v_first = vert_first( retrain_weights[chanind], ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); good = ( ((HF<=VF) && (!v_first)) || ((VF<=HF) && (v_first))); if ( good ) badstr[0] = 0; else { double r; if ( HF < VF ) r = (double)(VF-HF)/(double)HF; else r = (double)(HF-VF)/(double)VF; sprintf( badstr, " %.1f%% off", r*100 ); } sprintf( buf + l, "\n\n%s\nCh: %d Resize: %dx%d to %dx%d\nV: %d H: %d Order: %c (%s%s)\nClass: %d Scale: %.2f %s", fi[findex].filename,fi[findex].effective[chanind], ix,iy,ox,oy, VF, HF, v_first?'V':'H', good?"Good":"Wrong", badstr, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter" ); } else { int v_first, time0, time1; float (* weights)[4] = stbir__compute_weights[chanind]; int * ts1; char b0[32], b1[32]; ts1 = fi[1].timings + ( ts - fi[0].timings ); v_first = vert_first( weights, ox, oy, ix, iy, STBIR_FILTER_MITCHELL, &v_info ); time0 = ( v_first ) ? ts[0] : ts[1]; time1 = ( v_first ) ? ts1[0] : ts1[1]; b0[0] = b1[0] = 0; if ( time0 < time1 ) sprintf( b0," (%.f%% better)", ((double)time1-(double)time0)*100.0f/(double)time0); else sprintf( b1," (%.f%% better)", ((double)time0-(double)time1)*100.0f/(double)time1); sprintf( buf + l, "\n\n0: %s\n1: %s\nCh: %d Resize: %dx%d to %dx%d\nClass: %d Scale: %.2f %s\nTime0: %d%s\nTime1: %d%s", fi[0].filename, fi[1].filename, fi[0].effective[chanind], ix,iy,ox,oy, v_info.v_resize_classification, (double)oy/(double)iy, v_info.is_gather ? "Gather" : "Scatter", time0, b0, time1, b1 ); } } nope: rc.left = 32+320; rc.right = 512+320; SetTextColor( dc, RGB(0,0,128) ); DrawText( dc, buf, -1, &rc, DT_TOP ); } EndPaint( window, &ps ); return 0; } case WM_TIMER: InvalidateRect( window, 0, 0 ); return 0; case WM_DESTROY: PostQuitMessage( 0 ); return 0; } return DefWindowProc( window, message, wparam, lparam ); } static void SetHighDPI(void) { typedef HRESULT WINAPI setdpitype(int v); HMODULE h=LoadLibrary("Shcore.dll"); if (h) { setdpitype * sd = (setdpitype*)GetProcAddress(h,"SetProcessDpiAwareness"); if (sd ) sd(1); } } static void draw_window() { WNDCLASS wc; HWND w; MSG msg; instance = GetModuleHandle(NULL); wc.style = 0; wc.lpfnWndProc = WindowProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = instance; wc.hIcon = 0; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = 0; wc.lpszMenuName = 0; wc.lpszClassName = "WHTrain"; if ( !RegisterClass( &wc ) ) exit(1); SetHighDPI(); bmiHeader.biSize = sizeof(BITMAPINFOHEADER); bmiHeader.biWidth = bitmapp/3; bmiHeader.biHeight = -bitmaph; bmiHeader.biPlanes = 1; bmiHeader.biBitCount = 24; bmiHeader.biCompression = BI_RGB; w = CreateWindow( "WHTrain", "Vertical First Training", WS_CAPTION | WS_POPUP| WS_CLIPCHILDREN | WS_SYSMENU | WS_MINIMIZEBOX | WS_SIZEBOX, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, 0, 0, instance, 0 ); { RECT r, c; GetWindowRect( w, &r ); GetClientRect( w, &c ); extrawindoww = ( r.right - r.left ) - ( c.right - c.left ); extrawindowh = ( r.bottom - r.top ) - ( c.bottom - c.top ); SetWindowPos( w, 0, 0, 0, bitmapw * curzoom + extrawindoww, bitmaph * curzoom + extrawindowh + 164, SWP_NOMOVE ); } ShowWindow( w, SW_SHOWNORMAL ); SetTimer( w, 1, 250, 0 ); { BOOL ret; while( ( ret = GetMessage( &msg, w, 0, 0 ) ) != 0 ) { if ( ret == -1 ) break; TranslateMessage( &msg ); DispatchMessage( &msg ); } } } static void retrain() { HANDLE threads[ 16 ]; int chanind; trainstart = GetTickCount(); for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) threads[ chanind ] = CreateThread( 0, 2048*1024, retrain_shim, (LPVOID)(size_t)chanind, 0, 0 ); draw_window(); for( chanind = 0 ; chanind < fi[0].numtypes ; chanind++ ) { WaitForSingleObject( threads[ chanind ], INFINITE ); CloseHandle( threads[ chanind ] ); } write_bitmap(); print_struct( retrain_weights, "retained_weights" ); if ( windowstatus ) printf( "CANCELLED!\n" ); } static void info() { int findex; // display info about each input file for( findex = 0 ; findex < numfileinfo ; findex++ ) { int i, h,m,s; if ( findex ) printf( "\n" ); printf( "Timing file: %s\n", fi[findex].filename ); printf( "CPU type: %d %s\n", fi[findex].cpu, fi[findex].simd?(fi[findex].simd==2?"SIMD8":"SIMD4"):"Scalar" ); h = fi[findex].milliseconds/3600000; m = (fi[findex].milliseconds-h*3600000)/60000; s = (fi[findex].milliseconds-h*3600000-m*60000)/1000; printf( "Total time in test: %dh %dm %ds Cycles/sec: %.f\n", h,m,s, 1000.0/fi[findex].scale_time ); printf( "Each tile of samples is %dx%d, and is scaled by %dx%d.\n", fi[findex].dimensionx,fi[findex].dimensiony, fi[findex].outputscalex,fi[findex].outputscaley ); printf( "So the x coords are: " ); for( i=0; i < fi[findex].dimensionx ; i++ ) printf( "%d ",1+i*fi[findex].outputscalex ); printf( "\n" ); printf( "And the y coords are: " ); for( i=0; i < fi[findex].dimensiony ; i++ ) printf( "%d ",1+i*fi[findex].outputscaley ); printf( "\n" ); printf( "There are %d channel counts and they are: ", fi[findex].numtypes ); for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%d ",fi[findex].effective[i] ); printf( "\n" ); printf( "There are %d input rect sizes and they are: ", fi[findex].numinputrects ); for( i=0; i < fi[findex].numtypes ; i++ ) printf( "%dx%d ",fi[findex].inputrects[i*2],fi[findex].inputrects[i*2+1] ); printf( "\n" ); } } static void current( int do_win, int do_bitmap ) { int i, findex; trainstart = GetTickCount(); // clear progress memset( windowranges, 0, sizeof( windowranges ) ); // copy in appropriate weights memcpy( retrain_weights, stbir__compute_weights, sizeof( retrain_weights ) ); // build and print current errors and build current bitmap for( i = 0 ; i < fi[0].numtypes ; i++ ) { double curerr[STBIR_RESIZE_CLASSIFICATIONS]; int curtot[STBIR_RESIZE_CLASSIFICATIONS]; float (* weights)[4] = retrain_weights[i]; calc_errors( weights, curtot, curerr, i ); if ( !do_bitmap ) print_weights( weights, i, curtot, curerr ); for( findex = 0 ; findex < numfileinfo ; findex++ ) build_bitmap( weights, i, findex ); } if ( do_win ) draw_window(); if ( do_bitmap ) write_bitmap(); } static void compare() { int i; trainstart = GetTickCount(); windowstatus = 2; // comp mode // clear progress memset( windowranges, 0, sizeof( windowranges ) ); if ( ( fi[0].numtypes != fi[1].numtypes ) || ( fi[0].numinputrects != fi[1].numinputrects ) || ( fi[0].dimensionx != fi[1].dimensionx ) || ( fi[0].dimensiony != fi[1].dimensiony ) || ( fi[0].outputscalex != fi[1].outputscalex ) || ( fi[0].outputscaley != fi[1].outputscaley ) ) { err: printf( "Timing files don't match.\n" ); exit(5); } for( i=0; i < fi[0].numtypes ; i++ ) { if ( fi[0].effective[i] != fi[1].effective[i] ) goto err; if ( fi[0].inputrects[i*2] != fi[1].inputrects[i*2] ) goto err; if ( fi[0].inputrects[i*2+1] != fi[1].inputrects[i*2+1] ) goto err; } alloc_bitmap( 1 ); for( i = 0 ; i < fi[0].numtypes ; i++ ) { float (* weights)[4] = stbir__compute_weights[i]; build_comp_bitmap( weights, i ); } draw_window(); } static void load_files( char ** args, int count ) { int i; if ( count == 0 ) { printf( "No timing files listed!" ); exit(3); } for ( i = 0 ; i < count ; i++ ) { if ( !use_timing_file( args[i], i ) ) { printf( "Bad timing file %s\n", args[i] ); exit(2); } } numfileinfo = count; } int main( int argc, char ** argv ) { int check; if ( argc < 3 ) { err: printf( "vf_train retrain [timing_filenames....] - recalcs weights for all the files on the command line.\n"); printf( "vf_train info [timing_filenames....] - shows info about each timing file.\n"); printf( "vf_train check [timing_filenames...] - show results for the current weights for all files listed.\n"); printf( "vf_train compare - compare two timing files (must only be two files and same resolution).\n"); printf( "vf_train bitmap [timing_filenames...] - write out results.png, comparing against the current weights for all files listed.\n"); exit(1); } check = ( strcmp( argv[1], "check" ) == 0 ); if ( ( check ) || ( strcmp( argv[1], "bitmap" ) == 0 ) ) { load_files( argv + 2, argc - 2 ); alloc_bitmap( numfileinfo ); current( check, !check ); } else if ( strcmp( argv[1], "info" ) == 0 ) { load_files( argv + 2, argc - 2 ); info(); } else if ( strcmp( argv[1], "compare" ) == 0 ) { if ( argc != 4 ) { printf( "You must specify two files to compare.\n" ); exit(4); } load_files( argv + 2, argc - 2 ); compare(); } else if ( strcmp( argv[1], "retrain" ) == 0 ) { load_files( argv + 2, argc - 2 ); alloc_bitmap( numfileinfo ); retrain(); } else { goto err; } return 0; }