You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

oric.c 18 KiB

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