Mercurial > libavcodec.hg
annotate pngenc.c @ 12494:94eaea836bf4 libavcodec
Check avctx width/height more thoroughly (e.g. all values 0 except width would
have been accepted before).
Also do not fail if they are invalid but instead override them to 0.
This allows decoding e.g. MPEG video when only the container values are corrupted.
For encoding a value of 0,0 of course makes no sense, but was allowed
through before and will be caught by an extra check in the encode function.
author | reimar |
---|---|
date | Wed, 15 Sep 2010 04:46:55 +0000 |
parents | 8b28e74de2c0 |
children |
rev | line source |
---|---|
2342 | 1 /* |
2 * PNG image format | |
8629
04423b2f6e0b
cosmetics: Remove pointless period after copyright statement non-sentences.
diego
parents:
7235
diff
changeset
|
3 * Copyright (c) 2003 Fabrice Bellard |
2342 | 4 * |
3947
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
5 * This file is part of FFmpeg. |
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
6 * |
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
7 * FFmpeg is free software; you can redistribute it and/or |
2342 | 8 * modify it under the terms of the GNU Lesser General Public |
9 * License as published by the Free Software Foundation; either | |
3947
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
10 * version 2.1 of the License, or (at your option) any later version. |
2342 | 11 * |
3947
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
12 * FFmpeg is distributed in the hope that it will be useful, |
2342 | 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 | |
3947
c8c591fe26f8
Change license headers to say 'FFmpeg' instead of 'this program/this library'
diego
parents:
3777
diff
changeset
|
18 * License along with FFmpeg; if not, write to the Free Software |
3036
0b546eab515d
Update licensing information: The FSF changed postal address.
diego
parents:
2967
diff
changeset
|
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
2342 | 20 */ |
21 #include "avcodec.h" | |
5067 | 22 #include "bytestream.h" |
6759
6b7ba5b9af2e
dsputil.h is not a system header, use "" when #including it.
diego
parents:
6722
diff
changeset
|
23 #include "dsputil.h" |
5337 | 24 #include "png.h" |
2342 | 25 |
26 /* TODO: | |
27 * - add 2, 4 and 16 bit depth support | |
28 */ | |
29 | |
30 #include <zlib.h> | |
31 | |
32 //#define DEBUG | |
33 | |
5339 | 34 #define IOBUF_SIZE 4096 |
35 | |
36 typedef struct PNGEncContext { | |
6396 | 37 DSPContext dsp; |
38 | |
5339 | 39 uint8_t *bytestream; |
40 uint8_t *bytestream_start; | |
41 uint8_t *bytestream_end; | |
42 AVFrame picture; | |
43 | |
6396 | 44 int filter_type; |
45 | |
5339 | 46 z_stream zstream; |
47 uint8_t buf[IOBUF_SIZE]; | |
48 } PNGEncContext; | |
49 | |
2967 | 50 static void png_get_interlaced_row(uint8_t *dst, int row_size, |
51 int bits_per_pixel, int pass, | |
2342 | 52 const uint8_t *src, int width) |
53 { | |
54 int x, mask, dst_x, j, b, bpp; | |
55 uint8_t *d; | |
56 const uint8_t *s; | |
57 | |
5337 | 58 mask = ff_png_pass_mask[pass]; |
2342 | 59 switch(bits_per_pixel) { |
60 case 1: | |
61 memset(dst, 0, row_size); | |
62 dst_x = 0; | |
63 for(x = 0; x < width; x++) { | |
64 j = (x & 7); | |
65 if ((mask << j) & 0x80) { | |
66 b = (src[x >> 3] >> (7 - j)) & 1; | |
67 dst[dst_x >> 3] |= b << (7 - (dst_x & 7)); | |
68 dst_x++; | |
69 } | |
70 } | |
71 break; | |
72 default: | |
73 bpp = bits_per_pixel >> 3; | |
74 d = dst; | |
75 s = src; | |
76 for(x = 0; x < width; x++) { | |
77 j = x & 7; | |
78 if ((mask << j) & 0x80) { | |
79 memcpy(d, s, bpp); | |
80 d += bpp; | |
81 } | |
82 s += bpp; | |
83 } | |
84 break; | |
85 } | |
86 } | |
87 | |
6396 | 88 static void sub_png_paeth_prediction(uint8_t *dst, uint8_t *src, uint8_t *top, int w, int bpp) |
89 { | |
90 int i; | |
91 for(i = 0; i < w; i++) { | |
92 int a, b, c, p, pa, pb, pc; | |
93 | |
94 a = src[i - bpp]; | |
95 b = top[i]; | |
96 c = top[i - bpp]; | |
97 | |
98 p = b - c; | |
99 pc = a - c; | |
100 | |
101 pa = abs(p); | |
102 pb = abs(pc); | |
103 pc = abs(p + pc); | |
104 | |
105 if (pa <= pb && pa <= pc) | |
106 p = a; | |
107 else if (pb <= pc) | |
108 p = b; | |
109 else | |
110 p = c; | |
111 dst[i] = src[i] - p; | |
112 } | |
113 } | |
114 | |
115 static void png_filter_row(DSPContext *dsp, uint8_t *dst, int filter_type, | |
116 uint8_t *src, uint8_t *top, int size, int bpp) | |
117 { | |
118 int i; | |
119 | |
120 switch(filter_type) { | |
121 case PNG_FILTER_VALUE_NONE: | |
122 memcpy(dst, src, size); | |
123 break; | |
124 case PNG_FILTER_VALUE_SUB: | |
125 dsp->diff_bytes(dst, src, src-bpp, size); | |
126 memcpy(dst, src, bpp); | |
127 break; | |
128 case PNG_FILTER_VALUE_UP: | |
129 dsp->diff_bytes(dst, src, top, size); | |
130 break; | |
131 case PNG_FILTER_VALUE_AVG: | |
132 for(i = 0; i < bpp; i++) | |
133 dst[i] = src[i] - (top[i] >> 1); | |
134 for(; i < size; i++) | |
135 dst[i] = src[i] - ((src[i-bpp] + top[i]) >> 1); | |
136 break; | |
137 case PNG_FILTER_VALUE_PAETH: | |
138 for(i = 0; i < bpp; i++) | |
139 dst[i] = src[i] - top[i]; | |
140 sub_png_paeth_prediction(dst+i, src+i, top+i, size-i, bpp); | |
141 break; | |
142 } | |
143 } | |
144 | |
145 static uint8_t *png_choose_filter(PNGEncContext *s, uint8_t *dst, | |
146 uint8_t *src, uint8_t *top, int size, int bpp) | |
147 { | |
148 int pred = s->filter_type; | |
149 assert(bpp || !pred); | |
150 if(!top && pred) | |
151 pred = PNG_FILTER_VALUE_SUB; | |
152 if(pred == PNG_FILTER_VALUE_MIXED) { | |
153 int i; | |
154 int cost, bcost = INT_MAX; | |
155 uint8_t *buf1 = dst, *buf2 = dst + size + 16; | |
156 for(pred=0; pred<5; pred++) { | |
157 png_filter_row(&s->dsp, buf1+1, pred, src, top, size, bpp); | |
158 buf1[0] = pred; | |
159 cost = 0; | |
160 for(i=0; i<=size; i++) | |
6404 | 161 cost += abs((int8_t)buf1[i]); |
6396 | 162 if(cost < bcost) { |
163 bcost = cost; | |
164 FFSWAP(uint8_t*, buf1, buf2); | |
165 } | |
166 } | |
167 return buf2; | |
168 } else { | |
169 png_filter_row(&s->dsp, dst+1, pred, src, top, size, bpp); | |
170 dst[0] = pred; | |
171 return dst; | |
172 } | |
173 } | |
174 | |
4515 | 175 static void convert_from_rgb32(uint8_t *dst, const uint8_t *src, int width) |
2342 | 176 { |
177 uint8_t *d; | |
178 int j; | |
179 unsigned int v; | |
2967 | 180 |
2342 | 181 d = dst; |
182 for(j = 0; j < width; j++) { | |
3347
82277c821113
Add const to (mostly) char* and make some functions static, which aren't used
diego
parents:
3177
diff
changeset
|
183 v = ((const uint32_t *)src)[j]; |
2342 | 184 d[0] = v >> 16; |
185 d[1] = v >> 8; | |
186 d[2] = v; | |
187 d[3] = v >> 24; | |
188 d += 4; | |
189 } | |
190 } | |
191 | |
192 static void png_write_chunk(uint8_t **f, uint32_t tag, | |
193 const uint8_t *buf, int length) | |
194 { | |
195 uint32_t crc; | |
196 uint8_t tagbuf[4]; | |
197 | |
5067 | 198 bytestream_put_be32(f, length); |
2342 | 199 crc = crc32(0, Z_NULL, 0); |
5089 | 200 AV_WL32(tagbuf, tag); |
2342 | 201 crc = crc32(crc, tagbuf, 4); |
12129 | 202 bytestream_put_be32(f, av_bswap32(tag)); |
2342 | 203 if (length > 0) { |
204 crc = crc32(crc, buf, length); | |
205 memcpy(*f, buf, length); | |
206 *f += length; | |
207 } | |
5067 | 208 bytestream_put_be32(f, crc); |
2342 | 209 } |
210 | |
211 /* XXX: do filtering */ | |
5339 | 212 static int png_write_row(PNGEncContext *s, const uint8_t *data, int size) |
2342 | 213 { |
214 int ret; | |
215 | |
216 s->zstream.avail_in = size; | |
217 s->zstream.next_in = (uint8_t *)data; | |
218 while (s->zstream.avail_in > 0) { | |
219 ret = deflate(&s->zstream, Z_NO_FLUSH); | |
220 if (ret != Z_OK) | |
221 return -1; | |
222 if (s->zstream.avail_out == 0) { | |
2422 | 223 if(s->bytestream_end - s->bytestream > IOBUF_SIZE + 100) |
224 png_write_chunk(&s->bytestream, MKTAG('I', 'D', 'A', 'T'), s->buf, IOBUF_SIZE); | |
2342 | 225 s->zstream.avail_out = IOBUF_SIZE; |
226 s->zstream.next_out = s->buf; | |
227 } | |
228 } | |
229 return 0; | |
230 } | |
231 | |
232 static int encode_frame(AVCodecContext *avctx, unsigned char *buf, int buf_size, void *data){ | |
5339 | 233 PNGEncContext *s = avctx->priv_data; |
2342 | 234 AVFrame *pict = data; |
7235 | 235 AVFrame * const p= &s->picture; |
2342 | 236 int bit_depth, color_type, y, len, row_size, ret, is_progressive; |
237 int bits_per_pixel, pass_row_size; | |
5804
af7e6436d8bd
Allow to override zlib compression level in PNG encoder via avctx->compression_level
reimar
parents:
5339
diff
changeset
|
238 int compression_level; |
6396 | 239 uint8_t *ptr, *top; |
240 uint8_t *crow_base = NULL, *crow_buf, *crow; | |
241 uint8_t *progressive_buf = NULL; | |
242 uint8_t *rgba_buf = NULL; | |
243 uint8_t *top_buf = NULL; | |
2342 | 244 |
245 *p = *pict; | |
246 p->pict_type= FF_I_TYPE; | |
247 p->key_frame= 1; | |
2967 | 248 |
2342 | 249 s->bytestream_start= |
250 s->bytestream= buf; | |
251 s->bytestream_end= buf+buf_size; | |
252 | |
253 is_progressive = !!(avctx->flags & CODEC_FLAG_INTERLACED_DCT); | |
254 switch(avctx->pix_fmt) { | |
4494
ce643a22f049
Replace deprecated PIX_FMT names by the newer variants.
diego
parents:
4379
diff
changeset
|
255 case PIX_FMT_RGB32: |
2342 | 256 bit_depth = 8; |
257 color_type = PNG_COLOR_TYPE_RGB_ALPHA; | |
258 break; | |
259 case PIX_FMT_RGB24: | |
260 bit_depth = 8; | |
261 color_type = PNG_COLOR_TYPE_RGB; | |
262 break; | |
263 case PIX_FMT_GRAY8: | |
264 bit_depth = 8; | |
265 color_type = PNG_COLOR_TYPE_GRAY; | |
266 break; | |
267 case PIX_FMT_MONOBLACK: | |
268 bit_depth = 1; | |
269 color_type = PNG_COLOR_TYPE_GRAY; | |
270 break; | |
271 case PIX_FMT_PAL8: | |
272 bit_depth = 8; | |
273 color_type = PNG_COLOR_TYPE_PALETTE; | |
274 break; | |
275 default: | |
276 return -1; | |
277 } | |
5337 | 278 bits_per_pixel = ff_png_get_nb_channels(color_type) * bit_depth; |
2342 | 279 row_size = (avctx->width * bits_per_pixel + 7) >> 3; |
280 | |
5337 | 281 s->zstream.zalloc = ff_png_zalloc; |
282 s->zstream.zfree = ff_png_zfree; | |
2342 | 283 s->zstream.opaque = NULL; |
5804
af7e6436d8bd
Allow to override zlib compression level in PNG encoder via avctx->compression_level
reimar
parents:
5339
diff
changeset
|
284 compression_level = avctx->compression_level == FF_COMPRESSION_DEFAULT ? |
af7e6436d8bd
Allow to override zlib compression level in PNG encoder via avctx->compression_level
reimar
parents:
5339
diff
changeset
|
285 Z_DEFAULT_COMPRESSION : |
af7e6436d8bd
Allow to override zlib compression level in PNG encoder via avctx->compression_level
reimar
parents:
5339
diff
changeset
|
286 av_clip(avctx->compression_level, 0, 9); |
af7e6436d8bd
Allow to override zlib compression level in PNG encoder via avctx->compression_level
reimar
parents:
5339
diff
changeset
|
287 ret = deflateInit2(&s->zstream, compression_level, |
2342 | 288 Z_DEFLATED, 15, 8, Z_DEFAULT_STRATEGY); |
289 if (ret != Z_OK) | |
290 return -1; | |
6396 | 291 crow_base = av_malloc((row_size + 32) << (s->filter_type == PNG_FILTER_VALUE_MIXED)); |
292 if (!crow_base) | |
2342 | 293 goto fail; |
6396 | 294 crow_buf = crow_base + 15; // pixel data should be aligned, but there's a control byte before it |
2342 | 295 if (is_progressive) { |
6396 | 296 progressive_buf = av_malloc(row_size + 1); |
297 if (!progressive_buf) | |
298 goto fail; | |
299 } | |
300 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { | |
301 rgba_buf = av_malloc(row_size + 1); | |
302 if (!rgba_buf) | |
303 goto fail; | |
304 } | |
305 if (is_progressive || color_type == PNG_COLOR_TYPE_RGB_ALPHA) { | |
306 top_buf = av_malloc(row_size + 1); | |
307 if (!top_buf) | |
2342 | 308 goto fail; |
309 } | |
310 | |
311 /* write png header */ | |
5337 | 312 memcpy(s->bytestream, ff_pngsig, 8); |
2342 | 313 s->bytestream += 8; |
2967 | 314 |
5067 | 315 AV_WB32(s->buf, avctx->width); |
316 AV_WB32(s->buf + 4, avctx->height); | |
2342 | 317 s->buf[8] = bit_depth; |
318 s->buf[9] = color_type; | |
319 s->buf[10] = 0; /* compression type */ | |
320 s->buf[11] = 0; /* filter type */ | |
321 s->buf[12] = is_progressive; /* interlace type */ | |
2967 | 322 |
2342 | 323 png_write_chunk(&s->bytestream, MKTAG('I', 'H', 'D', 'R'), s->buf, 13); |
324 | |
325 /* put the palette if needed */ | |
326 if (color_type == PNG_COLOR_TYPE_PALETTE) { | |
327 int has_alpha, alpha, i; | |
328 unsigned int v; | |
329 uint32_t *palette; | |
330 uint8_t *alpha_ptr; | |
2967 | 331 |
2342 | 332 palette = (uint32_t *)p->data[1]; |
333 ptr = s->buf; | |
334 alpha_ptr = s->buf + 256 * 3; | |
335 has_alpha = 0; | |
336 for(i = 0; i < 256; i++) { | |
337 v = palette[i]; | |
338 alpha = v >> 24; | |
4018 | 339 if (alpha && alpha != 0xff) |
2342 | 340 has_alpha = 1; |
341 *alpha_ptr++ = alpha; | |
5089 | 342 bytestream_put_be24(&ptr, v); |
2342 | 343 } |
344 png_write_chunk(&s->bytestream, MKTAG('P', 'L', 'T', 'E'), s->buf, 256 * 3); | |
345 if (has_alpha) { | |
346 png_write_chunk(&s->bytestream, MKTAG('t', 'R', 'N', 'S'), s->buf + 256 * 3, 256); | |
347 } | |
348 } | |
349 | |
350 /* now put each row */ | |
351 s->zstream.avail_out = IOBUF_SIZE; | |
352 s->zstream.next_out = s->buf; | |
353 if (is_progressive) { | |
354 int pass; | |
355 | |
356 for(pass = 0; pass < NB_PASSES; pass++) { | |
357 /* NOTE: a pass is completely omited if no pixels would be | |
358 output */ | |
5337 | 359 pass_row_size = ff_png_pass_row_size(pass, bits_per_pixel, avctx->width); |
2342 | 360 if (pass_row_size > 0) { |
6396 | 361 top = NULL; |
2342 | 362 for(y = 0; y < avctx->height; y++) { |
5337 | 363 if ((ff_png_pass_ymask[pass] << (y & 7)) & 0x80) { |
2342 | 364 ptr = p->data[0] + y * p->linesize[0]; |
6396 | 365 FFSWAP(uint8_t*, progressive_buf, top_buf); |
2342 | 366 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { |
6396 | 367 convert_from_rgb32(rgba_buf, ptr, avctx->width); |
368 ptr = rgba_buf; | |
2342 | 369 } |
6396 | 370 png_get_interlaced_row(progressive_buf, pass_row_size, |
2967 | 371 bits_per_pixel, pass, |
6396 | 372 ptr, avctx->width); |
373 crow = png_choose_filter(s, crow_buf, progressive_buf, top, pass_row_size, bits_per_pixel>>3); | |
374 png_write_row(s, crow, pass_row_size + 1); | |
375 top = progressive_buf; | |
2342 | 376 } |
377 } | |
378 } | |
379 } | |
380 } else { | |
6396 | 381 top = NULL; |
2342 | 382 for(y = 0; y < avctx->height; y++) { |
383 ptr = p->data[0] + y * p->linesize[0]; | |
6396 | 384 if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) { |
385 FFSWAP(uint8_t*, rgba_buf, top_buf); | |
386 convert_from_rgb32(rgba_buf, ptr, avctx->width); | |
387 ptr = rgba_buf; | |
388 } | |
389 crow = png_choose_filter(s, crow_buf, ptr, top, row_size, bits_per_pixel>>3); | |
390 png_write_row(s, crow, row_size + 1); | |
391 top = ptr; | |
2342 | 392 } |
393 } | |
394 /* compress last bytes */ | |
395 for(;;) { | |
396 ret = deflate(&s->zstream, Z_FINISH); | |
397 if (ret == Z_OK || ret == Z_STREAM_END) { | |
398 len = IOBUF_SIZE - s->zstream.avail_out; | |
2422 | 399 if (len > 0 && s->bytestream_end - s->bytestream > len + 100) { |
2342 | 400 png_write_chunk(&s->bytestream, MKTAG('I', 'D', 'A', 'T'), s->buf, len); |
401 } | |
402 s->zstream.avail_out = IOBUF_SIZE; | |
403 s->zstream.next_out = s->buf; | |
404 if (ret == Z_STREAM_END) | |
405 break; | |
406 } else { | |
407 goto fail; | |
408 } | |
409 } | |
410 png_write_chunk(&s->bytestream, MKTAG('I', 'E', 'N', 'D'), NULL, 0); | |
411 | |
412 ret = s->bytestream - s->bytestream_start; | |
413 the_end: | |
6396 | 414 av_free(crow_base); |
415 av_free(progressive_buf); | |
416 av_free(rgba_buf); | |
417 av_free(top_buf); | |
2342 | 418 deflateEnd(&s->zstream); |
419 return ret; | |
420 fail: | |
421 ret = -1; | |
422 goto the_end; | |
423 } | |
424 | |
6517
48759bfbd073
Apply 'cold' attribute to init/uninit functions in libavcodec
zuxy
parents:
6404
diff
changeset
|
425 static av_cold int png_enc_init(AVCodecContext *avctx){ |
5339 | 426 PNGEncContext *s = avctx->priv_data; |
427 | |
7235 | 428 avcodec_get_frame_defaults(&s->picture); |
429 avctx->coded_frame= &s->picture; | |
6396 | 430 dsputil_init(&s->dsp, avctx); |
431 | |
432 s->filter_type = av_clip(avctx->prediction_method, PNG_FILTER_VALUE_NONE, PNG_FILTER_VALUE_MIXED); | |
433 if(avctx->pix_fmt == PIX_FMT_MONOBLACK) | |
434 s->filter_type = PNG_FILTER_VALUE_NONE; | |
5339 | 435 |
436 return 0; | |
437 } | |
438 | |
2342 | 439 AVCodec png_encoder = { |
440 "png", | |
11560
8a4984c5cacc
Define AVMediaType enum, and use it instead of enum CodecType, which
stefano
parents:
10146
diff
changeset
|
441 AVMEDIA_TYPE_VIDEO, |
2342 | 442 CODEC_ID_PNG, |
5339 | 443 sizeof(PNGEncContext), |
444 png_enc_init, | |
2342 | 445 encode_frame, |
446 NULL, //encode_end, | |
10146
38cfe222e1a4
Mark all pix_fmts and supported_framerates compound literals as const.
reimar
parents:
8629
diff
changeset
|
447 .pix_fmts= (const enum PixelFormat[]){PIX_FMT_RGB24, PIX_FMT_RGB32, PIX_FMT_PAL8, PIX_FMT_GRAY8, PIX_FMT_MONOBLACK, PIX_FMT_NONE}, |
7040
e943e1409077
Make AVCodec long_names definition conditional depending on CONFIG_SMALL.
stefano
parents:
6788
diff
changeset
|
448 .long_name= NULL_IF_CONFIG_SMALL("PNG image"), |
2342 | 449 }; |