| 1 | /* |
|---|
| 2 | * img2oric Convert an image to Oric Atmos colours |
|---|
| 3 | * Copyright (c) 2008 Sam Hocevar <sam@zoy.org> |
|---|
| 4 | * All Rights Reserved |
|---|
| 5 | * |
|---|
| 6 | * Changes: |
|---|
| 7 | * Jan 18, 2008: initial release |
|---|
| 8 | * Jan 23, 2008: add support for inverse video on attribute change |
|---|
| 9 | * improve Floyd-Steinberg coefficient values |
|---|
| 10 | * Jun 14, 2008: Win32 version |
|---|
| 11 | * |
|---|
| 12 | * This program is free software. It comes without any warranty, to |
|---|
| 13 | * the extent permitted by applicable law. You can redistribute it |
|---|
| 14 | * and/or modify it under the terms of the Do What The Fuck You Want |
|---|
| 15 | * To Public License, Version 2, as published by Sam Hocevar. See |
|---|
| 16 | * http://sam.zoy.org/wtfpl/COPYING for more details. |
|---|
| 17 | * |
|---|
| 18 | * To build this program on Linux: |
|---|
| 19 | * cc -O3 -funroll-loops -W -Wall img2oric.c -o img2oric \ |
|---|
| 20 | * $(pkg-config --cflags --libs sdl) -lSDL_image -lm |
|---|
| 21 | * To build a Windows executable: |
|---|
| 22 | * i586-mingw32msvc-cc -O3 -funroll-loops -W -Wall \ |
|---|
| 23 | * img2oric.c -o img2oric.exe -lgdi32 |
|---|
| 24 | */ |
|---|
| 25 | |
|---|
| 26 | #include <stdio.h> |
|---|
| 27 | #include <stdlib.h> |
|---|
| 28 | #include <string.h> |
|---|
| 29 | |
|---|
| 30 | #include <math.h> |
|---|
| 31 | |
|---|
| 32 | #if defined _WIN32 |
|---|
| 33 | # include <windows.h> |
|---|
| 34 | # define uint8_t unsigned char |
|---|
| 35 | #else |
|---|
| 36 | # include <SDL_image.h> |
|---|
| 37 | #endif |
|---|
| 38 | |
|---|
| 39 | /* |
|---|
| 40 | * BMP output name and Oric output name |
|---|
| 41 | */ |
|---|
| 42 | #define BMPFILE "output.bmp" |
|---|
| 43 | #define ORICFILE "OUTPUT" /* ".TAP" will be appended */ |
|---|
| 44 | |
|---|
| 45 | /* |
|---|
| 46 | * Image dimensions and recursion depth. DEPTH = 2 is a reasonable value, |
|---|
| 47 | * DEPTH = 3 gives good quality, and higher values may improve the results |
|---|
| 48 | * even more but at the cost of significantly longer computation times. |
|---|
| 49 | */ |
|---|
| 50 | #define WIDTH 240 |
|---|
| 51 | #define HEIGHT 200 |
|---|
| 52 | #define DEPTH 3 |
|---|
| 53 | |
|---|
| 54 | /* |
|---|
| 55 | * Error diffusion table, similar to Floyd-Steinberg. I choose not to |
|---|
| 56 | * propagate 100% of the error, because doing so creates awful artifacts |
|---|
| 57 | * (full lines of the same colour, massive colour bleeding) for unclear |
|---|
| 58 | * reasons. Atkinson dithering propagates 3/4 of the error, which is even |
|---|
| 59 | * less than our 31/32. I also choose to propagate slightly more in the |
|---|
| 60 | * X direction to avoid banding effects due to rounding errors. |
|---|
| 61 | * It would be interesting, for future versions of this software, to |
|---|
| 62 | * propagate the error to the second line, too. But right now I find it far |
|---|
| 63 | * too complex to do. |
|---|
| 64 | * |
|---|
| 65 | * +-------+-------+ |
|---|
| 66 | * | error |FS0/FSX| |
|---|
| 67 | * +-------+-------+-------+ |
|---|
| 68 | * |FS1/FSX|FS2/FSX|FS3/FSX| |
|---|
| 69 | * +-------+-------+-------+ |
|---|
| 70 | */ |
|---|
| 71 | #define FS0 15 |
|---|
| 72 | #define FS1 6 |
|---|
| 73 | #define FS2 9 |
|---|
| 74 | #define FS3 1 |
|---|
| 75 | #define FSX 32 |
|---|
| 76 | |
|---|
| 77 | /* |
|---|
| 78 | * The simple Oric RGB palette, made of the 8 Neugebauer primary colours. Each |
|---|
| 79 | * colour is repeated 6 times so that we can point to the palette to paste |
|---|
| 80 | * whole blocks of 6 pixels. It’s also organised so that palette[7-x] is the |
|---|
| 81 | * RGB negative of palette[x], and screen command X uses palette[X & 7]. |
|---|
| 82 | */ |
|---|
| 83 | #define o 0x0000 |
|---|
| 84 | #define X 0xffff |
|---|
| 85 | static const int palette[8][6 * 3] = |
|---|
| 86 | { |
|---|
| 87 | { o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o }, |
|---|
| 88 | { X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o }, |
|---|
| 89 | { o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o }, |
|---|
| 90 | { X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o }, |
|---|
| 91 | { o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X }, |
|---|
| 92 | { X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X }, |
|---|
| 93 | { o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X }, |
|---|
| 94 | { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }, |
|---|
| 95 | }; |
|---|
| 96 | |
|---|
| 97 | /* |
|---|
| 98 | * Gamma correction tables. itoc_table and ctoi_table accept overflow and |
|---|
| 99 | * underflow values to a reasonable extent, so that we don’t have to check |
|---|
| 100 | * for these cases later in the code. Tests kill performance. |
|---|
| 101 | */ |
|---|
| 102 | #define PAD 2048 |
|---|
| 103 | static int itoc_table_clip[PAD + 256 + PAD], ctoi_table_clip[PAD + 256 + PAD]; |
|---|
| 104 | static int *itoc_table = itoc_table_clip + PAD; |
|---|
| 105 | static int *ctoi_table = ctoi_table_clip + PAD; |
|---|
| 106 | |
|---|
| 107 | static void init_tables(void) |
|---|
| 108 | { |
|---|
| 109 | int i; |
|---|
| 110 | |
|---|
| 111 | for(i = 0; i < PAD + 256 + PAD; i++) |
|---|
| 112 | { |
|---|
| 113 | double f = 1.0 * (i - PAD) / 255.999; |
|---|
| 114 | if(f >= 0.) |
|---|
| 115 | { |
|---|
| 116 | itoc_table_clip[i] = (int)(65535.999 * pow(f, 1./2.2)); |
|---|
| 117 | ctoi_table_clip[i] = (int)(65535.999 * pow(f, 2.2)); |
|---|
| 118 | } |
|---|
| 119 | else |
|---|
| 120 | { |
|---|
| 121 | itoc_table_clip[i] = - (int)(65535.999 * pow(-f, 1./2.2)); |
|---|
| 122 | ctoi_table_clip[i] = - (int)(65535.999 * pow(-f, 2.2)); |
|---|
| 123 | } |
|---|
| 124 | } |
|---|
| 125 | } |
|---|
| 126 | |
|---|
| 127 | static inline int itoc(int p) { return itoc_table[p / 0x100]; } |
|---|
| 128 | static inline int ctoi(int p) { return ctoi_table[p / 0x100]; } |
|---|
| 129 | |
|---|
| 130 | /* |
|---|
| 131 | * Set new background and foreground colours according to the given command. |
|---|
| 132 | */ |
|---|
| 133 | static inline void domove(uint8_t command, uint8_t *bg, uint8_t *fg) |
|---|
| 134 | { |
|---|
| 135 | if((command & 0x78) == 0x00) |
|---|
| 136 | *fg = command & 0x7; |
|---|
| 137 | else if((command & 0x78) == 0x10) |
|---|
| 138 | *bg = command & 0x7; |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | /* |
|---|
| 142 | * Clamp pixel value to avoid colour bleeding. Deactivated because it |
|---|
| 143 | * does not give satisfactory results. |
|---|
| 144 | */ |
|---|
| 145 | #define CLAMP 0x1000 |
|---|
| 146 | static inline int clamp(int p) |
|---|
| 147 | { |
|---|
| 148 | #if 0 |
|---|
| 149 | /* FIXME: doesn’t give terribly good results on eg. eatme.png */ |
|---|
| 150 | if(p < - CLAMP) return - CLAMP; |
|---|
| 151 | if(p > 0xffff + CLAMP) return 0xffff + CLAMP; |
|---|
| 152 | #endif |
|---|
| 153 | return p; |
|---|
| 154 | } |
|---|
| 155 | |
|---|
| 156 | /* |
|---|
| 157 | * Compute the perceptual error caused by replacing the input pixels "in" |
|---|
| 158 | * with the output pixels "out". "inerr" is the diffused error that should |
|---|
| 159 | * be applied to "in"’s first pixel. "outerr" will hold the diffused error |
|---|
| 160 | * to apply after "in"’s last pixel upon next call. The return value does |
|---|
| 161 | * not mean much physically; it is one part of the algorithm where you need |
|---|
| 162 | * to play a bit in order to get appealing results. That’s how image |
|---|
| 163 | * processing works, dude. |
|---|
| 164 | */ |
|---|
| 165 | static inline int geterror(int const *in, int const *inerr, |
|---|
| 166 | int const *out, int *outerr) |
|---|
| 167 | { |
|---|
| 168 | int tmperr[9 * 3]; |
|---|
| 169 | int i, c, ret = 0; |
|---|
| 170 | |
|---|
| 171 | /* 9 cells: 1 for the end of line, 8 for the errors below */ |
|---|
| 172 | memcpy(tmperr, inerr, 3 * sizeof(int)); |
|---|
| 173 | memset(tmperr + 3, 0, 8 * 3 * sizeof(int)); |
|---|
| 174 | |
|---|
| 175 | for(i = 0; i < 6; i++) |
|---|
| 176 | { |
|---|
| 177 | for(c = 0; c < 3; c++) |
|---|
| 178 | { |
|---|
| 179 | /* Experiment shows that this is important at small depths */ |
|---|
| 180 | int a = clamp(in[i * 3 + c] + tmperr[c]); |
|---|
| 181 | int b = out[i * 3 + c]; |
|---|
| 182 | tmperr[c] = (a - b) * FS0 / FSX; |
|---|
| 183 | tmperr[c + (i * 3 + 3)] += (a - b) * FS1 / FSX; |
|---|
| 184 | tmperr[c + (i * 3 + 6)] += (a - b) * FS2 / FSX; |
|---|
| 185 | tmperr[c + (i * 3 + 9)] += (a - b) * FS3 / FSX; |
|---|
| 186 | ret += (a - b) / 256 * (a - b) / 256; |
|---|
| 187 | } |
|---|
| 188 | } |
|---|
| 189 | |
|---|
| 190 | for(i = 0; i < 4; i++) |
|---|
| 191 | { |
|---|
| 192 | for(c = 0; c < 3; c++) |
|---|
| 193 | { |
|---|
| 194 | /* Experiment shows that this is important at large depths */ |
|---|
| 195 | int a = itoc((in[i * 3 + c] + in[i * 3 + 3 + c] |
|---|
| 196 | + in[i * 3 + 6 + c]) / 3); |
|---|
| 197 | int b = itoc((out[i * 3 + c] + out[i * 3 + 3 + c] |
|---|
| 198 | + out[i * 3 + 6 + c]) / 3); |
|---|
| 199 | ret += (a - b) / 256 * (a - b) / 256; |
|---|
| 200 | } |
|---|
| 201 | } |
|---|
| 202 | |
|---|
| 203 | /* Using the diffused error as a perceptual error component is stupid, |
|---|
| 204 | * because that’s not what it is at all, but I found that it helped a |
|---|
| 205 | * bit in some cases. */ |
|---|
| 206 | for(i = 0; i < 3; i++) |
|---|
| 207 | ret += tmperr[i] / 256 * tmperr[i] / 256; |
|---|
| 208 | |
|---|
| 209 | memcpy(outerr, tmperr, 3 * sizeof(int)); |
|---|
| 210 | |
|---|
| 211 | return ret; |
|---|
| 212 | } |
|---|
| 213 | |
|---|
| 214 | static uint8_t bestmove(int const *in, uint8_t bg, uint8_t fg, |
|---|
| 215 | int const *errvec, int depth, int maxerror, |
|---|
| 216 | int *error, int *out) |
|---|
| 217 | { |
|---|
| 218 | int voidvec[3], nvoidvec[3], bestrgb[6 * 3], tmprgb[6 * 3], tmpvec[3]; |
|---|
| 219 | int const *voidrgb, *nvoidrgb, *vec, *rgb; |
|---|
| 220 | int besterror, curerror, suberror, statice, voide, nvoide; |
|---|
| 221 | int i, j, c; |
|---|
| 222 | uint8_t command, bestcommand; |
|---|
| 223 | |
|---|
| 224 | /* Precompute error for the case where we change the foreground colour |
|---|
| 225 | * and hence only print the background colour or its negative */ |
|---|
| 226 | voidrgb = palette[bg]; |
|---|
| 227 | voide = geterror(in, errvec, voidrgb, voidvec); |
|---|
| 228 | nvoidrgb = palette[7 - bg]; |
|---|
| 229 | nvoide = geterror(in, errvec, nvoidrgb, nvoidvec); |
|---|
| 230 | |
|---|
| 231 | /* Precompute sub-error for the case where we print pixels (and hence |
|---|
| 232 | * don’t change the palette). It’s not the exact error because we should |
|---|
| 233 | * be propagating the error to the first pixel here. */ |
|---|
| 234 | if(depth > 0) |
|---|
| 235 | { |
|---|
| 236 | int tmp[3] = { 0, 0, 0 }; |
|---|
| 237 | bestmove(in + 6 * 3, bg, fg, tmp, depth - 1, maxerror, &statice, NULL); |
|---|
| 238 | } |
|---|
| 239 | |
|---|
| 240 | /* Check every likely command: |
|---|
| 241 | * 0-7: change foreground to 0-7 |
|---|
| 242 | * 8-15: change foreground to 0-7, print negative background |
|---|
| 243 | * 16-23: change background to 0-7 |
|---|
| 244 | * 24-31: change background to 0-7, print negative background |
|---|
| 245 | * 32: normal stuff |
|---|
| 246 | * 33: inverse video stuff */ |
|---|
| 247 | besterror = 0x7ffffff; |
|---|
| 248 | bestcommand = 0x10; |
|---|
| 249 | memcpy(bestrgb, voidrgb, 6 * 3 * sizeof(int)); |
|---|
| 250 | for(j = 0; j < 34; j++) |
|---|
| 251 | { |
|---|
| 252 | static uint8_t const lookup[] = |
|---|
| 253 | { |
|---|
| 254 | 0x00, 0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07, |
|---|
| 255 | 0x80, 0x84, 0x81, 0x85, 0x82, 0x86, 0x83, 0x87, |
|---|
| 256 | 0x10, 0x14, 0x11, 0x15, 0x12, 0x16, 0x13, 0x17, |
|---|
| 257 | 0x90, 0x94, 0x91, 0x95, 0x92, 0x96, 0x93, 0x97, |
|---|
| 258 | 0x40, 0xc0 |
|---|
| 259 | }; |
|---|
| 260 | |
|---|
| 261 | uint8_t newbg = bg, newfg = fg; |
|---|
| 262 | |
|---|
| 263 | command = lookup[j]; |
|---|
| 264 | domove(command, &newbg, &newfg); |
|---|
| 265 | |
|---|
| 266 | /* Keeping bg and fg is useless, because we could use standard |
|---|
| 267 | * pixel printing instead */ |
|---|
| 268 | if((command & 0x40) == 0x00 && newbg == bg && newfg == fg) |
|---|
| 269 | continue; |
|---|
| 270 | |
|---|
| 271 | /* I *think* having newfg == newbg is useless, too, but I don’t |
|---|
| 272 | * want to miss some corner case where swapping bg and fg may be |
|---|
| 273 | * interesting, so we continue anyway. */ |
|---|
| 274 | |
|---|
| 275 | #if 0 |
|---|
| 276 | /* Bit 6 off and bit 5 on seems illegal */ |
|---|
| 277 | if((command & 0x60) == 0x20) |
|---|
| 278 | continue; |
|---|
| 279 | |
|---|
| 280 | /* Bits 6 and 5 off and bit 3 on seems illegal */ |
|---|
| 281 | if((command & 0x68) == 0x08) |
|---|
| 282 | continue; |
|---|
| 283 | #endif |
|---|
| 284 | |
|---|
| 285 | if((command & 0xf8) == 0x00) |
|---|
| 286 | { |
|---|
| 287 | curerror = voide; |
|---|
| 288 | rgb = voidrgb; |
|---|
| 289 | vec = voidvec; |
|---|
| 290 | } |
|---|
| 291 | else if((command & 0xf8) == 0x80) |
|---|
| 292 | { |
|---|
| 293 | curerror = nvoide; |
|---|
| 294 | rgb = nvoidrgb; |
|---|
| 295 | vec = nvoidvec; |
|---|
| 296 | } |
|---|
| 297 | else if((command & 0xf8) == 0x10) |
|---|
| 298 | { |
|---|
| 299 | rgb = palette[newbg]; |
|---|
| 300 | curerror = geterror(in, errvec, rgb, tmpvec); |
|---|
| 301 | vec = tmpvec; |
|---|
| 302 | } |
|---|
| 303 | else if((command & 0xf8) == 0x90) |
|---|
| 304 | { |
|---|
| 305 | rgb = palette[7 - newbg]; |
|---|
| 306 | curerror = geterror(in, errvec, rgb, tmpvec); |
|---|
| 307 | vec = tmpvec; |
|---|
| 308 | } |
|---|
| 309 | else |
|---|
| 310 | { |
|---|
| 311 | int const *bgcolor, *fgcolor; |
|---|
| 312 | |
|---|
| 313 | if((command & 0x80) == 0x00) |
|---|
| 314 | { |
|---|
| 315 | bgcolor = palette[bg]; fgcolor = palette[fg]; |
|---|
| 316 | } |
|---|
| 317 | else |
|---|
| 318 | { |
|---|
| 319 | bgcolor = palette[7 - bg]; fgcolor = palette[7 - fg]; |
|---|
| 320 | } |
|---|
| 321 | |
|---|
| 322 | memcpy(tmpvec, errvec, 3 * sizeof(int)); |
|---|
| 323 | curerror = 0; |
|---|
| 324 | |
|---|
| 325 | for(i = 0; i < 6; i++) |
|---|
| 326 | { |
|---|
| 327 | int vec1[3], vec2[3]; |
|---|
| 328 | int smalle1 = 0, smalle2 = 0; |
|---|
| 329 | |
|---|
| 330 | memcpy(vec1, tmpvec, 3 * sizeof(int)); |
|---|
| 331 | memcpy(vec2, tmpvec, 3 * sizeof(int)); |
|---|
| 332 | for(c = 0; c < 3; c++) |
|---|
| 333 | { |
|---|
| 334 | int delta1, delta2; |
|---|
| 335 | delta1 = clamp(in[i * 3 + c] + tmpvec[c]) - bgcolor[c]; |
|---|
| 336 | vec1[c] = delta1 * FS0 / FSX; |
|---|
| 337 | smalle1 += delta1 / 256 * delta1; |
|---|
| 338 | delta2 = clamp(in[i * 3 + c] + tmpvec[c]) - fgcolor[c]; |
|---|
| 339 | vec2[c] = delta2 * FS0 / FSX; |
|---|
| 340 | smalle2 += delta2 / 256 * delta2; |
|---|
| 341 | } |
|---|
| 342 | |
|---|
| 343 | if(smalle1 < smalle2) |
|---|
| 344 | { |
|---|
| 345 | memcpy(tmpvec, vec1, 3 * sizeof(int)); |
|---|
| 346 | memcpy(tmprgb + i * 3, bgcolor, 3 * sizeof(int)); |
|---|
| 347 | } |
|---|
| 348 | else |
|---|
| 349 | { |
|---|
| 350 | memcpy(tmpvec, vec2, 3 * sizeof(int)); |
|---|
| 351 | memcpy(tmprgb + i * 3, fgcolor, 3 * sizeof(int)); |
|---|
| 352 | command |= (1 << (5 - i)); |
|---|
| 353 | } |
|---|
| 354 | } |
|---|
| 355 | |
|---|
| 356 | /* Recompute full error */ |
|---|
| 357 | curerror += geterror(in, errvec, tmprgb, tmpvec); |
|---|
| 358 | |
|---|
| 359 | rgb = tmprgb; |
|---|
| 360 | vec = tmpvec; |
|---|
| 361 | } |
|---|
| 362 | |
|---|
| 363 | if(curerror > besterror) |
|---|
| 364 | continue; |
|---|
| 365 | |
|---|
| 366 | /* Try to avoid bad decisions now that will have a high cost |
|---|
| 367 | * later in the line by making the next error more important than |
|---|
| 368 | * the current error. */ |
|---|
| 369 | curerror = curerror * 3 / 4; |
|---|
| 370 | |
|---|
| 371 | if(depth == 0) |
|---|
| 372 | suberror = 0; /* It’s the end of the tree */ |
|---|
| 373 | else if((command & 0x68) == 0x00) |
|---|
| 374 | { |
|---|
| 375 | bestmove(in + 6 * 3, newbg, newfg, vec, depth - 1, |
|---|
| 376 | besterror - curerror, &suberror, NULL); |
|---|
| 377 | |
|---|
| 378 | #if 0 |
|---|
| 379 | /* Slight penalty for colour changes; they're hard to revert. The |
|---|
| 380 | * value of 2 was determined empirically. 1.5 is not enough and |
|---|
| 381 | * 3 is too much. */ |
|---|
| 382 | if(newbg != bg) |
|---|
| 383 | suberror = suberror * 10 / 8; |
|---|
| 384 | else if(newfg != fg) |
|---|
| 385 | suberror = suberror * 9 / 8; |
|---|
| 386 | #endif |
|---|
| 387 | } |
|---|
| 388 | else |
|---|
| 389 | suberror = statice; |
|---|
| 390 | |
|---|
| 391 | if(curerror + suberror < besterror) |
|---|
| 392 | { |
|---|
| 393 | besterror = curerror + suberror; |
|---|
| 394 | bestcommand = command; |
|---|
| 395 | memcpy(bestrgb, rgb, 6 * 3 * sizeof(int)); |
|---|
| 396 | } |
|---|
| 397 | } |
|---|
| 398 | |
|---|
| 399 | *error = besterror; |
|---|
| 400 | if(out) |
|---|
| 401 | memcpy(out, bestrgb, 6 * 3 * sizeof(int)); |
|---|
| 402 | |
|---|
| 403 | return bestcommand; |
|---|
| 404 | } |
|---|
| 405 | |
|---|
| 406 | int main(int argc, char *argv[]) |
|---|
| 407 | { |
|---|
| 408 | #if defined _WIN32 |
|---|
| 409 | PBITMAPINFO pbinfo; |
|---|
| 410 | BITMAPINFO binfo; |
|---|
| 411 | BITMAPFILEHEADER bfheader; |
|---|
| 412 | ULONG bisize; |
|---|
| 413 | HANDLE hfile; |
|---|
| 414 | HBITMAP tmp; |
|---|
| 415 | HDC hdc; |
|---|
| 416 | DWORD ret; |
|---|
| 417 | #else |
|---|
| 418 | SDL_Surface *tmp, *surface; |
|---|
| 419 | #endif |
|---|
| 420 | FILE *f; |
|---|
| 421 | uint8_t *pixels; |
|---|
| 422 | int *src, *dst, *srcl, *dstl; |
|---|
| 423 | int stride, x, y, depth, c; |
|---|
| 424 | |
|---|
| 425 | if(argc < 2) |
|---|
| 426 | return 1; |
|---|
| 427 | |
|---|
| 428 | #if defined _WIN32 |
|---|
| 429 | tmp = (HBITMAP)LoadImage(NULL, argv[1], IMAGE_BITMAP, |
|---|
| 430 | 0, 0, LR_LOADFROMFILE); |
|---|
| 431 | #else |
|---|
| 432 | tmp = IMG_Load(argv[1]); |
|---|
| 433 | #endif |
|---|
| 434 | if(!tmp) |
|---|
| 435 | return 2; |
|---|
| 436 | |
|---|
| 437 | f = fopen(ORICFILE ".TAP", "w"); |
|---|
| 438 | if(!f) |
|---|
| 439 | return 3; |
|---|
| 440 | fwrite("\x16\x16\x16\x16\x24", 1, 5, f); |
|---|
| 441 | fwrite("\x00\xff\x80\x00\xbf\x3f\xa0\x00", 1, 8, f); |
|---|
| 442 | fwrite(ORICFILE, 1, strlen(ORICFILE), f); |
|---|
| 443 | fwrite("\x00", 1, 1, f); |
|---|
| 444 | |
|---|
| 445 | init_tables(); |
|---|
| 446 | |
|---|
| 447 | /* Load the image into a friendly array of fast integers. We create it |
|---|
| 448 | * slightly bigger than the image so that we don’t have to care about |
|---|
| 449 | * borders when propagating the error later */ |
|---|
| 450 | src = calloc((WIDTH + 1) * (HEIGHT + 1) * 3, sizeof(int)); |
|---|
| 451 | dst = calloc((WIDTH + 1) * (HEIGHT + 1) * 3, sizeof(int)); |
|---|
| 452 | stride = (WIDTH + 1) * 3; |
|---|
| 453 | |
|---|
| 454 | #if defined _WIN32 |
|---|
| 455 | hdc = CreateCompatibleDC(NULL); |
|---|
| 456 | SelectObject(hdc, tmp); |
|---|
| 457 | for(y = 0; y < HEIGHT; y++) |
|---|
| 458 | for(x = 0; x < WIDTH; x++) |
|---|
| 459 | { |
|---|
| 460 | COLORREF color = GetPixel(hdc, x, y); |
|---|
| 461 | src[y * stride + x * 3] = ctoi(GetRValue(color) * 0x101); |
|---|
| 462 | src[y * stride + x * 3 + 1] = ctoi(GetGValue(color) * 0x101); |
|---|
| 463 | src[y * stride + x * 3 + 2] = ctoi(GetBValue(color) * 0x101); |
|---|
| 464 | for(c = 0; c < 3; c++) |
|---|
| 465 | dst[y * stride + x * 3 + c] = 0; |
|---|
| 466 | } |
|---|
| 467 | #else |
|---|
| 468 | /* FIXME: endianness */ |
|---|
| 469 | surface = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT, 32, |
|---|
| 470 | 0xff0000, 0xff00, 0xff, 0x0); |
|---|
| 471 | SDL_BlitSurface(tmp, NULL, surface, NULL); |
|---|
| 472 | pixels = (uint8_t *)surface->pixels; |
|---|
| 473 | for(y = 0; y < HEIGHT; y++) |
|---|
| 474 | for(x = 0; x < WIDTH; x++) |
|---|
| 475 | for(c = 0; c < 3; c++) |
|---|
| 476 | { |
|---|
| 477 | src[y * stride + x * 3 + c] |
|---|
| 478 | = ctoi(pixels[y * surface->pitch + x * 4 + 2 - c] * 0x101); |
|---|
| 479 | dst[y * stride + x * 3 + c] = 0; |
|---|
| 480 | } |
|---|
| 481 | #endif |
|---|
| 482 | |
|---|
| 483 | /* Let the fun begin */ |
|---|
| 484 | for(y = 0; y < HEIGHT; y++) |
|---|
| 485 | { |
|---|
| 486 | uint8_t bg = 0, fg = 7; |
|---|
| 487 | |
|---|
| 488 | fprintf(stderr, "\rProcessing... %i%%", (y * 100 + 99) / HEIGHT); |
|---|
| 489 | |
|---|
| 490 | for(x = 0; x < WIDTH; x += 6) |
|---|
| 491 | { |
|---|
| 492 | int errvec[3] = { 0, 0, 0 }; |
|---|
| 493 | int dummy, i; |
|---|
| 494 | uint8_t command; |
|---|
| 495 | |
|---|
| 496 | depth = (x + DEPTH < WIDTH) ? DEPTH : (WIDTH - x) / 6 - 1; |
|---|
| 497 | srcl = src + y * stride + x * 3; |
|---|
| 498 | dstl = dst + y * stride + x * 3; |
|---|
| 499 | |
|---|
| 500 | /* Recursively compute and apply best command */ |
|---|
| 501 | command = bestmove(srcl, bg, fg, errvec, depth, 0x7fffff, |
|---|
| 502 | &dummy, dstl); |
|---|
| 503 | /* Propagate error */ |
|---|
| 504 | for(c = 0; c < 3; c++) |
|---|
| 505 | { |
|---|
| 506 | for(i = 0; i < 6; i++) |
|---|
| 507 | { |
|---|
| 508 | int error = srcl[i * 3 + c] - dstl[i * 3 + c]; |
|---|
| 509 | srcl[i * 3 + c + 3] = |
|---|
| 510 | clamp(srcl[i * 3 + c + 3] + error * FS0 / FSX); |
|---|
| 511 | srcl[i * 3 + c + stride - 3] += error * FS1 / FSX; |
|---|
| 512 | srcl[i * 3 + c + stride] += error * FS2 / FSX; |
|---|
| 513 | srcl[i * 3 + c + stride + 3] += error * FS3 / FSX; |
|---|
| 514 | } |
|---|
| 515 | |
|---|
| 516 | for(i = -1; i < 7; i++) |
|---|
| 517 | srcl[i * 3 + c + stride] = clamp(srcl[i * 3 + c + stride]); |
|---|
| 518 | } |
|---|
| 519 | /* Iterate */ |
|---|
| 520 | domove(command, &bg, &fg); |
|---|
| 521 | /* Write byte to file */ |
|---|
| 522 | fwrite(&command, 1, 1, f); |
|---|
| 523 | } |
|---|
| 524 | } |
|---|
| 525 | |
|---|
| 526 | fclose(f); |
|---|
| 527 | |
|---|
| 528 | fprintf(stderr, " done.\n"); |
|---|
| 529 | |
|---|
| 530 | /* Save everything */ |
|---|
| 531 | #if defined _WIN32 |
|---|
| 532 | for(y = 0; y < HEIGHT; y++) |
|---|
| 533 | for(x = 0; x < WIDTH; x++) |
|---|
| 534 | { |
|---|
| 535 | uint8_t r = dst[y * stride + x * 3] / 0x100; |
|---|
| 536 | uint8_t g = dst[y * stride + x * 3 + 1] / 0x100; |
|---|
| 537 | uint8_t b = dst[y * stride + x * 3 + 2] / 0x100; |
|---|
| 538 | SetPixel(hdc, x, y, RGB(r, g, b)); |
|---|
| 539 | } |
|---|
| 540 | |
|---|
| 541 | binfo.bmiHeader.biSize = sizeof(binfo.bmiHeader); |
|---|
| 542 | binfo.bmiHeader.biBitCount = 0; |
|---|
| 543 | GetDIBits(hdc, tmp, 0, 0, NULL, &binfo, DIB_RGB_COLORS); |
|---|
| 544 | |
|---|
| 545 | switch(binfo.bmiHeader.biBitCount) |
|---|
| 546 | { |
|---|
| 547 | case 24: |
|---|
| 548 | bisize = sizeof(BITMAPINFOHEADER); |
|---|
| 549 | break; |
|---|
| 550 | case 16: |
|---|
| 551 | case 32: |
|---|
| 552 | bisize = sizeof(BITMAPINFOHEADER) + sizeof(DWORD) * 3; |
|---|
| 553 | break; |
|---|
| 554 | default: |
|---|
| 555 | bisize = sizeof(BITMAPINFOHEADER) |
|---|
| 556 | + sizeof(RGBQUAD) * (1 << binfo.bmiHeader.biBitCount); |
|---|
| 557 | break; |
|---|
| 558 | } |
|---|
| 559 | |
|---|
| 560 | pbinfo = (PBITMAPINFO)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, bisize); |
|---|
| 561 | memcpy(pbinfo, &binfo, sizeof(BITMAPINFOHEADER)); |
|---|
| 562 | |
|---|
| 563 | bfheader.bfType = 0x4D42; /* "BM" */ |
|---|
| 564 | bfheader.bfSize = sizeof(BITMAPFILEHEADER) |
|---|
| 565 | + bisize + pbinfo->bmiHeader.biSizeImage; |
|---|
| 566 | bfheader.bfReserved1 = 0; |
|---|
| 567 | bfheader.bfReserved2 = 0; |
|---|
| 568 | bfheader.bfOffBits = sizeof(BITMAPFILEHEADER) + bisize; |
|---|
| 569 | |
|---|
| 570 | pixels = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, |
|---|
| 571 | binfo.bmiHeader.biSizeImage); |
|---|
| 572 | GetDIBits(hdc, tmp, 0, pbinfo->bmiHeader.biHeight, |
|---|
| 573 | pixels, pbinfo, DIB_RGB_COLORS); |
|---|
| 574 | |
|---|
| 575 | hfile = CreateFile(BMPFILE, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, |
|---|
| 576 | FILE_ATTRIBUTE_ARCHIVE, NULL); |
|---|
| 577 | WriteFile(hfile, &bfheader, sizeof(BITMAPFILEHEADER), &ret, NULL); |
|---|
| 578 | WriteFile(hfile, pbinfo, bisize, &ret, NULL); |
|---|
| 579 | WriteFile(hfile, pixels, pbinfo->bmiHeader.biSizeImage, &ret, NULL); |
|---|
| 580 | CloseHandle(hfile); |
|---|
| 581 | |
|---|
| 582 | GlobalFree(pbinfo); |
|---|
| 583 | GlobalFree(pixels); |
|---|
| 584 | #else |
|---|
| 585 | for(y = 0; y < HEIGHT; y++) |
|---|
| 586 | for(x = 0; x < WIDTH; x++) |
|---|
| 587 | for(c = 0; c < 3; c++) |
|---|
| 588 | pixels[y * surface->pitch + x * 4 + 2 - c] |
|---|
| 589 | = itoc(dst[y * stride + x * 3 + c]) / 0x100; |
|---|
| 590 | SDL_SaveBMP(surface, BMPFILE); |
|---|
| 591 | #endif |
|---|
| 592 | |
|---|
| 593 | return 0; |
|---|
| 594 | } |
|---|
| 595 | |
|---|