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

Last change on this file since 4696 was 4696, checked in by Sam Hocevar, 8 years ago

Implement bicubic resampling. Lacks some blurring in the pre-pass, maybe.

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