# Copyright (c) 2012 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Presubmit script for Chromium browser resources. See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts for more details about the presubmit API built into gcl/git cl, and see http://www.chromium.org/developers/web-development-style-guide for the rules we're checking against here. """ import os import struct class ResourceScaleFactors(object): """Verifier of image dimensions for Chromium resources. This class verifies the image dimensions of resources in the various resource subdirectories. Attributes: paths: An array of tuples giving the folders to check and their relevant scale factors. For example: [(100, 'default_100_percent'), (200, 'default_200_percent')] """ def __init__(self, input_api, output_api, paths): """ Initializes ResourceScaleFactors with paths.""" self.input_api = input_api self.output_api = output_api self.paths = paths def RunChecks(self): """Verifies the scale factors of resources being added or modified. Returns: An array of presubmit errors if any images were detected not having the correct dimensions. """ def ImageSize(filename): with open(filename, 'rb', buffering=0) as f: data = f.read(24) assert data[:8] == '\x89PNG\r\n\x1A\n' and data[12:16] == 'IHDR' return struct.unpack('>ii', data[16:24]) # Returns a list of valid scaled image sizes. The valid sizes are the # floor and ceiling of (base_size * scale_percent / 100). This is equivalent # to requiring that the actual scaled size is less than one pixel away from # the exact scaled size. def ValidSizes(base_size, scale_percent): return sorted(set([(base_size * scale_percent) / 100, (base_size * scale_percent + 99) / 100])) repository_path = self.input_api.os_path.relpath( self.input_api.PresubmitLocalPath(), self.input_api.change.RepositoryRoot()) results = [] # Check for affected files in any of the paths specified. affected_files = self.input_api.AffectedFiles(include_deletes=False) files = [] for f in affected_files: for path_spec in self.paths: path_root = self.input_api.os_path.join( repository_path, path_spec[1]) if (f.LocalPath().endswith('.png') and f.LocalPath().startswith(path_root)): # Only save the relative path from the resource directory. relative_path = self.input_api.os_path.relpath(f.LocalPath(), path_root) if relative_path not in files: files.append(relative_path) for f in files: base_image = self.input_api.os_path.join(self.paths[0][1], f) if not os.path.exists(base_image): results.append(self.output_api.PresubmitError( 'Base image %s does not exist' % self.input_api.os_path.join( repository_path, base_image))) continue base_dimensions = ImageSize(base_image) # Find all scaled versions of the base image and verify their sizes. for i in range(1, len(self.paths)): image_path = self.input_api.os_path.join(self.paths[i][1], f) if not os.path.exists(image_path): continue # Ensure that each image for a particular scale factor is the # correct scale of the base image. scaled_dimensions = ImageSize(image_path) for dimension_name, base_size, scaled_size in zip( ('width', 'height'), base_dimensions, scaled_dimensions): valid_sizes = ValidSizes(base_size, self.paths[i][0]) if scaled_size not in valid_sizes: results.append(self.output_api.PresubmitError( 'Image %s has %s %d, expected to be %s' % ( self.input_api.os_path.join(repository_path, image_path), dimension_name, scaled_size, ' or '.join(map(str, valid_sizes))))) return results