Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(203)

Side by Side Diff: tools/resources/ico_tools.py

Issue 2481993002: optimize-ico-files: Compress all icon sizes as PNG images to save space. (Closed)
Patch Set: Created 4 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 # Copyright 2015 The Chromium Authors. All rights reserved. 1 # Copyright 2015 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be 2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file. 3 # found in the LICENSE file.
4 4
5 import logging 5 import logging
6 import math 6 import math
7 import os 7 import os
8 import struct 8 import struct
9 import subprocess 9 import subprocess
10 import sys 10 import sys
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after
136 136
137 return OptimizePngFile(temp_dir, png_filename) 137 return OptimizePngFile(temp_dir, png_filename)
138 138
139 finally: 139 finally:
140 if os.path.exists(ico_filename): 140 if os.path.exists(ico_filename):
141 os.unlink(ico_filename) 141 os.unlink(ico_filename)
142 if os.path.exists(png_filename): 142 if os.path.exists(png_filename):
143 os.unlink(png_filename) 143 os.unlink(png_filename)
144 os.rmdir(temp_dir) 144 os.rmdir(temp_dir)
145 145
146 def ComputeANDMaskFromAlpha(image_data, width, height):
147 """Compute an AND mask from 32-bit BGRA image data."""
148 and_bytes = []
149 for y in range(height):
150 bit_count = 0
151 current_byte = 0
152 for x in range(width):
153 alpha = image_data[(y * width + x) * 4 + 3]
154 current_byte <<= 1
155 if ord(alpha) == 0:
156 current_byte |= 1
157 bit_count += 1
158 if bit_count == 8:
159 and_bytes.append(current_byte)
160 bit_count = 0
161 current_byte = 0
162
163 # At the end of a row, pad the current byte.
164 if bit_count > 0:
165 current_byte <<= (8 - bit_count)
166 and_bytes.append(current_byte)
167 # And keep padding until a multiple of 4 bytes.
168 while len(and_bytes) % 4 != 0:
169 and_bytes.append(0)
170
171 and_bytes = ''.join(map(chr, and_bytes))
172 return and_bytes
173
174 def RebuildANDMask(iconimage):
175 """Rebuild the AND mask in an icon image.
176
177 GIMP (<=2.8.14) creates a bad AND mask on 32-bit icon images (pixels with <50%
178 opacity are marked as transparent, which end up looking black on Windows). So,
179 if this is a 32-bit image, throw the mask away and recompute it from the alpha
180 data. (See: https://bugzilla.gnome.org/show_bug.cgi?id=755200)
181
182 Args:
183 iconimage: Bytes of an icon image (the BMP data for an entry in an ICO
184 file). Must be in BMP format, not PNG. Does not need to be 32-bit (if it
185 is not 32-bit, this is a no-op).
186
187 Returns:
188 An updated |iconimage|, with the AND mask re-computed using
189 ComputeANDMaskFromAlpha.
190 """
191 # Parse BITMAPINFOHEADER.
192 (_, width, height, _, bpp, _, _, _, _, num_colors, _) = struct.unpack(
193 '<LLLHHLLLLLL', iconimage[:40])
194
195 if bpp != 32:
196 # No alpha channel, so the mask cannot be "wrong" (it is the only source of
197 # transparency information).
198 return iconimage
199
200 height /= 2
201 xor_size = int(math.ceil(width * bpp / 32.0)) * 4 * height
202
203 # num_colors can be 0, implying 2^bpp colors.
204 xor_palette_size = (num_colors or (1 << bpp if bpp < 24 else 0)) * 4
205 xor_data = iconimage[40 + xor_palette_size :
206 40 + xor_palette_size + xor_size]
207
208 and_data = ComputeANDMaskFromAlpha(xor_data, width, height)
209
210 # Replace the AND mask in the original icon data.
211 return iconimage[:40 + xor_palette_size + xor_size] + and_data
212
213 def OptimizeIcoFile(infile, outfile, optimization_level=None): 146 def OptimizeIcoFile(infile, outfile, optimization_level=None):
214 """Read an ICO file, optimize its PNGs, and write the output to outfile. 147 """Read an ICO file, optimize its PNGs, and write the output to outfile.
215 148
216 Args: 149 Args:
217 infile: The file to read from. Must be a seekable file-like object 150 infile: The file to read from. Must be a seekable file-like object
218 containing a Microsoft ICO file. 151 containing a Microsoft ICO file.
219 outfile: The file to write to. 152 outfile: The file to write to.
220 """ 153 """
221 filename = os.path.basename(infile.name) 154 filename = os.path.basename(infile.name)
222 icondir = infile.read(6) 155 icondir = infile.read(6)
(...skipping 21 matching lines...) Expand all
244 if len(icon_data) != size: 177 if len(icon_data) != size:
245 raise EOFError() 178 raise EOFError()
246 179
247 entry_is_png = IsPng(icon_data) 180 entry_is_png = IsPng(icon_data)
248 logging.info('%s entry #%d: %dx%d, %d bytes (%s)', filename, i + 1, width, 181 logging.info('%s entry #%d: %dx%d, %d bytes (%s)', filename, i + 1, width,
249 height, size, 'PNG' if entry_is_png else 'BMP') 182 height, size, 'PNG' if entry_is_png else 'BMP')
250 183
251 if entry_is_png: 184 if entry_is_png:
252 # It is a PNG. Crush it. 185 # It is a PNG. Crush it.
253 icon_data = OptimizePng(icon_data, optimization_level=optimization_level) 186 icon_data = OptimizePng(icon_data, optimization_level=optimization_level)
254 elif width >= 256 or height >= 256: 187 else:
255 # It is a large BMP. Reformat as a PNG, then crush it. 188 # It is a large BMP. Reformat as a PNG, then crush it.
256 # Note: Smaller images are kept uncompressed, for compatibility with 189 # Note: Icons smaller than 256x256 are supposed to be kept uncompressed,
257 # Windows XP. 190 # for compatibility with Windows XP and earlier. However, since Chrome no
258 # TODO(mgiuca): Now that we no longer support XP, we can probably compress 191 # longer supports XP, we just compress all the images to save space.
259 # all of the images. https://crbug.com/663136
260 icon_data = OptimizeBmp(icon_dir_entries[i], icon_data) 192 icon_data = OptimizeBmp(icon_dir_entries[i], icon_data)
261 else:
262 new_icon_data = RebuildANDMask(icon_data)
263 if new_icon_data != icon_data:
264 logging.info(' * Rebuilt AND mask for this image from alpha channel.')
265 icon_data = new_icon_data
266 193
267 new_size = len(icon_data) 194 new_size = len(icon_data)
268 current_offset += new_size 195 current_offset += new_size
269 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3, 196 icon_dir_entries[i] = (width % 256, height % 256, num_colors, r1, r2, r3,
270 new_size, offset) 197 new_size, offset)
271 icon_bitmap_data.append(icon_data) 198 icon_bitmap_data.append(icon_data)
272 199
273 # Write the data back to outfile. 200 # Write the data back to outfile.
274 outfile.write(icondir) 201 outfile.write(icondir)
275 for icon_dir_entry in icon_dir_entries: 202 for icon_dir_entry in icon_dir_entries:
276 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry)) 203 outfile.write(struct.pack('<BBBBHHLL', *icon_dir_entry))
277 for icon_bitmap in icon_bitmap_data: 204 for icon_bitmap in icon_bitmap_data:
278 outfile.write(icon_bitmap) 205 outfile.write(icon_bitmap)
OLDNEW
« no previous file with comments | « no previous file | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698