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 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 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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