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 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 65typedef struct CDGraphicsContext { 66 AVFrame frame; 67 int hscroll; 68 int vscroll; 69} CDGraphicsContext; 70 71static 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 81static 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 94static 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 116static 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 134static 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 173static 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 185static 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 194static 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 206static 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 268static 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 360static 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 370AVCodec cdgraphics_decoder = { 371 "cdgraphics", 372 AVMEDIA_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}; 382