blob: 9112d4cb5e0b37b7878276322fd9a4d15ce6f649
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", 0xFFBEBEBE }, |
96 | { "Green", 0xFF00FF00 }, |
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", 0xFFB03060 }, |
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", 0xFFA020F0 }, |
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 unsigned hex_char_to_number(uint8_t x) |
189 | { |
190 | if (x >= 'a' && x <= 'f') |
191 | x -= 'a' - 10; |
192 | else if (x >= 'A' && x <= 'F') |
193 | x -= 'A' - 10; |
194 | else if (x >= '0' && x <= '9') |
195 | x -= '0'; |
196 | else |
197 | x = 0; |
198 | return x; |
199 | } |
200 | |
201 | /* |
202 | * Function same as strcspn but ignores characters if they are inside a C style comments |
203 | */ |
204 | static size_t mod_strcspn(const char *string, const char *reject) |
205 | { |
206 | int i, j; |
207 | |
208 | for (i = 0; string && string[i]; i++) { |
209 | if (string[i] == '/' && string[i+1] == '*') { |
210 | i += 2; |
211 | while ( string && string[i] && (string[i] != '*' || string[i+1] != '/') ) |
212 | i++; |
213 | i++; |
214 | } else if (string[i] == '/' && string[i+1] == '/') { |
215 | i += 2; |
216 | while ( string && string[i] && string[i] != '\n' ) |
217 | i++; |
218 | } else { |
219 | for (j = 0; reject && reject[j]; j++) { |
220 | if (string[i] == reject[j]) |
221 | break; |
222 | } |
223 | if (reject && reject[j]) |
224 | break; |
225 | } |
226 | } |
227 | return i; |
228 | } |
229 | |
230 | static uint32_t color_string_to_rgba(const char *p, int len) |
231 | { |
232 | uint32_t ret = 0xFF000000; |
233 | const ColorEntry *entry; |
234 | char color_name[100]; |
235 | |
236 | if (*p == '#') { |
237 | p++; |
238 | len--; |
239 | if (len == 3) { |
240 | ret |= (hex_char_to_number(p[2]) << 4) | |
241 | (hex_char_to_number(p[1]) << 12) | |
242 | (hex_char_to_number(p[0]) << 20); |
243 | } else if (len == 4) { |
244 | ret = (hex_char_to_number(p[3]) << 4) | |
245 | (hex_char_to_number(p[2]) << 12) | |
246 | (hex_char_to_number(p[1]) << 20) | |
247 | (hex_char_to_number(p[0]) << 28); |
248 | } else if (len == 6) { |
249 | ret |= hex_char_to_number(p[5]) | |
250 | (hex_char_to_number(p[4]) << 4) | |
251 | (hex_char_to_number(p[3]) << 8) | |
252 | (hex_char_to_number(p[2]) << 12) | |
253 | (hex_char_to_number(p[1]) << 16) | |
254 | (hex_char_to_number(p[0]) << 20); |
255 | } else if (len == 8) { |
256 | ret = hex_char_to_number(p[7]) | |
257 | (hex_char_to_number(p[6]) << 4) | |
258 | (hex_char_to_number(p[5]) << 8) | |
259 | (hex_char_to_number(p[4]) << 12) | |
260 | (hex_char_to_number(p[3]) << 16) | |
261 | (hex_char_to_number(p[2]) << 20) | |
262 | (hex_char_to_number(p[1]) << 24) | |
263 | (hex_char_to_number(p[0]) << 28); |
264 | } |
265 | } else { |
266 | strncpy(color_name, p, len); |
267 | color_name[len] = '\0'; |
268 | |
269 | entry = bsearch(color_name, |
270 | color_table, |
271 | FF_ARRAY_ELEMS(color_table), |
272 | sizeof(ColorEntry), |
273 | color_table_compare); |
274 | |
275 | if (!entry) |
276 | return ret; |
277 | |
278 | ret = entry->rgb_color; |
279 | } |
280 | return ret; |
281 | } |
282 | |
283 | static int ascii2index(const uint8_t *cpixel, int cpp) |
284 | { |
285 | const uint8_t *p = cpixel; |
286 | int n = 0, m = 1, i; |
287 | |
288 | for (i = 0; i < cpp; i++) { |
289 | if (*p < ' ' || *p > '~') |
290 | return AVERROR_INVALIDDATA; |
291 | n += (*p++ - ' ') * m; |
292 | m *= 95; |
293 | } |
294 | return n; |
295 | } |
296 | |
297 | static int xpm_decode_frame(AVCodecContext *avctx, void *data, |
298 | int *got_frame, AVPacket *avpkt) |
299 | { |
300 | XPMDecContext *x = avctx->priv_data; |
301 | AVFrame *p=data; |
302 | const uint8_t *end, *ptr = avpkt->data; |
303 | int ncolors, cpp, ret, i, j; |
304 | int64_t size; |
305 | uint32_t *dst; |
306 | |
307 | avctx->pix_fmt = AV_PIX_FMT_BGRA; |
308 | |
309 | end = avpkt->data + avpkt->size; |
310 | while (memcmp(ptr, "/* XPM */", 9) && ptr < end - 9) |
311 | ptr++; |
312 | |
313 | if (ptr >= end) { |
314 | av_log(avctx, AV_LOG_ERROR, "missing signature\n"); |
315 | return AVERROR_INVALIDDATA; |
316 | } |
317 | |
318 | ptr += mod_strcspn(ptr, "\""); |
319 | if (sscanf(ptr, "\"%u %u %u %u\",", |
320 | &avctx->width, &avctx->height, &ncolors, &cpp) != 4) { |
321 | av_log(avctx, AV_LOG_ERROR, "missing image parameters\n"); |
322 | return AVERROR_INVALIDDATA; |
323 | } |
324 | |
325 | if ((ret = ff_set_dimensions(avctx, avctx->width, avctx->height)) < 0) |
326 | return ret; |
327 | |
328 | if ((ret = ff_get_buffer(avctx, p, 0)) < 0) |
329 | return ret; |
330 | |
331 | if (cpp <= 0 || cpp >= 5) { |
332 | av_log(avctx, AV_LOG_ERROR, "unsupported/invalid number of chars per pixel: %d\n", cpp); |
333 | return AVERROR_INVALIDDATA; |
334 | } |
335 | |
336 | size = 1; |
337 | for (i = 0; i < cpp; i++) |
338 | size *= 94; |
339 | |
340 | if (ncolors <= 0 || ncolors > size) { |
341 | av_log(avctx, AV_LOG_ERROR, "invalid number of colors: %d\n", ncolors); |
342 | return AVERROR_INVALIDDATA; |
343 | } |
344 | |
345 | size *= 4; |
346 | |
347 | av_fast_padded_malloc(&x->pixels, &x->pixels_size, size); |
348 | if (!x->pixels) |
349 | return AVERROR(ENOMEM); |
350 | |
351 | ptr += mod_strcspn(ptr, ",") + 1; |
352 | for (i = 0; i < ncolors; i++) { |
353 | const uint8_t *index; |
354 | int len; |
355 | |
356 | ptr += mod_strcspn(ptr, "\"") + 1; |
357 | if (ptr + cpp > end) |
358 | return AVERROR_INVALIDDATA; |
359 | index = ptr; |
360 | ptr += cpp; |
361 | |
362 | ptr = strstr(ptr, "c "); |
363 | if (ptr) { |
364 | ptr += 2; |
365 | } else { |
366 | return AVERROR_INVALIDDATA; |
367 | } |
368 | |
369 | len = strcspn(ptr, "\" "); |
370 | |
371 | if ((ret = ascii2index(index, cpp)) < 0) |
372 | return ret; |
373 | |
374 | x->pixels[ret] = color_string_to_rgba(ptr, len); |
375 | ptr += mod_strcspn(ptr, ",") + 1; |
376 | } |
377 | |
378 | for (i = 0; i < avctx->height; i++) { |
379 | dst = (uint32_t *)(p->data[0] + i * p->linesize[0]); |
380 | ptr += mod_strcspn(ptr, "\"") + 1; |
381 | |
382 | for (j = 0; j < avctx->width; j++) { |
383 | if (ptr + cpp > end) |
384 | return AVERROR_INVALIDDATA; |
385 | |
386 | if ((ret = ascii2index(ptr, cpp)) < 0) |
387 | return ret; |
388 | |
389 | *dst++ = x->pixels[ret]; |
390 | ptr += cpp; |
391 | } |
392 | ptr += mod_strcspn(ptr, ",") + 1; |
393 | } |
394 | |
395 | p->key_frame = 1; |
396 | p->pict_type = AV_PICTURE_TYPE_I; |
397 | |
398 | *got_frame = 1; |
399 | |
400 | return avpkt->size; |
401 | } |
402 | |
403 | static av_cold int xpm_decode_close(AVCodecContext *avctx) |
404 | { |
405 | XPMDecContext *x = avctx->priv_data; |
406 | av_freep(&x->pixels); |
407 | |
408 | return 0; |
409 | } |
410 | |
411 | AVCodec ff_xpm_decoder = { |
412 | .name = "xpm", |
413 | .type = AVMEDIA_TYPE_VIDEO, |
414 | .id = AV_CODEC_ID_XPM, |
415 | .priv_data_size = sizeof(XPMDecContext), |
416 | .close = xpm_decode_close, |
417 | .decode = xpm_decode_frame, |
418 | .capabilities = AV_CODEC_CAP_DR1, |
419 | .long_name = NULL_IF_CONFIG_SMALL("XPM (X PixMap) image") |
420 | }; |
421 |