1/*
2 * SGI image encoder
3 * Todd Kirby <doubleshot@pacbell.net>
4 *
5 * This file is part of FFmpeg.
6 *
7 * FFmpeg 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 * FFmpeg 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 FFmpeg; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22#include "avcodec.h"
23#include "bytestream.h"
24#include "sgi.h"
25#include "rle.h"
26
27#define SGI_SINGLE_CHAN 2
28#define SGI_MULTI_CHAN 3
29
30typedef struct SgiContext {
31    AVFrame picture;
32} SgiContext;
33
34static av_cold int encode_init(AVCodecContext *avctx)
35{
36    SgiContext *s = avctx->priv_data;
37
38    avcodec_get_frame_defaults(&s->picture);
39    avctx->coded_frame = &s->picture;
40
41    return 0;
42}
43
44static int encode_frame(AVCodecContext *avctx, unsigned char *buf,
45                        int buf_size, void *data)
46{
47    SgiContext *s = avctx->priv_data;
48    AVFrame * const p = &s->picture;
49    uint8_t *offsettab, *lengthtab, *in_buf, *encode_buf;
50    int x, y, z, length, tablesize;
51    unsigned int width, height, depth, dimension;
52    unsigned char *orig_buf = buf, *end_buf = buf + buf_size;
53
54    *p = *(AVFrame*)data;
55    p->pict_type = FF_I_TYPE;
56    p->key_frame = 1;
57
58    width  = avctx->width;
59    height = avctx->height;
60
61    switch (avctx->pix_fmt) {
62    case PIX_FMT_GRAY8:
63        dimension = SGI_SINGLE_CHAN;
64        depth     = SGI_GRAYSCALE;
65        break;
66    case PIX_FMT_RGB24:
67        dimension = SGI_MULTI_CHAN;
68        depth     = SGI_RGB;
69        break;
70    case PIX_FMT_RGBA:
71        dimension = SGI_MULTI_CHAN;
72        depth     = SGI_RGBA;
73        break;
74    default:
75        return AVERROR_INVALIDDATA;
76    }
77
78    tablesize = depth * height * 4;
79    length = tablesize * 2 + SGI_HEADER_SIZE;
80
81    if (buf_size < length) {
82        av_log(avctx, AV_LOG_ERROR, "buf_size too small(need %d, got %d)\n", length, buf_size);
83        return -1;
84    }
85
86    /* Encode header. */
87    bytestream_put_be16(&buf, SGI_MAGIC);
88    bytestream_put_byte(&buf, avctx->coder_type != FF_CODER_TYPE_RAW); /* RLE 1 - VERBATIM 0*/
89    bytestream_put_byte(&buf, 1); /* bytes_per_channel */
90    bytestream_put_be16(&buf, dimension);
91    bytestream_put_be16(&buf, width);
92    bytestream_put_be16(&buf, height);
93    bytestream_put_be16(&buf, depth);
94
95    /* The rest are constant in this implementation. */
96    bytestream_put_be32(&buf, 0L); /* pixmin */
97    bytestream_put_be32(&buf, 255L); /* pixmax */
98    bytestream_put_be32(&buf, 0L); /* dummy */
99
100    /* name */
101    memset(buf, 0, SGI_HEADER_SIZE);
102    buf += 80;
103
104     /* colormap */
105    bytestream_put_be32(&buf, 0L);
106
107    /* The rest of the 512 byte header is unused. */
108    buf += 404;
109    offsettab = buf;
110
111    if (avctx->coder_type  != FF_CODER_TYPE_RAW) {
112        /* Skip RLE offset table. */
113        buf += tablesize;
114        lengthtab = buf;
115
116        /* Skip RLE length table. */
117        buf += tablesize;
118
119        /* Make an intermediate consecutive buffer. */
120        if (!(encode_buf = av_malloc(width)))
121            return -1;
122
123        for (z = 0; z < depth; z++) {
124            in_buf = p->data[0] + p->linesize[0] * (height - 1) + z;
125
126            for (y = 0; y < height; y++) {
127                bytestream_put_be32(&offsettab, buf - orig_buf);
128
129                for (x = 0; x < width; x++)
130                    encode_buf[x] = in_buf[depth * x];
131
132                if ((length = ff_rle_encode(buf, end_buf - buf - 1, encode_buf, 1, width, 0, 0, 0x80, 0)) < 1) {
133                    av_free(encode_buf);
134                    return -1;
135                }
136
137                buf += length;
138                bytestream_put_byte(&buf, 0);
139                bytestream_put_be32(&lengthtab, length + 1);
140                in_buf -= p->linesize[0];
141            }
142        }
143
144        av_free(encode_buf);
145    } else {
146        for (z = 0; z < depth; z++) {
147            in_buf = p->data[0] + p->linesize[0] * (height - 1) + z;
148
149            for (y = 0; y < height; y++) {
150                for (x = 0; x < width * depth; x += depth)
151                    bytestream_put_byte(&buf, in_buf[x]);
152
153                in_buf -= p->linesize[0];
154            }
155        }
156    }
157
158    /* total length */
159    return buf - orig_buf;
160}
161
162AVCodec sgi_encoder = {
163    "sgi",
164    AVMEDIA_TYPE_VIDEO,
165    CODEC_ID_SGI,
166    sizeof(SgiContext),
167    encode_init,
168    encode_frame,
169    NULL,
170    .pix_fmts= (const enum PixelFormat[]){PIX_FMT_RGB24, PIX_FMT_RGBA, PIX_FMT_GRAY8, PIX_FMT_NONE},
171    .long_name= NULL_IF_CONFIG_SMALL("SGI image"),
172};
173