Merge "Shrank refocus test and reference image size"
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();
     }