From a22c996a851fbfd53f075838697759a18bb2f71a Mon Sep 17 00:00:00 2001 From: Michael Bradshaw Date: Sun, 12 Aug 2012 10:29:36 -0600 Subject: [PATCH] Add ICO muxer Signed-off-by: Michael Bradshaw Reviewed-by: Peter Ross Signed-off-by: Michael Niedermayer --- Changelog | 1 + doc/general.texi | 2 +- doc/muxers.texi | 33 +++++++ libavformat/Makefile | 1 + libavformat/allformats.c | 2 +- libavformat/icoenc.c | 204 +++++++++++++++++++++++++++++++++++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 libavformat/icoenc.c diff --git a/Changelog b/Changelog index c412e5bcb2..1bbe690cc9 100644 --- a/Changelog +++ b/Changelog @@ -45,6 +45,7 @@ version next: - smptebars source - asetpts filter - hue filter +- ICO muxer version 0.11: diff --git a/doc/general.texi b/doc/general.texi index f505173fac..9a224d823d 100644 --- a/doc/general.texi +++ b/doc/general.texi @@ -212,7 +212,7 @@ library: @tab General eXchange Format SMPTE 360M, used by Thomson Grass Valley playout servers. @item iCEDraw File @tab @tab X -@item ICO @tab @tab X +@item ICO @tab X @tab X @tab Microsoft Windows ICO @item id Quake II CIN video @tab @tab X @item id RoQ @tab X @tab X diff --git a/doc/muxers.texi b/doc/muxers.texi index aee90b53ce..2cb8e133c6 100644 --- a/doc/muxers.texi +++ b/doc/muxers.texi @@ -129,6 +129,39 @@ ffmpeg -i INPUT -f framemd5 - See also the @ref{md5} muxer. +@anchor{ico} +@section ico + +ICO file muxer. + +Microsoft's icon file format (ICO) has some strict limitations that should be noted: + +@itemize +@item +Size cannot exceed 256 pixels in any dimension + +@item +Only BMP and PNG images can be stored + +@item +If a BMP image is used, it must be one of the following pixel formats: +@example +BMP Bit Depth FFmpeg Pixel Format +1bit pal8 +4bit pal8 +8bit pal8 +16bit rgb555le +24bit bgr24 +32bit bgra +@end example + +@item +If a BMP image is used, it must use the BITMAPINFOHEADER DIB header + +@item +If a PNG image is used, it must use the rgba pixel format +@end itemize + @anchor{image2} @section image2 diff --git a/libavformat/Makefile b/libavformat/Makefile index 2994eef191..72f9c22f82 100644 --- a/libavformat/Makefile +++ b/libavformat/Makefile @@ -122,6 +122,7 @@ OBJS-$(CONFIG_H264_DEMUXER) += h264dec.o rawdec.o OBJS-$(CONFIG_H264_MUXER) += rawenc.o OBJS-$(CONFIG_HLS_DEMUXER) += hls.o OBJS-$(CONFIG_ICO_DEMUXER) += icodec.o +OBJS-$(CONFIG_ICO_MUXER) += icoenc.o OBJS-$(CONFIG_IDCIN_DEMUXER) += idcin.o OBJS-$(CONFIG_IDF_DEMUXER) += bintext.o OBJS-$(CONFIG_IFF_DEMUXER) += iff.o diff --git a/libavformat/allformats.c b/libavformat/allformats.c index d1b41e6ac7..9df6280e8e 100644 --- a/libavformat/allformats.c +++ b/libavformat/allformats.c @@ -112,7 +112,7 @@ void av_register_all(void) REGISTER_MUXDEMUX (H263, h263); REGISTER_MUXDEMUX (H264, h264); REGISTER_DEMUXER (HLS, hls); - REGISTER_DEMUXER (ICO, ico); + REGISTER_MUXDEMUX (ICO, ico); REGISTER_DEMUXER (IDCIN, idcin); REGISTER_DEMUXER (IDF, idf); REGISTER_DEMUXER (IFF, iff); diff --git a/libavformat/icoenc.c b/libavformat/icoenc.c new file mode 100644 index 0000000000..e755b211ac --- /dev/null +++ b/libavformat/icoenc.c @@ -0,0 +1,204 @@ +/* + * Microsoft Windows ICO muxer + * Copyright (c) 2012 Michael Bradshaw + * + * 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 + */ + +/** + * @file + * Microsoft Windows ICO muxer + */ + +#include "libavutil/intreadwrite.h" +#include "libavutil/pixdesc.h" +#include "avformat.h" + +typedef struct { + int offset; + int size; + unsigned char width; + unsigned char height; + short bits; +} IcoImage; + +typedef struct { + int current_image; + int nb_images; + IcoImage *images; +} IcoMuxContext; + +static int ico_check_attributes(AVFormatContext *s, const AVCodecContext *c) +{ + if (c->codec_id == CODEC_ID_BMP) { + if (c->pix_fmt == PIX_FMT_PAL8 && PIX_FMT_RGB32 != PIX_FMT_BGRA) { + av_log(s, AV_LOG_ERROR, "Wrong endianness for bmp pixel format\n"); + return AVERROR(EINVAL); + } else if (c->pix_fmt != PIX_FMT_PAL8 && + c->pix_fmt != PIX_FMT_RGB555LE && + c->pix_fmt != PIX_FMT_BGR24 && + c->pix_fmt != PIX_FMT_BGRA) { + av_log(s, AV_LOG_ERROR, "BMP must be 1bit, 4bit, 8bit, 16bit, 24bit, or 32bit\n"); + return AVERROR(EINVAL); + } + } else if (c->codec_id == CODEC_ID_PNG) { + if (c->pix_fmt != PIX_FMT_RGBA) { + av_log(s, AV_LOG_ERROR, "PNG in ico requires pixel format to be rgba\n"); + return AVERROR(EINVAL); + } + } else { + av_log(s, AV_LOG_ERROR, "Unsupported codec %s\n", c->codec_name); + return AVERROR(EINVAL); + } + + if (c->width > 256 || + c->height > 256) { + av_log(s, AV_LOG_ERROR, "Unsupported dimensions %dx%d (dimensions cannot exceed 256x256)\n", c->width, c->height); + return AVERROR(EINVAL); + } + + return 0; +} + +static int ico_write_header(AVFormatContext *s) +{ + IcoMuxContext *ico = s->priv_data; + AVIOContext *pb = s->pb; + int ret; + int i; + + if (!pb->seekable) { + av_log(s, AV_LOG_ERROR, "Output is not seekable\n"); + return AVERROR(EINVAL); + } + + ico->current_image = 0; + ico->nb_images = s->nb_streams; + + avio_wl16(pb, 0); // reserved + avio_wl16(pb, 1); // 1 == icon + avio_skip(pb, 2); // skip the number of images + + for (i = 0; i < s->nb_streams; i++) { + if (ret = ico_check_attributes(s, s->streams[i]->codec)) + return ret; + + // Fill in later when writing trailer... + avio_skip(pb, 16); + } + + ico->images = av_mallocz(ico->nb_images * sizeof(IcoMuxContext)); + if (!ico->images) + return AVERROR(ENOMEM); + + avio_flush(pb); + + return 0; +} + +static int ico_write_packet(AVFormatContext *s, AVPacket *pkt) +{ + IcoMuxContext *ico = s->priv_data; + IcoImage *image; + AVIOContext *pb = s->pb; + AVCodecContext *c = s->streams[pkt->stream_index]->codec; + int i; + + if (ico->current_image >= ico->nb_images) { + av_log(s, AV_LOG_ERROR, "ICO already contains %d images\n", ico->current_image); + return AVERROR(EIO); + } + + image = &ico->images[ico->current_image++]; + + image->offset = avio_tell(pb); + image->width = (c->width == 256) ? 0 : c->width; + image->height = (c->height == 256) ? 0 : c->height; + + if (c->codec_id == CODEC_ID_PNG) { + image->bits = c->bits_per_coded_sample; + image->size = pkt->size; + + avio_write(pb, pkt->data, pkt->size); + } else { // BMP + if (AV_RL32(pkt->data + 14) != 40) { // must be BITMAPINFOHEADER + av_log(s, AV_LOG_ERROR, "Invalid BMP\n"); + return AVERROR(EINVAL); + } + + image->bits = AV_RL16(pkt->data + 28); // allows things like 1bit and 4bit images to be preserved + image->size = pkt->size - 14 + c->height * (c->width + 7) / 8; + + avio_write(pb, pkt->data + 14, 8); // Skip the BITMAPFILEHEADER header + avio_wl32(pb, AV_RL32(pkt->data + 22) * 2); // rewrite height as 2 * height + avio_write(pb, pkt->data + 26, pkt->size - 26); + + for (i = 0; i < c->height * (c->width + 7) / 8; ++i) + avio_w8(pb, 0x00); // Write bitmask (opaque) + } + + avio_flush(pb); + + return 0; +} + +static int ico_write_trailer(AVFormatContext *s) +{ + IcoMuxContext *ico = s->priv_data; + AVIOContext *pb = s->pb; + int i; + + avio_seek(pb, 4, SEEK_SET); + + avio_wl16(pb, ico->current_image); + + for (i = 0; i < ico->nb_images; i++) { + avio_w8(pb, ico->images[i].width); + avio_w8(pb, ico->images[i].height); + + if (s->streams[i]->codec->codec_id == CODEC_ID_BMP && + s->streams[i]->codec->pix_fmt == PIX_FMT_PAL8) { + avio_w8(pb, (ico->images[i].bits >= 8) ? 0 : 1 << ico->images[i].bits); + } else { + avio_w8(pb, 0); + } + + avio_w8(pb, 0); // reserved + avio_wl16(pb, 1); // color planes + avio_wl16(pb, ico->images[i].bits); + avio_wl32(pb, ico->images[i].size); + avio_wl32(pb, ico->images[i].offset); + } + + av_freep(&ico->images); + + return 0; +} + +AVOutputFormat ff_ico_muxer = { + .name = "ico", + .long_name = NULL_IF_CONFIG_SMALL("Microsoft Windows ICO"), + .priv_data_size = sizeof(IcoMuxContext), + .mime_type = "image/vnd.microsoft.icon", + .extensions = "ico", + .audio_codec = CODEC_ID_NONE, + .video_codec = CODEC_ID_BMP, + .write_header = ico_write_header, + .write_packet = ico_write_packet, + .write_trailer = ico_write_trailer, + .flags = AVFMT_NOTIMESTAMPS, +};