diff options
author | Tor Norbye <tnorbye@google.com> | 2011-08-25 13:13:34 -0700 |
---|---|---|
committer | Tor Norbye <tnorbye@google.com> | 2011-09-01 13:56:48 -0700 |
commit | 59bddc5b09c091cfae54577ec36b16925b362b55 (patch) | |
tree | 6f23ab6c3e44ce59decd00667ab956bb306a9d0c /assetstudio/src | |
parent | b3ab7ef8a24a64bcd6f347e8e03bc647c12eb24b (diff) | |
download | sdk-59bddc5b09c091cfae54577ec36b16925b362b55.zip sdk-59bddc5b09c091cfae54577ec36b16925b362b55.tar.gz sdk-59bddc5b09c091cfae54577ec36b16925b362b55.tar.bz2 |
Add support for remaining asset types in Asset Studio Wizard
This changeset ports the remaining graphic generators from the HTML5
version (notifications, tabs, action bar), and hooks up wizard support
for them.
It also adds unit tests for the generators which generates images and
compares them to known good versions. I ran these tests comparing them
to the output from the HTML5 version of Asset Studio and all but 3 of
the images varied less than 5% (and I verified the remaining manually
and they're all fine and the difference is due to the images being
aligned slightly differently.)
The icon wizard is now also hooked up to the "New" button in the
Resource Chooser for drawable resources, and this changeset also fixes
a few related issues ("New" didn't work for file-based resources, and
newly created resources weren't showing up in the selection list.)
Change-Id: I48c49f1d5de452aa5b78e491d9b07e7156397fa9
Diffstat (limited to 'assetstudio/src')
16 files changed, 801 insertions, 126 deletions
diff --git a/assetstudio/src/com/android/assetstudiolib/ActionBarIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/ActionBarIconGenerator.java new file mode 100644 index 0000000..b54aad5 --- /dev/null +++ b/assetstudio/src/com/android/assetstudiolib/ActionBarIconGenerator.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.assetstudiolib.Util.Effect; +import com.android.assetstudiolib.Util.FillEffect; +import com.android.assetstudiolib.Util.ShadowEffect; + +import java.awt.Color; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; + +/** + * Generate icons for the action bar + */ +public class ActionBarIconGenerator extends GraphicGenerator { + + /** Creates a new {@link ActionBarIconGenerator} */ + public ActionBarIconGenerator() { + } + + @Override + public BufferedImage generate(GraphicGeneratorContext context, Options options) { + Rectangle iconSizeHdpi = new Rectangle(0, 0, 48, 48); + Rectangle targetRectHdpi = new Rectangle(6, 6, 36, 36); + final float scaleFactor = GraphicGenerator.getHdpiScaleFactor(options.density); + Rectangle imageRect = Util.scaleRectangle(iconSizeHdpi, scaleFactor); + Rectangle targetRect = Util.scaleRectangle(targetRectHdpi, scaleFactor); + BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); + Graphics2D g = (Graphics2D) outImage.getGraphics(); + + BufferedImage tempImage = Util.newArgbBufferedImage( + imageRect.width, imageRect.height); + Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); + Util.drawCenterInside(g2, options.sourceImage, targetRect); + + ActionBarOptions actionBarOptions = (ActionBarOptions) options; + if (actionBarOptions.theme == Theme.HOLO_LIGHT) { + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect(new Color(0x898989)), + }); + } else { + assert actionBarOptions.theme == Theme.HOLO_DARK; + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + // TODO: should be white @ 60% opacity, but + // the fill then blends with the drop shadow + new FillEffect(new Color(0x909090)), + new ShadowEffect( + 0, + 0, + 3 * scaleFactor, + Color.BLACK, + 0.85, + false), + }); + } + + g.dispose(); + g2.dispose(); + + return outImage; + } + + /** Options specific to generating action bar icons */ + public static class ActionBarOptions extends GraphicGenerator.Options { + /** The theme to generate icons for */ + public Theme theme = Theme.HOLO_LIGHT; + } + + /** The themes to generate action bar icons for */ + public enum Theme { + /** Theme.Holo - a dark (and default) version of the Honeycomb theme */ + HOLO_DARK, + + /** Theme.HoloLight - a light version of the Honeycomb theme */ + HOLO_LIGHT; + } +} diff --git a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java index 902b21d..dea6eeb 100644 --- a/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java +++ b/assetstudio/src/com/android/assetstudiolib/GraphicGenerator.java @@ -26,9 +26,13 @@ import java.net.URISyntaxException; import java.net.URL; 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; @@ -38,15 +42,165 @@ import javax.imageio.ImageIO; /** * The base Generator class. */ -public class GraphicGenerator { +public abstract class GraphicGenerator { /** * Options used for all generators. */ public static class Options { + /** 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; } - public static float getScaleFactor(Density density) { - return density.getDpiValue() / (float) Density.DEFAULT_DENSITY; + /** Shapes that can be used for icon backgrounds */ + public static enum Shape { + /** 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"), + /** "Fancy" effects */ + FANCY("fore2"), + /** A glossy look */ + GLOSSY("fore3"); + + /** 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>() { + public int compare(Density d1, Density d2) { + return d1.getDpiValue() - d2.getDpiValue(); + } + }); + + for (Density density : densityValues) { + if (!density.isValidValueForDevice()) { + continue; + } + if (density == Density.TV) { + // 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 HDPI 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 hdpi distances with to compute the target density + */ + public static float getHdpiScaleFactor(Density density) { + // We used to do this: + //return density.getDpiValue() / (float) Density.DEFAULT_DENSITY; + // However, the HTML5 version of the AssetStudio would end up with different + // sizes for the assets, because it uses this table: + // studio.util.getMultBaseHdpi = function(density) { + // switch (density) { + // case 'xhdpi': return 1.333333; + // case 'hdpi': return 1.0; + // case 'mdpi': return 0.666667; + // case 'ldpi': return 0.5; + // } + // return 1.0; + // }; + // This corresponds to dividing the dpi value not by Density.MEDIUM but + // Density.HIGH: + return density.getDpiValue() / (float) Density.HIGH.getDpiValue(); } /** diff --git a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java index b6715a4..66cb6ae 100644 --- a/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java +++ b/assetstudio/src/com/android/assetstudiolib/LauncherIconGenerator.java @@ -16,93 +16,136 @@ package com.android.assetstudiolib; -import com.android.resources.Density; - import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Graphics2D; import java.awt.Rectangle; import java.awt.image.BufferedImage; +import java.util.HashMap; +import java.util.Map; /** * A {@link GraphicGenerator} that generates Android "launcher" icons. */ public class LauncherIconGenerator extends GraphicGenerator { - private static final Rectangle BASE_IMAGE_RECT = new Rectangle(0, 0, 48, 48); - private static final Rectangle BASE_TARGET_RECT = new Rectangle(4, 4, 40, 40); - - private Options mOptions; - private BufferedImage mBackImage; - private BufferedImage mMaskImage; - private BufferedImage mForeImage; - - public LauncherIconGenerator(GraphicGeneratorContext context, Options options) { - mOptions = options; - mBackImage = context.loadImageResource("/images/launcher_stencil/" - + options.shape.id + "/" + options.density.getResourceValue() + "/back.png"); - mForeImage = context.loadImageResource("/images/launcher_stencil/" - + options.shape.id + "/" + options.density.getResourceValue() + "/" - + options.style.id + ".png"); - mMaskImage = context.loadImageResource("/images/launcher_stencil/" - + options.shape.id + "/" + options.density.getResourceValue() + "/mask.png"); - } - - public BufferedImage generate() { - final float scaleFactor = GraphicGenerator.getScaleFactor(mOptions.density); - Rectangle imageRect = Util.scaleRectangle(BASE_IMAGE_RECT, scaleFactor); - Rectangle targetRect = Util.scaleRectangle(BASE_TARGET_RECT, scaleFactor); + private static final Rectangle IMAGE_SIZE_HDPI = new Rectangle(0, 0, 72, 72); + /* TODO: Adapt from html version: + 'square-web-targetRect': { x: 57, y: 57, w: 398, h: 398 }, + 'circle-web-targetRect': { x: 42, y: 42, w: 428, h: 428 }, + 'square-xhdpi-targetRect': { x: 11, y: 11, w: 74, h: 74 }, + 'circle-xhdpi-targetRect': { x: 8, y: 8, w: 80, h: 80 }, + 'square-hdpi-targetRect': { x: 8, y: 8, w: 56, h: 56 }, + 'circle-hdpi-targetRect': { x: 6, y: 6, w: 60, h: 60 }, <==== + 'square-mdpi-targetRect': { x: 5, y: 5, w: 38, h: 38 }, + 'circle-mdpi-targetRect': { x: 4, y: 4, w: 40, h: 40 }, + 'square-ldpi-targetRect': { x: 4, y: 4, w: 28, h: 28 }, + 'circle-ldpi-targetRect': { x: 3, y: 3, w: 30, h: 30 } + */ + private static final Rectangle TARGET_RECT_HDPI = new Rectangle(6, 6, 60, 60); + + @Override + public BufferedImage generate(GraphicGeneratorContext context, Options options) { + LauncherOptions launcherOptions = (LauncherOptions) options; + + String density; + if (launcherOptions.isWebGraphic) { + density = "web"; + } else { + density = launcherOptions.density.getResourceValue(); + } + String shape = launcherOptions.shape.id; + BufferedImage mBackImage = context.loadImageResource("/images/launcher_stencil/" + + shape + "/" + density + "/back.png"); + BufferedImage mForeImage = context.loadImageResource("/images/launcher_stencil/" + + shape + "/" + density + "/" + launcherOptions.style.id + ".png"); + BufferedImage mMaskImage = context.loadImageResource("/images/launcher_stencil/" + + shape + "/" + density + "/mask.png"); + + float scaleFactor = GraphicGenerator.getHdpiScaleFactor(launcherOptions.density); + if (launcherOptions.isWebGraphic) { + // Target size for the web graphic is 512 + scaleFactor = 512 / (float) IMAGE_SIZE_HDPI.height; + } + Rectangle imageRect = Util.scaleRectangle(IMAGE_SIZE_HDPI, scaleFactor); + Rectangle targetRect = Util.scaleRectangle(TARGET_RECT_HDPI, scaleFactor); BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); Graphics2D g = (Graphics2D) outImage.getGraphics(); g.drawImage(mBackImage, 0, 0, null); - { - BufferedImage tempImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); - Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); - g2.drawImage(mMaskImage, 0, 0, null); - g2.setComposite(AlphaComposite.SrcAtop); - g2.setPaint(new Color(mOptions.backgroundColor)); - g2.fillRect(0, 0, imageRect.width, imageRect.height); - - if (mOptions.crop) { - Util.drawCenterCrop(g2, mOptions.sourceImage, targetRect); - } else { - Util.drawCenterInside(g2, mOptions.sourceImage, targetRect); - } - - g.drawImage(tempImage, 0, 0, null); + BufferedImage tempImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); + Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); + g2.drawImage(mMaskImage, 0, 0, null); + g2.setComposite(AlphaComposite.SrcAtop); + g2.setPaint(new Color(launcherOptions.backgroundColor)); + g2.fillRect(0, 0, imageRect.width, imageRect.height); + + if (launcherOptions.crop) { + Util.drawCenterCrop(g2, launcherOptions.sourceImage, targetRect); + } else { + Util.drawCenterInside(g2, launcherOptions.sourceImage, targetRect); } + g.drawImage(tempImage, 0, 0, null); g.drawImage(mForeImage, 0, 0, null); + + g.dispose(); + g2.dispose(); + return outImage; } - public static class Options { - public BufferedImage sourceImage; - public int backgroundColor = 0; - public boolean crop = true; - public Shape shape = Shape.SQUARE; - public Style style = Style.SIMPLE; - public Density density = Density.XHIGH; + @Override + public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, + GraphicGeneratorContext context, Options options, String name) { + LauncherOptions launcherOptions = (LauncherOptions) options; + boolean generateWebImage = launcherOptions.isWebGraphic; + launcherOptions.isWebGraphic = false; + super.generate(category, categoryMap, context, options, name); + + if (generateWebImage) { + launcherOptions.isWebGraphic = true; + BufferedImage image = generate(context, options); + if (image != null) { + Map<String, BufferedImage> imageMap = new HashMap<String, BufferedImage>(); + categoryMap.put("Web", imageMap); + imageMap.put(getIconPath(options, name), image); + } + } + } - public static enum Shape { - CIRCLE("circle"), SQUARE("square"); + @Override + protected String getIconPath(Options options, String name) { + if (((LauncherOptions) options).isWebGraphic) { + return name + "-web.png"; // Store at the root of the project + } - public String id; + return super.getIconPath(options, name); + } - Shape(String id) { - this.id = id; - } - } + /** Options specific to generating launcher icons */ + public static class LauncherOptions extends GraphicGenerator.Options { + /** Background color, as an RRGGBB packed integer */ + public int backgroundColor = 0; - public static enum Style { - SIMPLE("fore1"), FANCY("fore2"), GLOSSY("fore3"); + /** Whether the image should be cropped or not */ + public boolean crop = true; - public String id; + /** The shape to use for the background */ + public Shape shape = Shape.SQUARE; - Style(String id) { - this.id = id; - } - } + /** The effects to apply to the foreground */ + public Style style = Style.SIMPLE; + + /** + * Whether a web graphic should be generated (will ignore normal density + * setting). The {@link #generate(GraphicGeneratorContext, Options)} + * method will use this to decide whether to generate a normal density + * icon or a high res web image. The + * {@link GraphicGenerator#generate(String, Map, GraphicGeneratorContext, Options, String)} + * method will use this flag to determine whether it should include a + * web graphic in its iteration. + */ + public boolean isWebGraphic; } } diff --git a/assetstudio/src/com/android/assetstudiolib/MenuIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/MenuIconGenerator.java index 07b7a6b..8fdbddf 100644 --- a/assetstudio/src/com/android/assetstudiolib/MenuIconGenerator.java +++ b/assetstudio/src/com/android/assetstudiolib/MenuIconGenerator.java @@ -16,7 +16,9 @@ package com.android.assetstudiolib; -import com.android.resources.Density; +import com.android.assetstudiolib.Util.Effect; +import com.android.assetstudiolib.Util.FillEffect; +import com.android.assetstudiolib.Util.ShadowEffect; import java.awt.Color; import java.awt.GradientPaint; @@ -28,65 +30,59 @@ import java.awt.image.BufferedImage; * A {@link GraphicGenerator} that generates Android "menu" icons. */ public class MenuIconGenerator extends GraphicGenerator { - private static final Rectangle BASE_IMAGE_RECT = new Rectangle(0, 0, 48, 48); - private static final Rectangle BASE_TARGET_RECT = new Rectangle(8, 8, 32, 32); - - private Options mOptions; - - public MenuIconGenerator(GraphicGeneratorContext context, Options options) { - mOptions = options; + /** Creates a menu icon generator */ + public MenuIconGenerator() { } - public BufferedImage generate() { - final float scaleFactor = GraphicGenerator.getScaleFactor(mOptions.density); - Rectangle imageRect = Util.scaleRectangle(BASE_IMAGE_RECT, scaleFactor); - Rectangle targetRect = Util.scaleRectangle(BASE_TARGET_RECT, scaleFactor); + @Override + public BufferedImage generate(GraphicGeneratorContext context, Options options) { + Rectangle imageSizeHdpi = new Rectangle(0, 0, 72, 72); + Rectangle targetRectHdpi = new Rectangle(12, 12, 48, 48); + float scaleFactor = GraphicGenerator.getHdpiScaleFactor(options.density); + Rectangle imageRect = Util.scaleRectangle(imageSizeHdpi, scaleFactor); + Rectangle targetRect = Util.scaleRectangle(targetRectHdpi, scaleFactor); BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); Graphics2D g = (Graphics2D) outImage.getGraphics(); - { - BufferedImage tempImage = Util.newArgbBufferedImage( - imageRect.width, imageRect.height); - Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); - Util.drawCenterInside(g2, mOptions.sourceImage, targetRect); + BufferedImage tempImage = Util.newArgbBufferedImage( + imageRect.width, imageRect.height); + Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); + Util.drawCenterInside(g2, options.sourceImage, targetRect); - Util.drawEffects(g, tempImage, 0, 0, new Util.Effect[]{ - new Util.FillEffect( - new GradientPaint( - 0, 0, - new Color(0xa3a3a3), - 0, imageRect.height, - new Color(0x787878))), - new Util.ShadowEffect( - 0, - 3 * scaleFactor, - 3 * scaleFactor, - Color.black, - 0.2, - true), - new Util.ShadowEffect( - 0, - 1, - 0, - Color.black, - 0.35, - true), - new Util.ShadowEffect( - 0, - -1, - 0, - Color.white, - 0.35, - true), - }); - } + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect( + new GradientPaint( + 0, 0, + new Color(0xa3a3a3), + 0, imageRect.height, + new Color(0x787878))), + new ShadowEffect( + 0, + 3 * scaleFactor, + 3 * scaleFactor, + Color.BLACK, + 0.2, + true), + new ShadowEffect( + 0, + 1, + 0, + Color.BLACK, + 0.35, + true), + new ShadowEffect( + 0, + -1, + 0, + Color.WHITE, + 0.35, + true), + }); - return outImage; - } + g.dispose(); + g2.dispose(); - public static class Options { - public BufferedImage sourceImage; - public Density density = Density.XHIGH; + return outImage; } } diff --git a/assetstudio/src/com/android/assetstudiolib/NotificationIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/NotificationIconGenerator.java new file mode 100644 index 0000000..1237a89 --- /dev/null +++ b/assetstudio/src/com/android/assetstudiolib/NotificationIconGenerator.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.assetstudiolib.Util.Effect; +import com.android.assetstudiolib.Util.FillEffect; +import com.android.assetstudiolib.Util.ShadowEffect; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.Map; + +/** + * Generate icons for the notifications bar + */ +public class NotificationIconGenerator extends GraphicGenerator { + /** Creates a new {@link NotificationIconGenerator} */ + public NotificationIconGenerator() { + } + + @Override + public BufferedImage generate(GraphicGeneratorContext context, Options options) { + Rectangle iconSizeHdpi; + Rectangle targetRectHdpi; + NotificationOptions notificationOptions = (NotificationOptions) options; + if (notificationOptions.version == Version.OLDER) { + iconSizeHdpi = new Rectangle(0, 0, 38, 38); + targetRectHdpi = new Rectangle(6, 6, 26, 26); + } else if (notificationOptions.version == Version.V11) { + iconSizeHdpi = new Rectangle(0, 0, 48, 48); + targetRectHdpi = new Rectangle(6, 6, 36, 36); + } else { + assert notificationOptions.version == Version.V9; + iconSizeHdpi = new Rectangle(0, 0, 24, 38); + targetRectHdpi = new Rectangle(0, 7, 24, 24); + } + + final float scaleFactor = GraphicGenerator.getHdpiScaleFactor(options.density); + Rectangle imageRect = Util.scaleRectangle(iconSizeHdpi, scaleFactor); + Rectangle targetRect = Util.scaleRectangle(targetRectHdpi, scaleFactor); + + BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); + Graphics2D g = (Graphics2D) outImage.getGraphics(); + + BufferedImage tempImage = Util.newArgbBufferedImage( + imageRect.width, imageRect.height); + Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); + + if (notificationOptions.version == Version.OLDER) { + BufferedImage mBackImage = context.loadImageResource( + "/images/notification_stencil/" + + notificationOptions.shape.id + '/' + + notificationOptions.density.getResourceValue() + + ".png"); + g.drawImage(mBackImage, 0, 0, null); + BufferedImage top = options.sourceImage; + BufferedImage filled = Util.filledImage(top, Color.WHITE); + Util.drawCenterInside(g, filled, targetRect); + } else if (notificationOptions.version == Version.V11) { + Util.drawCenterInside(g2, options.sourceImage, targetRect); + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect( + Color.WHITE), + }); + } else { + assert notificationOptions.version == Version.V9; + Util.drawCenterInside(g2, options.sourceImage, targetRect); + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect( + new GradientPaint( + 0, 0, + new Color(0x919191), + 0, imageRect.height, + new Color(0x828282))), + new ShadowEffect( + 0, + 1, + 0, + Color.WHITE, + 0.10, + true), + }); + } + + g.dispose(); + g2.dispose(); + + return outImage; + } + + @Override + public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, + GraphicGeneratorContext context, Options options, String name) { + for (Version version : Version.values()) { + ((NotificationOptions) options).version = version; + super.generate(version.getDisplayName(), categoryMap, context, options, name); + } + } + + @Override + protected String getIconFolder(Options options) { + String folder = super.getIconFolder(options); + Version version = ((NotificationOptions) options).version; + if (version == Version.V11) { + return folder + "-v11"; //$NON-NLS-1$ + } else if (version == Version.V9) { + return folder + "-v9"; //$NON-NLS-1$ + } else { + return folder; + } + } + + /** + * Options specific to generating notification icons + */ + public static class NotificationOptions extends GraphicGenerator.Options { + /** + * The shape to use for graphics behind the icon (for {@link Version#OLDER} only) + */ + public Shape shape = Shape.SQUARE; + + /** + * The version of the icon to generate - different styles are used for different + * versions of Android + */ + public Version version = Version.V9; + } + + /** + * The version of the icon to generate - different styles are used for different + * versions of Android + */ + public enum Version { + /** Icon style used for -v9 and -v10 */ + V9("V9"), + + /** Icon style used for -v11 (Honeycomb) and later */ + V11("V11"), + + /** Icon style used for versions older than v9 */ + OLDER("Other"); + + private final String mDisplayName; + + Version(String displayName) { + mDisplayName = displayName; + } + + /** + * Returns the display name for this version, typically shown as a + * category + * + * @return the display name, never null + */ + public String getDisplayName() { + return mDisplayName; + } + } +} diff --git a/assetstudio/src/com/android/assetstudiolib/TabIconGenerator.java b/assetstudio/src/com/android/assetstudiolib/TabIconGenerator.java new file mode 100644 index 0000000..506aebd --- /dev/null +++ b/assetstudio/src/com/android/assetstudiolib/TabIconGenerator.java @@ -0,0 +1,188 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Eclipse Public License, Version 1.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.eclipse.org/org/documents/epl-v10.php + * + * 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.assetstudiolib.Util.Effect; +import com.android.assetstudiolib.Util.FillEffect; +import com.android.assetstudiolib.Util.ShadowEffect; + +import java.awt.Color; +import java.awt.GradientPaint; +import java.awt.Graphics2D; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; +import java.util.Map; + + +/** + * Generate icons for tabs + */ +public class TabIconGenerator extends GraphicGenerator { + /** Creates a new {@link TabIconGenerator} */ + public TabIconGenerator() { + } + + @Override + public BufferedImage generate(GraphicGeneratorContext context, Options options) { + Rectangle iconSizeHdpi = new Rectangle(0, 0, 48, 48); + Rectangle targetRectHdpi = new Rectangle(3, 3, 42, 42); + final float scaleFactor = GraphicGenerator.getHdpiScaleFactor(options.density); + Rectangle imageRect = Util.scaleRectangle(iconSizeHdpi, scaleFactor); + Rectangle targetRect = Util.scaleRectangle(targetRectHdpi, scaleFactor); + BufferedImage outImage = Util.newArgbBufferedImage(imageRect.width, imageRect.height); + Graphics2D g = (Graphics2D) outImage.getGraphics(); + + BufferedImage tempImage = Util.newArgbBufferedImage( + imageRect.width, imageRect.height); + Graphics2D g2 = (Graphics2D) tempImage.getGraphics(); + Util.drawCenterInside(g2, options.sourceImage, targetRect); + + TabOptions tabOptions = (TabOptions) options; + if (tabOptions.selected) { + if (tabOptions.oldStyle) { + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect( + new GradientPaint( + 0, 0, + new Color(0xa3a3a3), + 0, imageRect.height, + new Color(0x787878))), + new ShadowEffect( + 0, + 3 * scaleFactor, + 3 * scaleFactor, + Color.BLACK, + 0.2, + true), + new ShadowEffect( + 0, + 1, + 0, + Color.BLACK, + 0.35, + true), + new ShadowEffect( + 0, + -1, + 0, + Color.WHITE, + 0.35, + true), + }); + } else { + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect(Color.WHITE), + new ShadowEffect( + 0, + 0, + 5 * scaleFactor, + Color.BLACK, + 0.25, + false), + }); + } + } else { + // Unselected + if (tabOptions.oldStyle) { + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect( + new GradientPaint( + 0, 0.25f * imageRect.height, + new Color(0xf9f9f9), + 0, imageRect.height, + new Color(0xdfdfdf))), + new ShadowEffect( + 0, + 3 * scaleFactor, + 3 * scaleFactor, + Color.BLACK, + 0.1, + true), + new ShadowEffect( + 0, + 1, + 0, + Color.BLACK, + 0.35, + true), + new ShadowEffect( + 0, + -1, + 0, + Color.WHITE, + 0.35, + true), + }); + } else { + Util.drawEffects(g, tempImage, 0, 0, new Effect[] { + new FillEffect(new Color(0x808080)), + }); + } + } + + g.dispose(); + g2.dispose(); + + return outImage; + } + + @Override + public void generate(String category, Map<String, Map<String, BufferedImage>> categoryMap, + GraphicGeneratorContext context, Options options, String name) { + TabOptions tabOptions = (TabOptions) options; + // Generate all permutations of tabOptions.selected and tabOptions.oldStyle + tabOptions.selected = true; + tabOptions.oldStyle = false; + super.generate("Selected (v5+)", categoryMap, context, options, name); + tabOptions.oldStyle = true; + super.generate("Selected", categoryMap, context, options, name); + tabOptions.selected = false; + tabOptions.oldStyle = false; + super.generate("Unselected (v5+)", categoryMap, context, options, name); + tabOptions.oldStyle = true; + super.generate("Unselected", categoryMap, context, options, name); + } + + @Override + protected String getIconFolder(Options options) { + String folder = super.getIconFolder(options); + + TabOptions tabOptions = (TabOptions) options; + if (tabOptions.oldStyle) { + return folder; + } else { + return folder + "-v5"; //$NON-NLS-1$ + } + } + + @Override + protected String getIconName(Options options, String name) { + TabOptions tabOptions = (TabOptions) options; + if (tabOptions.selected) { + return name + "_selected.png"; //$NON-NLS-1$ + } else { + return name + "_unselected.png"; //$NON-NLS-1$ + } + } + + /** Options specific to generating tab icons */ + public static class TabOptions extends GraphicGenerator.Options { + /** Generate icon in the style used prior to v5 */ + public boolean oldStyle; + /** Generate "selected" icon if true, and "unselected" icon if false */ + public boolean selected = true; + } +} diff --git a/assetstudio/src/com/android/assetstudiolib/TextRenderUtil.java b/assetstudio/src/com/android/assetstudiolib/TextRenderUtil.java index a103701..e08a234 100644 --- a/assetstudio/src/com/android/assetstudiolib/TextRenderUtil.java +++ b/assetstudio/src/com/android/assetstudiolib/TextRenderUtil.java @@ -16,14 +16,14 @@ package com.android.assetstudiolib; +import java.awt.Color; import java.awt.Font; import java.awt.Graphics2D; +import java.awt.RenderingHints; import java.awt.font.FontRenderContext; -import java.awt.font.TextAttribute; +import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; import java.awt.image.BufferedImage; -import java.util.Hashtable; -import java.util.Map; /** * A set of utility classes for rendering text to a {@link BufferedImage}, suitable for use as a @@ -31,13 +31,18 @@ import java.util.Map; */ public class TextRenderUtil { /** - * Renders the given string with the provided {@link Options} to a {@link BufferedImage}. + * Renders the given string with the provided {@link Options} to a + * {@link BufferedImage}. * - * @param text The text to render. + * @param text The text to render. + * @param paddingPercentage If nonzero, a percentage of the width or height + * (whichever is smaller) to add as padding around the text * @param options The optional parameters for rendering the text. - * @return An image, suitable for use as an input to a {@link GraphicGenerator}. + * @return An image, suitable for use as an input to a + * {@link GraphicGenerator}. */ - public static BufferedImage renderTextImage(String text, Options options) { + public static BufferedImage renderTextImage(String text, int paddingPercentage, + Options options) { if (options == null) { options = new Options(); } @@ -59,13 +64,32 @@ public class TextRenderUtil { FontRenderContext frc = tempG.getFontRenderContext(); - Rectangle2D bounds = font.getStringBounds(text, frc); + TextLayout layout = new TextLayout(text, font, frc); + Rectangle2D bounds = layout.getBounds(); + + // The padding is a percentage relative to the overall minimum of the width or height + if (paddingPercentage != 0) { + double minDimension = Math.min(bounds.getWidth(), bounds.getHeight()); + double delta = minDimension * paddingPercentage / 100; + bounds.setRect(bounds.getMinX() - delta, bounds.getMinY() - delta, + bounds.getWidth() + 2 * delta, bounds.getHeight() + 2 * delta); + } BufferedImage image = Util.newArgbBufferedImage( Math.max(1, (int) bounds.getWidth()), Math.max(1, (int) bounds.getHeight())); Graphics2D g = (Graphics2D) image.getGraphics(); + g.setColor(new Color(options.foregroundColor, true)); g.setFont(font); - g.drawString(text, 0, (float) -bounds.getY()); + + g.setRenderingHint( + RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint( + RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON); + + g.drawString(text, (float) -bounds.getX(), (float) -bounds.getY()); + + g.dispose(); + tempG.dispose(); return image; } @@ -79,6 +103,9 @@ public class TextRenderUtil { // TODO: Instead, a graphic generator should use a different source image for each density. private static final int DEFAULT_FONT_SIZE = 512; + /** Foreground color to render text with, as an AARRGGBB packed integer */ + public int foregroundColor = 0xFFFFFFFF; + /** * The optional {@link Font} to use. If null, a {@link Font} object will be generated using * the other options. diff --git a/assetstudio/src/com/android/assetstudiolib/Util.java b/assetstudio/src/com/android/assetstudiolib/Util.java index 1a6b00e..927ca30 100644 --- a/assetstudio/src/com/android/assetstudiolib/Util.java +++ b/assetstudio/src/com/android/assetstudiolib/Util.java @@ -84,7 +84,7 @@ public class Util { } /** - * Applies a gaussion blur of the given radius to the given {@link BufferedImage} using a kernel + * Applies a gaussian blur of the given radius to the given {@link BufferedImage} using a kernel * convolution. * * @param source The source image. diff --git a/assetstudio/src/images/notification_stencil/circle/hdpi.png b/assetstudio/src/images/notification_stencil/circle/hdpi.png Binary files differnew file mode 100644 index 0000000..4d28710 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/circle/hdpi.png diff --git a/assetstudio/src/images/notification_stencil/circle/ldpi.png b/assetstudio/src/images/notification_stencil/circle/ldpi.png Binary files differnew file mode 100644 index 0000000..5c9a9f7 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/circle/ldpi.png diff --git a/assetstudio/src/images/notification_stencil/circle/mdpi.png b/assetstudio/src/images/notification_stencil/circle/mdpi.png Binary files differnew file mode 100644 index 0000000..1f064a2 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/circle/mdpi.png diff --git a/assetstudio/src/images/notification_stencil/circle/xhdpi.png b/assetstudio/src/images/notification_stencil/circle/xhdpi.png Binary files differnew file mode 100644 index 0000000..f6a57c1 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/circle/xhdpi.png diff --git a/assetstudio/src/images/notification_stencil/square/hdpi.png b/assetstudio/src/images/notification_stencil/square/hdpi.png Binary files differnew file mode 100644 index 0000000..f755f4f --- /dev/null +++ b/assetstudio/src/images/notification_stencil/square/hdpi.png diff --git a/assetstudio/src/images/notification_stencil/square/ldpi.png b/assetstudio/src/images/notification_stencil/square/ldpi.png Binary files differnew file mode 100644 index 0000000..23846bd --- /dev/null +++ b/assetstudio/src/images/notification_stencil/square/ldpi.png diff --git a/assetstudio/src/images/notification_stencil/square/mdpi.png b/assetstudio/src/images/notification_stencil/square/mdpi.png Binary files differnew file mode 100644 index 0000000..5f80247 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/square/mdpi.png diff --git a/assetstudio/src/images/notification_stencil/square/xhdpi.png b/assetstudio/src/images/notification_stencil/square/xhdpi.png Binary files differnew file mode 100644 index 0000000..7c27d82 --- /dev/null +++ b/assetstudio/src/images/notification_stencil/square/xhdpi.png |