1 /* 2 * Copyright (C) 2008 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 android.graphics.drawable.cts; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.Resources; 22 import android.content.res.XmlResourceParser; 23 import android.graphics.Bitmap; 24 import android.graphics.Canvas; 25 import android.graphics.Color; 26 import android.graphics.drawable.Drawable; 27 import android.support.annotation.IntegerRes; 28 import android.support.annotation.NonNull; 29 import android.support.annotation.Nullable; 30 import android.util.AttributeSet; 31 import android.util.Log; 32 import android.util.Xml; 33 34 import junit.framework.Assert; 35 36 import org.xmlpull.v1.XmlPullParser; 37 import org.xmlpull.v1.XmlPullParserException; 38 39 import java.io.File; 40 import java.io.FileOutputStream; 41 import java.io.IOException; 42 43 /** 44 * The useful methods for graphics.drawable test. 45 */ 46 public class DrawableTestUtils { 47 private static final String LOGTAG = "DrawableTestUtils"; 48 // A small value is actually making sure that the values are matching 49 // exactly with the golden image. 50 // We can increase the threshold if the Skia is drawing with some variance 51 // on different devices. So far, the tests show they are matching correctly. 52 static final float PIXEL_ERROR_THRESHOLD = 0.03f; 53 static final float PIXEL_ERROR_COUNT_THRESHOLD = 0.005f; 54 static final int PIXEL_ERROR_TOLERANCE = 3; 55 skipCurrentTag(XmlPullParser parser)56 public static void skipCurrentTag(XmlPullParser parser) 57 throws XmlPullParserException, IOException { 58 int outerDepth = parser.getDepth(); 59 int type; 60 while ((type=parser.next()) != XmlPullParser.END_DOCUMENT 61 && (type != XmlPullParser.END_TAG 62 || parser.getDepth() > outerDepth)) { 63 } 64 } 65 66 /** 67 * Retrieve an AttributeSet from a XML. 68 * 69 * @param parser the XmlPullParser to use for the xml parsing. 70 * @param searchedNodeName the name of the target node. 71 * @return the AttributeSet retrieved from specified node. 72 * @throws IOException 73 * @throws XmlPullParserException 74 */ getAttributeSet(XmlResourceParser parser, String searchedNodeName)75 public static AttributeSet getAttributeSet(XmlResourceParser parser, String searchedNodeName) 76 throws XmlPullParserException, IOException { 77 AttributeSet attrs = null; 78 int type; 79 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 80 && type != XmlPullParser.START_TAG) { 81 } 82 String nodeName = parser.getName(); 83 if (!"alias".equals(nodeName)) { 84 throw new RuntimeException(); 85 } 86 int outerDepth = parser.getDepth(); 87 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 88 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 89 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 90 continue; 91 } 92 nodeName = parser.getName(); 93 if (searchedNodeName.equals(nodeName)) { 94 outerDepth = parser.getDepth(); 95 while ((type = parser.next()) != XmlPullParser.END_DOCUMENT 96 && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { 97 if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { 98 continue; 99 } 100 nodeName = parser.getName(); 101 attrs = Xml.asAttributeSet(parser); 102 break; 103 } 104 break; 105 } else { 106 skipCurrentTag(parser); 107 } 108 } 109 return attrs; 110 } 111 getResourceParser(Resources res, int resId)112 public static XmlResourceParser getResourceParser(Resources res, int resId) 113 throws XmlPullParserException, IOException { 114 final XmlResourceParser parser = res.getXml(resId); 115 int type; 116 while ((type = parser.next()) != XmlPullParser.START_TAG 117 && type != XmlPullParser.END_DOCUMENT) { 118 // Empty loop 119 } 120 return parser; 121 } 122 setResourcesDensity(Resources res, int densityDpi)123 public static void setResourcesDensity(Resources res, int densityDpi) { 124 final Configuration config = new Configuration(); 125 config.setTo(res.getConfiguration()); 126 config.densityDpi = densityDpi; 127 res.updateConfiguration(config, null); 128 } 129 130 /** 131 * Implements scaling as used by the Bitmap class. Resulting values are 132 * rounded up (as distinct from resource scaling, which truncates or rounds 133 * to the nearest pixel). 134 * 135 * @param size the pixel size to scale 136 * @param sdensity the source density that corresponds to the size 137 * @param tdensity the target density 138 * @return the pixel size scaled for the target density 139 */ scaleBitmapFromDensity(int size, int sdensity, int tdensity)140 public static int scaleBitmapFromDensity(int size, int sdensity, int tdensity) { 141 if (sdensity == 0 || tdensity == 0 || sdensity == tdensity) { 142 return size; 143 } 144 145 // Scale by tdensity / sdensity, rounding up. 146 return ((size * tdensity) + (sdensity >> 1)) / sdensity; 147 } 148 149 /** 150 * Asserts that two images are similar within the given thresholds. 151 * 152 * @param message Error message 153 * @param expected Expected bitmap 154 * @param actual Actual bitmap 155 * @param pixelThreshold The total difference threshold for a single pixel 156 * @param pixelCountThreshold The total different pixel count threshold 157 * @param pixelDiffTolerance The pixel value difference tolerance 158 * 159 */ compareImages(String message, Bitmap expected, Bitmap actual, float pixelThreshold, float pixelCountThreshold, int pixelDiffTolerance)160 public static void compareImages(String message, Bitmap expected, Bitmap actual, 161 float pixelThreshold, float pixelCountThreshold, int pixelDiffTolerance) { 162 int idealWidth = expected.getWidth(); 163 int idealHeight = expected.getHeight(); 164 165 Assert.assertTrue(idealWidth == actual.getWidth()); 166 Assert.assertTrue(idealHeight == actual.getHeight()); 167 168 int totalDiffPixelCount = 0; 169 float totalPixelCount = idealWidth * idealHeight; 170 for (int x = 0; x < idealWidth; x++) { 171 for (int y = 0; y < idealHeight; y++) { 172 int idealColor = expected.getPixel(x, y); 173 int givenColor = actual.getPixel(x, y); 174 if (idealColor == givenColor) 175 continue; 176 177 float totalError = 0; 178 totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor)); 179 totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor)); 180 totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor)); 181 totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor)); 182 183 if ((totalError / 1024.0f) >= pixelThreshold) { 184 Assert.fail((message + ": totalError is " + totalError)); 185 } 186 187 if (totalError > pixelDiffTolerance) { 188 totalDiffPixelCount++; 189 } 190 } 191 } 192 if ((totalDiffPixelCount / totalPixelCount) >= pixelCountThreshold) { 193 Assert.fail((message +": totalDiffPixelCount is " + totalDiffPixelCount)); 194 } 195 } 196 197 /** 198 * Returns the {@link Color} at the specified location in the {@link Drawable}. 199 */ getPixel(Drawable d, int x, int y)200 public static int getPixel(Drawable d, int x, int y) { 201 final int w = Math.max(d.getIntrinsicWidth(), x + 1); 202 final int h = Math.max(d.getIntrinsicHeight(), y + 1); 203 final Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888); 204 final Canvas c = new Canvas(b); 205 d.setBounds(0, 0, w, h); 206 d.draw(c); 207 208 final int pixel = b.getPixel(x, y); 209 b.recycle(); 210 return pixel; 211 } 212 213 /** 214 * Save a bitmap for debugging or golden image (re)generation purpose. 215 * The file name will be referred from the resource id, plus optionally {@code extras}, and 216 * "_golden" 217 */ saveAutoNamedVectorDrawableIntoPNG(@onNull Context context, @NonNull Bitmap bitmap, @IntegerRes int resId, @Nullable String extras)218 static void saveAutoNamedVectorDrawableIntoPNG(@NonNull Context context, @NonNull Bitmap bitmap, 219 @IntegerRes int resId, @Nullable String extras) 220 throws IOException { 221 String originalFilePath = context.getResources().getString(resId); 222 File originalFile = new File(originalFilePath); 223 String fileFullName = originalFile.getName(); 224 String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf(".")); 225 String outputFolder = context.getExternalFilesDir(null).getAbsolutePath(); 226 if (extras != null) { 227 fileTitle += "_" + extras; 228 } 229 saveVectorDrawableIntoPNG(bitmap, outputFolder, fileTitle); 230 } 231 232 /** 233 * Save a {@code bitmap} to the {@code fileFullName} plus "_golden". 234 */ saveVectorDrawableIntoPNG(@onNull Bitmap bitmap, @NonNull String outputFolder, @NonNull String fileFullName)235 static void saveVectorDrawableIntoPNG(@NonNull Bitmap bitmap, @NonNull String outputFolder, 236 @NonNull String fileFullName) 237 throws IOException { 238 // Save the image to the disk. 239 FileOutputStream out = null; 240 try { 241 File folder = new File(outputFolder); 242 if (!folder.exists()) { 243 folder.mkdir(); 244 } 245 String outputFilename = outputFolder + "/" + fileFullName + "_golden"; 246 outputFilename +=".png"; 247 File outputFile = new File(outputFilename); 248 if (!outputFile.exists()) { 249 outputFile.createNewFile(); 250 } 251 252 out = new FileOutputStream(outputFile, false); 253 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); 254 Log.v(LOGTAG, "Write test No." + outputFilename + " to file successfully."); 255 } catch (Exception e) { 256 e.printStackTrace(); 257 } finally { 258 if (out != null) { 259 out.close(); 260 } 261 } 262 } 263 } 264