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

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

oric.c: allow to load invalid files that img2oric used to generate.

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#include "common.h"
21
22#include <stdio.h>
23#include <stdlib.h>
24#include <string.h>
25#include <ctype.h>
26
27#include "pipi.h"
28#include "pipi_internals.h"
29
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
37static int read_screen(char const *name, uint8_t *screen);
38static void write_screen(float const *data, uint8_t *screen);
39
40pipi_image_t *pipi_load_oric(char const *name)
41{
42    static uint8_t const pal[32] =
43    {
44        0x00, 0x00, 0x00, 0xff,
45        0x00, 0x00, 0xff, 0xff,
46        0x00, 0xff, 0x00, 0xff,
47        0x00, 0xff, 0xff, 0xff,
48        0xff, 0x00, 0x00, 0xff,
49        0xff, 0x00, 0xff, 0xff,
50        0xff, 0xff, 0x00, 0xff,
51        0xff, 0xff, 0xff, 0xff,
52    };
53
54    uint8_t screen[WIDTH * HEIGHT / 6];
55    pipi_image_t *img;
56    pipi_pixels_t *p;
57    uint8_t *data;
58    int x, y, i;
59
60    if(read_screen(name, screen) < 0)
61        return NULL;
62
63    img = pipi_new(WIDTH, HEIGHT);
64    p = pipi_getpixels(img, PIPI_PIXELS_RGBA_C);
65    data = p->pixels;
66
67    for(y = 0; y < HEIGHT; y++)
68    {
69        int bg = 0, fg = 7;
70
71        for(x = 0; x < 40; x++)
72        {
73            int col;
74            uint8_t c = screen[y * 40 + x];
75
76            if(c & 0x40)
77            {
78                for(i = 0; i < 6; i++)
79                {
80                    col = (c & (1 << (5 - i))) ? (c & 0x80) ? 7 - fg : fg
81                                               : (c & 0x80) ? 7 - bg : bg;
82                    memcpy(data + (y * WIDTH + x * 6 + i) * 4,
83                           pal + 4 * col, 4);
84                }
85            }
86            else if((c & 0x60) == 0x00)
87            {
88                if(c & 0x10)
89                    bg = c & 0x7;
90                else
91                    fg = c & 0x7;
92
93                col = (c & 0x80) ? 7 - bg : bg;
94
95                for(i = 0; i < 6; i++)
96                    memcpy(data + (y * WIDTH + x * 6 + i) * 4,
97                           pal + 4 * col, 4);
98            }
99            /* else: invalid sequence */
100        }
101    }
102
103    img->codec_priv = NULL;
104
105    img->wrap = 0;
106    img->u8 = 1;
107
108    return img;
109}
110
111int 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;
152}
153
154/*
155 * XXX: The following functions are local.
156 */
157
158static int read_screen(char const *name, uint8_t *screen)
159{
160    FILE *fp;
161    int ch;
162
163    fp = fopen(name, "r");
164    if(!fp)
165        return -1;
166
167    /* Skip the sync bytes */
168    ch = fgetc(fp);
169    if(ch != 0x16)
170        goto syntax_error;
171    while((ch = fgetc(fp)) == 0x16)
172        ;
173    if(ch != 0x24)
174        goto syntax_error;
175
176    /* Skip the header, ignoring the last byte’s value */
177    if(fgetc(fp) != 0x00 || fgetc(fp) != 0xff || fgetc(fp) != 0x80
178        || fgetc(fp) != 0x00 || fgetc(fp) != 0xbf || fgetc(fp) != 0x3f
179        || fgetc(fp) != 0xa0 || fgetc(fp) != 0x00 || fgetc(fp) == EOF)
180        goto syntax_error;
181
182    /* Skip the file name, including trailing nul char */
183    for(;;)
184    {
185        ch = fgetc(fp);
186        if(ch == EOF)
187            goto syntax_error;
188        if(ch == 0x00)
189            break;
190    }
191
192    /* Read screen data */
193    if(fread(screen, 1, WIDTH * HEIGHT / 6, fp) != WIDTH * HEIGHT / 6)
194        goto syntax_error;
195
196    fclose(fp);
197    return 0;
198
199syntax_error:
200    fclose(fp);
201    return -1;
202}
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
232static 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. */
245static 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
256static 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. */
273static 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
322static 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
514static 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 TracBrowser for help on using the repository browser.