1/*
2 * a64 video encoder - multicolor modes
3 * Copyright (c) 2009 Tobias Bindhammer
4 *
5 * This file is part of Libav.
6 *
7 * Libav is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * Libav is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with Libav; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22/**
23 * @file
24 * a64 video encoder - multicolor modes
25 */
26
27#include "a64enc.h"
28#include "a64colors.h"
29#include "a64tables.h"
30#include "elbg.h"
31#include "libavutil/intreadwrite.h"
32
33#define DITHERSTEPS   8
34#define CHARSET_CHARS 256
35#define INTERLACED    1
36#define CROP_SCREENS  1
37
38/* gray gradient */
39static const int mc_colors[5]={0x0,0xb,0xc,0xf,0x1};
40
41/* other possible gradients - to be tested */
42//static const int mc_colors[5]={0x0,0x8,0xa,0xf,0x7};
43//static const int mc_colors[5]={0x0,0x9,0x8,0xa,0x3};
44
45static void to_meta_with_crop(AVCodecContext *avctx, AVFrame *p, int *dest)
46{
47    int blockx, blocky, x, y;
48    int luma = 0;
49    int height = FFMIN(avctx->height, C64YRES);
50    int width  = FFMIN(avctx->width , C64XRES);
51    uint8_t *src = p->data[0];
52
53    for (blocky = 0; blocky < C64YRES; blocky += 8) {
54        for (blockx = 0; blockx < C64XRES; blockx += 8) {
55            for (y = blocky; y < blocky + 8 && y < C64YRES; y++) {
56                for (x = blockx; x < blockx + 8 && x < C64XRES; x += 2) {
57                    if(x < width && y < height) {
58                        /* build average over 2 pixels */
59                        luma = (src[(x + 0 + y * p->linesize[0])] +
60                                src[(x + 1 + y * p->linesize[0])]) / 2;
61                        /* write blocks as linear data now so they are suitable for elbg */
62                        dest[0] = luma;
63                    }
64                    dest++;
65                }
66            }
67        }
68    }
69}
70
71static void render_charset(AVCodecContext *avctx, uint8_t *charset,
72                           uint8_t *colrammap)
73{
74    A64Context *c = avctx->priv_data;
75    uint8_t row1, row2;
76    int charpos, x, y;
77    int a, b;
78    uint8_t pix;
79    int lowdiff, highdiff;
80    int *best_cb = c->mc_best_cb;
81    static uint8_t index1[256];
82    static uint8_t index2[256];
83    static uint8_t dither[256];
84    int i;
85    int distance;
86
87    /* generate lookup-tables for dither and index before looping */
88    i = 0;
89    for (a=0; a < 256; a++) {
90        if(i < c->mc_pal_size -1 && a == c->mc_luma_vals[i + 1]) {
91            distance = c->mc_luma_vals[i + 1] - c->mc_luma_vals[i];
92            for(b = 0; b <= distance; b++) {
93                  dither[c->mc_luma_vals[i] + b] = b * (DITHERSTEPS - 1) / distance;
94            }
95            i++;
96        }
97        if(i >= c->mc_pal_size - 1) dither[a] = 0;
98        index1[a] = i;
99        index2[a] = FFMIN(i + 1, c->mc_pal_size - 1);
100    }
101
102    /* and render charset */
103    for (charpos = 0; charpos < CHARSET_CHARS; charpos++) {
104        lowdiff  = 0;
105        highdiff = 0;
106        for (y = 0; y < 8; y++) {
107            row1 = 0; row2 = 0;
108            for (x = 0; x < 4; x++) {
109                pix = best_cb[y * 4 + x];
110
111                /* accumulate error for brightest/darkest color */
112                if (index1[pix] >= 3)
113                    highdiff += pix - c->mc_luma_vals[3];
114                if (index1[pix] < 1)
115                    lowdiff += c->mc_luma_vals[1] - pix;
116
117                row1 <<= 2;
118
119                if (INTERLACED) {
120                    row2 <<= 2;
121                    if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 0][x & 3])
122                        row1 |= 3-(index2[pix] & 3);
123                    else
124                        row1 |= 3-(index1[pix] & 3);
125
126                    if (interlaced_dither_patterns[dither[pix]][(y & 3) * 2 + 1][x & 3])
127                        row2 |= 3-(index2[pix] & 3);
128                    else
129                        row2 |= 3-(index1[pix] & 3);
130                }
131                else {
132                    if (multi_dither_patterns[dither[pix]][(y & 3)][x & 3])
133                        row1 |= 3-(index2[pix] & 3);
134                    else
135                        row1 |= 3-(index1[pix] & 3);
136                }
137            }
138            charset[y+0x000] = row1;
139            if (INTERLACED) charset[y+0x800] = row2;
140        }
141        /* do we need to adjust pixels? */
142        if (highdiff > 0 && lowdiff > 0 && c->mc_use_5col) {
143            if (lowdiff > highdiff) {
144                for (x = 0; x < 32; x++)
145                    best_cb[x] = FFMIN(c->mc_luma_vals[3], best_cb[x]);
146            } else {
147                for (x = 0; x < 32; x++)
148                    best_cb[x] = FFMAX(c->mc_luma_vals[1], best_cb[x]);
149            }
150            charpos--;          /* redo now adjusted char */
151        /* no adjustment needed, all fine */
152        } else {
153            /* advance pointers */
154            best_cb += 32;
155            charset += 8;
156
157            /* remember colorram value */
158            colrammap[charpos] = (highdiff > 0);
159        }
160    }
161}
162
163static av_cold int a64multi_close_encoder(AVCodecContext *avctx)
164{
165    A64Context *c = avctx->priv_data;
166    av_free(c->mc_meta_charset);
167    av_free(c->mc_best_cb);
168    av_free(c->mc_charset);
169    av_free(c->mc_charmap);
170    av_free(c->mc_colram);
171    return 0;
172}
173
174static av_cold int a64multi_init_encoder(AVCodecContext *avctx)
175{
176    A64Context *c = avctx->priv_data;
177    int a;
178    av_lfg_init(&c->randctx, 1);
179
180    if (avctx->global_quality < 1) {
181        c->mc_lifetime = 4;
182    } else {
183        c->mc_lifetime = avctx->global_quality /= FF_QP2LAMBDA;
184    }
185
186    av_log(avctx, AV_LOG_INFO, "charset lifetime set to %d frame(s)\n", c->mc_lifetime);
187
188    c->mc_frame_counter = 0;
189    c->mc_use_5col      = avctx->codec->id == CODEC_ID_A64_MULTI5;
190    c->mc_pal_size      = 4 + c->mc_use_5col;
191
192    /* precalc luma values for later use */
193    for (a = 0; a < c->mc_pal_size; a++) {
194        c->mc_luma_vals[a]=a64_palette[mc_colors[a]][0] * 0.30 +
195                           a64_palette[mc_colors[a]][1] * 0.59 +
196                           a64_palette[mc_colors[a]][2] * 0.11;
197    }
198
199    if (!(c->mc_meta_charset = av_malloc(32000 * c->mc_lifetime * sizeof(int))) ||
200       !(c->mc_best_cb       = av_malloc(CHARSET_CHARS * 32 * sizeof(int)))     ||
201       !(c->mc_charmap       = av_mallocz(1000 * c->mc_lifetime * sizeof(int))) ||
202       !(c->mc_colram        = av_mallocz(CHARSET_CHARS * sizeof(uint8_t)))     ||
203       !(c->mc_charset       = av_malloc(0x800 * (INTERLACED+1) * sizeof(uint8_t)))) {
204        av_log(avctx, AV_LOG_ERROR, "Failed to allocate buffer memory.\n");
205        return AVERROR(ENOMEM);
206    }
207
208    /* set up extradata */
209    if (!(avctx->extradata = av_mallocz(8 * 4 + FF_INPUT_BUFFER_PADDING_SIZE))) {
210        av_log(avctx, AV_LOG_ERROR, "Failed to allocate memory for extradata.\n");
211        return AVERROR(ENOMEM);
212    }
213    avctx->extradata_size = 8 * 4;
214    AV_WB32(avctx->extradata, c->mc_lifetime);
215    AV_WB32(avctx->extradata + 16, INTERLACED);
216
217    avcodec_get_frame_defaults(&c->picture);
218    avctx->coded_frame            = &c->picture;
219    avctx->coded_frame->pict_type = AV_PICTURE_TYPE_I;
220    avctx->coded_frame->key_frame = 1;
221    if (!avctx->codec_tag)
222         avctx->codec_tag = AV_RL32("a64m");
223
224    return 0;
225}
226
227static void a64_compress_colram(unsigned char *buf, int *charmap, uint8_t *colram)
228{
229    int a;
230    uint8_t temp;
231    /* only needs to be done in 5col mode */
232    /* XXX could be squeezed to 0x80 bytes */
233    for (a = 0; a < 256; a++) {
234        temp  = colram[charmap[a + 0x000]] << 0;
235        temp |= colram[charmap[a + 0x100]] << 1;
236        temp |= colram[charmap[a + 0x200]] << 2;
237        if (a < 0xe8) temp |= colram[charmap[a + 0x300]] << 3;
238        buf[a] = temp << 2;
239    }
240}
241
242static int a64multi_encode_frame(AVCodecContext *avctx, unsigned char *buf,
243                                 int buf_size, void *data)
244{
245    A64Context *c = avctx->priv_data;
246    AVFrame *pict = data;
247    AVFrame *const p = (AVFrame *) & c->picture;
248
249    int frame;
250    int x, y;
251    int b_height;
252    int b_width;
253
254    int req_size;
255
256    int *charmap     = c->mc_charmap;
257    uint8_t *colram  = c->mc_colram;
258    uint8_t *charset = c->mc_charset;
259    int *meta        = c->mc_meta_charset;
260    int *best_cb     = c->mc_best_cb;
261
262    int charset_size = 0x800 * (INTERLACED + 1);
263    int colram_size  = 0x100 * c->mc_use_5col;
264    int screen_size;
265
266    if(CROP_SCREENS) {
267        b_height = FFMIN(avctx->height,C64YRES) >> 3;
268        b_width  = FFMIN(avctx->width ,C64XRES) >> 3;
269        screen_size = b_width * b_height;
270    } else {
271        b_height = C64YRES >> 3;
272        b_width  = C64XRES >> 3;
273        screen_size = 0x400;
274    }
275
276    /* no data, means end encoding asap */
277    if (!data) {
278        /* all done, end encoding */
279        if (!c->mc_lifetime) return 0;
280        /* no more frames in queue, prepare to flush remaining frames */
281        if (!c->mc_frame_counter) {
282            c->mc_lifetime = 0;
283        }
284        /* still frames in queue so limit lifetime to remaining frames */
285        else c->mc_lifetime = c->mc_frame_counter;
286    /* still new data available */
287    } else {
288        /* fill up mc_meta_charset with data until lifetime exceeds */
289        if (c->mc_frame_counter < c->mc_lifetime) {
290            *p = *pict;
291            p->pict_type = AV_PICTURE_TYPE_I;
292            p->key_frame = 1;
293            to_meta_with_crop(avctx, p, meta + 32000 * c->mc_frame_counter);
294            c->mc_frame_counter++;
295            /* lifetime is not reached so wait for next frame first */
296            return 0;
297        }
298    }
299
300    /* lifetime reached so now convert X frames at once */
301    if (c->mc_frame_counter == c->mc_lifetime) {
302        req_size = 0;
303        /* any frames to encode? */
304        if (c->mc_lifetime) {
305            /* calc optimal new charset + charmaps */
306            ff_init_elbg(meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx);
307            ff_do_elbg  (meta, 32, 1000 * c->mc_lifetime, best_cb, CHARSET_CHARS, 50, charmap, &c->randctx);
308
309            /* create colorram map and a c64 readable charset */
310            render_charset(avctx, charset, colram);
311
312            /* copy charset to buf */
313            memcpy(buf,charset, charset_size);
314
315            /* advance pointers */
316            buf      += charset_size;
317            charset  += charset_size;
318            req_size += charset_size;
319        }
320        /* no charset so clean buf */
321        else memset(buf, 0, charset_size);
322
323        /* write x frames to buf */
324        for (frame = 0; frame < c->mc_lifetime; frame++) {
325            /* copy charmap to buf. buf is uchar*, charmap is int*, so no memcpy here, sorry */
326            for (y = 0; y < b_height; y++) {
327                for (x = 0; x < b_width; x++) {
328                    buf[y * b_width + x] = charmap[y * b_width + x];
329                }
330            }
331            /* advance pointers */
332            buf += screen_size;
333            req_size += screen_size;
334
335            /* compress and copy colram to buf */
336            if (c->mc_use_5col) {
337                a64_compress_colram(buf, charmap, colram);
338                /* advance pointers */
339                buf += colram_size;
340                req_size += colram_size;
341            }
342
343            /* advance to next charmap */
344            charmap += 1000;
345        }
346
347        AV_WB32(avctx->extradata + 4,  c->mc_frame_counter);
348        AV_WB32(avctx->extradata + 8,  charset_size);
349        AV_WB32(avctx->extradata + 12, screen_size + colram_size);
350
351        /* reset counter */
352        c->mc_frame_counter = 0;
353
354        if (req_size > buf_size) {
355            av_log(avctx, AV_LOG_ERROR, "buf size too small (need %d, got %d)\n", req_size, buf_size);
356            return -1;
357        }
358        return req_size;
359    }
360    return 0;
361}
362
363AVCodec ff_a64multi_encoder = {
364    .name           = "a64multi",
365    .type           = AVMEDIA_TYPE_VIDEO,
366    .id             = CODEC_ID_A64_MULTI,
367    .priv_data_size = sizeof(A64Context),
368    .init           = a64multi_init_encoder,
369    .encode         = a64multi_encode_frame,
370    .close          = a64multi_close_encoder,
371    .pix_fmts       = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE},
372    .long_name      = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64"),
373    .capabilities   = CODEC_CAP_DELAY,
374};
375
376AVCodec ff_a64multi5_encoder = {
377    .name           = "a64multi5",
378    .type           = AVMEDIA_TYPE_VIDEO,
379    .id             = CODEC_ID_A64_MULTI5,
380    .priv_data_size = sizeof(A64Context),
381    .init           = a64multi_init_encoder,
382    .encode         = a64multi_encode_frame,
383    .close          = a64multi_close_encoder,
384    .pix_fmts       = (const enum PixelFormat[]) {PIX_FMT_GRAY8, PIX_FMT_NONE},
385    .long_name      = NULL_IF_CONFIG_SMALL("Multicolor charset for Commodore 64, extended with 5th color (colram)"),
386    .capabilities   = CODEC_CAP_DELAY,
387};
388