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

Last change on this file since 3340 was 3340, checked in by Sam Hocevar, 14 years ago

Rename pipi_getpixel into pipi_get_pixels.

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