Trace-based testing app

Modifying angle_trace_tests app to run traces automatically on
invocation and dumps fps information. Also, compares the screenshot with
the golden images if provided and dumps the histogram of image diff.

Golden image naming format: <trace_name>_golden.png

- Run traces
- Capture screenshot
- Record FPS value
- Record image diff histogram

Bug: b/270421213
Doc: doc/StandaloneBenchmark.md
Change-Id: I42b8d229e1e358d48887f29f2424b9e771093ce9
Commit-Queue: Shashank Sharma <[email protected]>
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/6072349
Commit-Queue: Cody Northrop <[email protected]>
Auto-Submit: Shashank Sharma <[email protected]>
Reviewed-by: Roman Lavrov <[email protected]>
Reviewed-by: Cody Northrop <[email protected]>
diff --git a/doc/StandaloneBenchmark.md b/doc/StandaloneBenchmark.md
new file mode 100644
index 0000000..9429a3f
--- /dev/null
+++ b/doc/StandaloneBenchmark.md
@@ -0,0 +1,92 @@
+# ANGLE Standalone Benchmark
+
+This option builds the trace tests apk to run the selected traces from restricted traces in "src/tests/restricted_traces/". Running traces this way won't require additional trace data setup. Simply clicking on the apk or running the app activity will run the traces. Additionally, it will also dump the fps values of the traces it runs and also the histogram of the diff of the screenshot of the KeyFrame of the trace if golden images are provided.
+
+Setup is similar to ANGLE Restricted Traces and requires some additional GN args.
+
+## Accessing the traces
+
+Follow the `Accessing the traces` in [restricted_traces/README.md](../src/tests/restricted_traces/README.md#accessing-the-traces) and then return here.
+
+## Building the Standalone Benchmark apk
+
+This option is available only for Android. Follow the steps in [DevSetupAndroid.md](DevSetupAndroid.md) (Recommend using the [`Performance`](DevSetupAndroid.md#performance-config) arguments for best performance)
+
+When that is working, add the following GN arg to your setup:
+```
+build_angle_trace_perf_tests = true
+```
+### Selecting which traces to build
+
+Since the traces are numerous and trace data is huge in size, you should limit the compilation to a subset to keep the apk file size within limits.
+```
+angle_restricted_traces = ["pokemon_go 5", "car_chase 1"]
+angle_standalone_benchmark_traces = ["pokemon_go", "car_chase"]
+angle_standalone_benchmark_goldens_dir = "<Path to golden images of the traces if present>"
+```
+
+Names of the png files of golden images should match \<trace name\>_golden.png
+
+To build the apk:
+```
+autoninja -C out/<config> angle_trace_tests
+```
+
+## Installing the apk
+```
+adb install out/<config>/angle_trace_tests_apk/angle_trace_tests-debug.apk
+```
+
+### Set permissions for the app
+To allow the results to be available in the external storage, we need to provide appropriate permission.
+```
+adb shell "appops set com.android.angle.test MANAGE_EXTERNAL_STORAGE allow || true"
+```
+
+## Running the apk
+You can run the app either by clicking on the "ANGLEBench" app icon or by running the following command (after unlocking the phone).
+
+Note: It is important to unlock the device before running the adb command.
+```
+adb shell am start -n com.android.angle.test/com.android.angle.test.StandaloneBenchmarkActivity
+```
+
+## Run configs
+Below configs for the trace runs are set to hardcoded values
+```
+        gVerboseLogging  = true;
+        gScreenshotDir   = "<Application_Dir>/files";
+        gSaveScreenshots = true;
+        gUseANGLE        = "vulkan";
+```
+
+However, other configs can be set using `org.chromium.native_test.NativeTest.CommandLineFlags`. Example:
+```
+adb shell am start -n com.android.angle.test/com.android.angle.test.StandaloneBenchmarkActivity -e org.chromium.native_test.NativeTest.CommandLineFlags '--trials=1\ --trial-time=60'
+```
+
+## Results
+Once all the traces listed in `angle_standalone_benchmark_traces` are finished, the app will generate `traces_fps.txt`.
+
+If `angle_standalone_benchmark_goldens_dir` is specified in the GN args, it will also generate `traces_img_comp.txt`.
+
+Output files will be placed on the device in `chromium_tests_root` dir in external storage. For example: `/sdcard/chromium_tests_root/`
+
+### traces_fps.txt
+```
+<trace 1 name> <FPS value>
+<trace 2 name> <FPS value>
+...
+```
+
+### traces_img_comp.txt
+```
+<trace 1 name>:
+Diff in pixel value:    0-20, 20-40, 40-70, 70-100, 100-150, 150-255
+Number of pixels:       <#>, <#>, <#>, <#>, <#>, <#>,
+
+<trace 2 name>:
+Diff in pixel value:    0-20, 20-40, 40-70, 70-100, 100-150, 150-255
+Number of pixels:       <#>, <#>, <#>, <#>, <#>, <#>,
+...
+```
diff --git a/gni/angle.gni b/gni/angle.gni
index a51add5..1452076 100644
--- a/gni/angle.gni
+++ b/gni/angle.gni
@@ -26,11 +26,23 @@
   build_angle_deqp_tests = false
 
   build_angle_end2end_tests_aosp = false
+
+  # List of traces for benchmark mode
+  angle_standalone_benchmark_traces = []
+
+  # Benchmark mode golden default directory
+  angle_standalone_benchmark_goldens_dir = ""
 }
 
 declare_args() {
-  # Only bundle traces in the APK if we're building a subset
-  restricted_traces_outside_of_apk = is_android && angle_restricted_traces == []
+  # Enable angle standalone benchmark mode if list of traces is provided
+  angle_standalone_benchmark =
+      is_android && angle_standalone_benchmark_traces != []
+
+  # Only bundle traces in the APK if we're building a subset or if standalone benchmark is selected
+  restricted_traces_outside_of_apk =
+      is_android && angle_restricted_traces == [] &&
+      angle_standalone_benchmark_traces == []
 
   # Enables OpenCL testing support
   angle_enable_cl_testing = angle_enable_cl && (is_linux || is_android)
@@ -618,7 +630,11 @@
         configs -= [ "//build/config/android:hide_all_but_jni" ]
         use_default_launcher = false
         generate_final_jni = false
-        android_manifest_template = "$angle_root/src/tests/test_utils/runner/android/java/AndroidManifest.xml.jinja2"
+        if (angle_standalone_benchmark) {
+          android_manifest_template = "$angle_root/src/tests/test_utils/runner/android/java/StandaloneBenchmarkAndroidManifest.xml.jinja2"
+        } else {
+          android_manifest_template = "$angle_root/src/tests/test_utils/runner/android/java/AndroidManifest.xml.jinja2"
+        }
 
         deps += [
           "$angle_root/src/tests:native_test_java",
diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn
index 3f66fe5..5410ac1 100644
--- a/src/tests/BUILD.gn
+++ b/src/tests/BUILD.gn
@@ -24,11 +24,27 @@
 }
 
 if (is_android) {
+  if (angle_standalone_benchmark) {
+    android_assets("angle_standalone_benchmark_assets") {
+      disable_compression = true
+      sources = [ "$root_gen_dir/trace_list.json" ]
+      foreach(trace, angle_standalone_benchmark_traces) {
+        sources += [ "//src/tests/restricted_traces/" + trace + "/" + trace +
+                     ".angledata.gz" ]
+        sources +=
+            [ "//src/tests/restricted_traces/" + trace + "/" + trace + ".json" ]
+
+        if (angle_standalone_benchmark_goldens_dir != "") {
+          sources += [ angle_standalone_benchmark_goldens_dir + "/" + trace +
+                       "_golden.png" ]
+        }
+      }
+    }
+  }
   android_library("native_test_java") {
     testonly = true
     sources = [
       "test_utils/runner/android/java/src/com/android/angle/test/AngleNativeTest.java",
-      "test_utils/runner/android/java/src/com/android/angle/test/AngleUnitTestActivity.java",
       "test_utils/runner/android/java/src/com/android/angle/test/TestStatusReporter.java",
     ]
 
@@ -36,6 +52,13 @@
       "//build/android:build_java",
       "//build/android/gtest_apk:native_test_instrumentation_test_runner_java",
     ]
+
+    if (angle_standalone_benchmark) {
+      sources += [ "test_utils/runner/android/java/src/com/android/angle/test/StandaloneBenchmarkActivity.java" ]
+      deps += [ ":angle_standalone_benchmark_assets" ]
+    } else {
+      sources += [ "test_utils/runner/android/java/src/com/android/angle/test/AngleUnitTestActivity.java" ]
+    }
   }
 
   angle_source_set("native_test_support_android") {
@@ -524,6 +547,12 @@
   }
 }
 
+config("angle_standalone_benchmark_config") {
+  if (angle_standalone_benchmark) {
+    defines = [ "ANGLE_STANDALONE_BENCHMARK" ]
+  }
+}
+
 template("angle_perftests_common") {
   assert(defined(invoker.test_utils))
 
@@ -557,6 +586,7 @@
     public_configs += [
       ":angle_maybe_has_histograms",
       "$angle_root:libANGLE_config",
+      ":angle_standalone_benchmark_config",
     ]
   }
 }
diff --git a/src/tests/perf_tests/ANGLEPerfTest.cpp b/src/tests/perf_tests/ANGLEPerfTest.cpp
index d84053e..6cacf4e 100644
--- a/src/tests/perf_tests/ANGLEPerfTest.cpp
+++ b/src/tests/perf_tests/ANGLEPerfTest.cpp
@@ -27,6 +27,10 @@
 #include "util/shader_utils.h"
 #include "util/test_utils.h"
 
+#if defined(ANGLE_PLATFORM_ANDROID)
+#    include "util/android/AndroidWindow.h"
+#endif
+
 #include <cassert>
 #include <cmath>
 #include <fstream>
@@ -237,6 +241,19 @@
     }
 }
 
+void DumpFpsValues(const char *test, double mean_time)
+{
+#if defined(ANGLE_PLATFORM_ANDROID)
+    std::ofstream fp(AndroidWindow::GetExternalStorageDirectory() + "/traces_fps.txt",
+                     std::ios::app);
+#else
+    std::ofstream fp("traces_fps.txt", std::ios::app);
+#endif
+    double fps_value = 1000 / mean_time;
+    fp << test << " " << fps_value << std::endl;
+    fp.close();
+}
+
 #if defined(ANGLE_PLATFORM_ANDROID)
 constexpr bool kHasATrace = true;
 
@@ -383,6 +400,12 @@
         {
             printf("Mean result time: %.4lf ms.\n", mean);
         }
+
+        if (kStandaloneBenchmark)
+        {
+            DumpFpsValues(mStory.c_str(), mean);
+        }
+
         printf("Coefficient of variation: %.2lf%%\n", coefficientOfVariation * 100.0);
     }
 }
diff --git a/src/tests/perf_tests/ANGLEPerfTestArgs.cpp b/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
index 855505c..a67a93a 100644
--- a/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
+++ b/src/tests/perf_tests/ANGLEPerfTestArgs.cpp
@@ -14,6 +14,10 @@
 #include "common/debug.h"
 #include "util/test_utils.h"
 
+#if defined(ANGLE_PLATFORM_ANDROID)
+#    include "util/android/AndroidWindow.h"
+#endif
+
 namespace angle
 {
 
@@ -180,4 +184,16 @@
         gTestTrials       = 1;
         gTrialTimeSeconds = 600;
     }
+
+    if (kStandaloneBenchmark)
+    {
+        gVerboseLogging = true;
+#if defined(ANGLE_PLATFORM_ANDROID)
+        gScreenshotDir = strdup((AndroidWindow::GetApplicationDirectory() + "/files").c_str());
+#else
+        gScreenshotDir = ".";
+#endif
+        gSaveScreenshots = true;
+        gUseANGLE        = "vulkan";
+    }
 }
diff --git a/src/tests/perf_tests/ANGLEPerfTestArgs.h b/src/tests/perf_tests/ANGLEPerfTestArgs.h
index 780a7f4..42672dc 100644
--- a/src/tests/perf_tests/ANGLEPerfTestArgs.h
+++ b/src/tests/perf_tests/ANGLEPerfTestArgs.h
@@ -48,7 +48,11 @@
 
 constexpr int kDefaultScreenshotFrame   = 1;
 constexpr int kDefaultMaxStepsPerformed = 0;
-
+#ifdef ANGLE_STANDALONE_BENCHMARK
+constexpr bool kStandaloneBenchmark = true;
+#else
+constexpr bool kStandaloneBenchmark = false;
+#endif
 inline bool OneFrame()
 {
     return gStepsPerTrial == 1 || gMaxStepsPerformed == 1;
diff --git a/src/tests/test_utils/runner/android/java/StandaloneBenchmarkAndroidManifest.xml.jinja2 b/src/tests/test_utils/runner/android/java/StandaloneBenchmarkAndroidManifest.xml.jinja2
new file mode 100644
index 0000000..640808a
--- /dev/null
+++ b/src/tests/test_utils/runner/android/java/StandaloneBenchmarkAndroidManifest.xml.jinja2
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+Copyright 2024 The ANGLE Project Authors. All rights reserved.
+Use of this source code is governed by a BSD-style license that can be
+found in the LICENSE file.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.angle.test"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="android.permission.BLUETOOTH"/>
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
+    <uses-permission android:name="android.permission.CHANGE_CONFIGURATION"/>
+    <uses-permission android:name="android.permission.CAMERA" />
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
+    <uses-permission android:name="android.permission.RECORD_AUDIO"/>
+    <uses-permission android:name="android.permission.SET_TIME_ZONE"/>
+    <uses-permission android:name="android.permission.WAKE_LOCK"/>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+
+    <!-- Explicitly set the attribute requestLegacyExternalStorage to "true"
+         since it is "false" by default on apps targeting Android 10, and that
+         breaks test listing. See
+         https://developer.android.com/training/data-storage#scoped-storage and
+         https://developer.android.com/training/data-storage/compatibility. -->
+    <!-- Disable allowNativeHeapPointerTagging for siginfo_t POSIX struct
+         pointers to match heap allocations. This is required for the
+         SystemUtils.PageFaultHandler* tests.
+         https://source.android.com/devices/tech/debug/tagged-pointers -->
+    <application android:label="ANGLEBenchmarkTests"
+      android:requestLegacyExternalStorage="true"
+      android:allowNativeHeapPointerTagging="false">
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name=".StandaloneBenchmarkActivity"
+                android:label="ANGLEBench"
+                android:configChanges="orientation|screenSize|screenLayout|keyboardHidden"
+                android:exported="true"
+                android:process=":test_process">
+            <meta-data android:name="android.app.lib_name"
+                       android:value="{{ native_library_name }}" />
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation android:name="org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner"
+            android:targetPackage="com.android.angle.test"
+            android:label="Instrumentation entry point for com.android.angle.test"
+            chromium-junit3="true"/>
+
+</manifest>
diff --git a/src/tests/test_utils/runner/android/java/src/com/android/angle/test/StandaloneBenchmarkActivity.java b/src/tests/test_utils/runner/android/java/src/com/android/angle/test/StandaloneBenchmarkActivity.java
new file mode 100644
index 0000000..031d186
--- /dev/null
+++ b/src/tests/test_utils/runner/android/java/src/com/android/angle/test/StandaloneBenchmarkActivity.java
@@ -0,0 +1,364 @@
+// Copyright 2024 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// StandaloneBenchmarkActivity:
+//   A {@link android.app.NativeActivity} for running angle gtests.
+
+package com.android.angle.test;
+
+import android.app.NativeActivity;
+import android.content.Intent;
+import android.content.res.AssetManager;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.os.Environment;
+import android.util.Log;
+import android.view.WindowManager;
+
+import org.chromium.build.NativeLibraries;
+import org.chromium.build.gtest_apk.NativeTestIntent;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+public class StandaloneBenchmarkActivity extends NativeActivity
+{
+
+    private static final String TAG = "BenchmarkNativeTest";
+
+    private static final String STDOUT_FILE           = "stdout.txt";
+    private static final String FRAME_TIME_FILE       = "traces_fps.txt";
+    private static final String TRACE_LIST_FILE       = "trace_list.json";
+    private static final String IMAGE_COMPARISON_FILE = "traces_img_comp.txt";
+
+    private String[] traceNames       = {};
+    private String outDataDir         = "";
+    private String externalStorageDir = "";
+    private String traceListDir       = "";
+    private String commonTraceDir     = "";
+    private AngleNativeTest mTest     = new AngleNativeTest();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState)
+    {
+        // For NativeActivity based tests,
+        // dependency libraries must be loaded before NativeActivity::OnCreate,
+        // otherwise loading android.app.lib_name will fail
+        for (String library : NativeLibraries.LIBRARIES)
+        {
+            Log.i(TAG, "loading: " + library);
+            System.loadLibrary(library);
+            Log.i(TAG, "loaded: " + library);
+        }
+        super.onCreate(savedInstanceState);
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+
+        setupDirectories();
+        clearPrevResults();
+        setupTraceInfo();
+
+        Intent myIntent    = this.getIntent();
+        String gtestFilter = "";
+
+        for (String trace : this.traceNames)
+        {
+            gtestFilter += "TraceTest." + trace + ":";
+        }
+        myIntent.putExtra(NativeTestIntent.EXTRA_GTEST_FILTER, gtestFilter);
+        myIntent.putExtra(NativeTestIntent.EXTRA_STDOUT_FILE, this.outDataDir + STDOUT_FILE);
+
+        mTest.postCreate(this);
+    }
+
+    @Override
+    public void onStart()
+    {
+        super.onStart();
+        mTest.postStart(this);
+    }
+
+    @Override
+    public void onStop()
+    {
+        super.onStop();
+
+        for (String trace : this.traceNames)
+        {
+            String goldenImagePath  = this.commonTraceDir + trace + "/" + trace + "_golden.png";
+            String currentImagePath = this.outDataDir + "angle_vulkan_" + trace + ".png";
+
+            File goldenImageFile  = new File(goldenImagePath);
+            File currentImageFile = new File(currentImagePath);
+
+            if (goldenImageFile.exists() && currentImageFile.exists())
+            {
+                Bitmap golden  = BitmapFactory.decodeFile(goldenImagePath);
+                Bitmap current = BitmapFactory.decodeFile(currentImagePath);
+                compareImages(golden, current, trace);
+            }
+        }
+    }
+
+    // Method to set up trace information from assets folder to application data folder
+    // Moves only .json, .angledata.gz, _golden.png in the respective trace folder
+    private void setupTraceInfo()
+    {
+        // Get the AssetManager to access files in the assets folder
+        AssetManager assetManager = getAssets();
+        String[] files            = null;
+
+        try
+        {
+            // Get a list of all files in the assets folder
+            files = assetManager.list("");
+        }
+        catch (IOException e)
+        {
+            Log.e(TAG, "Failed to get asset file list.", e);
+        }
+
+        // Define the file suffixes to look for
+        List<String> suffixes = List.of(".json", ".angledata.gz", "_golden.png");
+        // Create a list to store the names of the trace names
+        List<String> traceList = new ArrayList<>();
+
+        for (String fileName : files)
+        {
+            InputStream in   = null;
+            OutputStream out = null;
+            String traceName;
+            try
+            {
+                for (String suffix : suffixes)
+                {
+                    // Open the file from the assets folder
+                    in = assetManager.open(fileName);
+                    // If the file is the trace list file
+                    // then we need to copy it in a different dir
+                    if (fileName.equals(TRACE_LIST_FILE))
+                    {
+                        // Create a new file to store the trace list file
+                        File outFile = new File(this.traceListDir, fileName);
+                        if (!outFile.exists())
+                        {
+                            // Copy the trace list file to the new file
+                            out = new FileOutputStream(outFile);
+                            copyFile(in, out);
+                            out.flush();
+                            out.close();
+                            out = null;
+                        }
+                        in.close();
+                        in = null;
+                        break;
+                    }
+                    // If the file is a trace data file
+                    else if (fileName.endsWith(suffix))
+                    {
+                        // Extract the trace name from the file name
+                        traceName = fileName.substring(0, fileName.length() - suffix.length());
+                        // Add the trace name if not already in the list
+                        if (!traceList.contains(traceName))
+                        {
+                            traceList.add(traceName);
+                            this.traceNames = traceList.toArray(new String[0]);
+                        }
+                        // Create a directory to store the trace file
+                        File dir = new File(this.commonTraceDir, traceName);
+                        if (!dir.exists())
+                        {
+                            dir.mkdirs();
+                        }
+                        // Create a new file to store the trace file
+                        File outFile = new File(dir, fileName);
+                        if (!outFile.exists())
+                        {
+                            // Copy the trace file to the new file
+                            out = new FileOutputStream(outFile);
+                            copyFile(in, out);
+                            out.flush();
+                            out.close();
+                            out = null;
+                        }
+                        in.close();
+                        in = null;
+                        break;
+                    }
+                }
+            }
+            catch (IOException e)
+            {
+                Log.i(TAG, "Failed to copy a file. Might not be fatal.");
+            }
+        }
+    }
+
+    // Method to copy the data from one file to another
+    private void copyFile(InputStream in, OutputStream out) throws IOException
+    {
+        byte[] buffer = new byte[1024];
+        int read;
+        while ((read = in.read(buffer)) != -1)
+        {
+            out.write(buffer, 0, read);
+        }
+    }
+
+    // Method to clear any previous data to avoid the data corruption
+    private void clearPrevResults()
+    {
+        String[] filesPath = {this.outDataDir + STDOUT_FILE,
+                this.externalStorageDir + FRAME_TIME_FILE,
+                this.externalStorageDir + IMAGE_COMPARISON_FILE};
+
+        for (String filePath : filesPath)
+        {
+            File file = new File(filePath);
+            if (file.exists())
+            {
+                file.delete();
+            }
+        }
+    }
+
+    // Method to setup all the required directories for the app run.
+    private void setupDirectories()
+    {
+        this.outDataDir   = getApplicationInfo().dataDir + "/files/";
+        this.traceListDir = getApplicationInfo().dataDir + "/chromium_tests_root/gen/";
+        this.commonTraceDir =
+                getApplicationInfo().dataDir + "/chromium_tests_root/src/tests/restricted_traces/";
+        this.externalStorageDir =
+                Environment.getExternalStorageDirectory() + "/chromium_tests_root/";
+
+        String[] dirsPath = {
+                this.outDataDir, this.traceListDir, this.commonTraceDir, this.externalStorageDir};
+
+        for (String dirPath : dirsPath)
+        {
+            File dir = new File(dirPath);
+            if (!dir.exists())
+            {
+                dir.mkdirs();
+            }
+        }
+    }
+
+    // Method to compare current screenshot to provided golden images
+    // It compares each component of the images pixel by pixel and stores the difference
+    // It also calculates the histogram and dumps it into a file
+    private void compareImages(Bitmap G, Bitmap C, String traceName)
+    {
+        int width  = G.getWidth();
+        int height = G.getHeight();
+
+        int[] pixelsGolden  = new int[width * height];
+        int[] pixelsCurrent = new int[width * height];
+
+        G.getPixels(pixelsGolden, 0, width, 0, 0, width, height);
+        C.getPixels(pixelsCurrent, 0, width, 0, 0, width, height);
+
+        int[] differences = new int[pixelsGolden.length];
+
+        for (int i = 0; i < pixelsGolden.length; i++)
+        {
+            int p1 = pixelsGolden[i];
+            int p2 = pixelsCurrent[i];
+
+            int r1 = Color.red(p1);
+            int g1 = Color.green(p1);
+            int b1 = Color.blue(p1);
+
+            int r2 = Color.red(p2);
+            int g2 = Color.green(p2);
+            int b2 = Color.blue(p2);
+
+            differences[i] = Math.abs(r1 - r2) + Math.abs(g1 - g2) + Math.abs(b1 - b2);
+        }
+
+        // Calculate the histogram
+        int[] histogram = calculateHistogram(differences);
+
+        // Write the histogram to a file
+        writeHistogramToFile(histogram, traceName);
+    }
+
+    // Method to calculate histogram
+    private static int[] calculateHistogram(int[] differences)
+    {
+        int[] histogram = new int[6]; // 6 categories
+        for (int diff : differences)
+        {
+            if (diff >= 0 && diff <= 20)
+            {
+                histogram[0]++;
+            }
+            else if (diff > 20 && diff <= 40)
+            {
+                histogram[1]++;
+            }
+            else if (diff > 40 && diff <= 70)
+            {
+                histogram[2]++;
+            }
+            else if (diff > 70 && diff <= 100)
+            {
+                histogram[3]++;
+            }
+            else if (diff > 100 && diff <= 150)
+            {
+                histogram[4]++;
+            }
+            else if (diff > 150 && diff <= 255)
+            {
+                histogram[5]++;
+            }
+        }
+        return histogram;
+    }
+
+    // Method to write the histogram data to a file
+    // Dumps the number of pixels having diff values in following 6 categories
+    // 0-20, 20-40, 40-70, 70-100, 100-150, 150-255
+    private void writeHistogramToFile(int[] histogram, String trace)
+    {
+        try
+        {
+            OutputStream out =
+                    new FileOutputStream(this.externalStorageDir + IMAGE_COMPARISON_FILE, true);
+
+            String categories =
+                    "Diff in pixel value:\t0-20, 20-40, 40-70, 70-100, 100-150, 150-255\n";
+            String traceName   = trace + ":\n";
+            String newLine     = "\n\n";
+            String numOfPixels = "Number of pixels:\t";
+
+            out.write(traceName.getBytes(StandardCharsets.UTF_8));
+            out.write(categories.getBytes(StandardCharsets.UTF_8));
+            out.write(numOfPixels.getBytes(StandardCharsets.UTF_8));
+
+            for (int i = 0; i < histogram.length; i++)
+            {
+                String line = histogram[i] + ", ";
+                out.write(line.getBytes(StandardCharsets.UTF_8));
+            }
+
+            out.write(newLine.getBytes(StandardCharsets.UTF_8));
+            out.close();
+        }
+        catch (IOException e)
+        {
+            Log.e(TAG, "Failed to write image comparison to the file", e);
+        }
+    }
+}