1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 # Use of this source code is governed by a BSD-style license that can be 3 # found in the LICENSE file. 4 5 """Presubmit script for Chromium browser resources. 6 7 See http://dev.chromium.org/developers/how-tos/depottools/presubmit-scripts 8 for more details about the presubmit API built into gcl/git cl, and see 9 http://www.chromium.org/developers/web-development-style-guide for the rules 10 we're checking against here. 11 """ 12 13 14 import os 15 import struct 16 17 18 class ResourceScaleFactors(object): 19 """Verifier of image dimensions for Chromium resources. 20 21 This class verifies the image dimensions of resources in the various 22 resource subdirectories. 23 24 Attributes: 25 paths: An array of tuples giving the folders to check and their 26 relevant scale factors. For example: 27 28 [(100, 'default_100_percent'), (200, 'default_200_percent')] 29 """ 30 31 def __init__(self, input_api, output_api, paths): 32 """ Initializes ResourceScaleFactors with paths.""" 33 self.input_api = input_api 34 self.output_api = output_api 35 self.paths = paths 36 37 def RunChecks(self): 38 """Verifies the scale factors of resources being added or modified. 39 40 Returns: 41 An array of presubmit errors if any images were detected not 42 having the correct dimensions. 43 """ 44 def ImageSize(filename): 45 with open(filename, 'rb', buffering=0) as f: 46 data = f.read(24) 47 assert data[:8] == '\x89PNG\r\n\x1A\n' and data[12:16] == 'IHDR' 48 return struct.unpack('>ii', data[16:24]) 49 50 # Returns a list of valid scaled image sizes. The valid sizes are the 51 # floor and ceiling of (base_size * scale_percent / 100). This is equivalent 52 # to requiring that the actual scaled size is less than one pixel away from 53 # the exact scaled size. 54 def ValidSizes(base_size, scale_percent): 55 return sorted(set([(base_size * scale_percent) / 100, 56 (base_size * scale_percent + 99) / 100])) 57 58 repository_path = self.input_api.os_path.relpath( 59 self.input_api.PresubmitLocalPath(), 60 self.input_api.change.RepositoryRoot()) 61 results = [] 62 63 # Check for affected files in any of the paths specified. 64 affected_files = self.input_api.AffectedFiles(include_deletes=False) 65 files = [] 66 for f in affected_files: 67 for path_spec in self.paths: 68 path_root = self.input_api.os_path.join( 69 repository_path, path_spec[1]) 70 if (f.LocalPath().endswith('.png') and 71 f.LocalPath().startswith(path_root)): 72 # Only save the relative path from the resource directory. 73 relative_path = self.input_api.os_path.relpath(f.LocalPath(), 74 path_root) 75 if relative_path not in files: 76 files.append(relative_path) 77 78 for f in files: 79 base_image = self.input_api.os_path.join(self.paths[0][1], f) 80 if not os.path.exists(base_image): 81 results.append(self.output_api.PresubmitError( 82 'Base image %s does not exist' % self.input_api.os_path.join( 83 repository_path, base_image))) 84 continue 85 base_dimensions = ImageSize(base_image) 86 # Find all scaled versions of the base image and verify their sizes. 87 for i in range(1, len(self.paths)): 88 image_path = self.input_api.os_path.join(self.paths[i][1], f) 89 if not os.path.exists(image_path): 90 continue 91 # Ensure that each image for a particular scale factor is the 92 # correct scale of the base image. 93 scaled_dimensions = ImageSize(image_path) 94 for dimension_name, base_size, scaled_size in zip( 95 ('width', 'height'), base_dimensions, scaled_dimensions): 96 valid_sizes = ValidSizes(base_size, self.paths[i][0]) 97 if scaled_size not in valid_sizes: 98 results.append(self.output_api.PresubmitError( 99 'Image %s has %s %d, expected to be %s' % ( 100 self.input_api.os_path.join(repository_path, image_path), 101 dimension_name, 102 scaled_size, 103 ' or '.join(map(str, valid_sizes))))) 104 return results 105