2 * CD Graphics Video Decoder
3 * Copyright (c) 2009 Michael Tison
5 * This file is part of FFmpeg.
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.
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.
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
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 av_cold
int cdg_decode_init(AVCodecContext
*avctx
)
74 CDGraphicsContext
*cc
= avctx
->priv_data
;
76 cc
->frame
= av_frame_alloc();
78 return AVERROR(ENOMEM
);
80 avctx
->width
= CDG_FULL_WIDTH
;
81 avctx
->height
= CDG_FULL_HEIGHT
;
82 avctx
->pix_fmt
= AV_PIX_FMT_PAL8
;
87 static void cdg_border_preset(CDGraphicsContext
*cc
, uint8_t *data
)
90 int lsize
= cc
->frame
->linesize
[0];
91 uint8_t *buf
= cc
->frame
->data
[0];
92 int color
= data
[0] & 0x0F;
94 if (!(data
[1] & 0x0F)) {
95 /// fill the top and bottom borders
96 memset(buf
, color
, CDG_BORDER_HEIGHT
* lsize
);
97 memset(buf
+ (CDG_FULL_HEIGHT
- CDG_BORDER_HEIGHT
) * lsize
,
98 color
, CDG_BORDER_HEIGHT
* lsize
);
100 /// fill the side borders
101 for (y
= CDG_BORDER_HEIGHT
; y
< CDG_FULL_HEIGHT
- CDG_BORDER_HEIGHT
; y
++) {
102 memset(buf
+ y
* lsize
, color
, CDG_BORDER_WIDTH
);
103 memset(buf
+ CDG_FULL_WIDTH
- CDG_BORDER_WIDTH
+ y
* lsize
,
104 color
, CDG_BORDER_WIDTH
);
109 static void cdg_load_palette(CDGraphicsContext
*cc
, uint8_t *data
, int low
)
114 int array_offset
= low
? 0 : 8;
115 uint32_t *palette
= (uint32_t *) cc
->frame
->data
[1];
117 for (i
= 0; i
< 8; i
++) {
118 color
= (data
[2 * i
] << 6) + (data
[2 * i
+ 1] & 0x3F);
119 r
= ((color
>> 8) & 0x000F) * 17;
120 g
= ((color
>> 4) & 0x000F) * 17;
121 b
= ((color
) & 0x000F) * 17;
122 palette
[i
+ array_offset
] = 0xFFU
<< 24 | r
<< 16 | g
<< 8 | b
;
124 cc
->frame
->palette_has_changed
= 1;
127 static int cdg_tile_block(CDGraphicsContext
*cc
, uint8_t *data
, int b
)
133 int stride
= cc
->frame
->linesize
[0];
134 uint8_t *buf
= cc
->frame
->data
[0];
136 ri
= (data
[2] & 0x1F) * CDG_TILE_HEIGHT
+ cc
->vscroll
;
137 ci
= (data
[3] & 0x3F) * CDG_TILE_WIDTH
+ cc
->hscroll
;
139 if (ri
> (CDG_FULL_HEIGHT
- CDG_TILE_HEIGHT
))
140 return AVERROR(EINVAL
);
141 if (ci
> (CDG_FULL_WIDTH
- CDG_TILE_WIDTH
))
142 return AVERROR(EINVAL
);
144 for (y
= 0; y
< CDG_TILE_HEIGHT
; y
++) {
145 for (x
= 0; x
< CDG_TILE_WIDTH
; x
++) {
146 if (!((data
[4 + y
] >> (5 - x
)) & 0x01))
147 color
= data
[0] & 0x0F;
149 color
= data
[1] & 0x0F;
151 ai
= ci
+ x
+ (stride
* (ri
+ y
));
166 static void cdg_copy_rect_buf(int out_tl_x
, int out_tl_y
, uint8_t *out
,
167 int in_tl_x
, int in_tl_y
, uint8_t *in
,
168 int w
, int h
, int stride
)
172 in
+= in_tl_x
+ in_tl_y
* stride
;
173 out
+= out_tl_x
+ out_tl_y
* stride
;
174 for (y
= 0; y
< h
; y
++)
175 memcpy(out
+ y
* stride
, in
+ y
* stride
, w
);
178 static void cdg_fill_rect_preset(int tl_x
, int tl_y
, uint8_t *out
,
179 int color
, int w
, int h
, int stride
)
183 for (y
= tl_y
; y
< tl_y
+ h
; y
++)
184 memset(out
+ tl_x
+ y
* stride
, color
, w
);
187 static void cdg_fill_wrapper(int out_tl_x
, int out_tl_y
, uint8_t *out
,
188 int in_tl_x
, int in_tl_y
, uint8_t *in
,
189 int color
, int w
, int h
, int stride
, int roll
)
192 cdg_copy_rect_buf(out_tl_x
, out_tl_y
, out
, in_tl_x
, in_tl_y
,
195 cdg_fill_rect_preset(out_tl_x
, out_tl_y
, out
, color
, w
, h
, stride
);
199 static void cdg_scroll(CDGraphicsContext
*cc
, uint8_t *data
,
200 AVFrame
*new_frame
, int roll_over
)
203 int hscmd
, h_off
, hinc
, vscmd
, v_off
, vinc
;
205 int stride
= cc
->frame
->linesize
[0];
206 uint8_t *in
= cc
->frame
->data
[0];
207 uint8_t *out
= new_frame
->data
[0];
209 color
= data
[0] & 0x0F;
210 hscmd
= (data
[1] & 0x30) >> 4;
211 vscmd
= (data
[2] & 0x30) >> 4;
213 h_off
= FFMIN(data
[1] & 0x07, CDG_BORDER_WIDTH
- 1);
214 v_off
= FFMIN(data
[2] & 0x0F, CDG_BORDER_HEIGHT
- 1);
216 /// find the difference and save the offset for cdg_tile_block usage
217 hinc
= h_off
- cc
->hscroll
;
218 vinc
= v_off
- cc
->vscroll
;
234 memcpy(new_frame
->data
[1], cc
->frame
->data
[1], CDG_PALETTE_SIZE
* 4);
236 for (y
= FFMAX(0, vinc
); y
< FFMIN(CDG_FULL_HEIGHT
+ vinc
, CDG_FULL_HEIGHT
); y
++)
237 memcpy(out
+ FFMAX(0, hinc
) + stride
* y
,
238 in
+ FFMAX(0, hinc
) - hinc
+ (y
- vinc
) * stride
,
239 FFMIN(stride
+ hinc
, stride
));
242 cdg_fill_wrapper(0, 0, out
,
243 0, CDG_FULL_HEIGHT
- vinc
, in
, color
,
244 stride
, vinc
, stride
, roll_over
);
246 cdg_fill_wrapper(0, CDG_FULL_HEIGHT
+ vinc
, out
,
248 stride
, -1 * vinc
, stride
, roll_over
);
251 cdg_fill_wrapper(0, 0, out
,
252 CDG_FULL_WIDTH
- hinc
, 0, in
, color
,
253 hinc
, CDG_FULL_HEIGHT
, stride
, roll_over
);
255 cdg_fill_wrapper(CDG_FULL_WIDTH
+ hinc
, 0, out
,
257 -1 * hinc
, CDG_FULL_HEIGHT
, stride
, roll_over
);
261 static int cdg_decode_frame(AVCodecContext
*avctx
,
262 void *data
, int *got_frame
, AVPacket
*avpkt
)
265 int buf_size
= avpkt
->size
;
267 uint8_t command
, inst
;
268 uint8_t cdg_data
[CDG_DATA_SIZE
] = {0};
269 AVFrame
*frame
= data
;
270 CDGraphicsContext
*cc
= avctx
->priv_data
;
272 if (buf_size
< CDG_MINIMUM_PKT_SIZE
) {
273 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for decoder\n");
274 return AVERROR(EINVAL
);
276 if (buf_size
> CDG_HEADER_SIZE
+ CDG_DATA_SIZE
) {
277 av_log(avctx
, AV_LOG_ERROR
, "buffer too big for decoder\n");
278 return AVERROR(EINVAL
);
281 bytestream2_init(&gb
, avpkt
->data
, avpkt
->size
);
283 if ((ret
= ff_reget_buffer(avctx
, cc
->frame
)) < 0)
285 if (!avctx
->frame_number
) {
286 memset(cc
->frame
->data
[0], 0, cc
->frame
->linesize
[0] * avctx
->height
);
287 memset(cc
->frame
->data
[1], 0, AVPALETTE_SIZE
);
290 command
= bytestream2_get_byte(&gb
);
291 inst
= bytestream2_get_byte(&gb
);
293 bytestream2_skip(&gb
, 2);
294 bytestream2_get_buffer(&gb
, cdg_data
, sizeof(cdg_data
));
296 if ((command
& CDG_MASK
) == CDG_COMMAND
) {
298 case CDG_INST_MEMORY_PRESET
:
299 if (!(cdg_data
[1] & 0x0F))
300 memset(cc
->frame
->data
[0], cdg_data
[0] & 0x0F,
301 cc
->frame
->linesize
[0] * CDG_FULL_HEIGHT
);
303 case CDG_INST_LOAD_PAL_LO
:
304 case CDG_INST_LOAD_PAL_HIGH
:
305 if (buf_size
- CDG_HEADER_SIZE
< CDG_DATA_SIZE
) {
306 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for loading palette\n");
307 return AVERROR(EINVAL
);
310 cdg_load_palette(cc
, cdg_data
, inst
== CDG_INST_LOAD_PAL_LO
);
312 case CDG_INST_BORDER_PRESET
:
313 cdg_border_preset(cc
, cdg_data
);
315 case CDG_INST_TILE_BLOCK_XOR
:
316 case CDG_INST_TILE_BLOCK
:
317 if (buf_size
- CDG_HEADER_SIZE
< CDG_DATA_SIZE
) {
318 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for drawing tile\n");
319 return AVERROR(EINVAL
);
322 ret
= cdg_tile_block(cc
, cdg_data
, inst
== CDG_INST_TILE_BLOCK_XOR
);
324 av_log(avctx
, AV_LOG_ERROR
, "tile is out of range\n");
328 case CDG_INST_SCROLL_PRESET
:
329 case CDG_INST_SCROLL_COPY
:
330 if (buf_size
- CDG_HEADER_SIZE
< CDG_MINIMUM_SCROLL_SIZE
) {
331 av_log(avctx
, AV_LOG_ERROR
, "buffer too small for scrolling\n");
332 return AVERROR(EINVAL
);
335 if ((ret
= ff_get_buffer(avctx
, frame
, AV_GET_BUFFER_FLAG_REF
)) < 0)
338 cdg_scroll(cc
, cdg_data
, frame
, inst
== CDG_INST_SCROLL_COPY
);
339 av_frame_unref(cc
->frame
);
340 ret
= av_frame_ref(cc
->frame
, frame
);
348 if (!frame
->data
[0]) {
349 ret
= av_frame_ref(frame
, cc
->frame
);
361 static av_cold
int cdg_decode_end(AVCodecContext
*avctx
)
363 CDGraphicsContext
*cc
= avctx
->priv_data
;
365 av_frame_free(&cc
->frame
);
370 AVCodec ff_cdgraphics_decoder
= {
371 .name
= "cdgraphics",
372 .long_name
= NULL_IF_CONFIG_SMALL("CD Graphics video"),
373 .type
= AVMEDIA_TYPE_VIDEO
,
374 .id
= AV_CODEC_ID_CDGRAPHICS
,
375 .priv_data_size
= sizeof(CDGraphicsContext
),
376 .init
= cdg_decode_init
,
377 .close
= cdg_decode_end
,
378 .decode
= cdg_decode_frame
,
379 .capabilities
= CODEC_CAP_DR1
,