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 InvalidPNGException(Exception): 19 pass 20 21 22 class ResourceScaleFactors(object): 23 """Verifier of image dimensions for Chromium resources. 24 25 This class verifies the image dimensions of resources in the various 26 resource subdirectories. 27 28 Attributes: 29 paths: An array of tuples giving the folders to check and their 30 relevant scale factors. For example: 31 32 [(100, 'default_100_percent'), (200, 'default_200_percent')] 33 """ 34 35 def __init__(self, input_api, output_api, paths): 36 """ Initializes ResourceScaleFactors with paths.""" 37 self.input_api = input_api 38 self.output_api = output_api 39 self.paths = paths 40 41 def RunChecks(self): 42 """Verifies the scale factors of resources being added or modified. 43 44 Returns: 45 An array of presubmit errors if any images were detected not 46 having the correct dimensions. 47 """ 48 def ImageSize(filename): 49 with open(filename, 'rb', buffering=0) as f: 50 data = f.read(24) 51 if data[:8] != '\x89PNG\r\n\x1A\n' or data[12:16] != 'IHDR': 52 raise InvalidPNGException 53 return struct.unpack('>ii', data[16:24]) 54 55 # Returns a list of valid scaled image sizes. The valid sizes are the 56 # floor and ceiling of (base_size * scale_percent / 100). This is equivalent 57 # to requiring that the actual scaled size is less than one pixel away from 58 # the exact scaled size. 59 def ValidSizes(base_size, scale_percent): 60 return sorted(set([(base_size * scale_percent) / 100, 61 (base_size * scale_percent + 99) / 100])) 62 63 repository_path = self.input_api.os_path.relpath( 64 self.input_api.PresubmitLocalPath(), 65 self.input_api.change.RepositoryRoot()) 66 results = [] 67 68 # Check for affected files in any of the paths specified. 69 affected_files = self.input_api.AffectedFiles(include_deletes=False) 70 files = [] 71 for f in affected_files: 72 for path_spec in self.paths: 73 path_root = self.input_api.os_path.join( 74 repository_path, path_spec[1]) 75 if (f.LocalPath().endswith('.png') and 76 f.LocalPath().startswith(path_root)): 77 # Only save the relative path from the resource directory. 78 relative_path = self.input_api.os_path.relpath(f.LocalPath(), 79 path_root) 80 if relative_path not in files: 81 files.append(relative_path) 82 83 corrupt_png_error = ('Corrupt PNG in file %s. Note that binaries are not ' 84 'correctly uploaded to the code review tool and must be directly ' 85 'submitted using the dcommit command.') 86 for f in files: 87 base_image = self.input_api.os_path.join(self.paths[0][1], f) 88 if not os.path.exists(base_image): 89 results.append(self.output_api.PresubmitError( 90 'Base image %s does not exist' % self.input_api.os_path.join( 91 repository_path, base_image))) 92 continue 93 try: 94 base_dimensions = ImageSize(base_image) 95 except InvalidPNGException: 96 results.append(self.output_api.PresubmitError(corrupt_png_error % 97 self.input_api.os_path.join(repository_path, base_image))) 98 continue 99 # Find all scaled versions of the base image and verify their sizes. 100 for i in range(1, len(self.paths)): 101 image_path = self.input_api.os_path.join(self.paths[i][1], f) 102 if not os.path.exists(image_path): 103 continue 104 # Ensure that each image for a particular scale factor is the 105 # correct scale of the base image. 106 try: 107 scaled_dimensions = ImageSize(image_path) 108 except InvalidPNGException: 109 results.append(self.output_api.PresubmitError(corrupt_png_error % 110 self.input_api.os_path.join(repository_path, image_path))) 111 continue 112 for dimension_name, base_size, scaled_size in zip( 113 ('width', 'height'), base_dimensions, scaled_dimensions): 114 valid_sizes = ValidSizes(base_size, self.paths[i][0]) 115 if scaled_size not in valid_sizes: 116 results.append(self.output_api.PresubmitError( 117 'Image %s has %s %d, expected to be %s' % ( 118 self.input_api.os_path.join(repository_path, image_path), 119 dimension_name, 120 scaled_size, 121 ' or '.join(map(str, valid_sizes))))) 122 return results 123