yading@11: /* yading@11: * Microsoft Windows ICO muxer yading@11: * Copyright (c) 2012 Michael Bradshaw yading@11: * yading@11: * This file is part of FFmpeg. yading@11: * yading@11: * FFmpeg is free software; you can redistribute it and/or yading@11: * modify it under the terms of the GNU Lesser General Public yading@11: * License as published by the Free Software Foundation; either yading@11: * version 2.1 of the License, or (at your option) any later version. yading@11: * yading@11: * FFmpeg is distributed in the hope that it will be useful, yading@11: * but WITHOUT ANY WARRANTY; without even the implied warranty of yading@11: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU yading@11: * Lesser General Public License for more details. yading@11: * yading@11: * You should have received a copy of the GNU Lesser General Public yading@11: * License along with FFmpeg; if not, write to the Free Software yading@11: * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA yading@11: */ yading@11: yading@11: /** yading@11: * @file yading@11: * Microsoft Windows ICO muxer yading@11: */ yading@11: yading@11: #include "libavutil/intreadwrite.h" yading@11: #include "libavutil/pixdesc.h" yading@11: #include "avformat.h" yading@11: yading@11: typedef struct { yading@11: int offset; yading@11: int size; yading@11: unsigned char width; yading@11: unsigned char height; yading@11: short bits; yading@11: } IcoImage; yading@11: yading@11: typedef struct { yading@11: int current_image; yading@11: int nb_images; yading@11: IcoImage *images; yading@11: } IcoMuxContext; yading@11: yading@11: static int ico_check_attributes(AVFormatContext *s, const AVCodecContext *c) yading@11: { yading@11: if (c->codec_id == AV_CODEC_ID_BMP) { yading@11: if (c->pix_fmt == AV_PIX_FMT_PAL8 && AV_PIX_FMT_RGB32 != AV_PIX_FMT_BGRA) { yading@11: av_log(s, AV_LOG_ERROR, "Wrong endianness for bmp pixel format\n"); yading@11: return AVERROR(EINVAL); yading@11: } else if (c->pix_fmt != AV_PIX_FMT_PAL8 && yading@11: c->pix_fmt != AV_PIX_FMT_RGB555LE && yading@11: c->pix_fmt != AV_PIX_FMT_BGR24 && yading@11: c->pix_fmt != AV_PIX_FMT_BGRA) { yading@11: av_log(s, AV_LOG_ERROR, "BMP must be 1bit, 4bit, 8bit, 16bit, 24bit, or 32bit\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: } else if (c->codec_id == AV_CODEC_ID_PNG) { yading@11: if (c->pix_fmt != AV_PIX_FMT_RGBA) { yading@11: av_log(s, AV_LOG_ERROR, "PNG in ico requires pixel format to be rgba\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: } else { yading@11: av_log(s, AV_LOG_ERROR, "Unsupported codec %s\n", c->codec_name); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: if (c->width > 256 || yading@11: c->height > 256) { yading@11: av_log(s, AV_LOG_ERROR, "Unsupported dimensions %dx%d (dimensions cannot exceed 256x256)\n", c->width, c->height); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int ico_write_header(AVFormatContext *s) yading@11: { yading@11: IcoMuxContext *ico = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: int ret; yading@11: int i; yading@11: yading@11: if (!pb->seekable) { yading@11: av_log(s, AV_LOG_ERROR, "Output is not seekable\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: ico->current_image = 0; yading@11: ico->nb_images = s->nb_streams; yading@11: yading@11: avio_wl16(pb, 0); // reserved yading@11: avio_wl16(pb, 1); // 1 == icon yading@11: avio_skip(pb, 2); // skip the number of images yading@11: yading@11: for (i = 0; i < s->nb_streams; i++) { yading@11: if (ret = ico_check_attributes(s, s->streams[i]->codec)) yading@11: return ret; yading@11: yading@11: // Fill in later when writing trailer... yading@11: avio_skip(pb, 16); yading@11: } yading@11: yading@11: ico->images = av_mallocz(ico->nb_images * sizeof(IcoMuxContext)); yading@11: if (!ico->images) yading@11: return AVERROR(ENOMEM); yading@11: yading@11: avio_flush(pb); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int ico_write_packet(AVFormatContext *s, AVPacket *pkt) yading@11: { yading@11: IcoMuxContext *ico = s->priv_data; yading@11: IcoImage *image; yading@11: AVIOContext *pb = s->pb; yading@11: AVCodecContext *c = s->streams[pkt->stream_index]->codec; yading@11: int i; yading@11: yading@11: if (ico->current_image >= ico->nb_images) { yading@11: av_log(s, AV_LOG_ERROR, "ICO already contains %d images\n", ico->current_image); yading@11: return AVERROR(EIO); yading@11: } yading@11: yading@11: image = &ico->images[ico->current_image++]; yading@11: yading@11: image->offset = avio_tell(pb); yading@11: image->width = (c->width == 256) ? 0 : c->width; yading@11: image->height = (c->height == 256) ? 0 : c->height; yading@11: yading@11: if (c->codec_id == AV_CODEC_ID_PNG) { yading@11: image->bits = c->bits_per_coded_sample; yading@11: image->size = pkt->size; yading@11: yading@11: avio_write(pb, pkt->data, pkt->size); yading@11: } else { // BMP yading@11: if (AV_RL32(pkt->data + 14) != 40) { // must be BITMAPINFOHEADER yading@11: av_log(s, AV_LOG_ERROR, "Invalid BMP\n"); yading@11: return AVERROR(EINVAL); yading@11: } yading@11: yading@11: image->bits = AV_RL16(pkt->data + 28); // allows things like 1bit and 4bit images to be preserved yading@11: image->size = pkt->size - 14 + c->height * (c->width + 7) / 8; yading@11: yading@11: avio_write(pb, pkt->data + 14, 8); // Skip the BITMAPFILEHEADER header yading@11: avio_wl32(pb, AV_RL32(pkt->data + 22) * 2); // rewrite height as 2 * height yading@11: avio_write(pb, pkt->data + 26, pkt->size - 26); yading@11: yading@11: for (i = 0; i < c->height * (c->width + 7) / 8; ++i) yading@11: avio_w8(pb, 0x00); // Write bitmask (opaque) yading@11: } yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: static int ico_write_trailer(AVFormatContext *s) yading@11: { yading@11: IcoMuxContext *ico = s->priv_data; yading@11: AVIOContext *pb = s->pb; yading@11: int i; yading@11: yading@11: avio_seek(pb, 4, SEEK_SET); yading@11: yading@11: avio_wl16(pb, ico->current_image); yading@11: yading@11: for (i = 0; i < ico->nb_images; i++) { yading@11: avio_w8(pb, ico->images[i].width); yading@11: avio_w8(pb, ico->images[i].height); yading@11: yading@11: if (s->streams[i]->codec->codec_id == AV_CODEC_ID_BMP && yading@11: s->streams[i]->codec->pix_fmt == AV_PIX_FMT_PAL8) { yading@11: avio_w8(pb, (ico->images[i].bits >= 8) ? 0 : 1 << ico->images[i].bits); yading@11: } else { yading@11: avio_w8(pb, 0); yading@11: } yading@11: yading@11: avio_w8(pb, 0); // reserved yading@11: avio_wl16(pb, 1); // color planes yading@11: avio_wl16(pb, ico->images[i].bits); yading@11: avio_wl32(pb, ico->images[i].size); yading@11: avio_wl32(pb, ico->images[i].offset); yading@11: } yading@11: yading@11: av_freep(&ico->images); yading@11: yading@11: return 0; yading@11: } yading@11: yading@11: AVOutputFormat ff_ico_muxer = { yading@11: .name = "ico", yading@11: .long_name = NULL_IF_CONFIG_SMALL("Microsoft Windows ICO"), yading@11: .priv_data_size = sizeof(IcoMuxContext), yading@11: .mime_type = "image/vnd.microsoft.icon", yading@11: .extensions = "ico", yading@11: .audio_codec = AV_CODEC_ID_NONE, yading@11: .video_codec = AV_CODEC_ID_BMP, yading@11: .write_header = ico_write_header, yading@11: .write_packet = ico_write_packet, yading@11: .write_trailer = ico_write_trailer, yading@11: .flags = AVFMT_NOTIMESTAMPS, yading@11: };