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

Unified Diff: tools/resources/optimize-ico-files.py

Issue 1372843002: optimize-ico-files: Automatically rebuild the AND mask of 32-bit images. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Added link to bug. Created 5 years, 3 months 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 side-by-side diff with in-line comments
Download patch
« no previous file with comments | « chrome/app/theme/README ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: tools/resources/optimize-ico-files.py
diff --git a/tools/resources/optimize-ico-files.py b/tools/resources/optimize-ico-files.py
index fcd9f5368fdffce45d01d6dac73a61ab16d6db6f..71185f1368611d1ca29a82f4124835a49ca5d3a2 100755
--- a/tools/resources/optimize-ico-files.py
+++ b/tools/resources/optimize-ico-files.py
@@ -17,6 +17,7 @@ image, it will not get smaller. 256x256 icons should be PNG-formatted first.
import argparse
import logging
+import math
import os
import StringIO
import struct
@@ -83,6 +84,73 @@ def OptimizePng(png_data, optimization_level=None):
os.unlink(png_filename)
os.rmdir(temp_dir)
+def ComputeANDMaskFromAlpha(image_data, width, height):
+ """Compute an AND mask from 32-bit BGRA image data."""
+ and_bytes = []
+ for y in range(height):
+ bit_count = 0
+ current_byte = 0
+ for x in range(width):
+ alpha = image_data[(y * width + x) * 4 + 3]
+ current_byte <<= 1
+ if ord(alpha) == 0:
+ current_byte |= 1
+ bit_count += 1
+ if bit_count == 8:
+ and_bytes.append(current_byte)
+ bit_count = 0
+ current_byte = 0
+
+ # At the end of a row, pad the current byte.
+ if bit_count > 0:
+ current_byte <<= (8 - bit_count)
+ and_bytes.append(current_byte)
+ # And keep padding until a multiple of 4 bytes.
+ while len(and_bytes) % 4 != 0:
+ and_bytes.append(0)
+
+ and_bytes = ''.join(map(chr, and_bytes))
+ return and_bytes
+
+def RebuildANDMask(iconimage):
+ """Rebuild the AND mask in an icon image.
+
+ GIMP (<=2.8.14) creates a bad AND mask on 32-bit icon images (pixels with <50%
+ opacity are marked as transparent, which end up looking black on Windows). So,
+ if this is a 32-bit image, throw the mask away and recompute it from the alpha
+ data. (See: https://bugzilla.gnome.org/show_bug.cgi?id=755200)
+
+ Args:
+ iconimage: Bytes of an icon image (the BMP data for an entry in an ICO
+ file). Must be in BMP format, not PNG. Does not need to be 32-bit (if it
+ is not 32-bit, this is a no-op).
+
+ Returns:
+ An updated |iconimage|, with the AND mask re-computed using
+ ComputeANDMaskFromAlpha.
+ """
+ # Parse BITMAPINFOHEADER.
+ (_, width, height, _, bpp, _, _, _, _, num_colors, _) = struct.unpack(
+ '<LLLHHLLLLLL', iconimage[:40])
+
+ if bpp != 32:
+ # No alpha channel, so the mask cannot be "wrong" (it is the only source of
+ # transparency information).
+ return iconimage
+
+ height /= 2
+ xor_size = int(math.ceil(width * bpp / 32.0)) * 4 * height
+
+ # num_colors can be 0, implying 2^bpp colors.
+ xor_palette_size = (num_colors or (1 << bpp if bpp < 24 else 0)) * 4
+ xor_data = iconimage[40 + xor_palette_size :
+ 40 + xor_palette_size + xor_size]
+
+ and_data = ComputeANDMaskFromAlpha(xor_data, width, height)
+
+ # Replace the AND mask in the original icon data.
+ return iconimage[:40 + xor_palette_size + xor_size] + and_data
+
def OptimizeIcoFile(infile, outfile, optimization_level=None):
"""Read an ICO file, optimize its PNGs, and write the output to outfile.
@@ -123,11 +191,17 @@ def OptimizeIcoFile(infile, outfile, optimization_level=None):
if entry_is_png:
icon_data = OptimizePng(icon_data, optimization_level=optimization_level)
- elif width >= 256 or height >= 256:
- # TODO(mgiuca): Automatically convert large BMP images to PNGs.
- logging.warning('Entry #%d is a large image in uncompressed BMP format. '
- 'Please manually convert to PNG format before running '
- 'this utility.', i + 1)
+ else:
+ new_icon_data = RebuildANDMask(icon_data)
+ if new_icon_data != icon_data:
+ logging.info(' * Rebuilt AND mask for this image from alpha channel.')
+ icon_data = new_icon_data
+
+ if width >= 256 or height >= 256:
+ # TODO(mgiuca): Automatically convert large BMP images to PNGs.
+ logging.warning('Entry #%d is a large image in uncompressed BMP '
+ 'format. Please manually convert to PNG format before '
+ 'running this utility.', i + 1)
new_size = len(icon_data)
current_offset += new_size
@@ -167,13 +241,16 @@ def main(args=None):
OptimizeIcoFile(file, buf, args.optimization_level)
new_length = len(buf.getvalue())
+
+ # Always write (even if file size not reduced), because we make other fixes
+ # such as regenerating the AND mask.
+ file.truncate(new_length)
+ file.seek(0)
+ file.write(buf.getvalue())
+
if new_length >= old_length:
logging.info('%s : Could not reduce file size.', file.name)
else:
- file.truncate(new_length)
- file.seek(0)
- file.write(buf.getvalue())
-
saving = old_length - new_length
saving_percent = float(saving) / old_length
logging.info('%s : %d => %d (%d bytes : %d %%)', file.name, old_length,
« no previous file with comments | « chrome/app/theme/README ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698