blob: e641f7bdc71922d014c5d0a019ac68a977b4c72a
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 AVCodecParameters *p) |
46 | { |
47 | if (p->codec_id == AV_CODEC_ID_BMP) { |
48 | if (p->format == 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 (p->format != AV_PIX_FMT_PAL8 && |
52 | p->format != AV_PIX_FMT_RGB555LE && |
53 | p->format != AV_PIX_FMT_BGR24 && |
54 | p->format != 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 (p->codec_id == AV_CODEC_ID_PNG) { |
59 | if (p->format != 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(p->codec_id); |
65 | av_log(s, AV_LOG_ERROR, "Unsupported codec %s\n", codesc ? codesc->name : ""); |
66 | return AVERROR(EINVAL); |
67 | } |
68 | |
69 | if (p->width > 256 || |
70 | p->height > 256) { |
71 | av_log(s, AV_LOG_ERROR, "Unsupported dimensions %dx%d (dimensions cannot exceed 256x256)\n", p->width, p->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 & AVIO_SEEKABLE_NORMAL)) { |
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]->codecpar)) |
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 | AVCodecParameters *par = s->streams[pkt->stream_index]->codecpar; |
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 = (par->width == 256) ? 0 : par->width; |
131 | image->height = (par->height == 256) ? 0 : par->height; |
132 | |
133 | if (par->codec_id == AV_CODEC_ID_PNG) { |
134 | image->bits = par->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 + par->height * (par->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 < par->height * (par->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]->codecpar->codec_id == AV_CODEC_ID_BMP && |
173 | s->streams[i]->codecpar->format == 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 | }; |
204 |