| OLD | NEW |
| 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 Loading... |
| 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 Loading... |
| 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) |
| OLD | NEW |