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