2 * CD Graphics Video Decoder
3 * Copyright (c) 2009 Michael Tison
5 * This file is part of Libav.
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.
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.
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
23 #include "bytestream.h"
28 * @brief CD Graphics Video Decoder
29 * @author Michael Tison
30 * @see http://wiki.multimedia.cx/index.php?title=CD_Graphics
31 * @see http://www.ccs.neu.edu/home/bchafy/cdb/info/cdg
34 /// default screen sizes
35 #define CDG_FULL_WIDTH 300
36 #define CDG_FULL_HEIGHT 216
37 #define CDG_DISPLAY_WIDTH 294
38 #define CDG_DISPLAY_HEIGHT 204
39 #define CDG_BORDER_WIDTH 6
40 #define CDG_BORDER_HEIGHT 12
43 #define CDG_COMMAND 0x09
47 #define CDG_INST_MEMORY_PRESET 1
48 #define CDG_INST_BORDER_PRESET 2
49 #define CDG_INST_TILE_BLOCK 6
50 #define CDG_INST_SCROLL_PRESET 20
51 #define CDG_INST_SCROLL_COPY 24
52 #define CDG_INST_LOAD_PAL_LO 30
53 #define CDG_INST_LOAD_PAL_HIGH 31
54 #define CDG_INST_TILE_BLOCK_XOR 38
57 #define CDG_PACKET_SIZE 24
58 #define CDG_DATA_SIZE 16
59 #define CDG_TILE_HEIGHT 12
60 #define CDG_TILE_WIDTH 6
61 #define CDG_MINIMUM_PKT_SIZE 6
62 #define CDG_MINIMUM_SCROLL_SIZE 3
63 #define CDG_HEADER_SIZE 8
64 #define CDG_PALETTE_SIZE 16
66 typedef struct CDGraphicsContext
{
72 static void cdg_init_frame(AVFrame
*frame
)
74 avcodec_get_frame_defaults(frame
);
76 frame
->buffer_hints
= FF_BUFFER_HINTS_VALID
|
77 FF_BUFFER_HINTS_READABLE
|
78 FF_BUFFER_HINTS_PRESERVE
|
79 FF_BUFFER_HINTS_REUSABLE
;
82 static av_cold
int cdg_decode_init(AVCodecContext
*avctx
)
84 CDGraphicsContext
*cc
= avctx
->priv_data
;
86 cdg_init_frame(&cc
->frame
);
88 avctx
->width
= CDG_FULL_WIDTH
;
89 avctx
->height
= CDG_FULL_HEIGHT
;
90 avctx
->pix_fmt
= AV_PIX_FMT_PAL8
;
95 static void cdg_border_preset(CDGraphicsContext
*cc
, uint8_t *data
)
98 int lsize
= cc
->frame
.linesize
[0];
99 uint8_t *buf
= cc
->frame
.data
[0];
100 int color
= data
[0] & 0x0F;
102 if (!(data
[1] & 0x0F)) {
103 /// fill the top and bottom borders
104 memset(buf
, color
, CDG_BORDER_HEIGHT
* lsize
);
105 memset(buf
+ (CDG_FULL_HEIGHT
- CDG_BORDER_HEIGHT
) * lsize
,
106 color
, CDG_BORDER_HEIGHT
* lsize
);
108 /// fill the side borders
109 for (y
= CDG_BORDER_HEIGHT
; y
< CDG_FULL_HEIGHT
- CDG_BORDER_HEIGHT
; y
++) {
110 memset(buf
+ y
* lsize
, color
, CDG_BORDER_WIDTH
);
111 memset(buf
+ CDG_FULL_WIDTH
- CDG_BORDER_WIDTH
+ y
* lsize
,
112 color
, CDG_BORDER_WIDTH
);
117 static void cdg_load_palette(CDGraphicsContext
*cc
, uint8_t *data
, int low
)
122 int array_offset
= low
? 0 : 8;
123 uint32_t *palette
= (uint32_t *) cc
->frame
.data
[1];
125 for (i
= 0; i
< 8; i
++) {
126 color
= (data
[2 * i
] << 6) + (data
[2 * i
+ 1] & 0x3F);
127 r
= ((color
>> 8) & 0x000F) * 17;
128 g
= ((color
>> 4) & 0x000F) * 17;
129 b
= ((color
) & 0x000F) * 17;
130 palette
[i
+ array_offset
] = r
<< 16 | g
<< 8 | b
;
132 cc
->frame
.palette_has_changed
= 1;
135 static int cdg_tile_block(CDGraphicsContext
*cc
, uint8_t *data
, int b
)
141 int stride
= cc
->frame
.linesize
[0];
142 uint8_t *buf
= cc
->frame
.data
[0];
144 ri
= (data
[2] & 0x1F) * CDG_TILE_HEIGHT
+ cc
->vscroll
;
145 ci
= (data
[3] & 0x3F) * CDG_TILE_WIDTH
+ cc
->hscroll
;
147 if (ri
> (CDG_FULL_HEIGHT
- CDG_TILE_HEIGHT
))
148 return AVERROR(EINVAL
);
149 if (ci
> (CDG_FULL_WIDTH
- CDG_TILE_WIDTH
))
150 return AVERROR(EINVAL
);
152 for (y
= 0; y
< CDG_TILE_HEIGHT
; y
++) {
153 for (x
= 0; x
< CDG_TILE_WIDTH
; x
++) {
154 if (!((data
[4 + y
] >> (5 - x
)) & 0x01))
155 color
= data
[0] & 0x0F;
157 color
= data
[1] & 0x0F;
159 ai
= ci
+ x
+ (stride
* (ri
+ y
));
174 static void cdg_copy_rect_buf(int out_tl_x
, int out_tl_y
, uint8_t *out
,
175 int in_tl_x
, int in_tl_y
, uint8_t *in
,
176 int w
, int h
, int stride
)
180 in
+= in_tl_x
+ in_tl_y
* stride
;
181 out
+= out_tl_x
+ out_tl_y
* stride
;
182 for (y
= 0; y
< h
; y
++)
183 memcpy(out
+ y
* stride
, in
+ y
* stride
, w
);
186 static void cdg_fill_rect_preset(int tl_x
, int tl_y
, uint8_t *out
,
187 int color
, int w
, int h
, int stride
)
191 for (y
= tl_y
; y
< tl_y
+ h
; y
++)
192 memset(out
+ tl_x
+ y
* stride
, color
, w
);
195 static void cdg_fill_wrapper(int out_tl_x
, int out_tl_y
, uint8_t *out
,
196 int in_tl_x
, int in_tl_y
, uint8_t *in
,
197 int color
, int w
, int h
, int stride
, int roll
)
200 cdg_copy_rect_buf(out_tl_x
, out_tl_y
, out
, in_tl_x
, in_tl_y
,
203 cdg_fill_rect_preset(out_tl_x
, out_tl_y
, out
, color
, w
, h
, stride
);
207 static void cdg_scroll(CDGraphicsContext
*cc
, uint8_t *data
,
208 AVFrame
*new_frame
, int roll_over
)
211 int hscmd
, h_off
, hinc
, vscmd
, v_off
, vinc
;
213 int stride
= cc
->frame
.linesize
[0];
214 uint8_t *in
= cc
->frame
.data
[0];
215 uint8_t *out
= new_frame
->data
[0];
217 color
= data
[0] & 0x0F;
218 hscmd
= (data
[1] & 0x30) >> 4;
219 vscmd
= (data
[2] & 0x30) >> 4;
221 h_off
= FFMIN(data
[1] & 0x07, CDG_BORDER_WIDTH
- 1);
222 v_off
= FFMIN(data
[2] & 0x0F, CDG_BORDER_HEIGHT
- 1);
224 /// find the difference and save the offset for cdg_tile_block usage
225 hinc
= h_off
- cc
->hscroll
;
226 vinc
= v_off
- cc
->vscroll
;
242 memcpy(new_frame
->data
[1], cc
->frame
.data
[1], CDG_PALETTE_SIZE
* 4);
244 for (y
= FFMAX(0, vinc
); y
< FFMIN(CDG_FULL_HEIGHT
+ vinc
, CDG_FULL_HEIGHT
); y
++)
245 memcpy(out
+ FFMAX(0, hinc
) + stride
* y
,
246 in
+ FFMAX(0, hinc
) - hinc
+ (y
- vinc
) * stride
,
247 FFMIN(stride
+ hinc
, stride
));
250 cdg_fill_wrapper(0, 0, out
,
251 0, CDG_FULL_HEIGHT
- vinc
, in
, color
,
252 stride
, vinc
, stride
, roll_over
);
254 cdg_fill_wrapper(0, CDG_FULL_HEIGHT
+ vinc
, out
,
256 stride
, -1 * vinc
, stride
, roll_over
);
259 cdg_fill_wrapper(0, 0, out
,
260 CDG_FULL_WIDTH
- hinc
, 0, in
, color
,
261 hinc
, CDG_FULL_HEIGHT
, stride
, roll_over
);
263 cdg_fill_wrapper(CDG_FULL_WIDTH
+ hinc
, 0, out
,
265 -1 * hinc
, CDG_FULL_HEIGHT
, stride
, roll_over
);
269 static int cdg_decode_frame(AVCodecContext
*avctx
,
270 void *data
, int *got_frame
, AVPacket
*avpkt
)
272 const uint8_t *buf
= avpkt
->data
;
273 int buf_size
= avpkt
->size
;
275 uint8_t command
, inst
;
276 uint8_t cdg_data
[CDG_DATA_SIZE
];
278 CDGraphicsContext
*cc
= avctx
->priv_data
;
280 if (buf_size
< CDG_MINIMUM_PKT_SIZE
) {
281 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for decoder\n");
282 return AVERROR(EINVAL
);
285 ret
= avctx
->reget_buffer(avctx
, &cc
->frame
);
287 av_log(avctx
, AV_LOG_ERROR
, "reget_buffer() failed\n");
290 if (!avctx
->frame_number
)
291 memset(cc
->frame
.data
[0], 0, cc
->frame
.linesize
[0] * avctx
->height
);
293 command
= bytestream_get_byte(&buf
);
294 inst
= bytestream_get_byte(&buf
);
296 buf
+= 2; /// skipping 2 unneeded bytes
297 bytestream_get_buffer(&buf
, cdg_data
, buf_size
- CDG_HEADER_SIZE
);
299 if ((command
& CDG_MASK
) == CDG_COMMAND
) {
301 case CDG_INST_MEMORY_PRESET
:
302 if (!(cdg_data
[1] & 0x0F))
303 memset(cc
->frame
.data
[0], cdg_data
[0] & 0x0F,
304 cc
->frame
.linesize
[0] * CDG_FULL_HEIGHT
);
306 case CDG_INST_LOAD_PAL_LO
:
307 case CDG_INST_LOAD_PAL_HIGH
:
308 if (buf_size
- CDG_HEADER_SIZE
< CDG_DATA_SIZE
) {
309 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for loading palette\n");
310 return AVERROR(EINVAL
);
313 cdg_load_palette(cc
, cdg_data
, inst
== CDG_INST_LOAD_PAL_LO
);
315 case CDG_INST_BORDER_PRESET
:
316 cdg_border_preset(cc
, cdg_data
);
318 case CDG_INST_TILE_BLOCK_XOR
:
319 case CDG_INST_TILE_BLOCK
:
320 if (buf_size
- CDG_HEADER_SIZE
< CDG_DATA_SIZE
) {
321 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for drawing tile\n");
322 return AVERROR(EINVAL
);
325 ret
= cdg_tile_block(cc
, cdg_data
, inst
== CDG_INST_TILE_BLOCK_XOR
);
327 av_log(avctx
, AV_LOG_ERROR
, "tile is out of range\n");
331 case CDG_INST_SCROLL_PRESET
:
332 case CDG_INST_SCROLL_COPY
:
333 if (buf_size
- CDG_HEADER_SIZE
< CDG_MINIMUM_SCROLL_SIZE
) {
334 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for scrolling\n");
335 return AVERROR(EINVAL
);
338 cdg_init_frame(&new_frame
);
339 ret
= ff_get_buffer(avctx
, &new_frame
);
341 av_log(avctx
, AV_LOG_ERROR
, "get_buffer() failed\n");
345 cdg_scroll(cc
, cdg_data
, &new_frame
, inst
== CDG_INST_SCROLL_COPY
);
346 avctx
->release_buffer(avctx
, &cc
->frame
);
347 cc
->frame
= new_frame
;
359 *(AVFrame
*) data
= cc
->frame
;
363 static av_cold
int cdg_decode_end(AVCodecContext
*avctx
)
365 CDGraphicsContext
*cc
= avctx
->priv_data
;
367 if (cc
->frame
.data
[0])
368 avctx
->release_buffer(avctx
, &cc
->frame
);
373 AVCodec ff_cdgraphics_decoder
= {
374 .name
= "cdgraphics",
375 .type
= AVMEDIA_TYPE_VIDEO
,
376 .id
= AV_CODEC_ID_CDGRAPHICS
,
377 .priv_data_size
= sizeof(CDGraphicsContext
),
378 .init
= cdg_decode_init
,
379 .close
= cdg_decode_end
,
380 .decode
= cdg_decode_frame
,
381 .capabilities
= CODEC_CAP_DR1
,
382 .long_name
= NULL_IF_CONFIG_SMALL("CD Graphics video"),