• 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 import com.google.common.io.Closeables;
21 
22 import java.awt.image.BufferedImage;
23 import java.io.File;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.net.URISyntaxException;
27 import java.net.URL;
28 import java.security.ProtectionDomain;
29 import java.util.ArrayList;
30 import java.util.Arrays;
31 import java.util.Comparator;
32 import java.util.Enumeration;
33 import java.util.Iterator;
34 import java.util.LinkedHashMap;
35 import java.util.List;
36 import java.util.Map;
37 import java.util.jar.JarFile;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipFile;
40 
41 import javax.imageio.ImageIO;
42 
43 /**
44  * The base Generator class.
45  */
46 public abstract class GraphicGenerator {
47     /**
48      * Options used for all generators.
49      */
50     public static class Options {
51         /** Minimum version (API level) of the SDK to generate icons for */
52         public int minSdk = 1;
53 
54         /** Source image to use as a basis for the icon */
55         public BufferedImage sourceImage;
56 
57         /** The density to generate the icon with */
58         public Density density = Density.XHIGH;
59     }
60 
61     /** Shapes that can be used for icon backgrounds */
62     public static enum Shape {
63         /** No background */
64         NONE("none"),
65         /** Circular background */
66         CIRCLE("circle"),
67         /** Square background */
68         SQUARE("square");
69 
70         /** Id, used in filenames to identify associated stencils */
71         public final String id;
72 
Shape(String id)73         Shape(String id) {
74             this.id = id;
75         }
76     }
77 
78     /** Foreground effects styles */
79     public static enum Style {
80         /** No effects */
81         SIMPLE("fore1");
82 
83         /** Id, used in filenames to identify associated stencils */
84         public final String id;
85 
Style(String id)86         Style(String id) {
87             this.id = id;
88         }
89     }
90 
91     /**
92      * Generate a single icon using the given options
93      *
94      * @param context render context to use for looking up resources etc
95      * @param options options controlling the appearance of the icon
96      * @return a {@link BufferedImage} with the generated icon
97      */
generate(GraphicGeneratorContext context, Options options)98     public abstract BufferedImage generate(GraphicGeneratorContext context, Options options);
99 
100     /**
101      * Computes the target filename (relative to the Android project folder)
102      * where an icon rendered with the given options should be stored. This is
103      * also used as the map keys in the result map used by
104      * {@link #generate(String, Map, GraphicGeneratorContext, Options, String)}.
105      *
106      * @param options the options object used by the generator for the current
107      *            image
108      * @param name the base name to use when creating the path
109      * @return a path relative to the res/ folder where the image should be
110      *         stored (will always use / as a path separator, not \ on Windows)
111      */
getIconPath(Options options, String name)112     protected String getIconPath(Options options, String name) {
113         return getIconFolder(options) + '/' + getIconName(options, name);
114     }
115 
116     /**
117      * Gets name of the file itself. It is sometimes modified by options, for
118      * example in unselected tabs we change foo.png to foo-unselected.png
119      */
getIconName(Options options, String name)120     protected String getIconName(Options options, String name) {
121         return name + ".png"; //$NON-NLS-1$
122     }
123 
124     /**
125      * Gets name of the folder to contain the resource. It usually includes the
126      * density, but is also sometimes modified by options. For example, in some
127      * notification icons we add in -v9 or -v11.
128      */
getIconFolder(Options options)129     protected String getIconFolder(Options options) {
130         return "res/drawable-" + options.density.getResourceValue(); //$NON-NLS-1$
131     }
132 
133     /**
134      * Generates a full set of icons into the given map. The values in the map
135      * will be the generated images, and each value is keyed by the
136      * corresponding relative path of the image, which is determined by the
137      * {@link #getIconPath(Options, String)} method.
138      *
139      * @param category the current category to place images into (if null the
140      *            density name will be used)
141      * @param categoryMap the map to put images into, should not be null. The
142      *            map is a map from a category name, to a map from file path to
143      *            image.
144      * @param context a generator context which for example can load resources
145      * @param options options to apply to this generator
146      * @param name the base name of the icons to generate
147      */
generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, GraphicGeneratorContext context, Options options, String name)148     public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap,
149             GraphicGeneratorContext context, Options options, String name) {
150         Density[] densityValues = Density.values();
151         // Sort density values into ascending order
152         Arrays.sort(densityValues, new Comparator<Density>() {
153             @Override
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 || density == Density.XXHIGH) {
164                 // TODO don't manually check and instead gracefully handle missing stencils.
165                 // Not yet supported -- missing stencil image
166                 continue;
167             }
168             options.density = density;
169             BufferedImage image = generate(context, options);
170             if (image != null) {
171                 String mapCategory = category;
172                 if (mapCategory == null) {
173                     mapCategory = options.density.getResourceValue();
174                 }
175                 Map<String, BufferedImage> imageMap = categoryMap.get(mapCategory);
176                 if (imageMap == null) {
177                     imageMap = new LinkedHashMap<String, BufferedImage>();
178                     categoryMap.put(mapCategory, imageMap);
179                 }
180                 imageMap.put(getIconPath(options, name), image);
181             }
182         }
183     }
184 
185     /**
186      * Returns the scale factor to apply for a given MDPI density to compute the
187      * absolute pixel count to use to draw an icon of the given target density
188      *
189      * @param density the density
190      * @return a factor to multiple mdpi distances with to compute the target density
191      */
getMdpiScaleFactor(Density density)192     public static float getMdpiScaleFactor(Density density) {
193         return density.getDpiValue() / (float) Density.MEDIUM.getDpiValue();
194     }
195 
196     /**
197      * Returns one of the built in stencil images, or null
198      *
199      * @param relativePath stencil path such as "launcher-stencil/square/web/back.png"
200      * @return the image, or null
201      * @throws IOException if an unexpected I/O error occurs
202      */
203     @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
getStencilImage(String relativePath)204     public static BufferedImage getStencilImage(String relativePath) throws IOException {
205         InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath);
206         try {
207             return ImageIO.read(is);
208         } finally {
209             Closeables.closeQuietly(is);
210         }
211     }
212 
213     /**
214      * Returns the icon (32x32) for a given clip art image.
215      *
216      * @param name the name of the image to be loaded (which can be looked up via
217      *            {@link #getClipartNames()})
218      * @return the icon image
219      * @throws IOException if the image cannot be loaded
220      */
221     @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
getClipartIcon(String name)222     public static BufferedImage getClipartIcon(String name) throws IOException {
223         InputStream is = GraphicGenerator.class.getResourceAsStream(
224                 "/images/clipart/small/" + name);
225         try {
226             return ImageIO.read(is);
227         } finally {
228             Closeables.closeQuietly(is);
229         }
230     }
231 
232     /**
233      * Returns the full size clip art image for a given image name.
234      *
235      * @param name the name of the image to be loaded (which can be looked up via
236      *            {@link #getClipartNames()})
237      * @return the clip art image
238      * @throws IOException if the image cannot be loaded
239      */
240     @SuppressWarnings("resource") // Eclipse doesn't know about Closeables#closeQuietly yet
getClipartImage(String name)241     public static BufferedImage getClipartImage(String name) throws IOException {
242         InputStream is = GraphicGenerator.class.getResourceAsStream(
243                 "/images/clipart/big/" + name);
244         try {
245             return ImageIO.read(is);
246         } finally {
247             Closeables.closeQuietly(is);
248         }
249     }
250 
251     /**
252      * Returns the names of available clip art images which can be obtained by passing the
253      * name to {@link #getClipartIcon(String)} or
254      * {@link GraphicGenerator#getClipartImage(String)}
255      *
256      * @return an iterator for the available image names
257      */
getClipartNames()258     public static Iterator<String> getClipartNames() {
259         List<String> names = new ArrayList<String>(80);
260         try {
261             String pathPrefix = "images/clipart/big/"; //$NON-NLS-1$
262             ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain();
263             URL url = protectionDomain.getCodeSource().getLocation();
264             File file;
265             try {
266                 file = new File(url.toURI());
267             } catch (URISyntaxException e) {
268                 file = new File(url.getPath());
269             }
270             final ZipFile zipFile = new JarFile(file);
271             Enumeration<? extends ZipEntry> enumeration = zipFile.entries();
272             while (enumeration.hasMoreElements()) {
273                 ZipEntry zipEntry = enumeration.nextElement();
274                 String name = zipEntry.getName();
275                 if (!name.startsWith(pathPrefix) || !name.endsWith(".png")) { //$NON-NLS-1$
276                     continue;
277                 }
278 
279                 int lastSlash = name.lastIndexOf('/');
280                 if (lastSlash != -1) {
281                     name = name.substring(lastSlash + 1);
282                 }
283                 names.add(name);
284             }
285         } catch (final Exception e) {
286             e.printStackTrace();
287         }
288 
289         return names.iterator();
290     }
291 }
292