diff --git a/Changelog b/Changelog index 86eb20cd5b..58b00dba30 100644 --- a/Changelog +++ b/Changelog @@ -5,6 +5,7 @@ version : - deprecated old scaler removed - VQF demuxer - Alpha channel scaler +- PCX encoder diff --git a/Makefile b/Makefile index c80284e4aa..48f9620b04 100644 --- a/Makefile +++ b/Makefile @@ -224,6 +224,7 @@ LAVF_TESTS = $(addprefix regtest-, \ voc \ ogg \ pixfmt \ + pcx \ ) REGFILES = $(addprefix tests/data/,$(addsuffix .$(1),$(2:regtest-%=%))) diff --git a/doc/general.texi b/doc/general.texi index 7f554332fb..6a14b17ebe 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -251,7 +251,7 @@ following image formats are supported: @tab PAM is a PNM extension with alpha support. @item PBM @tab X @tab X @tab Portable BitMap image -@item PCX @tab @tab X +@item PCX @tab X @tab X @tab PC Paintbrush @item PGM @tab X @tab X @tab Portable GrayMap image diff --git a/libavcodec/Makefile b/libavcodec/Makefile index 34d7dbc09e..1b3f93ae99 100644 --- a/libavcodec/Makefile +++ b/libavcodec/Makefile @@ -159,6 +159,7 @@ OBJS-$(CONFIG_NUV_DECODER) += nuv.o rtjpeg.o OBJS-$(CONFIG_PAM_ENCODER) += pnmenc.o pnm.o OBJS-$(CONFIG_PBM_ENCODER) += pnmenc.o pnm.o OBJS-$(CONFIG_PCX_DECODER) += pcx.o +OBJS-$(CONFIG_PCX_ENCODER) += pcxenc.o OBJS-$(CONFIG_PGM_ENCODER) += pnmenc.o pnm.o OBJS-$(CONFIG_PGMYUV_ENCODER) += pnmenc.o pnm.o OBJS-$(CONFIG_PNG_DECODER) += png.o pngdec.o diff --git a/libavcodec/allcodecs.c b/libavcodec/allcodecs.c index 4dcbcd8b13..815114a5f8 100644 --- a/libavcodec/allcodecs.c +++ b/libavcodec/allcodecs.c @@ -127,7 +127,7 @@ void avcodec_register_all(void) REGISTER_DECODER (NUV, nuv); REGISTER_ENCODER (PAM, pam); REGISTER_ENCODER (PBM, pbm); - REGISTER_DECODER (PCX, pcx); + REGISTER_ENCDEC (PCX, pcx); REGISTER_ENCODER (PGM, pgm); REGISTER_ENCODER (PGMYUV, pgmyuv); REGISTER_ENCDEC (PNG, png); diff --git a/libavcodec/pcxenc.c b/libavcodec/pcxenc.c new file mode 100644 index 0000000000..36f7d1d84c --- /dev/null +++ b/libavcodec/pcxenc.c @@ -0,0 +1,206 @@ +/* + * PC Paintbrush PCX (.pcx) image encoder + * Copyright (c) 2009 Daniel Verkamp + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * PCX image encoder + * @file libavcodec/pcxenc.c + * @author Daniel Verkamp + * @sa http://www.qzx.com/pc-gpe/pcx.txt + */ + +#include "avcodec.h" +#include "bytestream.h" + +typedef struct PCXContext { + AVFrame picture; +} PCXContext; + +static const uint32_t monoblack_pal[] = { 0x000000, 0xFFFFFF }; + +static av_cold int pcx_encode_init(AVCodecContext *avctx) +{ + PCXContext *s = avctx->priv_data; + + avcodec_get_frame_defaults(&s->picture); + avctx->coded_frame = &s->picture; + + return 0; +} + +/** + * PCX run-length encoder + * @param dst output buffer + * @param dst_size size of output buffer + * @param src input buffer + * @param src_plane_size size of one plane of input buffer in bytes + * @param nplanes number of planes in input buffer + * @return number of bytes written to dst or -1 on error + * @bug will not work for nplanes != 1 && bpp != 8 + */ +static int pcx_rle_encode( uint8_t *dst, int dst_size, + const uint8_t *src, int src_plane_size, int nplanes) +{ + int p; + const uint8_t *dst_start = dst; + + // check worst-case upper bound on dst_size + if (dst_size < 2LL * src_plane_size * nplanes || src_plane_size <= 0) + return -1; + + for (p = 0; p < nplanes; p++) { + int count = 1; + const uint8_t *src_plane = src + p; + const uint8_t *src_plane_end = src_plane + src_plane_size * nplanes; + uint8_t prev = *src_plane; + src_plane += nplanes; + + for (; ; src_plane += nplanes) { + if (src_plane < src_plane_end && *src_plane == prev && count < 0x3F) { + // current byte is same as prev + ++count; + } else { + // output prev * count + if (count != 1 || prev >= 0xC0) + *dst++ = 0xC0 | count; + *dst++ = prev; + + if (src_plane == src_plane_end) + break; + + // start new run + count = 1; + prev = *src_plane; + } + } + } + + return dst - dst_start; +} + +static int pcx_encode_frame(AVCodecContext *avctx, + unsigned char *buf, int buf_size, void *data) +{ + PCXContext *s = avctx->priv_data; + AVFrame *const pict = &s->picture; + const uint8_t *buf_start = buf; + const uint8_t *buf_end = buf + buf_size; + + int bpp, nplanes, i, y, line_bytes, written; + const uint32_t *pal = NULL; + const uint8_t *src; + + *pict = *(AVFrame *)data; + pict->pict_type = FF_I_TYPE; + pict->key_frame = 1; + + if (avctx->width > 65535 || avctx->height > 65535) { + av_log(avctx, AV_LOG_ERROR, "image dimensions do not fit in 16 bits\n"); + return -1; + } + + switch (avctx->pix_fmt) { + case PIX_FMT_RGB24: + bpp = 8; + nplanes = 3; + break; + case PIX_FMT_RGB8: + case PIX_FMT_BGR8: + case PIX_FMT_RGB4_BYTE: + case PIX_FMT_BGR4_BYTE: + case PIX_FMT_GRAY8: + case PIX_FMT_PAL8: + bpp = 8; + nplanes = 1; + pal = (uint32_t *)pict->data[1]; + break; + case PIX_FMT_MONOBLACK: + bpp = 1; + nplanes = 1; + pal = monoblack_pal; + break; + default: + av_log(avctx, AV_LOG_ERROR, "unsupported pixfmt\n"); + return -1; + } + + line_bytes = (avctx->width * bpp + 7) >> 3; + line_bytes = (line_bytes + 1) & ~1; + + bytestream_put_byte(&buf, 10); // manufacturer + bytestream_put_byte(&buf, 5); // version + bytestream_put_byte(&buf, 1); // encoding + bytestream_put_byte(&buf, bpp); // bits per pixel per plane + bytestream_put_le16(&buf, 0); // x min + bytestream_put_le16(&buf, 0); // y min + bytestream_put_le16(&buf, avctx->width - 1); // x max + bytestream_put_le16(&buf, avctx->height - 1); // y max + bytestream_put_le16(&buf, 0); // horizontal DPI + bytestream_put_le16(&buf, 0); // vertical DPI + for (i = 0; i < 16; i++) + bytestream_put_be24(&buf, pal ? pal[i] : 0);// palette (<= 16 color only) + bytestream_put_byte(&buf, 0); // reserved + bytestream_put_byte(&buf, nplanes); // number of planes + bytestream_put_le16(&buf, line_bytes); // scanline plane size in bytes + + while (buf - buf_start < 128) + *buf++= 0; + + src = pict->data[0]; + + for (y = 0; y < avctx->height; y++) { + if ((written = pcx_rle_encode(buf, buf_end - buf, + src, line_bytes, nplanes)) < 0) { + av_log(avctx, AV_LOG_ERROR, "buffer too small\n"); + return -1; + } + buf += written; + src += pict->linesize[0]; + } + + if (nplanes == 1 && bpp == 8) { + if (buf_end - buf < 257) { + av_log(avctx, AV_LOG_ERROR, "buffer too small\n"); + return -1; + } + bytestream_put_byte(&buf, 12); + for (i = 0; i < 256; i++) { + bytestream_put_be24(&buf, pal[i]); + } + } + + return buf - buf_start; +} + +AVCodec pcx_encoder = { + "pcx", + CODEC_TYPE_VIDEO, + CODEC_ID_PCX, + sizeof(PCXContext), + pcx_encode_init, + pcx_encode_frame, + NULL, + .pix_fmts = (enum PixelFormat[]){ + PIX_FMT_RGB24, + PIX_FMT_RGB8, PIX_FMT_BGR8, PIX_FMT_RGB4_BYTE, PIX_FMT_BGR4_BYTE, PIX_FMT_GRAY8, PIX_FMT_PAL8, + PIX_FMT_MONOBLACK, + PIX_FMT_NONE}, + .long_name = NULL_IF_CONFIG_SMALL("PC Paintbrush PCX image"), +}; diff --git a/libavformat/img2.c b/libavformat/img2.c index 113f431a48..48f3dfc4a5 100644 --- a/libavformat/img2.c +++ b/libavformat/img2.c @@ -428,7 +428,7 @@ AVOutputFormat image2_muxer = { "image2", NULL_IF_CONFIG_SMALL("image2 sequence"), "", - "bmp,jpeg,jpg,ljpg,pam,pbm,pgm,pgmyuv,png,ppm,sgi,tif,tiff,jp2", + "bmp,jpeg,jpg,ljpg,pam,pbm,pcx,pgm,pgmyuv,png,ppm,sgi,tif,tiff,jp2", sizeof(VideoData), CODEC_ID_NONE, CODEC_ID_MJPEG, diff --git a/tests/libav.regression.ref b/tests/libav.regression.ref index d5c488e6d8..817a209fb2 100644 --- a/tests/libav.regression.ref +++ b/tests/libav.regression.ref @@ -138,3 +138,6 @@ ac2c17f1a27d928e8b82f21dbafdd715 *./tests/data/b-libav-yuv440p.yuv 304128 ./tests/data/b-libav-yuv440p.yuv 10c8507ad38d0ce5e8cd0f1dd49b0d26 *./tests/data/b-libav-yuvj440p.yuv 304128 ./tests/data/b-libav-yuvj440p.yuv +574fb8fe0b2fe8cc0b3ded8549c052d4 *./tests/data/b-libav02.pcx +./tests/data/b-libav%02d.pcx CRC=0x6f775c0d +363436 ./tests/data/b-libav02.pcx diff --git a/tests/regression.sh b/tests/regression.sh index bd7bd1c780..888860f539 100755 --- a/tests/regression.sh +++ b/tests/regression.sh @@ -601,6 +601,10 @@ if [ -n "$do_jpg" ] ; then do_image_formats jpg "-flags +bitexact -dct fastint -idct simple -pix_fmt yuvj420p" "-f image2" fi +if [ -n "$do_pcx" ] ; then +do_image_formats pcx +fi + # audio only if [ -n "$do_wav" ] ; then