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