You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

377 lines
11KB

  1. /*
  2. * CD Graphics Video Decoder
  3. * Copyright (c) 2009 Michael Tison
  4. *
  5. * This file is part of Libav.
  6. *
  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.
  11. *
  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.
  16. *
  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
  20. */
  21. #include "avcodec.h"
  22. #include "bytestream.h"
  23. #include "internal.h"
  24. /**
  25. * @file
  26. * @brief CD Graphics Video Decoder
  27. * @author Michael Tison
  28. * @see http://wiki.multimedia.cx/index.php?title=CD_Graphics
  29. * @see http://www.ccs.neu.edu/home/bchafy/cdb/info/cdg
  30. */
  31. /// default screen sizes
  32. #define CDG_FULL_WIDTH 300
  33. #define CDG_FULL_HEIGHT 216
  34. #define CDG_DISPLAY_WIDTH 294
  35. #define CDG_DISPLAY_HEIGHT 204
  36. #define CDG_BORDER_WIDTH 6
  37. #define CDG_BORDER_HEIGHT 12
  38. /// masks
  39. #define CDG_COMMAND 0x09
  40. #define CDG_MASK 0x3F
  41. /// instruction codes
  42. #define CDG_INST_MEMORY_PRESET 1
  43. #define CDG_INST_BORDER_PRESET 2
  44. #define CDG_INST_TILE_BLOCK 6
  45. #define CDG_INST_SCROLL_PRESET 20
  46. #define CDG_INST_SCROLL_COPY 24
  47. #define CDG_INST_LOAD_PAL_LO 30
  48. #define CDG_INST_LOAD_PAL_HIGH 31
  49. #define CDG_INST_TILE_BLOCK_XOR 38
  50. /// data sizes
  51. #define CDG_PACKET_SIZE 24
  52. #define CDG_DATA_SIZE 16
  53. #define CDG_TILE_HEIGHT 12
  54. #define CDG_TILE_WIDTH 6
  55. #define CDG_MINIMUM_PKT_SIZE 6
  56. #define CDG_MINIMUM_SCROLL_SIZE 3
  57. #define CDG_HEADER_SIZE 8
  58. #define CDG_PALETTE_SIZE 16
  59. typedef struct CDGraphicsContext {
  60. AVFrame *frame;
  61. int hscroll;
  62. int vscroll;
  63. } CDGraphicsContext;
  64. static av_cold int cdg_decode_init(AVCodecContext *avctx)
  65. {
  66. CDGraphicsContext *cc = avctx->priv_data;
  67. cc->frame = av_frame_alloc();
  68. if (!cc->frame)
  69. return AVERROR(ENOMEM);
  70. avctx->width = CDG_FULL_WIDTH;
  71. avctx->height = CDG_FULL_HEIGHT;
  72. avctx->pix_fmt = AV_PIX_FMT_PAL8;
  73. return 0;
  74. }
  75. static void cdg_border_preset(CDGraphicsContext *cc, uint8_t *data)
  76. {
  77. int y;
  78. int lsize = cc->frame->linesize[0];
  79. uint8_t *buf = cc->frame->data[0];
  80. int color = data[0] & 0x0F;
  81. if (!(data[1] & 0x0F)) {
  82. /// fill the top and bottom borders
  83. memset(buf, color, CDG_BORDER_HEIGHT * lsize);
  84. memset(buf + (CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT) * lsize,
  85. color, CDG_BORDER_HEIGHT * lsize);
  86. /// fill the side borders
  87. for (y = CDG_BORDER_HEIGHT; y < CDG_FULL_HEIGHT - CDG_BORDER_HEIGHT; y++) {
  88. memset(buf + y * lsize, color, CDG_BORDER_WIDTH);
  89. memset(buf + CDG_FULL_WIDTH - CDG_BORDER_WIDTH + y * lsize,
  90. color, CDG_BORDER_WIDTH);
  91. }
  92. }
  93. }
  94. static void cdg_load_palette(CDGraphicsContext *cc, uint8_t *data, int low)
  95. {
  96. uint8_t r, g, b;
  97. uint16_t color;
  98. int i;
  99. int array_offset = low ? 0 : 8;
  100. uint32_t *palette = (uint32_t *) cc->frame->data[1];
  101. for (i = 0; i < 8; i++) {
  102. color = (data[2 * i] << 6) + (data[2 * i + 1] & 0x3F);
  103. r = ((color >> 8) & 0x000F) * 17;
  104. g = ((color >> 4) & 0x000F) * 17;
  105. b = ((color ) & 0x000F) * 17;
  106. palette[i + array_offset] = r << 16 | g << 8 | b;
  107. }
  108. cc->frame->palette_has_changed = 1;
  109. }
  110. static int cdg_tile_block(CDGraphicsContext *cc, uint8_t *data, int b)
  111. {
  112. unsigned ci, ri;
  113. int color;
  114. int x, y;
  115. int ai;
  116. int stride = cc->frame->linesize[0];
  117. uint8_t *buf = cc->frame->data[0];
  118. ri = (data[2] & 0x1F) * CDG_TILE_HEIGHT + cc->vscroll;
  119. ci = (data[3] & 0x3F) * CDG_TILE_WIDTH + cc->hscroll;
  120. if (ri > (CDG_FULL_HEIGHT - CDG_TILE_HEIGHT))
  121. return AVERROR(EINVAL);
  122. if (ci > (CDG_FULL_WIDTH - CDG_TILE_WIDTH))
  123. return AVERROR(EINVAL);
  124. for (y = 0; y < CDG_TILE_HEIGHT; y++) {
  125. for (x = 0; x < CDG_TILE_WIDTH; x++) {
  126. if (!((data[4 + y] >> (5 - x)) & 0x01))
  127. color = data[0] & 0x0F;
  128. else
  129. color = data[1] & 0x0F;
  130. ai = ci + x + (stride * (ri + y));
  131. if (b)
  132. color ^= buf[ai];
  133. buf[ai] = color;
  134. }
  135. }
  136. return 0;
  137. }
  138. #define UP 2
  139. #define DOWN 1
  140. #define LEFT 2
  141. #define RIGHT 1
  142. static void cdg_copy_rect_buf(int out_tl_x, int out_tl_y, uint8_t *out,
  143. int in_tl_x, int in_tl_y, uint8_t *in,
  144. int w, int h, int stride)
  145. {
  146. int y;
  147. in += in_tl_x + in_tl_y * stride;
  148. out += out_tl_x + out_tl_y * stride;
  149. for (y = 0; y < h; y++)
  150. memcpy(out + y * stride, in + y * stride, w);
  151. }
  152. static void cdg_fill_rect_preset(int tl_x, int tl_y, uint8_t *out,
  153. int color, int w, int h, int stride)
  154. {
  155. int y;
  156. for (y = tl_y; y < tl_y + h; y++)
  157. memset(out + tl_x + y * stride, color, w);
  158. }
  159. static void cdg_fill_wrapper(int out_tl_x, int out_tl_y, uint8_t *out,
  160. int in_tl_x, int in_tl_y, uint8_t *in,
  161. int color, int w, int h, int stride, int roll)
  162. {
  163. if (roll) {
  164. cdg_copy_rect_buf(out_tl_x, out_tl_y, out, in_tl_x, in_tl_y,
  165. in, w, h, stride);
  166. } else {
  167. cdg_fill_rect_preset(out_tl_x, out_tl_y, out, color, w, h, stride);
  168. }
  169. }
  170. static void cdg_scroll(CDGraphicsContext *cc, uint8_t *data,
  171. AVFrame *new_frame, int roll_over)
  172. {
  173. int color;
  174. int hscmd, h_off, hinc, vscmd, v_off, vinc;
  175. int y;
  176. int stride = cc->frame->linesize[0];
  177. uint8_t *in = cc->frame->data[0];
  178. uint8_t *out = new_frame->data[0];
  179. color = data[0] & 0x0F;
  180. hscmd = (data[1] & 0x30) >> 4;
  181. vscmd = (data[2] & 0x30) >> 4;
  182. h_off = FFMIN(data[1] & 0x07, CDG_BORDER_WIDTH - 1);
  183. v_off = FFMIN(data[2] & 0x0F, CDG_BORDER_HEIGHT - 1);
  184. /// find the difference and save the offset for cdg_tile_block usage
  185. hinc = h_off - cc->hscroll;
  186. vinc = v_off - cc->vscroll;
  187. cc->hscroll = h_off;
  188. cc->vscroll = v_off;
  189. if (vscmd == UP)
  190. vinc -= 12;
  191. if (vscmd == DOWN)
  192. vinc += 12;
  193. if (hscmd == LEFT)
  194. hinc -= 6;
  195. if (hscmd == RIGHT)
  196. hinc += 6;
  197. if (!hinc && !vinc)
  198. return;
  199. memcpy(new_frame->data[1], cc->frame->data[1], CDG_PALETTE_SIZE * 4);
  200. for (y = FFMAX(0, vinc); y < FFMIN(CDG_FULL_HEIGHT + vinc, CDG_FULL_HEIGHT); y++)
  201. memcpy(out + FFMAX(0, hinc) + stride * y,
  202. in + FFMAX(0, hinc) - hinc + (y - vinc) * stride,
  203. FFMIN(stride + hinc, stride));
  204. if (vinc > 0)
  205. cdg_fill_wrapper(0, 0, out,
  206. 0, CDG_FULL_HEIGHT - vinc, in, color,
  207. stride, vinc, stride, roll_over);
  208. else if (vinc < 0)
  209. cdg_fill_wrapper(0, CDG_FULL_HEIGHT + vinc, out,
  210. 0, 0, in, color,
  211. stride, -1 * vinc, stride, roll_over);
  212. if (hinc > 0)
  213. cdg_fill_wrapper(0, 0, out,
  214. CDG_FULL_WIDTH - hinc, 0, in, color,
  215. hinc, CDG_FULL_HEIGHT, stride, roll_over);
  216. else if (hinc < 0)
  217. cdg_fill_wrapper(CDG_FULL_WIDTH + hinc, 0, out,
  218. 0, 0, in, color,
  219. -1 * hinc, CDG_FULL_HEIGHT, stride, roll_over);
  220. }
  221. static int cdg_decode_frame(AVCodecContext *avctx,
  222. void *data, int *got_frame, AVPacket *avpkt)
  223. {
  224. GetByteContext gb;
  225. int buf_size = avpkt->size;
  226. int ret;
  227. uint8_t command, inst;
  228. uint8_t cdg_data[CDG_DATA_SIZE];
  229. AVFrame *frame = data;
  230. CDGraphicsContext *cc = avctx->priv_data;
  231. bytestream2_init(&gb, avpkt->data, avpkt->size);
  232. ret = ff_reget_buffer(avctx, cc->frame);
  233. if (ret) {
  234. av_log(avctx, AV_LOG_ERROR, "reget_buffer() failed\n");
  235. return ret;
  236. }
  237. if (!avctx->frame_number)
  238. memset(cc->frame->data[0], 0, cc->frame->linesize[0] * avctx->height);
  239. command = bytestream2_get_byte(&gb);
  240. inst = bytestream2_get_byte(&gb);
  241. inst &= CDG_MASK;
  242. bytestream2_skip(&gb, 2);
  243. bytestream2_get_buffer(&gb, cdg_data, sizeof(cdg_data));
  244. if ((command & CDG_MASK) == CDG_COMMAND) {
  245. switch (inst) {
  246. case CDG_INST_MEMORY_PRESET:
  247. if (!(cdg_data[1] & 0x0F))
  248. memset(cc->frame->data[0], cdg_data[0] & 0x0F,
  249. cc->frame->linesize[0] * CDG_FULL_HEIGHT);
  250. break;
  251. case CDG_INST_LOAD_PAL_LO:
  252. case CDG_INST_LOAD_PAL_HIGH:
  253. if (buf_size - CDG_HEADER_SIZE < CDG_DATA_SIZE) {
  254. av_log(avctx, AV_LOG_ERROR, "buffer too small for loading palette\n");
  255. return AVERROR(EINVAL);
  256. }
  257. cdg_load_palette(cc, cdg_data, inst == CDG_INST_LOAD_PAL_LO);
  258. break;
  259. case CDG_INST_BORDER_PRESET:
  260. cdg_border_preset(cc, cdg_data);
  261. break;
  262. case CDG_INST_TILE_BLOCK_XOR:
  263. case CDG_INST_TILE_BLOCK:
  264. if (buf_size - CDG_HEADER_SIZE < CDG_DATA_SIZE) {
  265. av_log(avctx, AV_LOG_ERROR, "buffer too small for drawing tile\n");
  266. return AVERROR(EINVAL);
  267. }
  268. ret = cdg_tile_block(cc, cdg_data, inst == CDG_INST_TILE_BLOCK_XOR);
  269. if (ret) {
  270. av_log(avctx, AV_LOG_ERROR, "tile is out of range\n");
  271. return ret;
  272. }
  273. break;
  274. case CDG_INST_SCROLL_PRESET:
  275. case CDG_INST_SCROLL_COPY:
  276. if (buf_size - CDG_HEADER_SIZE < CDG_MINIMUM_SCROLL_SIZE) {
  277. av_log(avctx, AV_LOG_ERROR, "buffer too small for scrolling\n");
  278. return AVERROR(EINVAL);
  279. }
  280. ret = ff_get_buffer(avctx, frame, AV_GET_BUFFER_FLAG_REF);
  281. if (ret) {
  282. av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n");
  283. return ret;
  284. }
  285. cdg_scroll(cc, cdg_data, frame, inst == CDG_INST_SCROLL_COPY);
  286. av_frame_unref(cc->frame);
  287. ret = av_frame_ref(cc->frame, frame);
  288. if (ret < 0)
  289. return ret;
  290. break;
  291. default:
  292. break;
  293. }
  294. if (!frame->data[0]) {
  295. ret = av_frame_ref(frame, cc->frame);
  296. if (ret < 0)
  297. return ret;
  298. }
  299. *got_frame = 1;
  300. } else {
  301. *got_frame = 0;
  302. }
  303. return avpkt->size;
  304. }
  305. static av_cold int cdg_decode_end(AVCodecContext *avctx)
  306. {
  307. CDGraphicsContext *cc = avctx->priv_data;
  308. av_frame_free(&cc->frame);
  309. return 0;
  310. }
  311. AVCodec ff_cdgraphics_decoder = {
  312. .name = "cdgraphics",
  313. .long_name = NULL_IF_CONFIG_SMALL("CD Graphics video"),
  314. .type = AVMEDIA_TYPE_VIDEO,
  315. .id = AV_CODEC_ID_CDGRAPHICS,
  316. .priv_data_size = sizeof(CDGraphicsContext),
  317. .init = cdg_decode_init,
  318. .close = cdg_decode_end,
  319. .decode = cdg_decode_frame,
  320. .capabilities = AV_CODEC_CAP_DR1,
  321. };