Home | History | Annotate | Download | only in resource_check
      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