/[projet1]/public/pc/tools/osdk/main/pictconv/sources/oric_converter_samhocevar.cpp
Defence Force logotype

Contents of /public/pc/tools/osdk/main/pictconv/sources/oric_converter_samhocevar.cpp

Parent Directory Parent Directory | Revision Log Revision Log


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

  ViewVC Help
Powered by ViewVC 1.1.26