Canola
0.8.D001
|
00001 // 00002 // canola - canon canola 1614p emulator 00003 // Copyright (C) 2011, 2012 Peter Miller 00004 // 00005 // This program is free software; you can redistribute it and/or modify 00006 // it under the terms of the GNU General Public License, version 3, as 00007 // published by the Free Software Foundation. 00008 // 00009 // This program is distributed in the hope that it will be useful, 00010 // but WITHOUT ANY WARRANTY; without even the implied warranty of 00011 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00012 // General Public License for more details. 00013 // 00014 // You should have received a copy of the GNU General Public License along 00015 // with this program. If not, see <http://www.gnu.org/licenses/>. 00016 // 00017 00018 #include <lib/ac/stdio.h> 00019 #include <lib/ac/stdlib.h> 00020 #include <lib/ac/string.h> 00021 #include <lib/ac/unistd.h> 00022 #include <libexplain/output.h> 00023 #include <libexplain/program_name.h> 00024 00025 #include <lib/calculator/asm.h> 00026 #include <lib/image.h> 00027 #include <lib/image/invert.h> 00028 #include <lib/image/monochrome.h> 00029 #include <lib/image/threshold.h> 00030 #include <lib/opcode.h> 00031 #include <lib/polyfit.h> 00032 #include <lib/print_version.h> 00033 #include <lib/sizeof.h> 00034 00035 00036 // #define FUZZY 1 00037 00038 00039 static void 00040 usage(void) 00041 { 00042 const char *prog = explain_program_name_get(); 00043 fprintf(stderr, "Usage: %s [ <option>... ] <card-image-file> " 00044 "<binary-code-file>\n", prog); 00045 fprintf(stderr, " %s -V\n", prog); 00046 exit(1); 00047 } 00048 00049 00050 #ifdef FUZZY 00051 00052 static bool 00053 is_black(const image::pointer &img, int x, int y) 00054 { 00055 icon_pixel p; 00056 img->get_pixel(x, y, p); 00057 return (p.get_red_short() < 0x8000); 00058 } 00059 00060 #endif 00061 00062 00063 static bool 00064 is_white(const image::pointer &img, int x, int y) 00065 { 00066 icon_pixel p; 00067 img->get_pixel(x, y, p); 00068 return (p.get_red_short() >= 0x8000); 00069 } 00070 00071 00072 #ifdef FUZZY 00073 00074 static bool 00075 hole_inner(const image::pointer &img, int x, int y, int hx, int hy) 00076 { 00077 // 0 | 0 | 0 y + hy * 0.6 00078 // -+---+---+---+ 00079 // | | 00080 // | 1 | y + hy * 0.2 00081 // 0 | 1 | 0 y 00082 // | 1 | y - hy * 0.2 00083 // | | 00084 // -+---+---+---+ 00085 // 0 | 0 | 0 y - hy * 0.6 00086 // 00087 // | | ^--- x + hx * 0.6 00088 // | `------- x 00089 // `------------x - hx * 0.6 00090 icon_pixel p; 00091 00092 int x1 = x - hx * 3/5; 00093 int x2 = x; 00094 int x3 = x + hx * 3/5; 00095 00096 int y1 = y - hy * 7/10; 00097 int y2 = y - hy / 5; 00098 int y3 = y; 00099 int y4 = y + hy / 5; 00100 int y5 = y + hy * 7/10; 00101 00102 return 00103 ( 00104 is_black(img, x1, y1) 00105 && 00106 is_black(img, x2, y1) 00107 && 00108 is_black(img, x3, y1) 00109 && 00110 is_white(img, x2, y2) 00111 && 00112 is_black(img, x1, y3) 00113 && 00114 is_white(img, x2, y3) 00115 && 00116 is_black(img, x3, y3) 00117 && 00118 is_white(img, x2, y4) 00119 && 00120 is_black(img, x1, y5) 00121 && 00122 is_black(img, x2, y5) 00123 && 00124 is_black(img, x3, y5) 00125 ); 00126 } 00127 00128 00129 static bool 00130 hole_fuzzy(const image::pointer &img, int x, int y, int hx, int hy) 00131 { 00132 for (int sdy = 1; sdy < hy; ++sdy) 00133 { 00134 int dy = (sdy >> 1) * ((sdy & 1) ? 1 : -1); 00135 for (int sdx = 1; sdx < hx; ++sdx) 00136 { 00137 int dx = (sdx >> 1) * ((sdx & 1) ? 1 : -1); 00138 if (hole_inner(img, x + dx, y + dy, hx, hy)) 00139 return true; 00140 } 00141 } 00142 return false; 00143 } 00144 00145 #endif 00146 00147 static bool invert; 00148 00149 static void 00150 box(const image::pointer &img, int xlo, int ylo, int xhi, int yhi) 00151 { 00152 icon_pixel green(0., 1., 0.); 00153 for (int x = xlo; x < xhi; ++x) 00154 { 00155 img->set_pixel(x, ylo, green); 00156 img->set_pixel(x, yhi, green); 00157 } 00158 for (int y = ylo; y < yhi; ++y) 00159 { 00160 img->set_pixel(xlo, y, green); 00161 img->set_pixel(xhi, y, green); 00162 } 00163 } 00164 00165 static bool debug; 00166 00167 // 00168 // Referencing http://www.quadibloc.com/comp/cardint.htm 00169 // 00170 // IBM card width 7.75 inch by 3.25 inch 00171 // at 600dpi that would be 4650 (wider than our cards) 00172 // 1950 (higher than our cards) 00173 // row pitch 0.25 inch @ 600dpi = 150 pixels which very close to the 00174 // calculated row pitch of 151.666 pixels. 00175 // 00176 // 13/150 = 0.08666... @600 dpi = 52.0 00177 // column pitch is stated as 0.087 inch @600 dpi = 52.2 pixels which is 00178 // very close to half of the calulated pitch of 104.641 pixels 00179 // (alternatively 0.0885 @ 600 dpi = 53.1 compared to 104.641 (52.32) less good) 00180 // 00181 // vertical height of holes stated as 0.125 inch @ 600dpi = 75 pixels, 00182 // horizontal width of holes not stated 00183 // 00184 // also 0.375 inch unpunched top and bottom @ 600dpi = 225 pixels 00185 // which is significantly different than the 310 calculated. 00186 // 00187 00188 static void 00189 process_image(const char *filename, const calculator::pointer &data) 00190 { 00191 // The ideal image is the card image measured to figure out where 00192 // the holes are. 00193 const int ideal_image_width = 4433; 00194 const int ideal_image_height = 1370; 00195 00196 // open image.png as greyscale 00197 image::pointer img = image::create_from_file(filename); 00198 00199 // verify proportions look right 00200 { 00201 double ratio = img->get_width() / (double)img->get_height(); 00202 // We want approximately 4432x1375 00203 double want = (double)ideal_image_width / (double)ideal_image_height; 00204 if (ratio < want * 0.98 || ratio > want * 1.02) 00205 { 00206 explain_output_error_and_die 00207 ( 00208 "%s: file has proportions %dx%d (%g:1) but was expecting %g:1", 00209 filename, 00210 img->get_width(), 00211 img->get_height(), 00212 ratio, 00213 want 00214 ); 00215 } 00216 } 00217 00218 // we only want gray 00219 img = image_monochrome::create(img); 00220 00221 // threshold based on averages 00222 if (debug) 00223 fprintf(stderr, "averaging...\n"); 00224 icon_pixel avg = img->average(); 00225 00226 img = image_threshold::create(img, (1. + avg.get_red()) / 2); 00227 00228 if (invert) 00229 img = image_invert::create(img); 00230 00231 // debug: write the image out so I can look at it 00232 if (debug) 00233 { 00234 fprintf(stderr, "writing debug1.png\n"); 00235 { 00236 const char *fn2 = "debug1.png"; 00237 image::pointer img2 = 00238 image::create_blank_from_file 00239 ( 00240 fn2, 00241 img->get_width(), 00242 img->get_height() 00243 ); 00244 img2->copy(img); 00245 img2->save_to_file(fn2); 00246 } 00247 } 00248 00249 double scale = img->get_width() / (double)ideal_image_width; 00250 if (debug) 00251 fprintf(stderr, "scale = %g\n", scale); 00252 00253 // punched holes are 48x79 00254 // (1/12 inch x 1/8 inch = 50x75) 00255 // (2mm x 3.5mm = 47x83) 00256 int hx = 34 * scale + 0.5; 00257 int hy = 75 * scale + 0.5; 00258 00259 // Get a bit smarter about detecting each row of holes. 00260 int row_centre[40]; 00261 int nrows = 0; 00262 { 00263 int w = img->get_width(); 00264 int h = img->get_height(); 00265 int *pixel_column_total = new int [w]; 00266 for (int x = 0; x < w; ++x) 00267 { 00268 int sum = 0; 00269 for (int y = 0; y < h; ++y) 00270 { 00271 if (is_white(img, x, y)) 00272 { 00273 ++sum; 00274 } 00275 } 00276 pixel_column_total[x] = (2 * sum > hy) ? sum : 0; 00277 } 00278 00279 int x = 0; 00280 while (x < w) 00281 { 00282 while (x < w && pixel_column_total[x] == 0) 00283 ++x; 00284 // start of row 00285 int x2 = x + 1; 00286 while (x2 < w && pixel_column_total[x2] != 0) 00287 ++x2; 00288 int rw = x2 - x; 00289 if (2 * rw >= hx) 00290 { 00291 row_centre[nrows++] = (x + x2) / 2; 00292 if (debug) 00293 { 00294 fprintf(stderr, "row%3d, at%5d,%3d pixels wide\n", nrows, 00295 row_centre[nrows - 1], rw); 00296 } 00297 } 00298 x = x2; 00299 } 00300 delete [] pixel_column_total; 00301 } 00302 00303 // now we look for each column of bits 00304 int col_centre[20]; 00305 { 00306 int ncols = 0; 00307 int w = img->get_width(); 00308 int h = img->get_height(); 00309 int *pixel_row_total = new int [h]; 00310 for (int y = 0; y < h; ++y) 00311 { 00312 if (y < 240 * scale || y >= 1285 * scale) 00313 { 00314 pixel_row_total[y] = 0; 00315 continue; 00316 } 00317 int sum = 0; 00318 // only scan the row centres, not the whole image 00319 for (int x = 0; x < w; ++x) 00320 { 00321 if (is_white(img, x, y)) 00322 ++sum; 00323 } 00324 pixel_row_total[y] = (2 * sum > hx) ? sum : 0; 00325 } 00326 00327 int y = 0; 00328 while (y < h && ncols < (int)SIZEOF(col_centre)) 00329 { 00330 while (y < h && pixel_row_total[y] == 0) 00331 ++y; 00332 // start of row 00333 int y2 = y + 1; 00334 while (y2 < h && pixel_row_total[y2] != 0) 00335 ++y2; 00336 int rh = y2 - y; 00337 if (2 * rh >= hy) 00338 { 00339 col_centre[ncols++] = (y + y2) / 2; 00340 if (debug) 00341 { 00342 fprintf(stderr, "col %d, at %3d, %2d pixels high\n", 00343 ncols - 1, col_centre[ncols - 1], rh); 00344 } 00345 } 00346 y = y2; 00347 } 00348 if (ncols == 1) 00349 { 00350 // assuming ~150 pixels between columns 00351 int c = (col_centre[0]/scale - 310 + 75) / 150; 00352 int offset = col_centre[0] - c * 150 * scale; 00353 for (int j = 0; j < 7; ++j) 00354 col_centre[j] = offset + j * 150 * scale; 00355 ncols = 7; 00356 } 00357 else if (ncols < 7) 00358 { 00359 // fit a line to the available data 00360 polyfit p(2); 00361 for (int j = 0; j < ncols; ++j) 00362 { 00363 int x = (col_centre[j]/scale - 310 + 75) / 150; 00364 int y = col_centre[j]; 00365 p.enter(x, y); 00366 } 00367 if (p.solve() != polyfit::solve_ok) 00368 goto yuck; 00369 for (int j = 0; j < 7; ++j) 00370 { 00371 col_centre[j] = p.evaluate(j); 00372 if (debug) 00373 fprintf(stderr, "col %d, at %3d\n", j, col_centre[j]); 00374 } 00375 ncols = 7; 00376 } 00377 if (ncols != 7) 00378 { 00379 yuck: 00380 fprintf(stderr, "found %d bit columns, expected 7\n", ncols); 00381 exit(1); 00382 } 00383 delete [] pixel_row_total; 00384 } 00385 // debug: write the image out so I can look at it 00386 image::pointer img2; 00387 if (debug) 00388 { 00389 fprintf(stderr, "write debug2.png\n"); 00390 const char *fn2 = "debug2.png"; 00391 img2 = 00392 image::create_blank_from_file 00393 ( 00394 fn2, 00395 img->get_width(), 00396 img->get_height() 00397 ); 00398 img2->copy(img); 00399 00400 icon_pixel red(1., 0., 0.); 00401 unsigned dy = col_centre[1] - col_centre[0]; 00402 unsigned ylo = col_centre[0] - dy / 2; 00403 unsigned yhi = col_centre[6] + dy / 2; 00404 for (int row = 0; row < 40 && row < nrows; ++row) 00405 { 00406 // int x = scale * (203 + 104.641025641 * row); 00407 // int x = scale * (203 + 104 * row); 00408 unsigned x = row_centre[row]; 00409 for (unsigned y = ylo; y < yhi; ++y) 00410 if (!is_white(img2, x, y)) 00411 img2->set_pixel(x, y, red); 00412 } 00413 unsigned dx = (nrows > 1 ? row_centre[1] - row_centre[0] : 104*scale); 00414 unsigned xlo = row_centre[0] - dx / 2; 00415 unsigned xhi = row_centre[nrows - 1] + dx / 2; 00416 for (int col = 6; col >= 0; --col) 00417 { 00418 // int y = scale * (310 + 151.666666667 * col); 00419 // int y = scale * (310 + 150 * col); 00420 int y = col_centre[col]; 00421 for (unsigned x = xlo; x < xhi; ++x) 00422 if (!is_white(img2, x, y)) 00423 img2->set_pixel(x, y, red); 00424 } 00425 img2->save_to_file(fn2); 00426 } 00427 00428 // interpret holes as data 00429 bool something = false; 00430 data->program_mode_set(calculator::program_mode_learn); 00431 for (int row = 0; row < 40 && row < nrows; ++row) 00432 { 00433 // int x = scale * (203 + 104.641025641 * row); 00434 // int x = scale * (203 + 104 * row); 00435 int x = row_centre[row]; 00436 int code = 0; 00437 for (int col = 6; col >= 0; --col) 00438 { 00439 // int y = scale * (310 + 151.666666667 * col); 00440 // int y = scale * (310 + 150 * col); 00441 int y = col_centre[col]; 00442 #ifdef FUZZY 00443 if (hole_fuzzy(img, x, y, hx, hy)) 00444 #else 00445 if (is_white(img, x, y)) 00446 #endif 00447 { 00448 code |= 1 << col; 00449 if (debug) 00450 box(img2, x - 7, y - 10, x + 7, y + 10); 00451 } 00452 } 00453 if (code != 0 && code != 0x7F) 00454 { 00455 data->on_opcode_lrn(opcode_t(code), location::pointer()); 00456 something = true; 00457 } 00458 } 00459 00460 if (!something) 00461 explain_output_error_and_die("%s: could not find any data", filename); 00462 if (debug) 00463 { 00464 fprintf(stderr, "writing debug3.png\n"); 00465 img2->save_to_file("debug3.png"); 00466 } 00467 } 00468 00469 00470 int 00471 main(int argc, char **argv) 00472 { 00473 bool binary = false; 00474 for (;;) 00475 { 00476 int c = getopt(argc, argv, "bdiV"); 00477 if (c == EOF) 00478 break; 00479 switch (c) 00480 { 00481 case 'b': 00482 binary = true; 00483 break; 00484 00485 case 'd': 00486 debug = true; 00487 break; 00488 00489 case 'i': 00490 invert = true; 00491 break; 00492 00493 case 'V': 00494 print_version(); 00495 return 0; 00496 00497 default: 00498 usage(); 00499 } 00500 } 00501 if (optind + 2 != argc) 00502 usage(); 00503 const char *input_file_name = argv[optind]; 00504 const char *output_file_name = argv[optind + 1]; 00505 00506 // read the data on the card 00507 calculator::pointer data = calculator_asm::create(); 00508 process_image(input_file_name, data); 00509 00510 // write the code out. 00511 data->save_program_as(output_file_name, binary); 00512 00513 return 0; 00514 }