Commit | Line | Data |
---|---|---|
2ba45a60 DM |
1 | /* |
2 | * Microsoft Windows ICO muxer | |
3 | * Copyright (c) 2012 Michael Bradshaw <mjbshaw gmail com> | |
4 | * | |
5 | * This file is part of FFmpeg. | |
6 | * | |
7 | * FFmpeg is free software; you can redistribute it and/or | |
8 | * modify it under the terms of the GNU Lesser General Public | |
9 | * License as published by the Free Software Foundation; either | |
10 | * version 2.1 of the License, or (at your option) any later version. | |
11 | * | |
12 | * FFmpeg is distributed in the hope that it will be useful, | |
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 | |
18 | * License along with FFmpeg; if not, write to the Free Software | |
19 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
20 | */ | |
21 | ||
22 | /** | |
23 | * @file | |
24 | * Microsoft Windows ICO muxer | |
25 | */ | |
26 | ||
27 | #include "libavutil/intreadwrite.h" | |
28 | #include "libavutil/pixdesc.h" | |
29 | #include "avformat.h" | |
30 | ||
31 | typedef struct { | |
32 | int offset; | |
33 | int size; | |
34 | unsigned char width; | |
35 | unsigned char height; | |
36 | short bits; | |
37 | } IcoImage; | |
38 | ||
39 | typedef struct { | |
40 | int current_image; | |
41 | int nb_images; | |
42 | IcoImage *images; | |
43 | } IcoMuxContext; | |
44 | ||
45 | static int ico_check_attributes(AVFormatContext *s, const AVCodecContext *c) | |
46 | { | |
47 | if (c->codec_id == AV_CODEC_ID_BMP) { | |
48 | if (c->pix_fmt == AV_PIX_FMT_PAL8 && AV_PIX_FMT_RGB32 != AV_PIX_FMT_BGRA) { | |
49 | av_log(s, AV_LOG_ERROR, "Wrong endianness for bmp pixel format\n"); | |
50 | return AVERROR(EINVAL); | |
51 | } else if (c->pix_fmt != AV_PIX_FMT_PAL8 && | |
52 | c->pix_fmt != AV_PIX_FMT_RGB555LE && | |
53 | c->pix_fmt != AV_PIX_FMT_BGR24 && | |
54 | c->pix_fmt != AV_PIX_FMT_BGRA) { | |
55 | av_log(s, AV_LOG_ERROR, "BMP must be 1bit, 4bit, 8bit, 16bit, 24bit, or 32bit\n"); | |
56 | return AVERROR(EINVAL); | |
57 | } | |
58 | } else if (c->codec_id == AV_CODEC_ID_PNG) { | |
59 | if (c->pix_fmt != AV_PIX_FMT_RGBA) { | |
60 | av_log(s, AV_LOG_ERROR, "PNG in ico requires pixel format to be rgba\n"); | |
61 | return AVERROR(EINVAL); | |
62 | } | |
63 | } else { | |
64 | const AVCodecDescriptor *codesc = avcodec_descriptor_get(c->codec_id); | |
65 | av_log(s, AV_LOG_ERROR, "Unsupported codec %s\n", codesc ? codesc->name : ""); | |
66 | return AVERROR(EINVAL); | |
67 | } | |
68 | ||
69 | if (c->width > 256 || | |
70 | c->height > 256) { | |
71 | av_log(s, AV_LOG_ERROR, "Unsupported dimensions %dx%d (dimensions cannot exceed 256x256)\n", c->width, c->height); | |
72 | return AVERROR(EINVAL); | |
73 | } | |
74 | ||
75 | return 0; | |
76 | } | |
77 | ||
78 | static int ico_write_header(AVFormatContext *s) | |
79 | { | |
80 | IcoMuxContext *ico = s->priv_data; | |
81 | AVIOContext *pb = s->pb; | |
82 | int ret; | |
83 | int i; | |
84 | ||
85 | if (!pb->seekable) { | |
86 | av_log(s, AV_LOG_ERROR, "Output is not seekable\n"); | |
87 | return AVERROR(EINVAL); | |
88 | } | |
89 | ||
90 | ico->current_image = 0; | |
91 | ico->nb_images = s->nb_streams; | |
92 | ||
93 | avio_wl16(pb, 0); // reserved | |
94 | avio_wl16(pb, 1); // 1 == icon | |
95 | avio_skip(pb, 2); // skip the number of images | |
96 | ||
97 | for (i = 0; i < s->nb_streams; i++) { | |
98 | if (ret = ico_check_attributes(s, s->streams[i]->codec)) | |
99 | return ret; | |
100 | ||
101 | // Fill in later when writing trailer... | |
102 | avio_skip(pb, 16); | |
103 | } | |
104 | ||
105 | ico->images = av_mallocz_array(ico->nb_images, sizeof(IcoMuxContext)); | |
106 | if (!ico->images) | |
107 | return AVERROR(ENOMEM); | |
108 | ||
109 | avio_flush(pb); | |
110 | ||
111 | return 0; | |
112 | } | |
113 | ||
114 | static int ico_write_packet(AVFormatContext *s, AVPacket *pkt) | |
115 | { | |
116 | IcoMuxContext *ico = s->priv_data; | |
117 | IcoImage *image; | |
118 | AVIOContext *pb = s->pb; | |
119 | AVCodecContext *c = s->streams[pkt->stream_index]->codec; | |
120 | int i; | |
121 | ||
122 | if (ico->current_image >= ico->nb_images) { | |
123 | av_log(s, AV_LOG_ERROR, "ICO already contains %d images\n", ico->current_image); | |
124 | return AVERROR(EIO); | |
125 | } | |
126 | ||
127 | image = &ico->images[ico->current_image++]; | |
128 | ||
129 | image->offset = avio_tell(pb); | |
130 | image->width = (c->width == 256) ? 0 : c->width; | |
131 | image->height = (c->height == 256) ? 0 : c->height; | |
132 | ||
133 | if (c->codec_id == AV_CODEC_ID_PNG) { | |
134 | image->bits = c->bits_per_coded_sample; | |
135 | image->size = pkt->size; | |
136 | ||
137 | avio_write(pb, pkt->data, pkt->size); | |
138 | } else { // BMP | |
139 | if (AV_RL32(pkt->data + 14) != 40) { // must be BITMAPINFOHEADER | |
140 | av_log(s, AV_LOG_ERROR, "Invalid BMP\n"); | |
141 | return AVERROR(EINVAL); | |
142 | } | |
143 | ||
144 | image->bits = AV_RL16(pkt->data + 28); // allows things like 1bit and 4bit images to be preserved | |
145 | image->size = pkt->size - 14 + c->height * (c->width + 7) / 8; | |
146 | ||
147 | avio_write(pb, pkt->data + 14, 8); // Skip the BITMAPFILEHEADER header | |
148 | avio_wl32(pb, AV_RL32(pkt->data + 22) * 2); // rewrite height as 2 * height | |
149 | avio_write(pb, pkt->data + 26, pkt->size - 26); | |
150 | ||
151 | for (i = 0; i < c->height * (c->width + 7) / 8; ++i) | |
152 | avio_w8(pb, 0x00); // Write bitmask (opaque) | |
153 | } | |
154 | ||
155 | return 0; | |
156 | } | |
157 | ||
158 | static int ico_write_trailer(AVFormatContext *s) | |
159 | { | |
160 | IcoMuxContext *ico = s->priv_data; | |
161 | AVIOContext *pb = s->pb; | |
162 | int i; | |
163 | ||
164 | avio_seek(pb, 4, SEEK_SET); | |
165 | ||
166 | avio_wl16(pb, ico->current_image); | |
167 | ||
168 | for (i = 0; i < ico->nb_images; i++) { | |
169 | avio_w8(pb, ico->images[i].width); | |
170 | avio_w8(pb, ico->images[i].height); | |
171 | ||
172 | if (s->streams[i]->codec->codec_id == AV_CODEC_ID_BMP && | |
173 | s->streams[i]->codec->pix_fmt == AV_PIX_FMT_PAL8) { | |
174 | avio_w8(pb, (ico->images[i].bits >= 8) ? 0 : 1 << ico->images[i].bits); | |
175 | } else { | |
176 | avio_w8(pb, 0); | |
177 | } | |
178 | ||
179 | avio_w8(pb, 0); // reserved | |
180 | avio_wl16(pb, 1); // color planes | |
181 | avio_wl16(pb, ico->images[i].bits); | |
182 | avio_wl32(pb, ico->images[i].size); | |
183 | avio_wl32(pb, ico->images[i].offset); | |
184 | } | |
185 | ||
186 | av_freep(&ico->images); | |
187 | ||
188 | return 0; | |
189 | } | |
190 | ||
191 | AVOutputFormat ff_ico_muxer = { | |
192 | .name = "ico", | |
193 | .long_name = NULL_IF_CONFIG_SMALL("Microsoft Windows ICO"), | |
194 | .priv_data_size = sizeof(IcoMuxContext), | |
195 | .mime_type = "image/vnd.microsoft.icon", | |
196 | .extensions = "ico", | |
197 | .audio_codec = AV_CODEC_ID_NONE, | |
198 | .video_codec = AV_CODEC_ID_BMP, | |
199 | .write_header = ico_write_header, | |
200 | .write_packet = ico_write_packet, | |
201 | .write_trailer = ico_write_trailer, | |
202 | .flags = AVFMT_NOTIMESTAMPS, | |
203 | }; |