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

Last change on this file since 2942 was 2942, checked in by Sam Hocevar, 12 years ago

libpipi: get rid of all remaining large stack allocations.

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