source: www/img2oric/img2oric.c @ 2449

Last change on this file since 2449 was 2449, checked in by sam, 6 years ago
  • Add meaningful error messages to img2oric.
File size: 19.5 KB
Line 
1/*
2 *  img2oric      Convert an image to Oric Atmos colours
3 *  Copyright (c) 2008 Sam Hocevar <sam@zoy.org>
4 *                All Rights Reserved
5 *
6 *  Changes:
7 *   Jan 18, 2008: initial release
8 *   Jan 23, 2008: add support for inverse video on attribute change
9 *                 improve Floyd-Steinberg coefficient values
10 *   Jun 14, 2008: Win32 version
11 *   Jun 18, 2008: add meaningful error messages
12 *
13 *  This program is free software. It comes without any warranty, to
14 *  the extent permitted by applicable law. You can redistribute it
15 *  and/or modify it under the terms of the Do What The Fuck You Want
16 *  To Public License, Version 2, as published by Sam Hocevar. See
17 *  http://sam.zoy.org/wtfpl/COPYING for more details.
18 *
19 *  To build this program on Linux:
20 *   cc -O3 -funroll-loops -W -Wall img2oric.c -o img2oric \
21 *               $(pkg-config --cflags --libs sdl) -lSDL_image -lm
22 *  To build a Windows executable:
23 *   i586-mingw32msvc-cc -O3 -funroll-loops -W -Wall \
24 *               img2oric.c -o img2oric.exe -lgdi32
25 */
26
27#include <stdio.h>
28#include <stdlib.h>
29#include <string.h>
30
31#include <math.h>
32
33#if defined _WIN32
34#   include <windows.h>
35#   define uint8_t unsigned char
36#else
37#   include <SDL_image.h>
38#endif
39
40/*
41 * BMP output name and Oric output name
42 */
43#define BMPFILE "output.bmp"
44#define ORICFILE "OUTPUT" /* ".TAP" will be appended */
45
46/*
47 * Image dimensions and recursion depth. DEPTH = 2 is a reasonable value,
48 * DEPTH = 3 gives good quality, and higher values may improve the results
49 * even more but at the cost of significantly longer computation times.
50 */
51#define WIDTH 240
52#define HEIGHT 200
53#define DEPTH 3
54
55/*
56 * Error diffusion table, similar to Floyd-Steinberg. I choose not to
57 * propagate 100% of the error, because doing so creates awful artifacts
58 * (full lines of the same colour, massive colour bleeding) for unclear
59 * reasons. Atkinson dithering propagates 3/4 of the error, which is even
60 * less than our 31/32. I also choose to propagate slightly more in the
61 * X direction to avoid banding effects due to rounding errors.
62 * It would be interesting, for future versions of this software, to
63 * propagate the error to the second line, too. But right now I find it far
64 * too complex to do.
65 *
66 *             +-------+-------+
67 *             | error |FS0/FSX|
68 *     +-------+-------+-------+
69 *     |FS1/FSX|FS2/FSX|FS3/FSX|
70 *     +-------+-------+-------+
71 */
72#define FS0 15
73#define FS1 6
74#define FS2 9
75#define FS3 1
76#define FSX 32
77
78/*
79 * The simple Oric RGB palette, made of the 8 Neugebauer primary colours. Each
80 * colour is repeated 6 times so that we can point to the palette to paste
81 * whole blocks of 6 pixels. It’s also organised so that palette[7-x] is the
82 * RGB negative of palette[x], and screen command X uses palette[X & 7].
83 */
84#define o 0x0000
85#define X 0xffff
86static const int palette[8][6 * 3] =
87{
88    { o, o, o,   o, o, o,   o, o, o,   o, o, o,   o, o, o,   o, o, o },
89    { X, o, o,   X, o, o,   X, o, o,   X, o, o,   X, o, o,   X, o, o },
90    { o, X, o,   o, X, o,   o, X, o,   o, X, o,   o, X, o,   o, X, o },
91    { X, X, o,   X, X, o,   X, X, o,   X, X, o,   X, X, o,   X, X, o },
92    { o, o, X,   o, o, X,   o, o, X,   o, o, X,   o, o, X,   o, o, X },
93    { X, o, X,   X, o, X,   X, o, X,   X, o, X,   X, o, X,   X, o, X },
94    { o, X, X,   o, X, X,   o, X, X,   o, X, X,   o, X, X,   o, X, X },
95    { X, X, X,   X, X, X,   X, X, X,   X, X, X,   X, X, X,   X, X, X },
96};
97
98/*
99 * Gamma correction tables. itoc_table and ctoi_table accept overflow and
100 * underflow values to a reasonable extent, so that we don’t have to check
101 * for these cases later in the code. Tests kill performance.
102 */
103#define PAD 2048
104static int itoc_table_clip[PAD + 256 + PAD], ctoi_table_clip[PAD + 256 + PAD];
105static int *itoc_table = itoc_table_clip + PAD;
106static int *ctoi_table = ctoi_table_clip + PAD;
107
108static void init_tables(void)
109{
110    int i;
111
112    for(i = 0; i < PAD + 256 + PAD; i++)
113    {
114        double f = 1.0 * (i - PAD) / 255.999;
115        if(f >= 0.)
116        {
117            itoc_table_clip[i] = (int)(65535.999 * pow(f, 1./2.2));
118            ctoi_table_clip[i] = (int)(65535.999 * pow(f, 2.2));
119        }
120        else
121        {
122            itoc_table_clip[i] = - (int)(65535.999 * pow(-f, 1./2.2));
123            ctoi_table_clip[i] = - (int)(65535.999 * pow(-f, 2.2));
124        }
125    }
126}
127
128static inline int itoc(int p) { return itoc_table[p / 0x100]; }
129static inline int ctoi(int p) { return ctoi_table[p / 0x100]; }
130
131/*
132 * Set new background and foreground colours according to the given command.
133 */
134static inline void domove(uint8_t command, uint8_t *bg, uint8_t *fg)
135{
136    if((command & 0x78) == 0x00)
137        *fg = command & 0x7;
138    else if((command & 0x78) == 0x10)
139        *bg = command & 0x7;
140}
141
142/*
143 * Clamp pixel value to avoid colour bleeding. Deactivated because it
144 * does not give satisfactory results.
145 */
146#define CLAMP 0x1000
147static inline int clamp(int p)
148{
149#if 0
150    /* FIXME: doesn’t give terribly good results on eg. eatme.png */
151    if(p < - CLAMP) return - CLAMP;
152    if(p > 0xffff + CLAMP) return 0xffff + CLAMP;
153#endif
154    return p;
155}
156
157/*
158 * Compute the perceptual error caused by replacing the input pixels "in"
159 * with the output pixels "out". "inerr" is the diffused error that should
160 * be applied to "in"’s first pixel. "outerr" will hold the diffused error
161 * to apply after "in"’s last pixel upon next call. The return value does
162 * not mean much physically; it is one part of the algorithm where you need
163 * to play a bit in order to get appealing results. That’s how image
164 * processing works, dude.
165 */
166static inline int geterror(int const *in, int const *inerr,
167                           int const *out, int *outerr)
168{
169    int tmperr[9 * 3];
170    int i, c, ret = 0;
171
172    /* 9 cells: 1 for the end of line, 8 for the errors below */
173    memcpy(tmperr, inerr, 3 * sizeof(int));
174    memset(tmperr + 3, 0, 8 * 3 * sizeof(int));
175
176    for(i = 0; i < 6; i++)
177    {
178        for(c = 0; c < 3; c++)
179        {
180            /* Experiment shows that this is important at small depths */
181            int a = clamp(in[i * 3 + c] + tmperr[c]);
182            int b = out[i * 3 + c];
183            tmperr[c] = (a - b) * FS0 / FSX;
184            tmperr[c + (i * 3 + 3)] += (a - b) * FS1 / FSX;
185            tmperr[c + (i * 3 + 6)] += (a - b) * FS2 / FSX;
186            tmperr[c + (i * 3 + 9)] += (a - b) * FS3 / FSX;
187            ret += (a - b) / 256 * (a - b) / 256;
188        }
189    }
190
191    for(i = 0; i < 4; i++)
192    {
193        for(c = 0; c < 3; c++)
194        {
195            /* Experiment shows that this is important at large depths */
196            int a = itoc((in[i * 3 + c] + in[i * 3 + 3 + c]
197                                        + in[i * 3 + 6 + c]) / 3);
198            int b = itoc((out[i * 3 + c] + out[i * 3 + 3 + c]
199                                         + out[i * 3 + 6 + c]) / 3);
200            ret += (a - b) / 256 * (a - b) / 256;
201        }
202    }
203
204    /* Using the diffused error as a perceptual error component is stupid,
205     * because that’s not what it is at all, but I found that it helped a
206     * bit in some cases. */
207    for(i = 0; i < 3; i++)
208        ret += tmperr[i] / 256 * tmperr[i] / 256;
209
210    memcpy(outerr, tmperr, 3 * sizeof(int));
211
212    return ret;
213}
214
215static uint8_t bestmove(int const *in, uint8_t bg, uint8_t fg,
216                        int const *errvec, int depth, int maxerror,
217                        int *error, int *out)
218{
219    int voidvec[3], nvoidvec[3], bestrgb[6 * 3], tmprgb[6 * 3], tmpvec[3];
220    int const *voidrgb, *nvoidrgb, *vec, *rgb;
221    int besterror, curerror, suberror, statice, voide, nvoide;
222    int i, j, c;
223    uint8_t command, bestcommand;
224
225    /* Precompute error for the case where we change the foreground colour
226     * and hence only print the background colour or its negative */
227    voidrgb = palette[bg];
228    voide = geterror(in, errvec, voidrgb, voidvec);
229    nvoidrgb = palette[7 - bg];
230    nvoide = geterror(in, errvec, nvoidrgb, nvoidvec);
231
232    /* Precompute sub-error for the case where we print pixels (and hence
233     * don’t change the palette). It’s not the exact error because we should
234     * be propagating the error to the first pixel here. */
235    if(depth > 0)
236    {
237        int tmp[3] = { 0, 0, 0 };
238        bestmove(in + 6 * 3, bg, fg, tmp, depth - 1, maxerror, &statice, NULL);
239    }
240
241    /* Check every likely command:
242     * 0-7: change foreground to 0-7
243     * 8-15: change foreground to 0-7, print negative background
244     * 16-23: change background to 0-7
245     * 24-31: change background to 0-7, print negative background
246     * 32: normal stuff
247     * 33: inverse video stuff */
248    besterror = 0x7ffffff;
249    bestcommand = 0x10;
250    memcpy(bestrgb, voidrgb, 6 * 3 * sizeof(int));
251    for(j = 0; j < 34; j++)
252    {
253        static uint8_t const lookup[] =
254        {
255            0x00, 0x04, 0x01, 0x05, 0x02, 0x06, 0x03, 0x07,
256            0x80, 0x84, 0x81, 0x85, 0x82, 0x86, 0x83, 0x87,
257            0x10, 0x14, 0x11, 0x15, 0x12, 0x16, 0x13, 0x17,
258            0x90, 0x94, 0x91, 0x95, 0x92, 0x96, 0x93, 0x97,
259            0x40, 0xc0
260        };
261
262        uint8_t newbg = bg, newfg = fg;
263
264        command = lookup[j];
265        domove(command, &newbg, &newfg);
266
267        /* Keeping bg and fg is useless, because we could use standard
268         * pixel printing instead */
269        if((command & 0x40) == 0x00 && newbg == bg && newfg == fg)
270            continue;
271
272        /* I *think* having newfg == newbg is useless, too, but I don’t
273         * want to miss some corner case where swapping bg and fg may be
274         * interesting, so we continue anyway. */
275
276#if 0
277        /* Bit 6 off and bit 5 on seems illegal */
278        if((command & 0x60) == 0x20)
279            continue;
280
281        /* Bits 6 and 5 off and bit 3 on seems illegal */
282        if((command & 0x68) == 0x08)
283            continue;
284#endif
285
286        if((command & 0xf8) == 0x00)
287        {
288            curerror = voide;
289            rgb = voidrgb;
290            vec = voidvec;
291        }
292        else if((command & 0xf8) == 0x80)
293        {
294            curerror = nvoide;
295            rgb = nvoidrgb;
296            vec = nvoidvec;
297        }
298        else if((command & 0xf8) == 0x10)
299        {
300            rgb = palette[newbg];
301            curerror = geterror(in, errvec, rgb, tmpvec);
302            vec = tmpvec;
303        }
304        else if((command & 0xf8) == 0x90)
305        {
306            rgb = palette[7 - newbg];
307            curerror = geterror(in, errvec, rgb, tmpvec);
308            vec = tmpvec;
309        }
310        else
311        {
312            int const *bgcolor, *fgcolor;
313
314            if((command & 0x80) == 0x00)
315            {
316                bgcolor = palette[bg]; fgcolor = palette[fg];
317            }
318            else
319            {
320                bgcolor = palette[7 - bg]; fgcolor = palette[7 - fg];
321            }
322
323            memcpy(tmpvec, errvec, 3 * sizeof(int));
324            curerror = 0;
325
326            for(i = 0; i < 6; i++)
327            {
328                int vec1[3], vec2[3];
329                int smalle1 = 0, smalle2 = 0;
330
331                memcpy(vec1, tmpvec, 3 * sizeof(int));
332                memcpy(vec2, tmpvec, 3 * sizeof(int));
333                for(c = 0; c < 3; c++)
334                {
335                    int delta1, delta2;
336                    delta1 = clamp(in[i * 3 + c] + tmpvec[c]) - bgcolor[c];
337                    vec1[c] = delta1 * FS0 / FSX;
338                    smalle1 += delta1 / 256 * delta1;
339                    delta2 = clamp(in[i * 3 + c] + tmpvec[c]) - fgcolor[c];
340                    vec2[c] = delta2 * FS0 / FSX;
341                    smalle2 += delta2 / 256 * delta2;
342                }
343
344                if(smalle1 < smalle2)
345                {
346                    memcpy(tmpvec, vec1, 3 * sizeof(int));
347                    memcpy(tmprgb + i * 3, bgcolor, 3 * sizeof(int));
348                }
349                else
350                {
351                    memcpy(tmpvec, vec2, 3 * sizeof(int));
352                    memcpy(tmprgb + i * 3, fgcolor, 3 * sizeof(int));
353                    command |= (1 << (5 - i));
354                }
355            }
356
357            /* Recompute full error */
358            curerror += geterror(in, errvec, tmprgb, tmpvec);
359
360            rgb = tmprgb;
361            vec = tmpvec;
362        }
363
364        if(curerror > besterror)
365            continue;
366
367        /* Try to avoid bad decisions now that will have a high cost
368         * later in the line by making the next error more important than
369         * the current error. */
370        curerror = curerror * 3 / 4;
371
372        if(depth == 0)
373            suberror = 0; /* It’s the end of the tree */
374        else if((command & 0x68) == 0x00)
375        {
376            bestmove(in + 6 * 3, newbg, newfg, vec, depth - 1,
377                     besterror - curerror, &suberror, NULL);
378
379#if 0
380            /* Slight penalty for colour changes; they're hard to revert. The
381             * value of 2 was determined empirically. 1.5 is not enough and
382             * 3 is too much. */
383            if(newbg != bg)
384                suberror = suberror * 10 / 8;
385            else if(newfg != fg)
386                suberror = suberror * 9 / 8;
387#endif
388        }
389        else
390            suberror = statice;
391
392        if(curerror + suberror < besterror)
393        {
394            besterror = curerror + suberror;
395            bestcommand = command;
396            memcpy(bestrgb, rgb, 6 * 3 * sizeof(int));
397        }
398    }
399
400    *error = besterror;
401    if(out)
402        memcpy(out, bestrgb, 6 * 3 * sizeof(int));
403
404    return bestcommand;
405}
406
407int main(int argc, char *argv[])
408{
409#if defined _WIN32
410    PBITMAPINFO pbinfo;
411    BITMAPINFO binfo;
412    BITMAPFILEHEADER bfheader;
413    ULONG bisize;
414    HANDLE hfile;
415    HBITMAP tmp;
416    HDC hdc;
417    DWORD ret;
418#else
419    SDL_Surface *tmp, *surface;
420#endif
421    FILE *f;
422    uint8_t *pixels;
423    int *src, *dst, *srcl, *dstl;
424    int stride, x, y, depth, c;
425
426    if(argc < 2)
427    {
428        fprintf(stderr, "Error: missing argument.\n");
429        fprintf(stderr, "Usage: img2oric <image>\n");
430        return 1;
431    }
432
433#if defined _WIN32
434    tmp = (HBITMAP)LoadImage(NULL, argv[1], IMAGE_BITMAP,
435                             0, 0, LR_LOADFROMFILE);
436#else
437    tmp = IMG_Load(argv[1]);
438#endif
439    if(!tmp)
440    {
441        fprintf(stderr, "Error: could not load image %s.\n", argv[1]);
442#if defined _WIN32
443        fprintf(stderr, "Maybe try with an 8-bpp or 24-bpp BMP file?\n");
444#endif
445        return 2;
446    }
447
448    f = fopen(ORICFILE ".TAP", "w");
449    if(!f)
450    {
451        fprintf(stderr, "Error: could not open %s.TAP for writing.\n",
452                        ORICFILE);
453        return 3;
454    }
455    fwrite("\x16\x16\x16\x16\x24", 1, 5, f);
456    fwrite("\x00\xff\x80\x00\xbf\x3f\xa0\x00", 1, 8, f);
457    fwrite(ORICFILE, 1, strlen(ORICFILE), f);
458    fwrite("\x00", 1, 1, f);
459
460    init_tables();
461
462    /* Load the image into a friendly array of fast integers. We create it
463     * slightly bigger than the image so that we don’t have to care about
464     * borders when propagating the error later */
465    src = calloc((WIDTH + 1) * (HEIGHT + 1) * 3, sizeof(int));
466    dst = calloc((WIDTH + 1) * (HEIGHT + 1) * 3, sizeof(int));
467    stride = (WIDTH + 1) * 3;
468
469#if defined _WIN32
470    hdc = CreateCompatibleDC(NULL);
471    SelectObject(hdc, tmp);
472    for(y = 0; y < HEIGHT; y++)
473        for(x = 0; x < WIDTH; x++)
474        {
475            COLORREF color = GetPixel(hdc, x, y);
476            src[y * stride + x * 3] = ctoi(GetRValue(color) * 0x101);
477            src[y * stride + x * 3 + 1] = ctoi(GetGValue(color) * 0x101);
478            src[y * stride + x * 3 + 2] = ctoi(GetBValue(color) * 0x101);
479            for(c = 0; c < 3; c++)
480                dst[y * stride + x * 3 + c] = 0;
481        }
482#else
483    /* FIXME: endianness */
484    surface = SDL_CreateRGBSurface(SDL_SWSURFACE, WIDTH, HEIGHT, 32,
485                                   0xff0000, 0xff00, 0xff, 0x0);
486    SDL_BlitSurface(tmp, NULL, surface, NULL);
487    pixels = (uint8_t *)surface->pixels;
488    for(y = 0; y < HEIGHT; y++)
489        for(x = 0; x < WIDTH; x++)
490            for(c = 0; c < 3; c++)
491            {
492                src[y * stride + x * 3 + c]
493                    = ctoi(pixels[y * surface->pitch + x * 4 + 2 - c] * 0x101);
494                dst[y * stride + x * 3 + c] = 0;
495            }
496#endif
497
498    /* Let the fun begin */
499    for(y = 0; y < HEIGHT; y++)
500    {
501        uint8_t bg = 0, fg = 7;
502
503        fprintf(stderr, "\rProcessing... %i%%", (y * 100 + 99) / HEIGHT);
504
505        for(x = 0; x < WIDTH; x += 6)
506        {
507            int errvec[3] = { 0, 0, 0 };
508            int dummy, i;
509            uint8_t command;
510
511            depth = (x + DEPTH < WIDTH) ? DEPTH : (WIDTH - x) / 6 - 1;
512            srcl = src + y * stride + x * 3;
513            dstl = dst + y * stride + x * 3;
514
515            /* Recursively compute and apply best command */
516            command = bestmove(srcl, bg, fg, errvec, depth, 0x7fffff,
517                               &dummy, dstl);
518            /* Propagate error */
519            for(c = 0; c < 3; c++)
520            {
521                for(i = 0; i < 6; i++)
522                {
523                    int error = srcl[i * 3 + c] - dstl[i * 3 + c];
524                    srcl[i * 3 + c + 3] =
525                            clamp(srcl[i * 3 + c + 3] + error * FS0 / FSX);
526                    srcl[i * 3 + c + stride - 3] += error * FS1 / FSX;
527                    srcl[i * 3 + c + stride] += error * FS2 / FSX;
528                    srcl[i * 3 + c + stride + 3] += error * FS3 / FSX;
529                }
530
531                for(i = -1; i < 7; i++)
532                    srcl[i * 3 + c + stride] = clamp(srcl[i * 3 + c + stride]);
533            }
534            /* Iterate */
535            domove(command, &bg, &fg);
536            /* Write byte to file */
537            fwrite(&command, 1, 1, f);
538        }
539    }
540
541    fclose(f);
542
543    fprintf(stderr, " done.\n");
544
545    /* Save everything */
546#if defined _WIN32
547    for(y = 0; y < HEIGHT; y++)
548        for(x = 0; x < WIDTH; x++)
549        {
550            uint8_t r = dst[y * stride + x * 3] / 0x100;
551            uint8_t g = dst[y * stride + x * 3 + 1] / 0x100;
552            uint8_t b = dst[y * stride + x * 3 + 2] / 0x100;
553            SetPixel(hdc, x, y, RGB(r, g, b));
554        }
555
556    binfo.bmiHeader.biSize = sizeof(binfo.bmiHeader);
557    binfo.bmiHeader.biBitCount = 0;
558    GetDIBits(hdc, tmp, 0, 0, NULL, &binfo, DIB_RGB_COLORS);
559
560    switch(binfo.bmiHeader.biBitCount)
561    {
562    case 24:
563        bisize = sizeof(BITMAPINFOHEADER);
564        break;
565    case 16:
566    case 32:
567        bisize = sizeof(BITMAPINFOHEADER) + sizeof(DWORD) * 3;
568        break;
569    default:
570        bisize = sizeof(BITMAPINFOHEADER)
571                    + sizeof(RGBQUAD) * (1 << binfo.bmiHeader.biBitCount);
572        break;
573    }
574
575    pbinfo = (PBITMAPINFO)GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT, bisize);
576    memcpy(pbinfo, &binfo, sizeof(BITMAPINFOHEADER));
577
578    bfheader.bfType = 0x4D42; /* "BM" */
579    bfheader.bfSize = sizeof(BITMAPFILEHEADER)
580                         + bisize + pbinfo->bmiHeader.biSizeImage;
581    bfheader.bfReserved1 = 0;
582    bfheader.bfReserved2 = 0;
583    bfheader.bfOffBits = sizeof(BITMAPFILEHEADER) + bisize;
584
585    pixels = GlobalAlloc(GMEM_FIXED | GMEM_ZEROINIT,
586                         binfo.bmiHeader.biSizeImage);
587    GetDIBits(hdc, tmp, 0, pbinfo->bmiHeader.biHeight,
588              pixels, pbinfo, DIB_RGB_COLORS);
589
590    hfile = CreateFile(BMPFILE, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS,
591                       FILE_ATTRIBUTE_ARCHIVE, NULL);
592    WriteFile(hfile, &bfheader, sizeof(BITMAPFILEHEADER), &ret, NULL);
593    WriteFile(hfile, pbinfo, bisize, &ret, NULL);
594    WriteFile(hfile, pixels, pbinfo->bmiHeader.biSizeImage, &ret, NULL);
595    CloseHandle(hfile);
596
597    GlobalFree(pbinfo);
598    GlobalFree(pixels);
599#else
600    for(y = 0; y < HEIGHT; y++)
601        for(x = 0; x < WIDTH; x++)
602            for(c = 0; c < 3; c++)
603                pixels[y * surface->pitch + x * 4 + 2 - c]
604                    = itoc(dst[y * stride + x * 3 + c]) / 0x100;
605    SDL_SaveBMP(surface, BMPFILE);
606#endif
607
608    return 0;
609}
610
Note: See TracBrowser for help on using the repository browser.