Libav 0.7.1
|
00001 /* 00002 * ASCII/ANSI art decoder 00003 * Copyright (c) 2010 Peter Ross <pross@xvid.org> 00004 * 00005 * This file is part of Libav. 00006 * 00007 * Libav is free software; you can redistribute it and/or 00008 * modify it under the terms of the GNU Lesser General Public 00009 * License as published by the Free Software Foundation; either 00010 * version 2.1 of the License, or (at your option) any later version. 00011 * 00012 * Libav is distributed in the hope that it will be useful, 00013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00015 * Lesser General Public License for more details. 00016 * 00017 * You should have received a copy of the GNU Lesser General Public 00018 * License along with Libav; if not, write to the Free Software 00019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00020 */ 00021 00027 #include "libavutil/lfg.h" 00028 #include "avcodec.h" 00029 #include "cga_data.h" 00030 00031 #define ATTR_BOLD 0x01 00032 #define ATTR_FAINT 0x02 00033 #define ATTR_UNDERLINE 0x08 00034 #define ATTR_BLINK 0x10 00035 #define ATTR_REVERSE 0x40 00036 #define ATTR_CONCEALED 0x80 00038 #define DEFAULT_FG_COLOR 7 00039 #define DEFAULT_BG_COLOR 0 00040 #define DEFAULT_SCREEN_MODE 3 00042 #define FONT_WIDTH 8 00045 static const uint8_t ansi_to_cga[16] = { 00046 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 00047 }; 00048 00049 typedef struct { 00050 AVFrame frame; 00051 int x; 00052 int y; 00053 int sx; 00054 int sy; 00055 const uint8_t* font; 00056 int font_height; 00057 int attributes; 00058 int fg; 00059 int bg; 00061 /* ansi parser state machine */ 00062 enum { 00063 STATE_NORMAL = 0, 00064 STATE_ESCAPE, 00065 STATE_CODE, 00066 STATE_MUSIC_PREAMBLE 00067 } state; 00068 #define MAX_NB_ARGS 4 00069 int args[MAX_NB_ARGS]; 00070 int nb_args; 00071 } AnsiContext; 00072 00073 static av_cold int decode_init(AVCodecContext *avctx) 00074 { 00075 AnsiContext *s = avctx->priv_data; 00076 avctx->pix_fmt = PIX_FMT_PAL8; 00077 00078 /* defaults */ 00079 s->font = ff_vga16_font; 00080 s->font_height = 16; 00081 s->fg = DEFAULT_FG_COLOR; 00082 s->bg = DEFAULT_BG_COLOR; 00083 00084 if (!avctx->width || !avctx->height) 00085 avcodec_set_dimensions(avctx, 80<<3, 25<<4); 00086 00087 return 0; 00088 } 00089 00090 static void hscroll(AVCodecContext *avctx) 00091 { 00092 AnsiContext *s = avctx->priv_data; 00093 int i; 00094 00095 if (s->y < avctx->height - s->font_height) { 00096 s->y += s->font_height; 00097 return; 00098 } 00099 00100 i = 0; 00101 for (; i < avctx->height - s->font_height; i++) 00102 memcpy(s->frame.data[0] + i * s->frame.linesize[0], 00103 s->frame.data[0] + (i + s->font_height) * s->frame.linesize[0], 00104 avctx->width); 00105 for (; i < avctx->height; i++) 00106 memset(s->frame.data[0] + i * s->frame.linesize[0], 00107 DEFAULT_BG_COLOR, avctx->width); 00108 } 00109 00110 static void erase_line(AVCodecContext * avctx, int xoffset, int xlength) 00111 { 00112 AnsiContext *s = avctx->priv_data; 00113 int i; 00114 for (i = 0; i < s->font_height; i++) 00115 memset(s->frame.data[0] + (s->y + i)*s->frame.linesize[0] + xoffset, 00116 DEFAULT_BG_COLOR, xlength); 00117 } 00118 00119 static void erase_screen(AVCodecContext *avctx) 00120 { 00121 AnsiContext *s = avctx->priv_data; 00122 int i; 00123 for (i = 0; i < avctx->height; i++) 00124 memset(s->frame.data[0] + i * s->frame.linesize[0], DEFAULT_BG_COLOR, avctx->width); 00125 s->x = s->y = 0; 00126 } 00127 00131 static void draw_char(AVCodecContext *avctx, int c) 00132 { 00133 AnsiContext *s = avctx->priv_data; 00134 int fg = s->fg; 00135 int bg = s->bg; 00136 00137 if ((s->attributes & ATTR_BOLD)) 00138 fg += 8; 00139 if ((s->attributes & ATTR_BLINK)) 00140 bg += 8; 00141 if ((s->attributes & ATTR_REVERSE)) 00142 FFSWAP(int, fg, bg); 00143 if ((s->attributes & ATTR_CONCEALED)) 00144 fg = bg; 00145 ff_draw_pc_font(s->frame.data[0] + s->y * s->frame.linesize[0] + s->x, 00146 s->frame.linesize[0], s->font, s->font_height, c, fg, bg); 00147 s->x += FONT_WIDTH; 00148 if (s->x >= avctx->width) { 00149 s->x = 0; 00150 hscroll(avctx); 00151 } 00152 } 00153 00158 static int execute_code(AVCodecContext * avctx, int c) 00159 { 00160 AnsiContext *s = avctx->priv_data; 00161 int ret, i, width, height; 00162 switch(c) { 00163 case 'A': //Cursor Up 00164 s->y = FFMAX(s->y - (s->nb_args > 0 ? s->args[0]*s->font_height : s->font_height), 0); 00165 break; 00166 case 'B': //Cursor Down 00167 s->y = FFMIN(s->y + (s->nb_args > 0 ? s->args[0]*s->font_height : s->font_height), avctx->height - s->font_height); 00168 break; 00169 case 'C': //Cursor Right 00170 s->x = FFMIN(s->x + (s->nb_args > 0 ? s->args[0]*FONT_WIDTH : FONT_WIDTH), avctx->width - FONT_WIDTH); 00171 break; 00172 case 'D': //Cursor Left 00173 s->x = FFMAX(s->x - (s->nb_args > 0 ? s->args[0]*FONT_WIDTH : FONT_WIDTH), 0); 00174 break; 00175 case 'H': //Cursor Position 00176 case 'f': //Horizontal and Vertical Position 00177 s->y = s->nb_args > 0 ? av_clip((s->args[0] - 1)*s->font_height, 0, avctx->height - s->font_height) : 0; 00178 s->x = s->nb_args > 1 ? av_clip((s->args[1] - 1)*FONT_WIDTH, 0, avctx->width - FONT_WIDTH) : 0; 00179 break; 00180 case 'h': //set creen mode 00181 case 'l': //reset screen mode 00182 if (s->nb_args < 2) 00183 s->args[0] = DEFAULT_SCREEN_MODE; 00184 switch(s->args[0]) { 00185 case 0: case 1: case 4: case 5: case 13: case 19: //320x200 (25 rows) 00186 s->font = ff_cga_font; 00187 s->font_height = 8; 00188 width = 40<<3; 00189 height = 25<<3; 00190 break; 00191 case 2: case 3: //640x400 (25 rows) 00192 s->font = ff_vga16_font; 00193 s->font_height = 16; 00194 width = 80<<3; 00195 height = 25<<4; 00196 break; 00197 case 6: case 14: //640x200 (25 rows) 00198 s->font = ff_cga_font; 00199 s->font_height = 8; 00200 width = 80<<3; 00201 height = 25<<3; 00202 break; 00203 case 7: //set line wrapping 00204 break; 00205 case 15: case 16: //640x350 (43 rows) 00206 s->font = ff_cga_font; 00207 s->font_height = 8; 00208 width = 80<<3; 00209 height = 43<<3; 00210 break; 00211 case 17: case 18: //640x480 (60 rows) 00212 s->font = ff_cga_font; 00213 s->font_height = 8; 00214 width = 80<<3; 00215 height = 60<<4; 00216 break; 00217 default: 00218 av_log_ask_for_sample(avctx, "unsupported screen mode\n"); 00219 } 00220 if (width != avctx->width || height != avctx->height) { 00221 if (s->frame.data[0]) 00222 avctx->release_buffer(avctx, &s->frame); 00223 avcodec_set_dimensions(avctx, width, height); 00224 ret = avctx->get_buffer(avctx, &s->frame); 00225 if (ret < 0) { 00226 av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n"); 00227 return ret; 00228 } 00229 s->frame.pict_type = AV_PICTURE_TYPE_I; 00230 s->frame.palette_has_changed = 1; 00231 memcpy(s->frame.data[1], ff_cga_palette, 16 * 4); 00232 erase_screen(avctx); 00233 } else if (c == 'l') { 00234 erase_screen(avctx); 00235 } 00236 break; 00237 case 'J': //Erase in Page 00238 switch (s->args[0]) { 00239 case 0: 00240 erase_line(avctx, s->x, avctx->width - s->x); 00241 if (s->y < avctx->height - s->font_height) 00242 memset(s->frame.data[0] + (s->y + s->font_height)*s->frame.linesize[0], 00243 DEFAULT_BG_COLOR, (avctx->height - s->y - s->font_height)*s->frame.linesize[0]); 00244 break; 00245 case 1: 00246 erase_line(avctx, 0, s->x); 00247 if (s->y > 0) 00248 memset(s->frame.data[0], DEFAULT_BG_COLOR, s->y * s->frame.linesize[0]); 00249 break; 00250 case 2: 00251 erase_screen(avctx); 00252 } 00253 break; 00254 case 'K': //Erase in Line 00255 switch(s->args[0]) { 00256 case 0: 00257 erase_line(avctx, s->x, avctx->width - s->x); 00258 break; 00259 case 1: 00260 erase_line(avctx, 0, s->x); 00261 break; 00262 case 2: 00263 erase_line(avctx, 0, avctx->width); 00264 } 00265 break; 00266 case 'm': //Select Graphics Rendition 00267 if (s->nb_args == 0) { 00268 s->nb_args = 1; 00269 s->args[0] = 0; 00270 } 00271 for (i = 0; i < FFMIN(s->nb_args, MAX_NB_ARGS); i++) { 00272 int m = s->args[i]; 00273 if (m == 0) { 00274 s->attributes = 0; 00275 s->fg = DEFAULT_FG_COLOR; 00276 s->bg = DEFAULT_BG_COLOR; 00277 } else if (m == 1 || m == 2 || m == 4 || m == 5 || m == 7 || m == 8) { 00278 s->attributes |= 1 << (m - 1); 00279 } else if (m >= 30 && m <= 38) { 00280 s->fg = ansi_to_cga[m - 30]; 00281 } else if (m == 39) { 00282 s->fg = ansi_to_cga[DEFAULT_FG_COLOR]; 00283 } else if (m >= 40 && m <= 47) { 00284 s->bg = ansi_to_cga[m - 40]; 00285 } else if (m == 49) { 00286 s->fg = ansi_to_cga[DEFAULT_BG_COLOR]; 00287 } else { 00288 av_log_ask_for_sample(avctx, "unsupported rendition parameter\n"); 00289 } 00290 } 00291 break; 00292 case 'n': //Device Status Report 00293 case 'R': //report current line and column 00294 /* ignore */ 00295 break; 00296 case 's': //Save Cursor Position 00297 s->sx = s->x; 00298 s->sy = s->y; 00299 break; 00300 case 'u': //Restore Cursor Position 00301 s->x = av_clip(s->sx, 0, avctx->width - FONT_WIDTH); 00302 s->y = av_clip(s->sy, 0, avctx->height - s->font_height); 00303 break; 00304 default: 00305 av_log_ask_for_sample(avctx, "unsupported escape code\n"); 00306 break; 00307 } 00308 return 0; 00309 } 00310 00311 static int decode_frame(AVCodecContext *avctx, 00312 void *data, int *data_size, 00313 AVPacket *avpkt) 00314 { 00315 AnsiContext *s = avctx->priv_data; 00316 uint8_t *buf = avpkt->data; 00317 int buf_size = avpkt->size; 00318 const uint8_t *buf_end = buf+buf_size; 00319 int ret, i, count; 00320 00321 ret = avctx->reget_buffer(avctx, &s->frame); 00322 if (ret < 0){ 00323 av_log(avctx, AV_LOG_ERROR, "get_buffer() failed\n"); 00324 return ret; 00325 } 00326 s->frame.pict_type = AV_PICTURE_TYPE_I; 00327 s->frame.palette_has_changed = 1; 00328 memcpy(s->frame.data[1], ff_cga_palette, 16 * 4); 00329 00330 while(buf < buf_end) { 00331 switch(s->state) { 00332 case STATE_NORMAL: 00333 switch (buf[0]) { 00334 case 0x00: //NUL 00335 case 0x07: //BEL 00336 case 0x1A: //SUB 00337 /* ignore */ 00338 break; 00339 case 0x08: //BS 00340 s->x = FFMAX(s->x - 1, 0); 00341 break; 00342 case 0x09: //HT 00343 i = s->x / FONT_WIDTH; 00344 count = ((i + 8) & ~7) - i; 00345 for (i = 0; i < count; i++) 00346 draw_char(avctx, ' '); 00347 break; 00348 case 0x0A: //LF 00349 hscroll(avctx); 00350 case 0x0D: //CR 00351 s->x = 0; 00352 break; 00353 case 0x0C: //FF 00354 erase_screen(avctx); 00355 break; 00356 case 0x1B: //ESC 00357 s->state = STATE_ESCAPE; 00358 break; 00359 default: 00360 draw_char(avctx, buf[0]); 00361 } 00362 break; 00363 case STATE_ESCAPE: 00364 if (buf[0] == '[') { 00365 s->state = STATE_CODE; 00366 s->nb_args = 0; 00367 s->args[0] = 0; 00368 } else { 00369 s->state = STATE_NORMAL; 00370 draw_char(avctx, 0x1B); 00371 return -1; 00372 continue; 00373 } 00374 break; 00375 case STATE_CODE: 00376 switch(buf[0]) { 00377 case '0': case '1': case '2': case '3': case '4': 00378 case '5': case '6': case '7': case '8': case '9': 00379 if (s->nb_args < MAX_NB_ARGS) 00380 s->args[s->nb_args] = s->args[s->nb_args] * 10 + buf[0] - '0'; 00381 break; 00382 case ';': 00383 s->nb_args++; 00384 if (s->nb_args < MAX_NB_ARGS) 00385 s->args[s->nb_args] = 0; 00386 break; 00387 case 'M': 00388 s->state = STATE_MUSIC_PREAMBLE; 00389 break; 00390 case '=': case '?': 00391 /* ignore */ 00392 break; 00393 default: 00394 if (s->nb_args > MAX_NB_ARGS) 00395 av_log(avctx, AV_LOG_WARNING, "args overflow (%i)\n", s->nb_args); 00396 if (s->nb_args < MAX_NB_ARGS && s->args[s->nb_args]) 00397 s->nb_args++; 00398 if (execute_code(avctx, buf[0]) < 0) 00399 return -1; 00400 s->state = STATE_NORMAL; 00401 } 00402 break; 00403 case STATE_MUSIC_PREAMBLE: 00404 if (buf[0] == 0x0E || buf[0] == 0x1B) 00405 s->state = STATE_NORMAL; 00406 /* ignore music data */ 00407 break; 00408 } 00409 buf++; 00410 } 00411 00412 *data_size = sizeof(AVFrame); 00413 *(AVFrame*)data = s->frame; 00414 return buf_size; 00415 } 00416 00417 static av_cold int decode_close(AVCodecContext *avctx) 00418 { 00419 AnsiContext *s = avctx->priv_data; 00420 if (s->frame.data[0]) 00421 avctx->release_buffer(avctx, &s->frame); 00422 return 0; 00423 } 00424 00425 AVCodec ff_ansi_decoder = { 00426 .name = "ansi", 00427 .type = AVMEDIA_TYPE_VIDEO, 00428 .id = CODEC_ID_ANSI, 00429 .priv_data_size = sizeof(AnsiContext), 00430 .init = decode_init, 00431 .close = decode_close, 00432 .decode = decode_frame, 00433 .capabilities = CODEC_CAP_DR1, 00434 .long_name = NULL_IF_CONFIG_SMALL("ASCII/ANSI art"), 00435 };