ffmpeg / libavcodec / cdgraphics.c @ b03a63d1
History | View | Annotate | Download (11.4 KB)
1 |
/*
|
---|---|
2 |
* CD Graphics Video Decoder
|
3 |
* Copyright (c) 2009 Michael Tison
|
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 |
|
25 |
/**
|
26 |
* @file libavcodec/cdgraphics.c
|
27 |
* @brief CD Graphics Video Decoder
|
28 |
* @author Michael Tison
|
29 |
* @sa http://wiki.multimedia.cx/index.php?title=CD_Graphics
|
30 |
* @sa http://www.ccs.neu.edu/home/bchafy/cdb/info/cdg
|
31 |
*/
|
32 |
|
33 |
/// default screen sizes
|
34 |
#define CDG_FULL_WIDTH 300 |
35 |
#define CDG_FULL_HEIGHT 216 |
36 |
#define CDG_DISPLAY_WIDTH 294 |
37 |
#define CDG_DISPLAY_HEIGHT 204 |
38 |
#define CDG_BORDER_WIDTH 6 |
39 |
#define CDG_BORDER_HEIGHT 12 |
40 |
|
41 |
/// masks
|
42 |
#define CDG_COMMAND 0x09 |
43 |
#define CDG_MASK 0x3F |
44 |
|
45 |
/// instruction codes
|
46 |
#define CDG_INST_MEMORY_PRESET 1 |
47 |
#define CDG_INST_BORDER_PRESET 2 |
48 |
#define CDG_INST_TILE_BLOCK 6 |
49 |
#define CDG_INST_SCROLL_PRESET 20 |
50 |
#define CDG_INST_SCROLL_COPY 24 |
51 |
#define CDG_INST_LOAD_PAL_LO 30 |
52 |
#define CDG_INST_LOAD_PAL_HIGH 31 |
53 |
#define CDG_INST_TILE_BLOCK_XOR 38 |
54 |
|
55 |
/// data sizes
|
56 |
#define CDG_PACKET_SIZE 24 |
57 |
#define CDG_DATA_SIZE 16 |
58 |
#define CDG_TILE_HEIGHT 12 |
59 |
#define CDG_TILE_WIDTH 6 |
60 |
#define CDG_MINIMUM_PKT_SIZE 6 |
61 |
#define CDG_MINIMUM_SCROLL_SIZE 3 |
62 |
#define CDG_HEADER_SIZE 8 |
63 |
#define CDG_PALETTE_SIZE 16 |
64 |
|
65 |
typedef struct CDGraphicsContext { |
66 |
AVFrame frame; |
67 |
int hscroll;
|
68 |
int vscroll;
|
69 |
} CDGraphicsContext; |
70 |
|
71 |
static void cdg_init_frame(AVFrame *frame) |
72 |
{ |
73 |
avcodec_get_frame_defaults(frame); |
74 |
frame->reference = 3;
|
75 |
frame->buffer_hints = FF_BUFFER_HINTS_VALID | |
76 |
FF_BUFFER_HINTS_READABLE | |
77 |
FF_BUFFER_HINTS_PRESERVE | |
78 |
FF_BUFFER_HINTS_REUSABLE; |
79 |
} |
80 |
|
81 |
static av_cold int cdg_decode_init(AVCodecContext *avctx) |
82 |
{ |
83 |
CDGraphicsContext *cc = avctx->priv_data; |
84 |
|
85 |
cdg_init_frame(&cc->frame); |
86 |
|
87 |
avctx->width = CDG_FULL_WIDTH; |
88 |
avctx->height = CDG_FULL_HEIGHT; |
89 |
avctx->pix_fmt = PIX_FMT_PAL8; |
90 |
|
91 |
return 0; |
92 |
} |
93 |
|
94 |
static void cdg_border_preset(CDGraphicsContext *cc, uint8_t *data) |
95 |
{ |
96 |
int y;
|
97 |
int lsize = cc->frame.linesize[0]; |
98 |
uint8_t *buf = cc->frame.data[0];
|
99 |
int color = data[0] & 0x0F; |
100 |
|
101 |
if (!(data[1] & 0x0F)) { |
102 |
/// fill the top and bottom borders
|
103 |
memset(buf, color, CDG_BORDER_HEIGHT * lsize); |
104 |
memset(buf + (CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT) * lsize, |
105 |
color, CDG_BORDER_HEIGHT * lsize); |
106 |
|
107 |
/// fill the side borders
|
108 |
for (y = CDG_BORDER_HEIGHT; y < CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT; y++) {
|
109 |
memset(buf + y * lsize, color, CDG_BORDER_WIDTH); |
110 |
memset(buf + CDG_FULL_WIDTH - CDG_BORDER_WIDTH + y * lsize, |
111 |
color, CDG_BORDER_WIDTH); |
112 |
} |
113 |
} |
114 |
} |
115 |
|
116 |
static void cdg_load_palette(CDGraphicsContext *cc, uint8_t *data, int low) |
117 |
{ |
118 |
uint8_t r, g, b; |
119 |
uint16_t color; |
120 |
int i;
|
121 |
int array_offset = low ? 0 : 8; |
122 |
uint32_t *palette = (uint32_t *) cc->frame.data[1];
|
123 |
|
124 |
for (i = 0; i < 8; i++) { |
125 |
color = (data[2 * i] << 6) + (data[2 * i + 1] & 0x3F); |
126 |
r = ((color >> 8) & 0x000F) * 17; |
127 |
g = ((color >> 4) & 0x000F) * 17; |
128 |
b = ((color ) & 0x000F) * 17; |
129 |
palette[i + array_offset] = r << 16 | g << 8 | b; |
130 |
} |
131 |
cc->frame.palette_has_changed = 1;
|
132 |
} |
133 |
|
134 |
static int cdg_tile_block(CDGraphicsContext *cc, uint8_t *data, int b) |
135 |
{ |
136 |
unsigned ci, ri;
|
137 |
int color;
|
138 |
int x, y;
|
139 |
int ai;
|
140 |
int stride = cc->frame.linesize[0]; |
141 |
uint8_t *buf = cc->frame.data[0];
|
142 |
|
143 |
ri = (data[2] & 0x1F) * CDG_TILE_HEIGHT + cc->vscroll; |
144 |
ci = (data[3] & 0x3F) * CDG_TILE_WIDTH + cc->hscroll; |
145 |
|
146 |
if (ri > (CDG_FULL_HEIGHT - CDG_TILE_HEIGHT))
|
147 |
return AVERROR(EINVAL);
|
148 |
if (ci > (CDG_FULL_WIDTH - CDG_TILE_WIDTH))
|
149 |
return AVERROR(EINVAL);
|
150 |
|
151 |
for (y = 0; y < CDG_TILE_HEIGHT; y++) { |
152 |
for (x = 0; x < CDG_TILE_WIDTH; x++) { |
153 |
if (!((data[4 + y] >> (5 - x)) & 0x01)) |
154 |
color = data[0] & 0x0F; |
155 |
else
|
156 |
color = data[1] & 0x0F; |
157 |
|
158 |
ai = ci + x + (stride * (ri + y)); |
159 |
if (b)
|
160 |
color ^= buf[ai]; |
161 |
buf[ai] = color; |
162 |
} |
163 |
} |
164 |
|
165 |
return 0; |
166 |
} |
167 |
|
168 |
#define UP 2 |
169 |
#define DOWN 1 |
170 |
#define LEFT 2 |
171 |
#define RIGHT 1 |
172 |
|
173 |
static void cdg_copy_rect_buf(int out_tl_x, int out_tl_y, uint8_t *out, |
174 |
int in_tl_x, int in_tl_y, uint8_t *in, |
175 |
int w, int h, int stride) |
176 |
{ |
177 |
int y;
|
178 |
|
179 |
in += in_tl_x + in_tl_y * stride; |
180 |
out += out_tl_x + out_tl_y * stride; |
181 |
for (y = 0; y < h; y++) |
182 |
memcpy(out + y * stride, in + y * stride, w); |
183 |
} |
184 |
|
185 |
static void cdg_fill_rect_preset(int tl_x, int tl_y, uint8_t *out, |
186 |
int color, int w, int h, int stride) |
187 |
{ |
188 |
int y;
|
189 |
|
190 |
for (y = tl_y; y < tl_y + h; y++)
|
191 |
memset(out + tl_x + y * stride, color, w); |
192 |
} |
193 |
|
194 |
static void cdg_fill_wrapper(int out_tl_x, int out_tl_y, uint8_t *out, |
195 |
int in_tl_x, int in_tl_y, uint8_t *in, |
196 |
int color, int w, int h, int stride, int roll) |
197 |
{ |
198 |
if (roll) {
|
199 |
cdg_copy_rect_buf(out_tl_x, out_tl_y, out, in_tl_x, in_tl_y, |
200 |
in, w, h, stride); |
201 |
} else {
|
202 |
cdg_fill_rect_preset(out_tl_x, out_tl_y, out, color, w, h, stride); |
203 |
} |
204 |
} |
205 |
|
206 |
static void cdg_scroll(CDGraphicsContext *cc, uint8_t *data, |
207 |
AVFrame *new_frame, int roll_over)
|
208 |
{ |
209 |
int color;
|
210 |
int hscmd, h_off, hinc, vscmd, v_off, vinc;
|
211 |
int y;
|
212 |
int stride = cc->frame.linesize[0]; |
213 |
uint8_t *in = cc->frame.data[0];
|
214 |
uint8_t *out = new_frame->data[0];
|
215 |
|
216 |
color = data[0] & 0x0F; |
217 |
hscmd = (data[1] & 0x30) >> 4; |
218 |
vscmd = (data[2] & 0x30) >> 4; |
219 |
|
220 |
h_off = FFMIN(data[1] & 0x07, CDG_BORDER_WIDTH - 1); |
221 |
v_off = FFMIN(data[2] & 0x07, CDG_BORDER_HEIGHT - 1); |
222 |
|
223 |
/// find the difference and save the offset for cdg_tile_block usage
|
224 |
hinc = h_off - cc->hscroll; |
225 |
vinc = v_off - cc->vscroll; |
226 |
cc->hscroll = h_off; |
227 |
cc->vscroll = v_off; |
228 |
|
229 |
if (vscmd == UP)
|
230 |
vinc -= 12;
|
231 |
if (vscmd == DOWN)
|
232 |
vinc += 12;
|
233 |
if (hscmd == LEFT)
|
234 |
hinc -= 6;
|
235 |
if (hscmd == RIGHT)
|
236 |
hinc += 6;
|
237 |
|
238 |
if (!hinc && !vinc)
|
239 |
return;
|
240 |
|
241 |
memcpy(new_frame->data[1], cc->frame.data[1], CDG_PALETTE_SIZE * 4); |
242 |
|
243 |
for (y = FFMAX(0, vinc); y < FFMIN(CDG_FULL_HEIGHT + vinc, CDG_FULL_HEIGHT); y++) |
244 |
memcpy(out + FFMAX(0, hinc) + stride * y,
|
245 |
in + FFMAX(0, hinc) - hinc + (y - vinc) * stride,
|
246 |
FFMIN(stride + hinc, stride)); |
247 |
|
248 |
if (vinc > 0) |
249 |
cdg_fill_wrapper(0, 0, out, |
250 |
0, CDG_FULL_HEIGHT - vinc, in, color,
|
251 |
stride, vinc, stride, roll_over); |
252 |
else if (vinc < 0) |
253 |
cdg_fill_wrapper(0, CDG_FULL_HEIGHT + vinc, out,
|
254 |
0, 0, in, color, |
255 |
stride, -1 * vinc, stride, roll_over);
|
256 |
|
257 |
if (hinc > 0) |
258 |
cdg_fill_wrapper(0, 0, out, |
259 |
CDG_FULL_WIDTH - hinc, 0, in, color,
|
260 |
hinc, CDG_FULL_HEIGHT, stride, roll_over); |
261 |
else if (hinc < 0) |
262 |
cdg_fill_wrapper(CDG_FULL_WIDTH + hinc, 0, out,
|
263 |
0, 0, in, color, |
264 |
-1 * hinc, CDG_FULL_HEIGHT, stride, roll_over);
|
265 |
|
266 |
} |
267 |
|
268 |
static int cdg_decode_frame(AVCodecContext *avctx, |
269 |
void *data, int *data_size, AVPacket *avpkt) |
270 |
{ |
271 |
const uint8_t *buf = avpkt->data;
|
272 |
int buf_size = avpkt->size;
|
273 |
int ret;
|
274 |
uint8_t command, inst; |
275 |
uint8_t cdg_data[CDG_DATA_SIZE]; |
276 |
AVFrame new_frame; |
277 |
CDGraphicsContext *cc = avctx->priv_data; |
278 |
|
279 |
if (buf_size < CDG_MINIMUM_PKT_SIZE) {
|
280 |
av_log(avctx, AV_LOG_ERROR, "buffer too small for decoder\n");
|
281 |
return AVERROR(EINVAL);
|
282 |
} |
283 |
|
284 |
ret = avctx->reget_buffer(avctx, &cc->frame); |
285 |
if (ret) {
|
286 |
av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n");
|
287 |
return ret;
|
288 |
} |
289 |
|
290 |
command = bytestream_get_byte(&buf); |
291 |
inst = bytestream_get_byte(&buf); |
292 |
inst &= CDG_MASK; |
293 |
buf += 2; /// skipping 2 unneeded bytes |
294 |
bytestream_get_buffer(&buf, cdg_data, buf_size - CDG_HEADER_SIZE); |
295 |
|
296 |
if ((command & CDG_MASK) == CDG_COMMAND) {
|
297 |
switch (inst) {
|
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);
|
302 |
break;
|
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);
|
308 |
} |
309 |
|
310 |
cdg_load_palette(cc, cdg_data, inst == CDG_INST_LOAD_PAL_LO); |
311 |
break;
|
312 |
case CDG_INST_BORDER_PRESET:
|
313 |
cdg_border_preset(cc, cdg_data); |
314 |
break;
|
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);
|
320 |
} |
321 |
|
322 |
ret = cdg_tile_block(cc, cdg_data, inst == CDG_INST_TILE_BLOCK_XOR); |
323 |
if (ret) {
|
324 |
av_log(avctx, AV_LOG_ERROR, "tile is out of range\n");
|
325 |
return ret;
|
326 |
} |
327 |
break;
|
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);
|
333 |
} |
334 |
|
335 |
cdg_init_frame(&new_frame); |
336 |
ret = avctx->get_buffer(avctx, &new_frame); |
337 |
if (ret) {
|
338 |
av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
|
339 |
return ret;
|
340 |
} |
341 |
|
342 |
cdg_scroll(cc, cdg_data, &new_frame, inst == CDG_INST_SCROLL_COPY); |
343 |
avctx->release_buffer(avctx, &cc->frame); |
344 |
cc->frame = new_frame; |
345 |
break;
|
346 |
default:
|
347 |
break;
|
348 |
} |
349 |
|
350 |
*data_size = sizeof(AVFrame);
|
351 |
} else {
|
352 |
*data_size = 0;
|
353 |
buf_size = 0;
|
354 |
} |
355 |
|
356 |
*(AVFrame *) data = cc->frame; |
357 |
return buf_size;
|
358 |
} |
359 |
|
360 |
static av_cold int cdg_decode_end(AVCodecContext *avctx) |
361 |
{ |
362 |
CDGraphicsContext *cc = avctx->priv_data; |
363 |
|
364 |
if (cc->frame.data[0]) |
365 |
avctx->release_buffer(avctx, &cc->frame); |
366 |
|
367 |
return 0; |
368 |
} |
369 |
|
370 |
AVCodec cdgraphics_decoder = { |
371 |
"cdgraphics",
|
372 |
CODEC_TYPE_VIDEO, |
373 |
CODEC_ID_CDGRAPHICS, |
374 |
sizeof(CDGraphicsContext),
|
375 |
cdg_decode_init, |
376 |
NULL,
|
377 |
cdg_decode_end, |
378 |
cdg_decode_frame, |
379 |
CODEC_CAP_DR1, |
380 |
.long_name = NULL_IF_CONFIG_SMALL("CD Graphics video"),
|
381 |
}; |