Changeset 2845 for libpipi/trunk


Ignore:
Timestamp:
Sep 28, 2008, 5:55:04 PM (12 years ago)
Author:
Sam Hocevar
Message:

Wrote an Oric hires file writer, based on img2oric.

Location:
libpipi/trunk/pipi
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • libpipi/trunk/pipi/codec.c

    r2844 r2845  
    7878    int ret = -1;
    7979
     80    if(ret < 0)
     81        ret = pipi_save_oric(img, name);
     82
    8083#if USE_IMLIB2
    8184    if(ret < 0)
     
    9598#endif
    9699
    97     if(!ret)
    98         ret = pipi_save_oric(img, name);
    99 
    100100    return ret;
    101101}
  • libpipi/trunk/pipi/codec/oric.c

    r2844 r2845  
    2323#include <stdlib.h>
    2424#include <string.h>
     25#include <ctype.h>
    2526
    2627#include "pipi.h"
    2728#include "pipi_internals.h"
    2829
    29 static int load_data(const char *name, uint8_t *screen);
    30 
    31 pipi_image_t *pipi_load_oric(const char *name)
     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)
    3241{
    3342    static uint8_t const pal[32] =
     
    4352    };
    4453
    45     uint8_t screen[8000];
     54    uint8_t screen[WIDTH * HEIGHT / 6];
    4655    pipi_image_t *img;
    4756    pipi_pixels_t *p;
     
    4958    int x, y, i;
    5059
    51     if(load_data(name, screen) < 0)
     60    if(read_screen(name, screen) < 0)
    5261        return NULL;
    5362
    54     img = pipi_new(240, 200);
     63    img = pipi_new(WIDTH, HEIGHT);
    5564    p = pipi_getpixels(img, PIPI_PIXELS_RGBA_C);
    5665    data = p->pixels;
    5766
    58     for(y = 0; y < 200; y++)
     67    for(y = 0; y < HEIGHT; y++)
    5968    {
    6069        int bg = 0, fg = 7;
     
    7180                    col = (c & (1 << (5 - i))) ? (c & 0x80) ? 7 - fg : fg
    7281                                               : (c & 0x80) ? 7 - bg : bg;
    73                     memcpy(data + (y * 240 + x * 6 + i) * 4, pal + 4 * col, 4);
     82                    memcpy(data + (y * WIDTH + x * 6 + i) * 4,
     83                           pal + 4 * col, 4);
    7484                }
    7585            }
     
    8494
    8595                for(i = 0; i < 6; i++)
    86                     memcpy(data + (y * 240 + x * 6 + i) * 4, pal + 4 * col, 4);
     96                    memcpy(data + (y * WIDTH + x * 6 + i) * 4,
     97                           pal + 4 * col, 4);
    8798            }
    8899            /* else: invalid sequence */
     
    98109}
    99110
    100 int pipi_save_oric(pipi_image_t *img, const char *name)
    101 {
    102     return -1;
     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;
    103152}
    104153
     
    107156 */
    108157
    109 static int load_data(const char *name, uint8_t *screen)
     158static int read_screen(char const *name, uint8_t *screen)
    110159{
    111160    FILE *fp;
     
    131180        goto syntax_error;
    132181
    133     /* Skip the file name */
     182    /* Skip the file name, including trailing nul char */
    134183    for(;;)
    135184    {
     
    142191
    143192    /* Read screen data */
    144     if(fread(screen, 1, 8000, fp) != 8000)
     193    if(fread(screen, 1, WIDTH * HEIGHT / 6, fp) != WIDTH * HEIGHT / 6)
    145194        goto syntax_error;
    146195
     
    153202}
    154203
     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 TracChangeset for help on using the changeset viewer.