/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.assetstudiolib; import com.android.resources.Density; import com.android.utils.SdkUtils; import com.google.common.collect.Lists; import com.google.common.io.Closeables; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.net.JarURLConnection; import java.net.URISyntaxException; import java.net.URL; import java.net.URLConnection; import java.security.ProtectionDomain; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.imageio.ImageIO; /** * The base Generator class. */ public abstract class GraphicGenerator { /** * Options used for all generators. */ public static class Options { /** Minimum version (API level) of the SDK to generate icons for */ public int minSdk = 1; /** Source image to use as a basis for the icon */ public BufferedImage sourceImage; /** The density to generate the icon with */ public Density density = Density.XHIGH; } /** Shapes that can be used for icon backgrounds */ public static enum Shape { /** No background */ NONE("none"), /** Circular background */ CIRCLE("circle"), /** Square background */ SQUARE("square"); /** Id, used in filenames to identify associated stencils */ public final String id; Shape(String id) { this.id = id; } } /** Foreground effects styles */ public static enum Style { /** No effects */ SIMPLE("fore1"); /** Id, used in filenames to identify associated stencils */ public final String id; Style(String id) { this.id = id; } } /** * Generate a single icon using the given options * * @param context render context to use for looking up resources etc * @param options options controlling the appearance of the icon * @return a {@link BufferedImage} with the generated icon */ public abstract BufferedImage generate(GraphicGeneratorContext context, Options options); /** * Computes the target filename (relative to the Android project folder) * where an icon rendered with the given options should be stored. This is * also used as the map keys in the result map used by * {@link #generate(String, Map, GraphicGeneratorContext, Options, String)}. * * @param options the options object used by the generator for the current * image * @param name the base name to use when creating the path * @return a path relative to the res/ folder where the image should be * stored (will always use / as a path separator, not \ on Windows) */ protected String getIconPath(Options options, String name) { return getIconFolder(options) + '/' + getIconName(options, name); } /** * Gets name of the file itself. It is sometimes modified by options, for * example in unselected tabs we change foo.png to foo-unselected.png */ protected String getIconName(Options options, String name) { return name + ".png"; //$NON-NLS-1$ } /** * Gets name of the folder to contain the resource. It usually includes the * density, but is also sometimes modified by options. For example, in some * notification icons we add in -v9 or -v11. */ protected String getIconFolder(Options options) { return "res/drawable-" + options.density.getResourceValue(); //$NON-NLS-1$ } /** * Generates a full set of icons into the given map. The values in the map * will be the generated images, and each value is keyed by the * corresponding relative path of the image, which is determined by the * {@link #getIconPath(Options, String)} method. * * @param category the current category to place images into (if null the * density name will be used) * @param categoryMap the map to put images into, should not be null. The * map is a map from a category name, to a map from file path to * image. * @param context a generator context which for example can load resources * @param options options to apply to this generator * @param name the base name of the icons to generate */ public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, GraphicGeneratorContext context, Options options, String name) { Density[] densityValues = Density.values(); // Sort density values into ascending order Arrays.sort(densityValues, new Comparator<Density>() { @Override public int compare(Density d1, Density d2) { return d1.getDpiValue() - d2.getDpiValue(); } }); for (Density density : densityValues) { if (!density.isValidValueForDevice()) { continue; } if (density == Density.LOW || !density.isRecommended() || density == Density.XXXHIGH) { // TODO don't manually check and instead gracefully handle missing stencils. // Not yet supported -- missing stencil image continue; } options.density = density; BufferedImage image = generate(context, options); if (image != null) { String mapCategory = category; if (mapCategory == null) { mapCategory = options.density.getResourceValue(); } Map<String, BufferedImage> imageMap = categoryMap.get(mapCategory); if (imageMap == null) { imageMap = new LinkedHashMap<String, BufferedImage>(); categoryMap.put(mapCategory, imageMap); } imageMap.put(getIconPath(options, name), image); } } } /** * Returns the scale factor to apply for a given MDPI density to compute the * absolute pixel count to use to draw an icon of the given target density * * @param density the density * @return a factor to multiple mdpi distances with to compute the target density */ public static float getMdpiScaleFactor(Density density) { return density.getDpiValue() / (float) Density.MEDIUM.getDpiValue(); } /** * Returns one of the built in stencil images, or null * * @param relativePath stencil path such as "launcher-stencil/square/web/back.png" * @return the image, or null * @throws IOException if an unexpected I/O error occurs */ public static BufferedImage getStencilImage(String relativePath) throws IOException { InputStream is = GraphicGenerator.class.getResourceAsStream(relativePath); if (is == null) { return null; } try { return ImageIO.read(is); } finally { Closeables.close(is, true /* swallowIOException */); } } /** * Returns the icon (32x32) for a given clip art image. * * @param name the name of the image to be loaded (which can be looked up via * {@link #getClipartNames()}) * @return the icon image * @throws IOException if the image cannot be loaded */ public static BufferedImage getClipartIcon(String name) throws IOException { InputStream is = GraphicGenerator.class.getResourceAsStream( "/images/clipart/small/" + name); try { return ImageIO.read(is); } finally { Closeables.close(is, true /* swallowIOException */); } } /** * Returns the full size clip art image for a given image name. * * @param name the name of the image to be loaded (which can be looked up via * {@link #getClipartNames()}) * @return the clip art image * @throws IOException if the image cannot be loaded */ public static BufferedImage getClipartImage(String name) throws IOException { InputStream is = GraphicGenerator.class.getResourceAsStream( "/images/clipart/big/" + name); try { return ImageIO.read(is); } finally { Closeables.close(is, true /* swallowIOException */); } } /** * Returns the names of available clip art images which can be obtained by passing the * name to {@link #getClipartIcon(String)} or * {@link GraphicGenerator#getClipartImage(String)} * * @return an iterator for the available image names */ public static Iterator<String> getClipartNames() { List<String> names = new ArrayList<String>(80); try { String pathPrefix = "images/clipart/big/"; //$NON-NLS-1$ ZipFile zipFile = null; ProtectionDomain protectionDomain = GraphicGenerator.class.getProtectionDomain(); URL url = protectionDomain.getCodeSource().getLocation(); if (url != null) { File file = SdkUtils.urlToFile(url); zipFile = new JarFile(file); } else { Enumeration<URL> en = GraphicGenerator.class.getClassLoader().getResources(pathPrefix); if (en.hasMoreElements()) { url = en.nextElement(); URLConnection urlConnection = url.openConnection(); if (urlConnection instanceof JarURLConnection) { JarURLConnection urlConn = (JarURLConnection)(urlConnection); zipFile = urlConn.getJarFile(); } else if ("file".equals(url.getProtocol())) { //$NON-NLS-1$ File directory = new File(url.getPath()); return Lists.newArrayList(directory.list()).iterator(); } } } Enumeration<? extends ZipEntry> enumeration = zipFile.entries(); while (enumeration.hasMoreElements()) { ZipEntry zipEntry = enumeration.nextElement(); String name = zipEntry.getName(); if (!name.startsWith(pathPrefix) || !name.endsWith(".png")) { //$NON-NLS-1$ continue; } int lastSlash = name.lastIndexOf('/'); if (lastSlash != -1) { name = name.substring(lastSlash + 1); } names.add(name); } } catch (final Exception e) { e.printStackTrace(); } return names.iterator(); } }