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