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