| /* |
| * Copyright (C) 2013 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.tools.idea.ddms.screenshot; |
| |
| import com.android.SdkConstants; |
| import com.android.resources.ScreenOrientation; |
| import com.android.tools.idea.rendering.ImageUtils; |
| import com.android.utils.XmlUtils; |
| import com.google.common.base.Charsets; |
| import com.google.common.collect.Lists; |
| import com.google.common.io.Files; |
| import com.intellij.openapi.application.PathManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.util.text.StringUtil; |
| import com.intellij.util.containers.HashSet; |
| import org.jetbrains.annotations.NonNls; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.w3c.dom.Document; |
| import org.w3c.dom.Element; |
| import org.w3c.dom.NodeList; |
| |
| import java.awt.*; |
| import java.awt.image.BufferedImage; |
| import java.io.File; |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Set; |
| |
| import static com.android.tools.lint.detector.api.LintUtils.getChildren; |
| |
| /** |
| * Descriptor for a device frame picture (background, shadow, reflection) which can be |
| * painted around a screenshot or device rendering |
| */ |
| public class DeviceArtDescriptor { |
| @NotNull public static final DeviceArtDescriptor NONE = new DeviceArtDescriptor(null, null); |
| |
| private final String myId; |
| private final String myName; |
| private final File myFolder; |
| private OrientationData myPortrait; |
| private OrientationData myLandscape; |
| |
| @NonNls private static final String FN_BASE = "device-art-resources"; |
| @NonNls private static final String FN_DESCRIPTOR = "device-art.xml"; |
| |
| /** Returns the absolute path to {@link #FN_BASE} folder, or null if it couldn't be located. */ |
| @Nullable |
| public static File getBundledDescriptorsFolder() { |
| // In the IDE distribution, this should be in plugins/android/lib/FN_BASE |
| String androidJarPath = PathManager.getJarPathForClass(DeviceArtDescriptor.class); |
| if (androidJarPath != null) { |
| File androidJar = new File(androidJarPath); |
| if (androidJar.isFile()) { |
| File base = new File(androidJar.getParentFile(), FN_BASE); |
| if (base.exists() && base.isDirectory()) { |
| return base; |
| } |
| } |
| } |
| |
| // In development environments, search a few other folders |
| String basePath = PathManager.getHomePath(); |
| String[] paths = new String[] { |
| "plugins" + File.separatorChar + "android" + File.separatorChar, |
| ".." + File.separator + "adt" + File.separator + "idea" + File.separator + "android" + File.separatorChar, |
| "android" + File.separatorChar + "android" + File.separatorChar, |
| "community" + File.separatorChar + "android" + File.separatorChar + "android" + File.separatorChar, |
| }; |
| |
| for (String p : paths) { |
| File base = new File(basePath, p); |
| if (base.isDirectory()) { |
| File files = new File(base, FN_BASE); |
| if (files.isDirectory()) { |
| return files; |
| } |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private static File getDescriptorFile(@NotNull File folder) { |
| File file = new File(folder, FN_DESCRIPTOR); |
| return file.isFile() ? file : null; |
| } |
| |
| private static List<File> getDescriptorFiles(@Nullable File[] additionalRoots) { |
| Set<File> roots = new HashSet<File>(); |
| |
| File base = getBundledDescriptorsFolder(); |
| if (base != null) { |
| roots.add(base); |
| } |
| |
| if (additionalRoots != null) { |
| Collections.addAll(roots, additionalRoots); |
| } |
| |
| List<File> files = new ArrayList<File>(roots.size()); |
| for (File root : roots) { |
| File file = getDescriptorFile(root); |
| if (file != null) { |
| files.add(file); |
| } |
| } |
| |
| return files; |
| } |
| |
| public static List<DeviceArtDescriptor> getDescriptors(@Nullable File[] folders) { |
| List<File> files = getDescriptorFiles(folders); |
| List<DeviceArtDescriptor> result = Lists.newArrayList(); |
| |
| for (File file : files) |
| try { |
| String xml = Files.toString(file, Charsets.UTF_8); |
| Document document = XmlUtils.parseDocumentSilently(xml, false); |
| if (document != null) { |
| File baseFolder = file.getParentFile(); |
| addDescriptors(result, document, baseFolder); |
| } |
| else { |
| Logger.getInstance(DeviceArtDescriptor.class).error("Couldn't parse " + file); |
| } |
| } |
| catch (IOException e) { |
| Logger.getInstance(DeviceArtDescriptor.class).error(e); |
| } |
| |
| return result; |
| } |
| |
| private DeviceArtDescriptor(@Nullable File baseFolder, @Nullable Element element) { |
| if (element == null) { |
| myId = myName = ""; |
| myFolder = null; |
| } else { |
| myId = element.getAttribute(SdkConstants.ATTR_ID); |
| myName = element.getAttribute(SdkConstants.ATTR_NAME); |
| myFolder = new File(baseFolder, myId); |
| |
| List<Element> children = getChildren(element); |
| for (Element child : children) { |
| OrientationData orientation = new OrientationData(this, child); |
| if (orientation.isPortrait()) { |
| myPortrait = orientation; |
| } else { |
| myLandscape = orientation; |
| } |
| } |
| } |
| } |
| |
| static void addDescriptors(List<DeviceArtDescriptor> result, Document document, File baseFolder) { |
| NodeList deviceList = document.getElementsByTagName("device"); |
| for (int i = 0; i < deviceList.getLength(); i++) { |
| Element element = (Element)deviceList.item(i); |
| DeviceArtDescriptor descriptor = new DeviceArtDescriptor(baseFolder, element); |
| result.add(descriptor); |
| } |
| } |
| |
| @NotNull |
| public String getId() { |
| return myId; |
| } |
| |
| @NotNull |
| public String getName() { |
| return myName; |
| } |
| |
| @NotNull |
| public OrientationData getArtDescriptor(@NotNull ScreenOrientation orientation) { |
| return orientation == ScreenOrientation.PORTRAIT ? myPortrait : myLandscape; |
| } |
| |
| public File getBaseFolder() { |
| return myFolder; |
| } |
| |
| public Dimension getScreenSize(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getScreenSize(); |
| } |
| |
| public Point getScreenPos(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getScreenPos(); |
| } |
| |
| public Dimension getFrameSize(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getFrameSize(); |
| } |
| |
| public Rectangle getCrop(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getCrop(); |
| } |
| |
| @Nullable |
| public File getFrame(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getBackgroundFile(); |
| } |
| |
| @Nullable |
| public File getDropShadow(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getShadowFile(); |
| } |
| |
| @Nullable |
| public File getReflectionOverlay(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getReflectionFile(); |
| } |
| |
| @Nullable |
| public File getMask(@NotNull ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getMaskFile(); |
| } |
| |
| public double getAspectRatio(ScreenOrientation orientation) { |
| return getArtDescriptor(orientation).getAspectRatio(); |
| } |
| |
| public boolean isStretchable() { |
| return myId.equals("phone") || myId.equals("tablet"); |
| } |
| |
| /** Returns whether this descriptor can frame the given image. */ |
| public boolean canFrameImage(BufferedImage image, ScreenOrientation orientation) { |
| if (isStretchable()) { |
| return true; |
| } |
| |
| // Not all devices are available in all orientations |
| if (orientation == ScreenOrientation.PORTRAIT && myPortrait == null) { |
| return false; |
| } else if (orientation == ScreenOrientation.LANDSCAPE && myLandscape == null) { |
| return false; |
| } |
| |
| // Don't support framing images smaller than our screen size (we don't want to stretch the image) |
| Dimension screenSize = getArtDescriptor(orientation).getScreenSize(); |
| if (image.getWidth() < screenSize.getWidth() || image.getHeight() < screenSize.getHeight()) { |
| return false; |
| } |
| |
| // Make sure that the aspect ratio is nearly identical to the image aspect ratio |
| double imgAspectRatio = image.getWidth() / (double) image.getHeight(); |
| double descriptorAspectRatio = getAspectRatio(orientation); |
| return Math.abs(imgAspectRatio - descriptorAspectRatio) < ImageUtils.EPSILON; |
| } |
| |
| /** Descriptor for a particular device frame (e.g. a set of images for a particular device in a particular orientation) */ |
| private static class OrientationData { |
| private final DeviceArtDescriptor myDevice; |
| private final String myShadowName; |
| private final String myBackgroundName; |
| private final String myReflectionName; |
| private final String myMaskName; |
| private final Dimension myScreenSize; |
| private final Point myScreenPos; |
| private final Dimension myFrameSize; |
| private final Rectangle myCrop; |
| private final ScreenOrientation myOrientation; |
| |
| OrientationData(DeviceArtDescriptor device, Element element) { |
| myDevice = device; |
| String orientation = element.getAttribute(SdkConstants.ATTR_NAME); |
| if ("port".equals(orientation)) { |
| myOrientation = ScreenOrientation.PORTRAIT; |
| } else { |
| assert "land".equals(orientation) : orientation; |
| myOrientation = ScreenOrientation.LANDSCAPE; |
| } |
| |
| myFrameSize = getDimension(element.getAttribute("size")); |
| myScreenSize = getDimension(element.getAttribute("screenSize")); |
| myScreenPos = getPoint(element.getAttribute("screenPos")); |
| myCrop = getRectangle(element.getAttribute("crop")); |
| |
| myBackgroundName = getFileName(element, "back"); |
| myShadowName = getFileName(element, "shadow"); |
| myReflectionName = getFileName(element, "lights"); |
| myMaskName = getFileName(element, "mask"); |
| } |
| |
| @Nullable |
| private static String getFileName(Element element, String name) { |
| return name != null && !name.isEmpty() ? element.getAttribute(name) : null; |
| } |
| |
| @Nullable |
| private static Dimension getDimension(String value) { |
| if (value == null || value.isEmpty()) { |
| return null; |
| } |
| |
| int comma = value.indexOf(','); |
| if (comma == -1) { |
| return null; |
| } |
| return new Dimension(getInteger(value.substring(0, comma)), getInteger(value.substring(comma + 1))); |
| } |
| |
| @Nullable |
| private static Point getPoint(String value) { |
| if (value == null || value.isEmpty()) { |
| return null; |
| } |
| |
| int comma = value.indexOf(','); |
| if (comma == -1) { |
| return null; |
| } |
| return new Point(getInteger(value.substring(0, comma)), getInteger(value.substring(comma + 1))); |
| } |
| |
| @Nullable |
| private static Rectangle getRectangle(String value) { |
| if (value == null || value.isEmpty()) { |
| return null; |
| } |
| |
| int comma1 = value.indexOf(','); |
| if (comma1 == -1) { |
| return null; |
| } |
| int comma2 = value.indexOf(',', comma1 + 1); |
| if (comma2 == -1) { |
| return null; |
| } |
| int comma3 = value.indexOf(',', comma2 + 1); |
| if (comma3 == -1) { |
| return null; |
| } |
| String x = value.substring(0, comma1); |
| String y = value.substring(comma1 + 1, comma2); |
| String w = value.substring(comma2 + 1, comma3); |
| String h = value.substring(comma3 + 1); |
| return new Rectangle(getInteger(x), getInteger(y), getInteger(w), getInteger(h)); |
| } |
| |
| private static int getInteger(String value) { |
| return Integer.parseInt(value); |
| } |
| |
| public boolean isPortrait() { |
| return myOrientation == ScreenOrientation.PORTRAIT; |
| } |
| |
| public Dimension getScreenSize() { |
| return myScreenSize; |
| } |
| |
| public Point getScreenPos() { |
| return myScreenPos; |
| } |
| |
| public Dimension getFrameSize() { |
| return myFrameSize; |
| } |
| |
| public Rectangle getCrop() { |
| return myCrop; |
| } |
| |
| @NotNull |
| public ScreenOrientation getOrientation() { |
| return myOrientation; |
| } |
| |
| @Nullable |
| public File getBackgroundFile() { |
| return !StringUtil.isEmpty(myBackgroundName) ? new File(myDevice.getBaseFolder(), myBackgroundName) : null; |
| } |
| |
| @Nullable |
| public File getShadowFile() { |
| return !StringUtil.isEmpty(myShadowName) ? new File(myDevice.getBaseFolder(), myShadowName) : null; |
| } |
| |
| @Nullable |
| public File getReflectionFile() { |
| return !StringUtil.isEmpty(myReflectionName) ? new File(myDevice.getBaseFolder(), myReflectionName) : null; |
| } |
| |
| @Nullable |
| public File getMaskFile() { |
| return !StringUtil.isEmpty(myMaskName) ? new File(myDevice.getBaseFolder(), myMaskName) : null; |
| } |
| |
| public double getAspectRatio() { |
| return myScreenSize.width / (double) myScreenSize.height; |
| } |
| } |
| } |