- Timestamp:
- Sep 28, 2008, 5:55:04 PM (12 years ago)
- Location:
- libpipi/trunk/pipi
- Files:
-
- 2 edited
Legend:
- Unmodified
- Added
- Removed
-
libpipi/trunk/pipi/codec.c
r2844 r2845 78 78 int ret = -1; 79 79 80 if(ret < 0) 81 ret = pipi_save_oric(img, name); 82 80 83 #if USE_IMLIB2 81 84 if(ret < 0) … … 95 98 #endif 96 99 97 if(!ret)98 ret = pipi_save_oric(img, name);99 100 100 return ret; 101 101 } -
libpipi/trunk/pipi/codec/oric.c
r2844 r2845 23 23 #include <stdlib.h> 24 24 #include <string.h> 25 #include <ctype.h> 25 26 26 27 #include "pipi.h" 27 28 #include "pipi_internals.h" 28 29 29 static int load_data(const char *name, uint8_t *screen); 30 31 pipi_image_t *pipi_load_oric(const char *name) 30 /* Image dimensions and recursion depth. DEPTH = 2 is a reasonable value, 31 * DEPTH = 3 gives good quality, and higher values may improve the results 32 * even more but at the cost of significantly longer computation times. */ 33 #define WIDTH 240 34 #define HEIGHT 200 35 #define DEPTH 2 36 37 static int read_screen(char const *name, uint8_t *screen); 38 static void write_screen(float const *data, uint8_t *screen); 39 40 pipi_image_t *pipi_load_oric(char const *name) 32 41 { 33 42 static uint8_t const pal[32] = … … 43 52 }; 44 53 45 uint8_t screen[ 8000];54 uint8_t screen[WIDTH * HEIGHT / 6]; 46 55 pipi_image_t *img; 47 56 pipi_pixels_t *p; … … 49 58 int x, y, i; 50 59 51 if( load_data(name, screen) < 0)60 if(read_screen(name, screen) < 0) 52 61 return NULL; 53 62 54 img = pipi_new( 240, 200);63 img = pipi_new(WIDTH, HEIGHT); 55 64 p = pipi_getpixels(img, PIPI_PIXELS_RGBA_C); 56 65 data = p->pixels; 57 66 58 for(y = 0; y < 200; y++)67 for(y = 0; y < HEIGHT; y++) 59 68 { 60 69 int bg = 0, fg = 7; … … 71 80 col = (c & (1 << (5 - i))) ? (c & 0x80) ? 7 - fg : fg 72 81 : (c & 0x80) ? 7 - bg : bg; 73 memcpy(data + (y * 240 + x * 6 + i) * 4, pal + 4 * col, 4); 82 memcpy(data + (y * WIDTH + x * 6 + i) * 4, 83 pal + 4 * col, 4); 74 84 } 75 85 } … … 84 94 85 95 for(i = 0; i < 6; i++) 86 memcpy(data + (y * 240 + x * 6 + i) * 4, pal + 4 * col, 4); 96 memcpy(data + (y * WIDTH + x * 6 + i) * 4, 97 pal + 4 * col, 4); 87 98 } 88 99 /* else: invalid sequence */ … … 98 109 } 99 110 100 int pipi_save_oric(pipi_image_t *img, const char *name) 101 { 102 return -1; 111 int pipi_save_oric(pipi_image_t *img, char const *name) 112 { 113 uint8_t screen[WIDTH * HEIGHT / 6]; 114 pipi_image_t *tmp = NULL; 115 pipi_pixels_t *p; 116 float *data; 117 FILE *fp; 118 size_t len; 119 120 len = strlen(name); 121 if(len < 4 || name[len - 4] != '.' 122 || toupper(name[len - 3]) != 'T' 123 || toupper(name[len - 2]) != 'A' 124 || toupper(name[len - 1]) != 'P') 125 return -1; 126 127 fp = fopen(name, "w"); 128 if(!fp) 129 return -1; 130 131 fwrite("\x16\x16\x16\x16\x24", 1, 5, fp); 132 fwrite("\x00\xff\x80\x00\xbf\x3f\xa0\x00\x00", 1, 9, fp); 133 fwrite(name, 1, len - 4, fp); 134 fwrite("\x00", 1, 1, fp); 135 136 if(img->w != WIDTH || img->h != HEIGHT) 137 { 138 tmp = pipi_resize(img, WIDTH, HEIGHT); 139 p = pipi_getpixels(tmp, PIPI_PIXELS_RGBA_F); 140 } 141 else 142 p = pipi_getpixels(img, PIPI_PIXELS_RGBA_F); 143 data = p->pixels; 144 write_screen(data, screen); 145 if(tmp) 146 pipi_free(tmp); 147 148 fwrite(screen, 1, WIDTH * HEIGHT / 6, fp); 149 fclose(fp); 150 151 return 0; 103 152 } 104 153 … … 107 156 */ 108 157 109 static int load_data(const char*name, uint8_t *screen)158 static int read_screen(char const *name, uint8_t *screen) 110 159 { 111 160 FILE *fp; … … 131 180 goto syntax_error; 132 181 133 /* Skip the file name */182 /* Skip the file name, including trailing nul char */ 134 183 for(;;) 135 184 { … … 142 191 143 192 /* Read screen data */ 144 if(fread(screen, 1, 8000, fp) != 8000)193 if(fread(screen, 1, WIDTH * HEIGHT / 6, fp) != WIDTH * HEIGHT / 6) 145 194 goto syntax_error; 146 195 … … 153 202 } 154 203 204 /* Error diffusion table, similar to Floyd-Steinberg. I choose not to 205 * propagate 100% of the error, because doing so creates awful artifacts 206 * (full lines of the same colour, massive colour bleeding) for unclear 207 * reasons. Atkinson dithering propagates 3/4 of the error, which is even 208 * less than our 31/32. I also choose to propagate slightly more in the 209 * X direction to avoid banding effects due to rounding errors. 210 * It would be interesting, for future versions of this software, to 211 * propagate the error to the second line, too. But right now I find it far 212 * too complex to do. 213 * 214 * +-------+-------+ 215 * | error |FS0/FSX| 216 * +-------+-------+-------+ 217 * |FS1/FSX|FS2/FSX|FS3/FSX| 218 * +-------+-------+-------+ 219 */ 220 #define FS0 15 221 #define FS1 6 222 #define FS2 9 223 #define FS3 1 224 #define FSX 32 225 226 /* The simple Oric RGB palette, made of the 8 Neugebauer primary colours. Each 227 * colour is repeated 6 times so that we can point to the palette to paste 228 * whole blocks of 6 pixels. It’s also organised so that palette[7-x] is the 229 * RGB negative of palette[x], and screen command X uses palette[X & 7]. */ 230 #define o 0x0000 231 #define X 0xffff 232 static const int palette[8][6 * 3] = 233 { 234 { o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o, o }, 235 { X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o }, 236 { o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o }, 237 { X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o }, 238 { o, o, X, o, o, X, o, o, X, o, o, X, o, o, X, o, o, X }, 239 { X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X }, 240 { o, X, X, o, X, X, o, X, X, o, X, X, o, X, X, o, X, X }, 241 { X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X }, 242 }; 243 244 /* Set new background and foreground colours according to the given command. */ 245 static inline void domove(uint8_t command, uint8_t *bg, uint8_t *fg) 246 { 247 if((command & 0x78) == 0x00) 248 *fg = command & 0x7; 249 else if((command & 0x78) == 0x10) 250 *bg = command & 0x7; 251 } 252 253 /* Clamp pixel value to avoid colour bleeding. Deactivated because it 254 * does not give satisfactory results. */ 255 #define CLAMP 0x1000 256 static inline int clamp(int p) 257 { 258 #if 0 259 /* FIXME: doesn’t give terribly good results on eg. eatme.png */ 260 if(p < - CLAMP) return - CLAMP; 261 if(p > 0xffff + CLAMP) return 0xffff + CLAMP; 262 #endif 263 return p; 264 } 265 266 /* Compute the perceptual error caused by replacing the input pixels "in" 267 * with the output pixels "out". "inerr" is the diffused error that should 268 * be applied to "in"’s first pixel. "outerr" will hold the diffused error 269 * to apply after "in"’s last pixel upon next call. The return value does 270 * not mean much physically; it is one part of the algorithm where you need 271 * to play a bit in order to get appealing results. That’s how image 272 * processing works, dude. */ 273 static inline int geterror(int const *in, int const *inerr, 274 int const *out, int *outerr) 275 { 276 int tmperr[9 * 3]; 277 int i, c, ret = 0; 278 279 /* 9 cells: 1 for the end of line, 8 for the errors below */ 280 memcpy(tmperr, inerr, 3 * sizeof(int)); 281 memset(tmperr + 3, 0, 8 * 3 * sizeof(int)); 282 283 for(i = 0; i < 6; i++) 284 { 285 for(c = 0; c < 3; c++) 286 { 287 /* Experiment shows that this is important at small depths */ 288 int a = clamp(in[i * 3 + c] + tmperr[c]); 289 int b = out[i * 3 + c]; 290 tmperr[c] = (a - b) * FS0 / FSX; 291 tmperr[c + (i * 3 + 3)] += (a - b) * FS1 / FSX; 292 tmperr[c + (i * 3 + 6)] += (a - b) * FS2 / FSX; 293 tmperr[c + (i * 3 + 9)] += (a - b) * FS3 / FSX; 294 ret += (a - b) / 256 * (a - b) / 256; 295 } 296 } 297 298 for(i = 0; i < 4; i++) 299 { 300 for(c = 0; c < 3; c++) 301 { 302 /* Experiment shows that this is important at large depths */ 303 int a = ((in[i * 3 + c] + in[i * 3 + 3 + c] 304 + in[i * 3 + 6 + c]) / 3); 305 int b = ((out[i * 3 + c] + out[i * 3 + 3 + c] 306 + out[i * 3 + 6 + c]) / 3); 307 ret += (a - b) / 256 * (a - b) / 256; 308 } 309 } 310 311 /* Using the diffused error as a perceptual error component is stupid, 312 * because that’s not what it is at all, but I found that it helped a 313 * bit in some cases. */ 314 for(i = 0; i < 3; i++) 315 ret += tmperr[i] / 256 * tmperr[i] / 256; 316 317 memcpy(outerr, tmperr, 3 * sizeof(int)); 318 319 return ret; 320 } 321 322 static uint8_t bestmove(int const *in, uint8_t bg, uint8_t fg, 323 int const *errvec, int depth, int maxerror, 324 int *error, int *out) 325 { 326 int voidvec[3], nvoidvec[3], bestrgb[6 * 3], tmprgb[6 * 3], tmpvec[3]; 327 int const *voidrgb, *nvoidrgb, *vec, *rgb; 328 int besterror, curerror, suberror, statice, voide, nvoide; 329 int i, j, c; 330 uint8_t command, bestcommand; 331 332 /* Precompute error for the case where we change the foreground colour 333 * and hence only print the background colour or its negative */ 334 voidrgb = palette[bg]; 335 voide = geterror(in, errvec, voidrgb, voidvec); 336 nvoidrgb = palette[7 - bg]; 337 nvoide = geterror(in, errvec, nvoidrgb, nvoidvec); 338 339 /* Precompute sub-error for the case where we print pixels (and hence 340 * don’t change the palette). It’s not the exact error because we should 341 * be propagating the error to the first pixel here. */ 342 if(depth > 0) 343 { 344 int tmp[3] = { 0, 0, 0 }; 345 bestmove(in + 6 * 3, bg, fg, tmp, depth - 1, maxerror, &statice, NULL); 346 } 347 348 /* Check every likely command: 349 * 0-7: change foreground to 0-7 350 * 8-15: change foreground to 0-7, print negative background 351 * 16-23: change background to 0-7 352 * 24-31: change background to 0-7, print negative background 353 * 32: normal stuff 354 * 33: inverse video stuff */ 355 besterror = 0x7ffffff; 356 bestcommand = 0x10; 357 memcpy(bestrgb, voidrgb, 6 * 3 * sizeof(int)); 358 for(j = 0; j < 34; j++) 359 { 360 static uint8_t const lookup[] = 361 { 362 0x00, 0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07, 363 0x80, 0x84, 0x81, 0x85, 0x82, 0x86, 0x83, 0x87, 364 0x10, 0x14, 0x11, 0x15, 0x12, 0x16, 0x13, 0x17, 365 0x90, 0x94, 0x91, 0x95, 0x92, 0x96, 0x93, 0x97, 366 0x40, 0xc0 367 }; 368 369 uint8_t newbg = bg, newfg = fg; 370 371 command = lookup[j]; 372 domove(command, &newbg, &newfg); 373 374 /* Keeping bg and fg is useless, because we could use standard 375 * pixel printing instead */ 376 if((command & 0x40) == 0x00 && newbg == bg && newfg == fg) 377 continue; 378 379 /* I *think* having newfg == newbg is useless, too, but I don’t 380 * want to miss some corner case where swapping bg and fg may be 381 * interesting, so we continue anyway. */ 382 383 #if 0 384 /* Bit 6 off and bit 5 on seems illegal */ 385 if((command & 0x60) == 0x20) 386 continue; 387 388 /* Bits 6 and 5 off and bit 3 on seems illegal */ 389 if((command & 0x68) == 0x08) 390 continue; 391 #endif 392 393 if((command & 0xf8) == 0x00) 394 { 395 curerror = voide; 396 rgb = voidrgb; 397 vec = voidvec; 398 } 399 else if((command & 0xf8) == 0x80) 400 { 401 curerror = nvoide; 402 rgb = nvoidrgb; 403 vec = nvoidvec; 404 } 405 else if((command & 0xf8) == 0x10) 406 { 407 rgb = palette[newbg]; 408 curerror = geterror(in, errvec, rgb, tmpvec); 409 vec = tmpvec; 410 } 411 else if((command & 0xf8) == 0x90) 412 { 413 rgb = palette[7 - newbg]; 414 curerror = geterror(in, errvec, rgb, tmpvec); 415 vec = tmpvec; 416 } 417 else 418 { 419 int const *bgcolor, *fgcolor; 420 421 if((command & 0x80) == 0x00) 422 { 423 bgcolor = palette[bg]; fgcolor = palette[fg]; 424 } 425 else 426 { 427 bgcolor = palette[7 - bg]; fgcolor = palette[7 - fg]; 428 } 429 430 memcpy(tmpvec, errvec, 3 * sizeof(int)); 431 curerror = 0; 432 433 for(i = 0; i < 6; i++) 434 { 435 int vec1[3], vec2[3]; 436 int smalle1 = 0, smalle2 = 0; 437 438 memcpy(vec1, tmpvec, 3 * sizeof(int)); 439 memcpy(vec2, tmpvec, 3 * sizeof(int)); 440 for(c = 0; c < 3; c++) 441 { 442 int delta1, delta2; 443 delta1 = clamp(in[i * 3 + c] + tmpvec[c]) - bgcolor[c]; 444 vec1[c] = delta1 * FS0 / FSX; 445 smalle1 += delta1 / 256 * delta1; 446 delta2 = clamp(in[i * 3 + c] + tmpvec[c]) - fgcolor[c]; 447 vec2[c] = delta2 * FS0 / FSX; 448 smalle2 += delta2 / 256 * delta2; 449 } 450 451 if(smalle1 < smalle2) 452 { 453 memcpy(tmpvec, vec1, 3 * sizeof(int)); 454 memcpy(tmprgb + i * 3, bgcolor, 3 * sizeof(int)); 455 } 456 else 457 { 458 memcpy(tmpvec, vec2, 3 * sizeof(int)); 459 memcpy(tmprgb + i * 3, fgcolor, 3 * sizeof(int)); 460 command |= (1 << (5 - i)); 461 } 462 } 463 464 /* Recompute full error */ 465 curerror += geterror(in, errvec, tmprgb, tmpvec); 466 467 rgb = tmprgb; 468 vec = tmpvec; 469 } 470 471 if(curerror > besterror) 472 continue; 473 474 /* Try to avoid bad decisions now that will have a high cost 475 * later in the line by making the next error more important than 476 * the current error. */ 477 curerror = curerror * 3 / 4; 478 479 if(depth == 0) 480 suberror = 0; /* It’s the end of the tree */ 481 else if((command & 0x68) == 0x00) 482 { 483 bestmove(in + 6 * 3, newbg, newfg, vec, depth - 1, 484 besterror - curerror, &suberror, NULL); 485 486 #if 0 487 /* Slight penalty for colour changes; they're hard to revert. The 488 * value of 2 was determined empirically. 1.5 is not enough and 489 * 3 is too much. */ 490 if(newbg != bg) 491 suberror = suberror * 10 / 8; 492 else if(newfg != fg) 493 suberror = suberror * 9 / 8; 494 #endif 495 } 496 else 497 suberror = statice; 498 499 if(curerror + suberror < besterror) 500 { 501 besterror = curerror + suberror; 502 bestcommand = command; 503 memcpy(bestrgb, rgb, 6 * 3 * sizeof(int)); 504 } 505 } 506 507 *error = besterror; 508 if(out) 509 memcpy(out, bestrgb, 6 * 3 * sizeof(int)); 510 511 return bestcommand; 512 } 513 514 static void write_screen(float const *data, uint8_t *screen) 515 { 516 int src[(WIDTH + 1) * (HEIGHT + 1) * 3]; 517 int dst[(WIDTH + 1) * (HEIGHT + 1) * 3]; 518 int *srcl, *dstl; 519 int stride, x, y, depth, c; 520 521 stride = (WIDTH + 1) * 3; 522 523 memset(src, 0, sizeof(src)); 524 memset(dst, 0, sizeof(dst)); 525 526 /* Import pixels into our custom format */ 527 for(y = 0; y < HEIGHT; y++) 528 for(x = 0; x < WIDTH; x++) 529 for(c = 0; c < 3; c++) 530 src[y * stride + x * 3 + c] = 531 0xffff * data[(y * WIDTH + x) * 4 + (2 - c)]; 532 533 /* Let the fun begin */ 534 for(y = 0; y < HEIGHT; y++) 535 { 536 uint8_t bg = 0, fg = 7; 537 538 //fprintf(stderr, "\rProcessing... %i%%", (y * 100 + 99) / HEIGHT); 539 540 for(x = 0; x < WIDTH; x += 6) 541 { 542 int errvec[3] = { 0, 0, 0 }; 543 int dummy, i; 544 uint8_t command; 545 546 depth = (x + DEPTH < WIDTH) ? DEPTH : (WIDTH - x) / 6 - 1; 547 srcl = src + y * stride + x * 3; 548 dstl = dst + y * stride + x * 3; 549 550 /* Recursively compute and apply best command */ 551 command = bestmove(srcl, bg, fg, errvec, depth, 0x7fffff, 552 &dummy, dstl); 553 /* Propagate error */ 554 for(c = 0; c < 3; c++) 555 { 556 for(i = 0; i < 6; i++) 557 { 558 int error = srcl[i * 3 + c] - dstl[i * 3 + c]; 559 srcl[i * 3 + c + 3] = 560 clamp(srcl[i * 3 + c + 3] + error * FS0 / FSX); 561 srcl[i * 3 + c + stride - 3] += error * FS1 / FSX; 562 srcl[i * 3 + c + stride] += error * FS2 / FSX; 563 srcl[i * 3 + c + stride + 3] += error * FS3 / FSX; 564 } 565 566 for(i = -1; i < 7; i++) 567 srcl[i * 3 + c + stride] = clamp(srcl[i * 3 + c + stride]); 568 } 569 /* Iterate */ 570 domove(command, &bg, &fg); 571 /* Write byte to file */ 572 screen[y * (WIDTH / 6) + (x / 6)] = command; 573 } 574 } 575 576 //fprintf(stderr, " done.\n"); 577 } 578
Note: See TracChangeset
for help on using the changeset viewer.