source: libpipi/trunk/pipi/codec/oric.c @ 2902

Last change on this file since 2902 was 2902, checked in by Sam Hocevar, 11 years ago

Support C99 types on Win32 through the same hacks as in libcaca.

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