| @@ -0,0 +1,425 @@ |
| | 1 | /* |
| | 2 | * XPM image format |
| | 3 | * |
| | 4 | * Copyright (c) 2012 Paul B Mahol |
| | 5 | * Copyright (c) 2017 Paras Chadha |
| | 6 | * |
| | 7 | * This file is part of FFmpeg. |
| | 8 | * |
| | 9 | * FFmpeg is free software; you can redistribute it and/or |
| | 10 | * modify it under the terms of the GNU Lesser General Public |
| | 11 | * License as published by the Free Software Foundation; either |
| | 12 | * version 2.1 of the License, or (at your option) any later version. |
| | 13 | * |
| | 14 | * FFmpeg is distributed in the hope that it will be useful, |
| | 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| | 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| | 17 | * Lesser General Public License for more details. |
| | 18 | * |
| | 19 | * You should have received a copy of the GNU Lesser General Public |
| | 20 | * License along with FFmpeg; if not, write to the Free Software |
| | 21 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
| | 22 | */ |
| | 23 | |
| | 24 | #include "libavutil/parseutils.h" |
| | 25 | #include "libavutil/avstring.h" |
| | 26 | #include "avcodec.h" |
| | 27 | #include "internal.h" |
| | 28 | |
| | 29 | typedef struct XPMContext { |
| | 30 | uint32_t *pixels; |
| | 31 | int pixels_size; |
| | 32 | } XPMDecContext; |
| | 33 | |
| | 34 | typedef struct ColorEntry { |
| | 35 | const char *name; ///< a string representing the name of the color |
| | 36 | uint32_t rgb_color; ///< RGB values for the color |
| | 37 | } ColorEntry; |
| | 38 | |
| | 39 | static int color_table_compare(const void *lhs, const void *rhs) |
| | 40 | { |
| | 41 | return av_strcasecmp(lhs, ((const ColorEntry *)rhs)->name); |
| | 42 | } |
| | 43 | |
| | 44 | static const ColorEntry color_table[] = { |
| | 45 | { "AliceBlue", 0xFFF0F8FF }, |
| | 46 | { "AntiqueWhite", 0xFFFAEBD7 }, |
| | 47 | { "Aqua", 0xFF00FFFF }, |
| | 48 | { "Aquamarine", 0xFF7FFFD4 }, |
| | 49 | { "Azure", 0xFFF0FFFF }, |
| | 50 | { "Beige", 0xFFF5F5DC }, |
| | 51 | { "Bisque", 0xFFFFE4C4 }, |
| | 52 | { "Black", 0xFF000000 }, |
| | 53 | { "BlanchedAlmond", 0xFFFFEBCD }, |
| | 54 | { "Blue", 0xFF0000FF }, |
| | 55 | { "BlueViolet", 0xFF8A2BE2 }, |
| | 56 | { "Brown", 0xFFA52A2A }, |
| | 57 | { "BurlyWood", 0xFFDEB887 }, |
| | 58 | { "CadetBlue", 0xFF5F9EA0 }, |
| | 59 | { "Chartreuse", 0xFF7FFF00 }, |
| | 60 | { "Chocolate", 0xFFD2691E }, |
| | 61 | { "Coral", 0xFFFF7F50 }, |
| | 62 | { "CornflowerBlue", 0xFF6495ED }, |
| | 63 | { "Cornsilk", 0xFFFFF8DC }, |
| | 64 | { "Crimson", 0xFFDC143C }, |
| | 65 | { "Cyan", 0xFF00FFFF }, |
| | 66 | { "DarkBlue", 0xFF00008B }, |
| | 67 | { "DarkCyan", 0xFF008B8B }, |
| | 68 | { "DarkGoldenRod", 0xFFB8860B }, |
| | 69 | { "DarkGray", 0xFFA9A9A9 }, |
| | 70 | { "DarkGreen", 0xFF006400 }, |
| | 71 | { "DarkKhaki", 0xFFBDB76B }, |
| | 72 | { "DarkMagenta", 0xFF8B008B }, |
| | 73 | { "DarkOliveGreen", 0xFF556B2F }, |
| | 74 | { "Darkorange", 0xFFFF8C00 }, |
| | 75 | { "DarkOrchid", 0xFF9932CC }, |
| | 76 | { "DarkRed", 0xFF8B0000 }, |
| | 77 | { "DarkSalmon", 0xFFE9967A }, |
| | 78 | { "DarkSeaGreen", 0xFF8FBC8F }, |
| | 79 | { "DarkSlateBlue", 0xFF483D8B }, |
| | 80 | { "DarkSlateGray", 0xFF2F4F4F }, |
| | 81 | { "DarkTurquoise", 0xFF00CED1 }, |
| | 82 | { "DarkViolet", 0xFF9400D3 }, |
| | 83 | { "DeepPink", 0xFFFF1493 }, |
| | 84 | { "DeepSkyBlue", 0xFF00BFFF }, |
| | 85 | { "DimGray", 0xFF696969 }, |
| | 86 | { "DodgerBlue", 0xFF1E90FF }, |
| | 87 | { "FireBrick", 0xFFB22222 }, |
| | 88 | { "FloralWhite", 0xFFFFFAF0 }, |
| | 89 | { "ForestGreen", 0xFF228B22 }, |
| | 90 | { "Fuchsia", 0xFFFF00FF }, |
| | 91 | { "Gainsboro", 0xFFDCDCDC }, |
| | 92 | { "GhostWhite", 0xFFF8F8FF }, |
| | 93 | { "Gold", 0xFFFFD700 }, |
| | 94 | { "GoldenRod", 0xFFDAA520 }, |
| | 95 | { "Gray", 0xFF808080 }, |
| | 96 | { "Green", 0xFF008000 }, |
| | 97 | { "GreenYellow", 0xFFADFF2F }, |
| | 98 | { "HoneyDew", 0xFFF0FFF0 }, |
| | 99 | { "HotPink", 0xFFFF69B4 }, |
| | 100 | { "IndianRed", 0xFFCD5C5C }, |
| | 101 | { "Indigo", 0xFF4B0082 }, |
| | 102 | { "Ivory", 0xFFFFFFF0 }, |
| | 103 | { "Khaki", 0xFFF0E68C }, |
| | 104 | { "Lavender", 0xFFE6E6FA }, |
| | 105 | { "LavenderBlush", 0xFFFFF0F5 }, |
| | 106 | { "LawnGreen", 0xFF7CFC00 }, |
| | 107 | { "LemonChiffon", 0xFFFFFACD }, |
| | 108 | { "LightBlue", 0xFFADD8E6 }, |
| | 109 | { "LightCoral", 0xFFF08080 }, |
| | 110 | { "LightCyan", 0xFFE0FFFF }, |
| | 111 | { "LightGoldenRodYellow", 0xFFFAFAD2 }, |
| | 112 | { "LightGreen", 0xFF90EE90 }, |
| | 113 | { "LightGrey", 0xFFD3D3D3 }, |
| | 114 | { "LightPink", 0xFFFFB6C1 }, |
| | 115 | { "LightSalmon", 0xFFFFA07A }, |
| | 116 | { "LightSeaGreen", 0xFF20B2AA }, |
| | 117 | { "LightSkyBlue", 0xFF87CEFA }, |
| | 118 | { "LightSlateGray", 0xFF778899 }, |
| | 119 | { "LightSteelBlue", 0xFFB0C4DE }, |
| | 120 | { "LightYellow", 0xFFFFFFE0 }, |
| | 121 | { "Lime", 0xFF00FF00 }, |
| | 122 | { "LimeGreen", 0xFF32CD32 }, |
| | 123 | { "Linen", 0xFFFAF0E6 }, |
| | 124 | { "Magenta", 0xFFFF00FF }, |
| | 125 | { "Maroon", 0xFF800000 }, |
| | 126 | { "MediumAquaMarine", 0xFF66CDAA }, |
| | 127 | { "MediumBlue", 0xFF0000CD }, |
| | 128 | { "MediumOrchid", 0xFFBA55D3 }, |
| | 129 | { "MediumPurple", 0xFF9370D8 }, |
| | 130 | { "MediumSeaGreen", 0xFF3CB371 }, |
| | 131 | { "MediumSlateBlue", 0xFF7B68EE }, |
| | 132 | { "MediumSpringGreen", 0xFF00FA9A }, |
| | 133 | { "MediumTurquoise", 0xFF48D1CC }, |
| | 134 | { "MediumVioletRed", 0xFFC71585 }, |
| | 135 | { "MidnightBlue", 0xFF191970 }, |
| | 136 | { "MintCream", 0xFFF5FFFA }, |
| | 137 | { "MistyRose", 0xFFFFE4E1 }, |
| | 138 | { "Moccasin", 0xFFFFE4B5 }, |
| | 139 | { "NavajoWhite", 0xFFFFDEAD }, |
| | 140 | { "Navy", 0xFF000080 }, |
| | 141 | { "None", 0x00000000 }, |
| | 142 | { "OldLace", 0xFFFDF5E6 }, |
| | 143 | { "Olive", 0xFF808000 }, |
| | 144 | { "OliveDrab", 0xFF6B8E23 }, |
| | 145 | { "Orange", 0xFFFFA500 }, |
| | 146 | { "OrangeRed", 0xFFFF4500 }, |
| | 147 | { "Orchid", 0xFFDA70D6 }, |
| | 148 | { "PaleGoldenRod", 0xFFEEE8AA }, |
| | 149 | { "PaleGreen", 0xFF98FB98 }, |
| | 150 | { "PaleTurquoise", 0xFFAFEEEE }, |
| | 151 | { "PaleVioletRed", 0xFFD87093 }, |
| | 152 | { "PapayaWhip", 0xFFFFEFD5 }, |
| | 153 | { "PeachPuff", 0xFFFFDAB9 }, |
| | 154 | { "Peru", 0xFFCD853F }, |
| | 155 | { "Pink", 0xFFFFC0CB }, |
| | 156 | { "Plum", 0xFFDDA0DD }, |
| | 157 | { "PowderBlue", 0xFFB0E0E6 }, |
| | 158 | { "Purple", 0xFF800080 }, |
| | 159 | { "Red", 0xFFFF0000 }, |
| | 160 | { "RosyBrown", 0xFFBC8F8F }, |
| | 161 | { "RoyalBlue", 0xFF4169E1 }, |
| | 162 | { "SaddleBrown", 0xFF8B4513 }, |
| | 163 | { "Salmon", 0xFFFA8072 }, |
| | 164 | { "SandyBrown", 0xFFF4A460 }, |
| | 165 | { "SeaGreen", 0xFF2E8B57 }, |
| | 166 | { "SeaShell", 0xFFFFF5EE }, |
| | 167 | { "Sienna", 0xFFA0522D }, |
| | 168 | { "Silver", 0xFFC0C0C0 }, |
| | 169 | { "SkyBlue", 0xFF87CEEB }, |
| | 170 | { "SlateBlue", 0xFF6A5ACD }, |
| | 171 | { "SlateGray", 0xFF708090 }, |
| | 172 | { "Snow", 0xFFFFFAFA }, |
| | 173 | { "SpringGreen", 0xFF00FF7F }, |
| | 174 | { "SteelBlue", 0xFF4682B4 }, |
| | 175 | { "Tan", 0xFFD2B48C }, |
| | 176 | { "Teal", 0xFF008080 }, |
| | 177 | { "Thistle", 0xFFD8BFD8 }, |
| | 178 | { "Tomato", 0xFFFF6347 }, |
| | 179 | { "Turquoise", 0xFF40E0D0 }, |
| | 180 | { "Violet", 0xFFEE82EE }, |
| | 181 | { "Wheat", 0xFFF5DEB3 }, |
| | 182 | { "White", 0xFFFFFFFF }, |
| | 183 | { "WhiteSmoke", 0xFFF5F5F5 }, |
| | 184 | { "Yellow", 0xFFFFFF00 }, |
| | 185 | { "YellowGreen", 0xFF9ACD32 } |
| | 186 | }; |
| | 187 | |
| | 188 | static int convert(uint8_t x) |
| | 189 | { |
| | 190 | if (x >= 'a') { |
| | 191 | x -= 87; |
| | 192 | } else if (x >= 'A') { |
| | 193 | x -= 55; |
| | 194 | } else { |
| | 195 | x -= '0'; |
| | 196 | } |
| | 197 | return x; |
| | 198 | } |
| | 199 | |
| | 200 | /* |
| | 201 | ** functions same as strcspn but ignores characters in reject if they are inside a C style comment... |
| | 202 | ** @param string, reject - same as that of strcspn |
| | 203 | ** @return length till any character in reject does not occur in string |
| | 204 | */ |
| | 205 | static size_t mod_strcspn(const char *string, const char *reject) |
| | 206 | { |
| | 207 | int i, j; |
| | 208 | |
| | 209 | for (i = 0; string && string[i]; i++) { |
| | 210 | if (string[i] == '/' && string[i+1] == '*') { |
| | 211 | i += 2; |
| | 212 | while ( string && string[i] && (string[i] != '*' || string[i+1] != '/') ) |
| | 213 | i++; |
| | 214 | i++; |
| | 215 | } else if (string[i] == '/' && string[i+1] == '/') { |
| | 216 | i += 2; |
| | 217 | while ( string && string[i] && string[i] != '\n' ) |
| | 218 | i++; |
| | 219 | } else { |
| | 220 | for (j = 0; reject && reject[j]; j++) { |
| | 221 | if (string[i] == reject[j]) |
| | 222 | break; |
| | 223 | } |
| | 224 | if (reject && reject[j]) |
| | 225 | break; |
| | 226 | } |
| | 227 | } |
| | 228 | return i; |
| | 229 | } |
| | 230 | |
| | 231 | static uint32_t hexstring_to_rgba(const char *p, int len) |
| | 232 | { |
| | 233 | uint32_t ret = 0xFF000000; |
| | 234 | const ColorEntry *entry; |
| | 235 | char color_name[100]; |
| | 236 | |
| | 237 | if (*p == '#') { |
| | 238 | p++; |
| | 239 | len--; |
| | 240 | if (len == 3) { |
| | 241 | ret |= (convert(p[2]) << 4) | |
| | 242 | (convert(p[1]) << 12) | |
| | 243 | (convert(p[0]) << 20); |
| | 244 | } else if (len == 4) { |
| | 245 | ret = (convert(p[3]) << 4) | |
| | 246 | (convert(p[2]) << 12) | |
| | 247 | (convert(p[1]) << 20) | |
| | 248 | (convert(p[0]) << 28); |
| | 249 | } else if (len == 6) { |
| | 250 | ret |= convert(p[5]) | |
| | 251 | (convert(p[4]) << 4) | |
| | 252 | (convert(p[3]) << 8) | |
| | 253 | (convert(p[2]) << 12) | |
| | 254 | (convert(p[1]) << 16) | |
| | 255 | (convert(p[0]) << 20); |
| | 256 | } else if (len == 8) { |
| | 257 | ret = convert(p[7]) | |
| | 258 | (convert(p[6]) << 4) | |
| | 259 | (convert(p[5]) << 8) | |
| | 260 | (convert(p[4]) << 12) | |
| | 261 | (convert(p[3]) << 16) | |
| | 262 | (convert(p[2]) << 20) | |
| | 263 | (convert(p[1]) << 24) | |
| | 264 | (convert(p[0]) << 28); |
| | 265 | } |
| | 266 | } else { |
| | 267 | strncpy(color_name, p, len); |
| | 268 | color_name[len] = '\0'; |
| | 269 | |
| | 270 | entry = bsearch(color_name, |
| | 271 | color_table, |
| | 272 | FF_ARRAY_ELEMS(color_table), |
| | 273 | sizeof(ColorEntry), |
| | 274 | color_table_compare); |
| | 275 | |
| | 276 | if (!entry) |
| | 277 | return ret; |
| | 278 | |
| | 279 | ret = entry->rgb_color; |
| | 280 | } |
| | 281 | return ret; |
| | 282 | } |
| | 283 | |
| | 284 | static int ascii2index(const uint8_t *cpixel, int cpp) |
| | 285 | { |
| | 286 | const uint8_t *p = cpixel; |
| | 287 | int n = 0, m = 1, i; |
| | 288 | |
| | 289 | for (i = 0; i < cpp; i++) { |
| | 290 | if (*p < ' ' || *p > '~') |
| | 291 | return AVERROR_INVALIDDATA; |
| | 292 | n += (*p++ - ' ') * m; |
| | 293 | m *= 95; |
| | 294 | } |
| | 295 | return n; |
| | 296 | } |
| | 297 | |
| | 298 | static int xpm_decode_frame(AVCodecContext *avctx, void *data, |
| | 299 | int *got_frame, AVPacket *avpkt) |
| | 300 | { |
| | 301 | XPMDecContext *x = avctx->priv_data; |
| | 302 | AVFrame *p=data; |
| | 303 | const uint8_t *end, *ptr = avpkt->data; |
| | 304 | int ncolors, cpp, ret, i, j; |
| | 305 | int64_t size; |
| | 306 | uint32_t *dst; |
| | 307 | |
| | 308 | avctx->pix_fmt = AV_PIX_FMT_BGRA; |
| | 309 | |
| | 310 | end = avpkt->data + avpkt->size; |
| | 311 | if (memcmp(ptr, "/* XPM */", 9)) { |
| | 312 | av_log(avctx, AV_LOG_ERROR, "missing signature\n"); |
| | 313 | return AVERROR_INVALIDDATA; |
| | 314 | } |
| | 315 | |
| | 316 | ptr += mod_strcspn(ptr, "\""); |
| | 317 | if (sscanf(ptr, "\"%u %u %u %u\",", |
| | 318 | &avctx->width, &avctx->height, &ncolors, &cpp) != 4) { |
| | 319 | av_log(avctx, AV_LOG_ERROR, "missing image parameters\n"); |
| | 320 | return AVERROR_INVALIDDATA; |
| | 321 | } |
| | 322 | |
| | 323 | if ((ret = ff_set_dimensions(avctx, avctx->width, avctx->height)) < 0) |
| | 324 | return ret; |
| | 325 | |
| | 326 | if ((ret = ff_get_buffer(avctx, p, 0)) < 0) |
| | 327 | return ret; |
| | 328 | |
| | 329 | if (ncolors <= 0) { |
| | 330 | av_log(avctx, AV_LOG_ERROR, "invalid number of colors: %d\n", ncolors); |
| | 331 | return AVERROR_INVALIDDATA; |
| | 332 | } |
| | 333 | |
| | 334 | if (cpp <= 0) { |
| | 335 | av_log(avctx, AV_LOG_ERROR, "invalid number of chars per pixel: %d\n", cpp); |
| | 336 | return AVERROR_INVALIDDATA; |
| | 337 | } |
| | 338 | |
| | 339 | size = 1; |
| | 340 | j = 1; |
| | 341 | for (i = 0; i < cpp; i++) { |
| | 342 | size += j * 94; |
| | 343 | j *= 95; |
| | 344 | } |
| | 345 | size *= 4; |
| | 346 | |
| | 347 | if (size < 0) { |
| | 348 | av_log(avctx, AV_LOG_ERROR, "unsupported number of chars per pixel: %d\n", cpp); |
| | 349 | return AVERROR(ENOMEM); |
| | 350 | } |
| | 351 | |
| | 352 | av_fast_padded_malloc(&x->pixels, &x->pixels_size, size); |
| | 353 | if (!x->pixels) |
| | 354 | return AVERROR(ENOMEM); |
| | 355 | |
| | 356 | ptr += mod_strcspn(ptr, ",") + 1; |
| | 357 | for (i = 0; i < ncolors; i++) { |
| | 358 | const uint8_t *index; |
| | 359 | int len; |
| | 360 | |
| | 361 | ptr += mod_strcspn(ptr, "\"") + 1; |
| | 362 | if (ptr + cpp > end) |
| | 363 | return AVERROR_INVALIDDATA; |
| | 364 | index = ptr; |
| | 365 | ptr += cpp; |
| | 366 | |
| | 367 | ptr = strstr(ptr, "c "); |
| | 368 | if (ptr) { |
| | 369 | ptr += 2; |
| | 370 | } else { |
| | 371 | return AVERROR_INVALIDDATA; |
| | 372 | } |
| | 373 | |
| | 374 | len = strcspn(ptr, "\" "); |
| | 375 | |
| | 376 | if ((ret = ascii2index(index, cpp)) < 0) |
| | 377 | return ret; |
| | 378 | |
| | 379 | x->pixels[ret] = hexstring_to_rgba(ptr, len); |
| | 380 | ptr += mod_strcspn(ptr, ",") + 1; |
| | 381 | } |
| | 382 | |
| | 383 | for (i = 0; i < avctx->height; i++) { |
| | 384 | dst = (uint32_t *)(p->data[0] + i * p->linesize[0]); |
| | 385 | ptr += mod_strcspn(ptr, "\"") + 1; |
| | 386 | |
| | 387 | for (j = 0; j < avctx->width; j++) { |
| | 388 | if (ptr + cpp > end) |
| | 389 | return AVERROR_INVALIDDATA; |
| | 390 | |
| | 391 | if ((ret = ascii2index(ptr, cpp)) < 0) |
| | 392 | return ret; |
| | 393 | |
| | 394 | *dst++ = x->pixels[ret]; |
| | 395 | ptr += cpp; |
| | 396 | } |
| | 397 | ptr += mod_strcspn(ptr, ",") + 1; |
| | 398 | } |
| | 399 | |
| | 400 | p->key_frame = 1; |
| | 401 | p->pict_type = AV_PICTURE_TYPE_I; |
| | 402 | |
| | 403 | *got_frame = 1; |
| | 404 | |
| | 405 | return avpkt->size; |
| | 406 | } |
| | 407 | |
| | 408 | static av_cold int xpm_decode_close(AVCodecContext *avctx) |
| | 409 | { |
| | 410 | XPMDecContext *x = avctx->priv_data; |
| | 411 | av_freep(&x->pixels); |
| | 412 | |
| | 413 | return 0; |
| | 414 | } |
| | 415 | |
| | 416 | AVCodec ff_xpm_decoder = { |
| | 417 | .name = "xpm", |
| | 418 | .type = AVMEDIA_TYPE_VIDEO, |
| | 419 | .id = AV_CODEC_ID_XPM, |
| | 420 | .priv_data_size = sizeof(XPMDecContext), |
| | 421 | .close = xpm_decode_close, |
| | 422 | .decode = xpm_decode_frame, |
| | 423 | .capabilities = CODEC_CAP_DR1, |
| | 424 | .long_name = NULL_IF_CONFIG_SMALL("XPM (X PixMap) image") |
| | 425 | }; |