• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.assetstudiolib;
18 
19 import com.android.resources.Density;
20 
21 import java.awt.image.BufferedImage;
22 import java.io.File;
23 import java.io.IOException;
24 import java.io.InputStream;
25 import java.net.URISyntaxException;
26 import java.net.URL;
27 import java.security.ProtectionDomain;
28 import java.util.ArrayList;
29 import java.util.Arrays;
30 import java.util.Comparator;
31 import java.util.Enumeration;
32 import java.util.Iterator;
33 import java.util.LinkedHashMap;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.jar.JarFile;
37 import java.util.zip.ZipEntry;
38 import java.util.zip.ZipFile;
39 
40 import javax.imageio.ImageIO;
41 
42 /**
43  * The base Generator class.
44  */
45 public abstract class GraphicGenerator {
46     /**
47      * Options used for all generators.
48      */
49     public static class Options {
50         /** Minimum version (API level) of the SDK to generate icons for */
51         public int minSdk = 1;
52 
53         /** Source image to use as a basis for the icon */
54         public BufferedImage sourceImage;
55 
56         /** The density to generate the icon with */
57         public Density density = Density.XHIGH;
58     }
59 
60     /** Shapes that can be used for icon backgrounds */
61     public static enum Shape {
62         /** Circular background */
63         CIRCLE("circle"),
64         /** Square background */
65         SQUARE("square");
66 
67         /** Id, used in filenames to identify associated stencils */
68         public final String id;
69 
Shape(String id)70         Shape(String id) {
71             this.id = id;
72         }
73     }
74 
75     /** Foreground effects styles */
76     public static enum Style {
77         /** No effects */
78         SIMPLE("fore1"),
79         /** "Fancy" effects */
80         FANCY("fore2"),
81         /** A glossy look */
82         GLOSSY("fore3");
83 
84         /** Id, used in filenames to identify associated stencils */
85         public final String id;
86 
Style(String id)87         Style(String id) {
88             this.id = id;
89         }
90     }
91 
92     /**
93      * Generate a single icon using the given options
94      *
95      * @param context render context to use for looking up resources etc
96      * @param options options controlling the appearance of the icon
97      * @return a {@link BufferedImage} with the generated icon
98      */
generate(GraphicGeneratorContext context, Options options)99     public abstract BufferedImage generate(GraphicGeneratorContext context, Options options);
100 
101     /**
102      * Computes the target filename (relative to the Android project folder)
103      * where an icon rendered with the given options should be stored. This is
104      * also used as the map keys in the result map used by
105      * {@link #generate(String, Map, GraphicGeneratorContext, Options, String)}.
106      *
107      * @param options the options object used by the generator for the current
108      *            image
109      * @param name the base name to use when creating the path
110      * @return a path relative to the res/ folder where the image should be
111      *         stored (will always use / as a path separator, not \ on Windows)
112      */
getIconPath(Options options, String name)113     protected String getIconPath(Options options, String name) {
114         return getIconFolder(options) + '/' + getIconName(options, name);
115     }
116 
117     /**
118      * Gets name of the file itself. It is sometimes modified by options, for
119      * example in unselected tabs we change foo.png to foo-unselected.png
120      */
getIconName(Options options, String name)121     protected String getIconName(Options options, String name) {
122         return name + ".png"; //$NON-NLS-1$
123     }
124 
125     /**
126      * Gets name of the folder to contain the resource. It usually includes the
127      * density, but is also sometimes modified by options. For example, in some
128      * notification icons we add in -v9 or -v11.
129      */
getIconFolder(Options options)130     protected String getIconFolder(Options options) {
131         return "res/drawable-" + options.density.getResourceValue(); //$NON-NLS-1$
132     }
133 
134     /**
135      * Generates a full set of icons into the given map. The values in the map
136      * will be the generated images, and each value is keyed by the
137      * corresponding relative path of the image, which is determined by the
138      * {@link #getIconPath(Options, String)} method.
139      *
140      * @param category the current category to place images into (if null the
141      *            density name will be used)
142      * @param categoryMap the map to put images into, should not be null. The
143      *            map is a map from a category name, to a map from file path to
144      *            image.
145      * @param context a generator context which for example can load resources
146      * @param options options to apply to this generator
147      * @param name the base name of the icons to generate
148      */
generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, GraphicGeneratorContext context, Options options, String name)149     public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap,
150             GraphicGeneratorContext context, Options options, String name) {
151         Density[] densityValues = Density.values();
152         // Sort density values into ascending order
153         Arrays.sort(densityValues, new Comparator<Density>() {
154             public int compare(Density d1, Density d2) {
155                 return d1.getDpiValue() - d2.getDpiValue();
156             }
157         });
158 
159         for (Density density : densityValues) {
160             if (!density.isValidValueForDevice()) {
161                 continue;
162             }
163             if (density == Density.TV) {
164                 // Not yet supported -- missing stencil image
165                 continue;
166             }
167             options.density = density;
168             BufferedImage image = generate(context, options);
169             if (image != null) {
170                 String mapCategory = category;
171                 if (mapCategory == null) {
172                     mapCategory = options.density.getResourceValue();
173                 }
174                 Map<String, BufferedImage> imageMap = categoryMap.get(mapCategory);
175                 if (imageMap == null) {
176                     imageMap = new LinkedHashMap<String, BufferedImage>();
177                     categoryMap.put(mapCategory, imageMap);
178                 }
179                 imageMap.put(getIconPath(options, name), image);
180             }
181         }
182     }
183 
184     /**
185      * Returns the scale factor to apply for a given HDPI density to compute the
186      * absolute pixel count to use to draw an icon of the given target density
187      *
188      * @param density the density
189      * @return a factor to multiple hdpi distances with to compute the target density
190      */
getHdpiScaleFactor(Density density)191     public static float getHdpiScaleFactor(Density density) {
192         // We used to do this:
193         //return density.getDpiValue() / (float) Density.DEFAULT_DENSITY;
194         // However, the HTML5 version of the AssetStudio would end up with different
195         // sizes for the assets, because it uses this table:
196         //    studio.util.getMultBaseHdpi = function(density) {
197         //        switch (density) {
198         //          case 'xhdpi': return 1.333333;
199         //          case  'hdpi': return 1.0;
200         //          case  'mdpi': return 0.666667;
201         //          case  'ldpi': return 0.5;
202         //        }
203         //        return 1.0;
204         //      };
205         // This corresponds to dividing the dpi value not by Density.MEDIUM but
206         // Density.HIGH:
207         return density.getDpiValue() / (float) Density.HIGH.getDpiValue();
208     }
209 
210     /**
211      * Returns one of the built in stencil images, or null
212      *
213      * @param relativePath stencil path such as "launcher-stencil/square/web/back.png"
214      * @return the image, or null
215      * @throws IOException if an unexpected I/O error occurs
216      */
getStencilImage(String relativePath)217     public static BufferedImage getStencilImage(String relativePath) throws IOException {
218         InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath);
219         return ImageIO.read(is);
220     }
221 
222     /**
223      * Returns the icon (32x32) for a given clip art image.
224      *
225      * @param name the name of the image to be loaded (which can be looked up via
226      *            {@link #getClipartNames()})
227      * @return the icon image
228      * @throws IOException if the image cannot be loaded
229      */
getClipartIcon(String name)230     public static BufferedImage getClipartIcon(String name) throws IOException {
231         InputStream is = GraphicGenerator.class.getResourceAsStream(
232                 "/images/clipart/small/" + name);
233         return ImageIO.read(is);
234     }
235 
236     /**
237      * Returns the full size clip art image for a given image name.
238      *
239      * @param name the name of the image to be loaded (which can be looked up via
240      *            {@link #getClipartNames()})
241      * @return the clip art image
242      * @throws IOException if the image cannot be loaded
243      */
getClipartImage(String name)244     public static BufferedImage getClipartImage(String name) throws IOException {
245         InputStream is = GraphicGenerator.class.getResourceAsStream(
246                 "/images/clipart/big/" + name);
247         return ImageIO.read(is);
248     }
249 
250     /**
251      * Returns the names of available clip art images which can be obtained by passing the
252      * name to {@link #getClipartIcon(String)} or
253      * {@link GraphicGenerator#getClipartImage(String)}
254      *
255      * @return an iterator for the available image names
256      */
getClipartNames()257     public static Iterator<String> getClipartNames() {
258         List<String> names = new ArrayList<String>(80);
259         try {
260             String pathPrefix = "images/clipart/big/"; //$NON-NLS-1$
261             ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain();
262             URL url = protectionDomain.getCodeSource().getLocation();
263             File file;
264             try {
265                 file = new File(url.toURI());
266             } catch (URISyntaxException e) {
267                 file = new File(url.getPath());
268             }
269             final ZipFile zipFile = new JarFile(file);
270             Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
271             while (enumeration.hasMoreElements()) {
272                 ZipEntry zipEntry = enumeration.nextElement();
273                 String name = zipEntry.getName();
274                 if (!name.startsWith(pathPrefix) || !name.endsWith(".png")) { //$NON-NLS-1$
275                     continue;
276                 }
277 
278                 int lastSlash = name.lastIndexOf('/');
279                 if (lastSlash != -1) {
280                     name = name.substring(lastSlash + 1);
281                 }
282                 names.add(name);
283             }
284         } catch (final Exception e) {
285             e.printStackTrace();
286         }
287 
288         return names.iterator();
289     }
290 }
291