Shrank refocus test and reference image size

Bug: 27747093

Replaced the test image with a smaller one, so that it can be loaded
into memory on an X86 emulator, or a device with small RAM.

Separated the depthmap from the test image itself, to make it easy to
resize both the image and the depthmap in sync. Adjusted DepthImage
and RGBZ classes to handle a separate image and depthmap.

Adjusted the DepthOfFieldOptions class to make the output image
look reasonable. The blur-at-infinity parameter was improperly set
previously.

Use PSNR for image comparison. Updated the ImageCompare class to do
in-place comparision, instead of loading redundant copies of the images
into memory.

Adjusted MediaStoreSaver class to use a different path to write the
output image, in the case where Pictures directory does not exist,
e.g., on an X86 emulator.

Use PNG instaed of JPG test images, to avoid noises from decompression
on different target platforms.

Change-Id: I281c738780d98bb70404d05b59a430735c546715
diff --git a/tests/tests/renderscript/res/drawable-nodpi/expected_output.png b/tests/tests/renderscript/res/drawable-nodpi/expected_output.png
new file mode 100644
index 0000000..77c907e
--- /dev/null
+++ b/tests/tests/renderscript/res/drawable-nodpi/expected_output.png
Binary files differ
diff --git a/tests/tests/renderscript/res/drawable-nodpi/refocus_image.jpg b/tests/tests/renderscript/res/drawable-nodpi/refocus_image.jpg
deleted file mode 100755
index 347c3f9..0000000
--- a/tests/tests/renderscript/res/drawable-nodpi/refocus_image.jpg
+++ /dev/null
Binary files differ
diff --git a/tests/tests/renderscript/res/drawable-nodpi/refocus_reference.png b/tests/tests/renderscript/res/drawable-nodpi/refocus_reference.png
deleted file mode 100644
index 7d077ef..0000000
--- a/tests/tests/renderscript/res/drawable-nodpi/refocus_reference.png
+++ /dev/null
Binary files differ
diff --git a/tests/tests/renderscript/res/drawable-nodpi/test_depthmap.png b/tests/tests/renderscript/res/drawable-nodpi/test_depthmap.png
new file mode 100644
index 0000000..d5e8c25
--- /dev/null
+++ b/tests/tests/renderscript/res/drawable-nodpi/test_depthmap.png
Binary files differ
diff --git a/tests/tests/renderscript/res/drawable-nodpi/test_image.png b/tests/tests/renderscript/res/drawable-nodpi/test_image.png
new file mode 100644
index 0000000..0107b38
--- /dev/null
+++ b/tests/tests/renderscript/res/drawable-nodpi/test_image.png
Binary files differ
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthImage.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthImage.java
index d626650..31399f4 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthImage.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthImage.java
@@ -18,8 +18,11 @@
 
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.renderscript.cts.refocus.image.RangeInverseDepthTransform;
 import android.net.Uri;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
@@ -34,20 +37,58 @@
     private final double mFocalPointX;
     private final double mFocalPointY;
     private final DepthTransform mDepthTransform;
-    public DepthImage(Context context, Uri data) throws IOException {
-        InputStream input = context.getContentResolver().openInputStream(data);
+
+    public DepthImage(String format, double far, double near,
+                      Bitmap depthBitmap, double blurAtInfinity,
+                      double focalDistance, double depthOfField,
+                      double focalPointX, double focalPointY,
+                      DepthTransform depthTransform) {
+        mFormat = format;
+        mFar = far;
+        mNear = near;
+        mDepthBitmap = depthBitmap;
+        mBlurAtInfinity = blurAtInfinity;
+        mFocalDistance = focalDistance;
+        mDepthOfFiled = depthOfField;
+        mFocalPointX = focalPointX;
+        mFocalPointY = focalPointY;
+        mDepthTransform = depthTransform;
+    }
+
+    public static DepthImage createFromXMPMetadata(Context context, Uri image)
+            throws IOException {
+        InputStream input = context.getContentResolver().openInputStream(image);
         XmpDepthDecode decode = new XmpDepthDecode(input);
-        mFormat = decode.getFormat();
-        mFar = decode.getFar();
-        mNear = decode.getNear();
-        mDepthBitmap = decode.getDepthBitmap();
-        mBlurAtInfinity = decode.getBlurAtInfinity();
-        mFocalDistance = decode.getFocalDistance();
-        mDepthOfFiled = decode.getDepthOfField();
-        mFocalPointX = decode.getFocalPointX();
-        mFocalPointY = decode.getFocalPointY();
-        input = context.getContentResolver().openInputStream(data);
-        mDepthTransform = decode.getDepthTransform();
+        return new DepthImage(decode.getFormat(), decode.getFar(),
+                              decode.getNear(), decode.getDepthBitmap(),
+                              decode.getBlurAtInfinity(),
+                              decode.getFocalDistance(),
+                              decode.getDepthOfField(),
+                              decode.getFocalPointX(),
+                              decode.getFocalPointY(),
+                              decode.getDepthTransform());
+    }
+
+    public static DepthImage createFromDepthmap(Context context, Uri uriDepthmap)
+            throws IOException {
+        Bitmap bitmap = BitmapFactory.decodeStream(context.getContentResolver().openInputStream(uriDepthmap));
+        if (bitmap == null) {
+            throw new FileNotFoundException(uriDepthmap.toString());
+        }
+
+        double near = 12.0;
+        double far = 120.0;
+        DepthTransform transform = new RangeInverseDepthTransform((float)near, (float)far);
+        return new DepthImage(RangeInverseDepthTransform.FORMAT,
+                              far,
+                              near,
+                              bitmap, // depthmap
+                              5.0,    // blur at ininity
+                              15.0,   // focal distance
+                              0.1,    // depth of field
+                              0.5,    // x of focal point
+                              0.5,    // y of focla point
+                              transform);
     }
 
     public Bitmap getDepthBitmap() {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthOfFieldOptions.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthOfFieldOptions.java
index 7bc9068..fee51ee 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthOfFieldOptions.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/DepthOfFieldOptions.java
@@ -37,14 +37,12 @@
   public DepthOfFieldOptions(RGBZ rgbz) {
     this.focalDepth = (float)rgbz.getFocusDepth();
     this.depthOfField = (float)rgbz.getDepthOfField();
-    this.blurInfinity = (float)rgbz.getBlurInfinity() * Math.max(rgbz.getHeight(), rgbz.getWidth());
+    this.blurInfinity = (float)rgbz.getBlurInfinity();
     this.rgbz = rgbz;
   }
 
   public void setFocusPoint(float x, float y) {
     this.focalDepth = rgbz.getDepth((int)(x * rgbz.getWidth()), (int)(y * rgbz.getHeight()));
-    //this.blurInfinity = lensController.blurInfinityFromAverageBlur(this.focalDepth, this.depthOfField, averageBlur);
-    //System.out.println("new focal depth: " + this.focalDepth);
   }
 
   public void setBokeh(float bokeh) {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/ImageCompare.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/ImageCompare.java
index 4f15b0a..0ea2af0 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/ImageCompare.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/ImageCompare.java
@@ -17,46 +17,65 @@
 package android.renderscript.cts.refocus;
 
 import android.graphics.Bitmap;
-
-import java.nio.ByteBuffer;
+import android.graphics.Color;
+import java.lang.Math;
 
 public class ImageCompare {
 
-    private static byte[] loadBitmapByteArray(Bitmap bitmap) {
-        int bytes = bitmap.getByteCount();
-        ByteBuffer buffer = ByteBuffer.allocate(bytes);
-        bitmap.copyPixelsToBuffer(buffer);
-        byte[] array = buffer.array();
-        return array;
+    /**
+     * Compute the luma channel of an RGB image, i.e., the Y channel of the
+     * equivalent YCbCr image.
+     * https://en.wikipedia.org/wiki/YCbCr
+     */
+    private static double luma(int pixel) {
+        final int R = Color.red(pixel);
+        final int G = Color.green(pixel);
+        final int B = Color.blue(pixel);
+        return 0.299 * R + 0.587 * G + 0.114 * B;
     }
 
-    public static class CompareValue {
-        float aveDiff = 0f;
-        float diffPercent = 0f;
-    }
-
-    public static void compareBitmap(Bitmap bitmap1, Bitmap bitmap2, CompareValue result) {
-
-        if (bitmap1.getWidth() != bitmap2.getWidth() || bitmap1.getHeight() != bitmap2.getHeight()) {
+    /**
+     * Compute peak signal-to-noise ration (PSNR) between two images
+     * The greater the value of psnr, the closer the two images are to each
+     * other. For identical images, psnr = +infinity.
+     * For 8-bit images, a psnr above 50 is commonly acceptable for a lossy
+     * conversion.
+     *
+     * References:
+     * http://www.mathworks.com/help/vision/ref/psnr.html
+     * https://en.wikipedia.org/wiki/Peak_signal-to-noise_ratio
+     */
+    public static double psnr(Bitmap bitmap1, Bitmap bitmap2) {
+        if (bitmap1.getWidth() != bitmap2.getWidth() ||
+            bitmap1.getHeight() != bitmap2.getHeight()) {
             throw new RuntimeException("images were of diffrent size");
         }
 
-        byte[] first = loadBitmapByteArray(bitmap1);
-        byte[] second = loadBitmapByteArray(bitmap2);
-        int loopCount = first.length;
+        if (bitmap1.sameAs(bitmap2)) {
+            android.util.Log.i("RefocusTest",
+                               "bitmaps verified to be identical in fast path.");
+            return Double.POSITIVE_INFINITY;
+        }
 
-        int diffCount = 0;
-        long diffSum = 0;
-        for (int i = 0; i < loopCount; i++) {
-            int v1 = 0xFF & first[i];
-            int v2 = 0xFF & second[i];
-            int error = Math.abs(v1 - v2);
-            if (error > 0) {
-                diffCount++;
-                diffSum += error;
+        final int width = bitmap1.getWidth();
+        final int height = bitmap1.getHeight();
+        final int numPixels = width * height;
+
+        double noise = 0;
+        for (int y = 0; y < height; y++) {
+            for (int x = 0; x < width; x++) {
+                int pixel1 = bitmap1.getPixel(x, y);
+                int pixel2 = bitmap2.getPixel(x, y);
+                if (pixel1 != pixel2) {
+                    final double Y1 = luma(pixel1);
+                    final double Y2 = luma(pixel2);
+                    noise += (Y1 - Y2) * (Y1 - Y2);
+                }
             }
         }
-        result.diffPercent = ((float) diffCount) / first.length;
-        result.aveDiff = ((float) diffSum) / first.length;
+
+        final double mse = noise / numPixels;
+        final double psnr = 20 * Math.log10(255) - 10 * Math.log10(mse);
+        return psnr;
     }
 }
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/MediaStoreSaver.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/MediaStoreSaver.java
index a97af3a..6a1ed16 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/MediaStoreSaver.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/MediaStoreSaver.java
@@ -36,32 +36,34 @@
                                        String folderName,
                                        String imageName,
                                        Context mContext) {
-        File folder = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
-        String folder_path = folder.getAbsolutePath();
-        String file_path = folder_path + "/" + folderName;
-        File dir = new File(file_path);
+        File picDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
 
-        if (!dir.exists()) {
-            System.out.println("make directory: " + dir.getAbsolutePath());
-            dir.mkdirs();
+        if (!picDir.exists()) {
+            // The Pictures directory does not exist on an x86 emulator
+            picDir = mContext.getFilesDir();
         }
-        File file = null;
+
+        File dir = new File(picDir, folderName);
+        dir.mkdirs();
+
         try {
-            file =  File.createTempFile( imageName, ".png",dir);
+            File file = File.createTempFile(imageName, ".png", dir);
             FileOutputStream fOut = new FileOutputStream(file);
             bitmap.compress(Bitmap.CompressFormat.PNG, 0, fOut);
             System.out.println("saved image: " + file.getAbsolutePath());
             fOut.flush();
             fOut.close();
+            MediaStorageScan(mContext, file);
+            return file.getAbsolutePath();
         } catch (FileNotFoundException e) {
             e.printStackTrace();
         } catch (IOException e) {
             e.printStackTrace();
         }
 
-        MediaStorageScan(mContext, file);
-        return file.getAbsolutePath();
+        return "";
     }
+
     /*
      * Refresh image files to view them on computer
      */
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RGBZ.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RGBZ.java
index f882247..1e42bf1 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RGBZ.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RGBZ.java
@@ -22,6 +22,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.Color;
 import android.net.Uri;
+import android.renderscript.cts.refocus.image.RangeInverseDepthTransform;
 import android.util.Log;
 
 import java.io.FileNotFoundException;
@@ -55,10 +56,29 @@
     if (preview == null) {
       throw new FileNotFoundException(uri.toString());
     }
-    this.depthImage = new DepthImage(context, uri);
-    this.depthBitmap = depthImage.getDepthBitmap();
-    this.bitmap = setAlphaChannel(preview, this.depthBitmap);
-    this.depthTransform = depthImage.getDepthTransform();
+    depthImage = DepthImage.createFromXMPMetadata(context, uri);
+    depthBitmap = depthImage.getDepthBitmap();
+    bitmap = setAlphaChannel(preview, depthBitmap);
+    depthTransform = depthImage.getDepthTransform();
+  }
+
+  /**
+   * Creates an RGBZ from uris to an image and a depthmap.
+   *
+   * @param uriImage The uri name of the image
+   * @param uriDepthmap The uri name of the depthmap
+   * @throws FileNotFoundException if the RGBZ could not be read
+   */
+  public RGBZ(Uri uriImage, Uri uriDepthmap, ContentResolver contentResolver,
+              Context context) throws IOException {
+    preview = BitmapFactory.decodeStream(contentResolver.openInputStream(uriImage));
+    if (preview == null) {
+      throw new FileNotFoundException(uriImage.toString());
+    }
+    depthImage = DepthImage.createFromDepthmap(context, uriDepthmap);
+    depthBitmap = depthImage.getDepthBitmap();
+    bitmap = setAlphaChannel(preview, depthBitmap);
+    depthTransform = depthImage.getDepthTransform();
   }
 
   /**
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
index 666d9c5..706296b 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/refocus/RefocusTest.java
@@ -38,47 +38,50 @@
      * Test the orignal and current refocus code
      */
     public void testOriginalRefocus() {
-        refocus(RenderScriptTask.script.f32);
+        refocus(RenderScriptTask.script.f32, Double.POSITIVE_INFINITY);
     }
 
     /**
      * Test the orignal and current refocus code
      */
     public void testNewRefocus() {
-        refocus(RenderScriptTask.script.d1new);
+        // The new implementation may run on a GPU using relaxed floating point
+        // mathematics. Hence more relaxed precision requirement.
+        refocus(RenderScriptTask.script.d1new, 50);
     }
 
     /**
      * Test a refocus operator against the refocus_reference image
      * @param d1new which version of refocus to run
      */
-    private void refocus(RenderScriptTask.script d1new) {
+    private void refocus(RenderScriptTask.script d1new, double minimumPSNR) {
         Context ctx = getContext();
 
         RenderScript rs = RenderScript.create(ctx);
         RGBZ current_rgbz = null;
         try {
-            current_rgbz = new RGBZ(getResourceRef(R.drawable.refocus_image), ctx.getContentResolver(), ctx);
+            current_rgbz = new RGBZ(getResourceRef(R.drawable.test_image),
+                                    getResourceRef(R.drawable.test_depthmap),
+                                    ctx.getContentResolver(), ctx);
         } catch (IOException e) {
             e.printStackTrace();
             assertNull(e);
         }
         DepthOfFieldOptions current_depth_options = new DepthOfFieldOptions(current_rgbz);
-        current_depth_options.setFocusPoint(0.7f, 0.5f);
-        current_depth_options.setBokeh(2f);
-
         RsTaskParams rsTaskParam = new RsTaskParams(rs, current_depth_options);
 
         RenderScriptTask renderScriptTask = new RenderScriptTask(rs, d1new);
         Bitmap outputImage = renderScriptTask.applyRefocusFilter(rsTaskParam.mOptions);
-        Bitmap refrenceImage = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.refocus_reference);
-        ImageCompare.CompareValue result = new ImageCompare.CompareValue();
 
-        ImageCompare.compareBitmap(outputImage, refrenceImage, result);
-        if (result.diffPercent >= 0.0001 && result.aveDiff > 5) {
-            MediaStoreSaver.savePNG(outputImage, "Errors", "RefocusErr" , ctx);
-            assertTrue("% difference from reference = " + (result.diffPercent * 100) +
-                       " with avg. diff = " + result.aveDiff, false);
+        Bitmap expectedImage = BitmapFactory.decodeResource(ctx.getResources(), R.drawable.expected_output);
+
+        double psnr = ImageCompare.psnr(outputImage, expectedImage);
+        android.util.Log.i("RefocusTest", "psnr = " + String.format("%.02f", psnr));
+        if (psnr < minimumPSNR) {
+            MediaStoreSaver.savePNG(outputImage, "refocus", "refocus_output" , ctx);
+            assertTrue("Required minimum psnr = " + String.format("%.02f; ", minimumPSNR) +
+                       "Actual psnr = " + String.format("%.02f", psnr),
+                       false);
         }
         rs.destroy();
     }