Added tests to look for a MacBeth color checker.

- Java class ColorCheckerTest dispatches the tests and controls the camera and
the UI thread.
- C++ class ColorCheckerTest handles the data from the camera and looks
for the color checker in the image.

Change-Id: I49626f0d2245d19664715565472da131a37d9dfb
diff --git a/apps/CtsVerifier/include/colorchecker/colorcheckertest.h b/apps/CtsVerifier/include/colorchecker/colorcheckertest.h
new file mode 100644
index 0000000..f733b32
--- /dev/null
+++ b/apps/CtsVerifier/include/colorchecker/colorcheckertest.h
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef COLORCHECKERTEST_H
+#define COLORCHECKERTEST_H
+
+#include <vector>
+#include <string>
+
+#include "testingimage.h"
+#include "imagetesthandler.h"
+
+class ColorCheckerTest : public ImageTestHandler {
+public:
+    ColorCheckerTest() : ImageTestHandler() {
+        mImage = NULL;
+        mSuccess = false;
+    }
+    ColorCheckerTest(int debugHeight, int inputWidth) :
+            ImageTestHandler(debugHeight, inputWidth) {
+        mImage = NULL;
+        mSuccess = false;
+    }
+    ~ColorCheckerTest();
+
+    void addTestingImage(TestingImage* inputImage);
+    void processData();
+
+    const std::vector<std::vector<Vec2f> >* getCheckerCenterAdd() const {
+        std::vector<std::vector<Vec2f> >* returnPositions =
+                new std::vector<std::vector<Vec2f> >(
+                        4, std::vector<Vec2f>(6, Vec2f(0.f, 0.f)));
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 6; ++j) {
+                (*returnPositions)[i][j] = (*mMatchPositions[i][j]);
+            }
+        }
+        return (returnPositions);
+    }
+
+    const std::vector<std::vector<float> >* getCheckerRadiusAdd() const {
+        std::vector<std::vector<float> >* returnRadius=
+              new std::vector<std::vector<float> >(
+                      4, std::vector<float>(6, 0.f));
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 6; ++j) {
+                (*returnRadius)[i][j] = mMatchRadius[i][j];
+            }
+        }
+        return (returnRadius);
+    }
+
+    bool getSuccess() const {
+        return mSuccess;
+    }
+
+private:
+    void initializeRefColor();
+
+    void edgeDetection();
+    void computeGradient(unsigned char* layer, float* gradientMap);
+    void houghLineDetection(bool* edgeMap, float* gradientAbsolute,
+                            float* gradientDirection);
+
+    void findCheckerBoards(std::vector<std::vector<int> > linesDir1,
+                           std::vector<std::vector<int> > linesDir2);
+    Vec2f findCrossing(std::vector<int> line1, std::vector<int> line2);
+    void findBestMatch(int i1, int i2, int j1, int j2);
+
+    bool verifyPointPair(Vec2f pointUpperLeft, Vec2f pointBottomRight,
+                         Vec2f* pointCenter, Vec3i* color);
+    void verifyColorGrid();
+
+    void fillRefColorGrid();
+
+    TestingImage* mImage;
+
+    std::vector<std::vector< Vec3i* > > mCandidateColors;
+    std::vector<std::vector< Vec2f* > > mCandidatePositions;
+
+    std::vector<std::vector< Vec3i* > > mReferenceColors;
+    std::vector<std::vector< Vec2f* > > mMatchPositions;
+    std::vector<std::vector< Vec3f* > > mMatchColors;
+    std::vector<std::vector< float > > mMatchRadius;
+
+    bool mSuccess;
+};
+
+#endif
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/Android.mk b/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
index 59ca11a..e4eecef 100644
--- a/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
+++ b/apps/CtsVerifier/jni/cameraanalyzer/Android.mk
@@ -22,9 +22,9 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := #com_android_cts_verifier_camera_analyzer_CameraTests.cpp \
+LOCAL_SRC_FILES := com_android_cts_verifier_camera_analyzer_CameraTests.cpp \
                 com_android_cts_verifier_camera_analyzer_ColorCheckerTest.cpp \
-                com_android_cts_verifier_camera_analyzer_ExposureCompensationTest.cpp \
+                #com_android_cts_verifier_camera_analyzer_ExposureCompensationTest.cpp \
                 com_android_cts_verifier_camera_analyzer_WhiteBalanceTest.cpp \
                 com_android_cts_verifier_camera_analyzer_AutoLockTest.cpp \
                 com_android_cts_verifier_camera_analyzer_MeteringTest.cpp
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.cpp b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.cpp
new file mode 100644
index 0000000..94e3ac2
--- /dev/null
+++ b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "FindColorCheckerJNI"
+#include <utils/Log.h>
+#include "com_android_cts_verifier_camera_analyzer_ColorCheckerTest.h"
+
+#include <string.h>
+#include "android/bitmap.h"
+#include "colorcheckertest.h"
+#include "testingimage.h"
+
+jlong Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_createColorCheckerTest(
+        JNIEnv*      env,
+        jobject      thiz,
+        jint         debugHeight,
+        jint         debugWidth) {
+    ColorCheckerTest* testHandler = new ColorCheckerTest(debugHeight,
+                                                         debugWidth);
+    long testHandlerAddress = (long)testHandler;
+    return testHandlerAddress;
+}
+
+void Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_createColorCheckerClass(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        inputImageAddress,
+        jlong        inputHandlerAddress) {
+    ALOGV("JNI createColorCheckerClass starts!");
+
+    TestingImage *testImage = (TestingImage*) (long) inputImageAddress;
+    ColorCheckerTest *testHandler = (ColorCheckerTest*)
+            (long) inputHandlerAddress;
+
+    testHandler->addTestingImage(testImage);
+}
+
+jboolean Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_processColorCheckerTest(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        inputHandlerAddress) {
+
+    ColorCheckerTest *testHandler = (ColorCheckerTest*)
+            (long) inputHandlerAddress;
+    testHandler->processData();
+    return testHandler->getSuccess();
+}
+
+jlong Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_getColorCheckerRadiusAdd(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        inputHandlerAddress) {
+
+    ColorCheckerTest *testHandler = (ColorCheckerTest*)
+            (long) inputHandlerAddress;
+    long rtn = (long) testHandler->getCheckerRadiusAdd();
+    return rtn;
+}
+
+jlong Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_getColorCheckerCenterAdd(
+        JNIEnv*      env,
+        jobject      thiz,
+        jlong        inputHandlerAddress) {
+
+    ColorCheckerTest *testHandler = (ColorCheckerTest*)
+            (long) inputHandlerAddress;
+
+    long rtn = (long) testHandler->getCheckerCenterAdd();
+    return rtn;
+}
diff --git a/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.h b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.h
new file mode 100644
index 0000000..fb87735
--- /dev/null
+++ b/apps/CtsVerifier/jni/cameraanalyzer/com_android_cts_verifier_camera_analyzer_ColorCheckerTest.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef JNI_CAMERAANALYZER_COLORCHECKERTEST_H
+#define JNI_CAMERAANALYZER_COLORCHECKERTEST_H
+
+#include <jni.h>
+#include <stdio.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+JNIEXPORT jlong JNICALL
+Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_createColorCheckerTest(
+    JNIEnv*      env,
+    jobject      thiz,
+    jint         output_height,
+    jint         output_width);
+
+JNIEXPORT void JNICALL
+Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_createColorCheckerClass(
+    JNIEnv *env,
+    jobject thiz,
+    jlong buffer_address,
+    jlong handler_address);
+
+JNIEXPORT jlong JNICALL
+Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_getColorCheckerRadiusAdd(
+    JNIEnv *env,
+    jobject thiz,
+    jlong handler_address);
+
+JNIEXPORT jlong JNICALL
+Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_getColorCheckerCenterAdd(
+    JNIEnv *env,
+    jobject thiz,
+    jlong handler_address);
+
+JNIEXPORT jboolean JNICALL
+Java_com_android_cts_verifier_camera_analyzer_ColorCheckerTest_processColorCheckerTest(
+    JNIEnv*      env,
+    jobject      thiz,
+    jlong        handler_address);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/apps/CtsVerifier/lib/colorchecker/Android.mk b/apps/CtsVerifier/lib/colorchecker/Android.mk
index fcd51c4..076a89a 100644
--- a/apps/CtsVerifier/lib/colorchecker/Android.mk
+++ b/apps/CtsVerifier/lib/colorchecker/Android.mk
@@ -27,9 +27,9 @@
 LOCAL_SRC_FILES += testingimage.cpp \
                    vec3.cpp \
                    vec2.cpp \
-                   #imagetesthandler.cpp \
-                   whitebalancetest.cpp \
+                   imagetesthandler.cpp \
                    colorcheckertest.cpp \
+                   #whitebalancetest.cpp \
                    exposurecompensationtest.cpp \
                    autolocktest.cpp \
                    meteringtest.cpp
diff --git a/apps/CtsVerifier/lib/colorchecker/colorcheckertest.cpp b/apps/CtsVerifier/lib/colorchecker/colorcheckertest.cpp
new file mode 100644
index 0000000..bc421eb
--- /dev/null
+++ b/apps/CtsVerifier/lib/colorchecker/colorcheckertest.cpp
@@ -0,0 +1,943 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_NDEBUG 0
+
+#define LOG_TAG "ColorCheckerTest"
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <cmath>
+#include <string>
+
+#include "vec2.h"
+#include "vec3.h"
+#include "colorcheckertest.h"
+
+const float GAMMA_CORRECTION = 2.2f;
+ColorCheckerTest::~ColorCheckerTest() {
+    ALOGV("Deleting color checker test handler");
+
+    if (mImage != NULL) {
+        delete mImage;
+    }
+    ALOGV("Image deleted");
+
+    int numHorizontalLines = mCandidateColors.size();
+    int numVerticalLines = mCandidateColors[0].size();
+
+    for (int i = 0; i < numHorizontalLines; ++i) {
+        for (int j = 0; j < numVerticalLines; ++j) {
+            if (mCandidateColors[i][j] != NULL) {
+                delete mCandidateColors[i][j];
+            }
+            if (mCandidatePositions[i][j] != NULL) {
+                delete mCandidatePositions[i][j];
+            }
+        }
+    }
+    ALOGV("Candidates deleted!");
+
+    for (int i = 0; i < 4; ++i) {
+        for (int j = 0; j < 6; ++j) {
+            if (mMatchPositions[i][j] != NULL) {
+                delete mMatchPositions[i][j];
+            }
+            if (mReferenceColors[i][j] != NULL) {
+                delete mReferenceColors[i][j];
+            }
+            if (mMatchColors[i][j] != NULL) {
+                delete mMatchColors[i][j];
+            }
+        }
+    }
+}
+
+// Adds a new image to the test handler.
+void ColorCheckerTest::addTestingImage(TestingImage* inputImage) {
+    if (mImage != NULL) {
+        delete mImage;
+    }
+    mImage = NULL;
+    ALOGV("Original image deleted");
+    mImage = inputImage;
+
+    if ((mImage->getHeight() == getDebugHeight()) &&
+        (mImage->getWidth() == getDebugWidth())) {
+        copyDebugImage(getDebugHeight(), getDebugWidth(), mImage->getImage());
+    }
+}
+
+void ColorCheckerTest::processData() {
+    mSuccess = false;
+    initializeRefColor();
+    edgeDetection();
+}
+
+void ColorCheckerTest::initializeRefColor() {
+    mReferenceColors.resize(4, std::vector<Vec3i*>(6, NULL));
+    mMatchPositions.resize(4, std::vector<Vec2f*>(6, NULL));
+    mMatchColors.resize(4, std::vector<Vec3f*>(6, NULL));
+    mMatchRadius.resize(4, std::vector<float>(6, 0.f));
+
+    mReferenceColors[0][0]= new Vec3i(115, 82, 68);
+    mReferenceColors[0][1]= new Vec3i(194, 150, 130);
+    mReferenceColors[0][2]= new Vec3i(98, 122, 157);
+    mReferenceColors[0][3]= new Vec3i(87, 108, 67);
+    mReferenceColors[0][4]= new Vec3i(133, 128, 177);
+    mReferenceColors[0][5]= new Vec3i(103, 189, 170);
+    mReferenceColors[1][0]= new Vec3i(214, 126, 44);
+    mReferenceColors[1][1]= new Vec3i(80, 91, 166);
+    mReferenceColors[1][2]= new Vec3i(193, 90, 99);
+    mReferenceColors[1][3]= new Vec3i(94,  60, 108);
+    mReferenceColors[1][4]= new Vec3i(157, 188, 64);
+    mReferenceColors[1][5]= new Vec3i(224, 163, 46);
+    mReferenceColors[2][0]= new Vec3i(56, 61, 150);
+    mReferenceColors[2][1]= new Vec3i(70, 148, 73);
+    mReferenceColors[2][2]= new Vec3i(175, 54, 60);
+    mReferenceColors[2][3]= new Vec3i(231, 199, 31);
+    mReferenceColors[2][4]= new Vec3i(187, 86, 149);
+    mReferenceColors[2][5]= new Vec3i(8, 133, 161);
+    mReferenceColors[3][0]= new Vec3i(243, 243, 242);
+    mReferenceColors[3][1]= new Vec3i(200, 200, 200);
+    mReferenceColors[3][2]= new Vec3i(160, 160, 160);
+    mReferenceColors[3][3]= new Vec3i(122, 122, 121);
+    mReferenceColors[3][4]= new Vec3i(85, 85, 85);
+    mReferenceColors[3][5]= new Vec3i(52, 52, 52);
+}
+
+void ColorCheckerTest::edgeDetection() {
+    int width = mImage->getWidth();
+    int height = mImage->getHeight();
+
+    bool* edgeMap = new bool[height * width];
+    unsigned char* grayImage = new unsigned char[height * width];
+
+    // If the image is a color image and can be converted to a luminance layer
+    if (mImage->rgbToGrayScale(grayImage)) {
+        float* gradientMap = new float[height * width * 2];
+
+        // Computes the gradient image on the luminance layer.
+        computeGradient(grayImage, gradientMap);
+
+        float* gradientMagnitude = new float[height * width];
+        int* gradientDirectionInt = new int[height * width];
+        float* gradientDirection = new float[height * width];
+
+        // Computes the absolute gradient of the image without padding.
+        for (int i = 1; i < height - 1; ++i) {
+            for (int j = 1; j < width - 1; ++j) {
+                gradientMagnitude[i * width + j] =
+                        sqrt(gradientMap[(i * width + j) * 2] *
+                             gradientMap[(i * width + j) * 2] +
+                             gradientMap[(i * width + j ) * 2 + 1] *
+                             gradientMap[(i * width + j ) * 2 + 1]);
+
+                // Computes the gradient direction of the image.
+                if (gradientMap[(i * width + j) * 2] == 0 ) {
+                    // If the vertical gradient is 0, the edge is horizontal
+                    // Mark the gradient direction as 90 degrees.
+                    gradientDirectionInt[i * width + j] = 2;
+                    gradientDirection[i * width + j] = 90.0f;
+                } else {
+                    // Otherwise the atan operation is valid and can decide
+                    // the gradient direction of the edge.
+                    float gradient = atan(gradientMap[(i * width + j) * 2 + 1]
+                            / gradientMap[(i * width + j) * 2])
+                            / (M_PI / 4);
+
+                    gradientDirection[i * width + j] = gradient * 45.0f;
+
+                    // Maps the gradient direction to 4 major directions with
+                    // 0 mapped to up and 2 mapped to right.
+                    if (gradient - floor(gradient) > 0.5) {
+                        gradientDirectionInt[i * width + j] =
+                                (static_cast<int>(ceil(gradient)) + 4) % 4;
+                    } else {
+                        gradientDirectionInt[i * width + j] =
+                                (static_cast<int>(floor(gradient)) + 4) % 4;
+                    }
+                }
+            }
+        }
+
+        // Compute a boolean map to show whether a pixel is on the edge.
+        for (int i = 1; i < height - 1; ++i) {
+            for (int j = 1; j < width - 1; ++j) {
+                edgeMap[i * width + j] = false;
+
+                switch (gradientDirectionInt[i * width + j]) {
+                    case 0:
+                        // If the gradient points rightwards, the pixel is
+                        // on an edge if it has a larger absolute gradient than
+                        // pixels on its left and right sides.
+                        if ((gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[i * width + j + 1]) &&
+                            (gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[i * width + j - 1])) {
+                            edgeMap[i * width + j] = true;
+                        }
+                        break;
+                    case 1:
+                        // If the gradient points right-downwards, the pixel is
+                        // on an edge if it has a larger absolute gradient than
+                        // pixels on its upper left and bottom right sides.
+                        if ((gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i + 1) * width + j + 1]) &&
+                            (gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i - 1) * width + j - 1])) {
+                            edgeMap[i * width + j] = true;
+                        }
+                        break;
+                    case 2:
+                        // If the gradient points upwards, the pixel is
+                        // on an edge if it has a larger absolute gradient than
+                        // pixels on its up and down sides.
+                        if ((gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i + 1) * width + j]) &&
+                            (gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i - 1) * width + j])) {
+                            edgeMap[i * width + j] = true;
+                        }
+                        break;
+                    case 3:
+                        // If the gradient points right-upwards, the pixel is
+                        // on an edge if it has a larger absolute gradient than
+                        // pixels on its bottom left and upper right sides.
+                        if ((gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i - 1) * width + j + 1]) &&
+                            (gradientMagnitude[i * width + j] >=
+                                gradientMagnitude[(i + 1) * width + j - 1])) {
+                            edgeMap[i * width + j] = true;
+                        }
+                  }
+
+             }
+        }
+
+        houghLineDetection(edgeMap, gradientMagnitude, gradientDirection);
+
+        // Cleans up
+        delete[] gradientMap;
+        delete[] gradientDirectionInt;
+        delete[] gradientMagnitude;
+        delete[] gradientDirection;
+
+    } else {
+        LOGE("Not a color image!");
+    }
+
+    delete[] edgeMap;
+    delete[] grayImage;
+}
+
+// Runs the hough voting algorithm to find the grid of the color checker
+// with the edge map, gradient direction and gradient magnitude as inputs.
+void ColorCheckerTest::houghLineDetection(bool* edgeMap,
+                                          float* gradientMagnitude,
+                                          float* gradientDirection) {
+    // Constructs a graph for Hough voting. The vertical axis counts the vote
+    // for a certain angle. The horizontal axis counts the vote for the distance
+    // of a line from the origin of the image.
+    int houghHeight = 180;
+    int houghWidth = 200;
+    int houghCounts[houghHeight][houghWidth];
+    int houghSum[houghHeight][houghWidth];
+
+    int** houghVote;
+    houghVote = new int*[180];
+    for (int i = 0; i < 180; ++i) {
+        houghVote[i] = new int[200];
+    }
+
+    for (int i = 0; i < houghHeight; ++i) {
+        for (int j = 0; j < houghWidth; ++j) {
+            houghCounts[i][j] = 0;
+            houghVote[i][j] = 0;
+            houghSum[i][j] = 0;
+        }
+    }
+
+    // Vectors to record lines in two orthogonal directions.
+    // Each line is represented by its direction and its distance to the origin.
+    std::vector<std::vector<int> > verticalLines;
+    std::vector<std::vector<int> > horizontalLines;
+    float radius;
+    int height = mImage->getHeight();
+    int width = mImage->getWidth();
+
+    // Processes the signicant edge pixels and cast vote for the corresponding
+    // edge passing this pixel.
+    for (int i = 1; i < height - 1; ++i) {
+        for (int j = 1; j < width - 1; ++j) {
+            // Sets threashold for the gradient magnitude to discount noises
+            if (edgeMap[i * width + j] &&
+                (gradientMagnitude[i * width + j] > 15)) {
+                int shiftedAngle;
+
+                // Shifts angles for 45 degrees so the vertical and horizontal
+                // direction is mapped to 45 and 135 degrees to avoid padding.
+                // This uses the assumption that the color checker is placed
+                // roughly parallel to the image boarders. So that the edges
+                // at the angle of 45 will be rare.
+                shiftedAngle = (static_cast<int>(
+                        -gradientDirection[i * width + j]) + 225 % 180);
+                float shiftedAngleRad = static_cast<float>(shiftedAngle)
+                        * M_PI / 180.0f;
+
+                // Computes the distance of the line from the origin.
+                float a, b;
+                a = static_cast<float>(i - j) / sqrt(2.0f);
+                b = static_cast<float>(i + j) / sqrt(2.0f);
+                radius = a * sin(shiftedAngleRad) - b * cos(shiftedAngleRad);
+
+                // Adds one vote for the line. The line's angle is shifted by
+                // 45 degrees to avoid avoid padding for the vertical lines,
+                // which is more common than diagonal lines. The line's
+                // distance is mapped to [0, 200] from [-200, 200].
+                ++houghCounts[shiftedAngle][static_cast<int>((radius / 2.0f) +
+                                                              100.0f)];
+
+                drawPoint(i, j, Vec3i(255, 255, 255));
+            }
+        }
+    }
+
+    int houghAngleSum[houghHeight];
+    int primaryVerticalAngle, primaryHorizontalAngle;
+    int max1 = 0;
+    int max2 = 0;
+
+    // Looking for the two primary angles of the lines.
+    for (int i = 5; i < houghHeight - 5; ++i) {
+        houghAngleSum[i] = 0;
+        for (int j = 0; j < houghWidth; ++j) {
+            for (int l = -5; l <= 5; ++l) {
+                houghSum[i][j] += houghCounts[i + l][j];
+            }
+            houghAngleSum[i] += houghSum[i][j];
+        }
+
+        if ((i < houghHeight / 2) && (houghAngleSum[i] > max1)) {
+            max1 = houghAngleSum[i];
+            primaryVerticalAngle = i;
+        } else if ((i > houghHeight / 2) && (houghAngleSum[i] > max2)) {
+            max2 = houghAngleSum[i];
+            primaryHorizontalAngle = i;
+        }
+    }
+
+    ALOGV("Primary angles are %d, %d",
+         primaryVerticalAngle, primaryHorizontalAngle);
+
+    int angle;
+
+    // For each primary angle, look for the highest voted lines.
+    for (int k = 0; k < 2; ++k) {
+        if (k == 0) {
+            angle = primaryVerticalAngle;
+        } else {
+            angle = primaryHorizontalAngle;
+        }
+
+        std::vector<int> line(2);
+        for (int j = 2; j < houghWidth - 2; ++j) {
+            houghVote[angle][j] = houghSum[angle][j];
+            houghSum[angle][j] = 0;
+        }
+
+        // For each radius, average the vote with nearby ones.
+        for (int j = 2; j < houghWidth - 2; ++j) {
+            for (int m = -2; m <= 2; ++m) {
+                houghSum[angle][j] += houghVote[angle][j + m];
+            }
+        }
+
+        bool isCandidate[houghWidth];
+
+        // Find whether a lines is a candidate by rejecting the ones that have
+        // lower vote than others in the neighborhood.
+        for (int j = 2; j < houghWidth - 2; ++j) {
+            isCandidate[j] = true;
+            for (int m = -2; ((isCandidate[j]) && (m <= 2)); ++m) {
+                if ((houghSum[angle][j] < 20) ||
+                    (houghSum[angle][j] < houghSum[angle][j + m])) {
+                    isCandidate[j] = false;
+                }
+            }
+        }
+
+        int iter1 = 0;
+        int iter2 = 0;
+        int count = 0;
+
+        // Finds the lines that are not too close to each other and add to the
+        // detected lines.
+        while (iter2 < houghWidth) {
+            while ((!isCandidate[iter2]) && (iter2 < houghWidth)) {
+                ++iter2;
+            }
+            if ((isCandidate[iter2]) && (iter2 - iter1 < 5)) {
+                iter1 = (iter2 + iter1) / 2;
+                ++iter2;
+            } else {
+                line[0] = angle;
+                line[1] = (iter1 - 100) * 2;
+                if (iter1 != 0) {
+                    if (k == 0) {
+                        verticalLines.push_back(line);
+                        Vec3i color(verticalLines.size() * 20, 0, 0);
+                        drawLine(line[0], line[1], color);
+                    } else {
+                        horizontalLines.push_back(line);
+                        Vec3i color(0, horizontalLines.size() * 20, 0);
+                        drawLine(line[0], line[1], color);
+                    }
+                }
+                iter1 = iter2;
+                ++iter2;
+                ALOGV("pushing back line %d %d", line[0], line[1]);
+            }
+        }
+    }
+
+    ALOGV("Numbers of lines in each direction is %d, %d",
+         verticalLines.size(), horizontalLines.size());
+
+    for (int i = 0; i < 180; ++i) {
+        delete[] houghVote[i];
+    }
+    delete[] houghVote;
+
+    findCheckerBoards(verticalLines, horizontalLines);
+}
+
+// Computes the gradient in both x and y direction of a layer
+void ColorCheckerTest::computeGradient(unsigned char* layer,
+                                       float* gradientMap) {
+    int width = mImage->getWidth();
+    int height = mImage->getHeight();
+
+    // Computes the gradient in the whole image except the image boarders.
+    for (int i = 1; i < height - 1; ++i) {
+        for (int j = 1; j < width - 1; ++j) {
+            gradientMap[(i * width + j) * 2] =
+                    0.5f * (layer[i * width + j + 1] -
+                            layer[i * width + j - 1]);
+            gradientMap[(i * width + j) * 2 + 1] =
+                    0.5f * (layer[(i + 1) * width + j] -
+                           layer[(i - 1) * width + j]);
+        }
+    }
+}
+
+// Tries to find the checker boards with the highest voted lines
+void ColorCheckerTest::findCheckerBoards(
+        std::vector<std::vector<int> > verticalLines,
+        std::vector<std::vector<int> > horizontalLines) {
+    ALOGV("Start looking for Color checker");
+    int numVerticalLines = verticalLines.size();
+    int numHorizontalLines = horizontalLines.size();
+    Vec2f pointUpperLeft;
+    Vec2f pointBottomRight;
+
+    mCandidateColors.resize(numHorizontalLines - 1);
+    mCandidatePositions.resize(numHorizontalLines - 1);
+
+    for (int i = numVerticalLines - 1; i >= 1; --i) {
+        for (int j = 0; j < numHorizontalLines - 1; ++j) {
+            // Finds the upper left and bottom right corner of each rectangle
+            // formed by two neighboring highest voted lines.
+            pointUpperLeft = findCrossing(verticalLines[i], horizontalLines[j]);
+            pointBottomRight = findCrossing(verticalLines[i - 1],
+                                            horizontalLines[j + 1]);
+
+            Vec3i* color = new Vec3i();
+            Vec2f* pointCenter = new Vec2f();
+            // Verifies if they are separated by a reasonable distance.
+            if (verifyPointPair(pointUpperLeft, pointBottomRight,
+                                pointCenter, color)) {
+                mCandidatePositions[j].push_back(pointCenter);
+                mCandidateColors[j].push_back(color);
+            } else {
+                mCandidatePositions[j].push_back(NULL);
+                mCandidateColors[j].push_back(NULL);
+                delete color;
+                delete pointCenter;
+            }
+        }
+    }
+
+    // Verifies whether the current line candidates form a valid color checker.
+    verifyColorGrid();
+}
+
+// Returns the corssing point of two lines given the lines.
+Vec2f ColorCheckerTest::findCrossing(std::vector<int> line1,
+                                     std::vector<int> line2) {
+    Vec2f crossingPoint;
+    float r1 = static_cast<float>(line1[1]);
+    float r2 = static_cast<float>(line2[1]);
+    float ang1, ang2;
+    float y1, y2;
+
+    ang1 = static_cast<float>(line1[0]) / 180.0f * M_PI;
+    ang2 = static_cast<float>(line2[0]) / 180.0f * M_PI;
+
+    float x, y;
+    x = (r1 * cos(ang2) - r2 * cos(ang1)) / sin(ang1 - ang2);
+    y = (r1 * sin(ang2) - r2 * sin(ang1)) / sin(ang1 - ang2);
+
+    crossingPoint.set((x + y) / sqrt(2.0), (y - x) / sqrt(2.0));
+
+    //ALOGV("Crosspoint at (%f, %f)", crossingPoint.x(), crossingPoint.y());
+    return crossingPoint;
+}
+
+// Verifies whether two opposite corners on a quadrilateral actually can be
+// the two corners of a color checker.
+bool ColorCheckerTest::verifyPointPair(Vec2f pointUpperLeft,
+                                       Vec2f pointBottomRight,
+                                       Vec2f* pointCenter,
+                                       Vec3i* color) {
+    bool success = true;
+
+    /** 5 and 30 are the threshold tuned for resolution 640*480*/
+    if ((pointUpperLeft.x() < 0) ||
+        (pointUpperLeft.x() >= mImage->getHeight()) ||
+        (pointUpperLeft.y() < 0) ||
+        (pointUpperLeft.y() >= mImage->getWidth()) ||
+        (pointBottomRight.x() < 0) ||
+        (pointBottomRight.x() >= mImage->getHeight()) ||
+        (pointBottomRight.y() < 0) ||
+        (pointBottomRight.y() >= mImage->getWidth()) ||
+        (abs(pointUpperLeft.x() - pointBottomRight.x()) <= 5) ||
+        (abs(pointUpperLeft.y() - pointBottomRight.y()) <= 5) ||
+        (abs(pointUpperLeft.x() - pointBottomRight.x()) >= 30) ||
+        (abs(pointUpperLeft.y() - pointBottomRight.y()) >= 30)) {
+
+        // If any of the quadrilateral corners are out of the image or if
+        // the distance between them are too large or too big, the quadrilateral
+        // could not be one of the checkers
+        success = false;
+    } else {
+        // Find the checker center if the corners of the rectangle meet criteria
+        pointCenter->set((pointUpperLeft.x() + pointBottomRight.x()) / 2.0f,
+                       (pointUpperLeft.y() + pointBottomRight.y()) / 2.0f);
+        color->set(mImage->getPixelValue(*pointCenter).r(),
+                   mImage->getPixelValue(*pointCenter).g(),
+                   mImage->getPixelValue(*pointCenter).b());
+        ALOGV("Color at (%f, %f) is (%d, %d, %d)", pointCenter->x(), pointCenter->y(),color->r(), color->g(), color->b());
+    }
+    return success;
+}
+
+// Verifies the color checker centers and finds the match between the detected
+// color checker and the reference MacBeth color checker
+void ColorCheckerTest::verifyColorGrid() {
+    ALOGV("Start looking for Color Grid");
+    int numHorizontalLines = mCandidateColors.size();
+    int numVerticalLines = mCandidateColors[0].size();
+    bool success = false;
+
+    // Computes the standard deviation of one row/column of the proposed color
+    // checker. Discards the row/column if the std is below a threshold.
+    for (int i = 0; i < numHorizontalLines; ++i) {
+        Vec3f meanColor(0.f, 0.f, 0.f);
+        int numNonZero = 0;
+
+        for (int j = 0; j < numVerticalLines; ++j) {
+            if (mCandidateColors[i][j] != NULL) {
+                ALOGV("candidate color (%d, %d) is (%d, %d, %d)", i, j, mCandidateColors[i][j]->r(), mCandidateColors[i][j]->g(), mCandidateColors[i][j]->b());
+
+                meanColor = meanColor + (*mCandidateColors[i][j]);
+                ++numNonZero;
+            }
+        }
+        if (numNonZero > 0) {
+            meanColor = meanColor / numNonZero;
+        }
+        ALOGV("Mean color for vertical direction computed!");
+
+        float std = 0;
+        for (int j = 0; j < numVerticalLines; ++j) {
+            if (mCandidateColors[i][j] != NULL) {
+                std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
+            }
+        }
+        if (numNonZero > 0) {
+            std = sqrt(std / (3 * numNonZero));
+        }
+        ALOGV("st. deviation for the %d dir1 is %d", i, static_cast<int>(std));
+
+        if ((std <= 30) && (numNonZero > 1)) {
+            for (int j = 0; j < numVerticalLines; ++j) {
+                if (mCandidateColors[i][j] != NULL) {
+                    delete mCandidateColors[i][j];
+                    mCandidateColors[i][j] = NULL;
+                }
+            }
+        }
+    }
+
+    // Discards the column/row of the color checker if the std is below a
+    // threshold.
+    for (int j = 0; j < numVerticalLines; ++j) {
+        Vec3f meanColor(0.f, 0.f, 0.f);
+        int numNonZero = 0;
+
+        for (int i = 0; i < numHorizontalLines; ++i) {
+            if (mCandidateColors[i][j] != NULL) {
+                meanColor = meanColor + (*mCandidateColors[i][j]);
+                ++numNonZero;
+            }
+        }
+        if (numNonZero > 0) {
+            meanColor = meanColor / numNonZero;
+        }
+
+        float std = 0;
+        for (int i = 0; i < numHorizontalLines; ++i) {
+            if (mCandidateColors[i][j] != NULL) {
+                std += mCandidateColors[i][j]->squareDistance<float>(meanColor);
+            }
+        }
+        if (numNonZero > 0) {
+            std = sqrt(std / (3 * numNonZero));
+        }
+
+        ALOGV("st. deviation for the %d dir2 is %d", j, static_cast<int>(std));
+
+        if ((std <= 30) && (numNonZero > 1)) {
+            for (int i = 0; i < numHorizontalLines; ++i) {
+                if (mCandidateColors[i][j] != NULL) {
+                    delete mCandidateColors[i][j];
+                    mCandidateColors[i][j] = NULL;
+                }
+            }
+        }
+    }
+
+    for (int i = 0; i < numHorizontalLines; ++i) {
+        for (int j = 0; j < numVerticalLines; ++j) {
+            if (mCandidateColors[i][j] != NULL) {
+                ALOGV("position (%d, %d) is at (%f, %f) with color (%d, %d, %d)",
+                     i, j,
+                     mCandidatePositions[i][j]->x(),
+                     mCandidatePositions[i][j]->y(),
+                     mCandidateColors[i][j]->r(),
+                     mCandidateColors[i][j]->g(),
+                     mCandidateColors[i][j]->b());
+            } else {
+                ALOGV("position (%d, %d) is 0", i, j);
+            }
+        }
+    }
+
+    // Finds the match between the detected color checker and the reference
+    // MacBeth color checker.
+    int rowStart = 0;
+    int rowEnd = 0;
+
+    // Loops until all dectected color checker has been processed.
+    while (!success) {
+        int columnStart = 0;
+        int columnEnd = 0;
+        bool isRowStart = false;
+        bool isRowEnd = true;
+
+        // Finds the row start of the next block of detected color checkers.
+        while ((!isRowStart) && (rowStart <  numHorizontalLines)) {
+            for (int j = 0; j < numVerticalLines; ++j) {
+                if (mCandidateColors[rowStart][j] != NULL) {
+                    isRowStart = true;
+                }
+            }
+            ++rowStart;
+        }
+        rowStart--;
+        rowEnd = rowStart;
+        ALOGV("rowStart is %d", rowStart);
+
+        // Finds the row end of the next block of detected color checkers.
+        while ((isRowEnd) && (rowEnd < numHorizontalLines)) {
+            isRowEnd = false;
+            for (int j = 0; j < numVerticalLines; ++j) {
+                if (mCandidateColors[rowEnd][j] != NULL) {
+                    isRowEnd= true;
+                }
+            }
+            if (isRowEnd) {
+                ++rowEnd;
+            }
+        }
+        if ((!isRowEnd) && isRowStart) {
+            rowEnd--;
+        }
+        if ((isRowEnd) && (rowEnd == numHorizontalLines)) {
+            rowEnd--;
+            isRowEnd = false;
+        }
+        ALOGV("rowEnd is %d", rowEnd);
+
+        // Matches color checkers between the start row and the end row.
+        bool successVertical = false;
+
+        while (!successVertical) {
+            bool isColumnEnd = true;
+            bool isColumnStart = false;
+
+            // Finds the start column of the next block of color checker
+            while ((!isColumnStart) && (columnStart < numVerticalLines)) {
+                if (mCandidateColors[rowStart][columnStart] != NULL) {
+                    isColumnStart = true;
+                }
+                ++columnStart;
+            }
+            columnStart--;
+            columnEnd = columnStart;
+
+            // Finds the end column of the next block of color checker
+            while ((isColumnEnd) && (columnEnd < numVerticalLines)) {
+                isColumnEnd = false;
+                if (mCandidateColors[rowStart][columnEnd] != NULL)
+                    isColumnEnd = true;
+                if (isColumnEnd) {
+                    ++columnEnd;
+                }
+            }
+
+            if ((!isColumnEnd) && isColumnStart) {
+                columnEnd--;
+            }
+            if ((isColumnEnd) && (columnEnd == numVerticalLines)) {
+                columnEnd--;
+                isColumnEnd = false;
+            }
+
+            // Finds the best match on the MacBeth reference color checker for
+            // the continuous block of detected color checker
+            if (isRowStart && (!isRowEnd) &&
+                isColumnStart && (!isColumnEnd)) {
+                findBestMatch(rowStart, rowEnd, columnStart, columnEnd);
+            }
+            ALOGV("FindBestMatch for %d, %d, %d, %d", rowStart,
+                 rowEnd, columnStart, columnEnd);
+
+            // If the column search finishes, go out of the loop
+            if (columnEnd >= numVerticalLines - 1) {
+                successVertical = true;
+            } else {
+                columnStart = columnEnd + 1;
+            }
+        }
+        ALOGV("Continuing to search for direction 1");
+
+        // If the row search finishes, go out of the loop
+        if (rowEnd >= numHorizontalLines - 1) {
+            success = true;
+        } else {
+            rowStart = rowEnd + 1;
+        }
+    }
+
+    for (int i = 0; i < 4; ++i) {
+        for (int j = 0; j < 6; ++j) {
+            if (mMatchPositions[i][j] != NULL) {
+                ALOGV("Reference Match position for (%d, %d) is (%f, %f)", i, j,
+                     mMatchPositions[i][j]->x(), mMatchPositions[i][j]->y());
+            }
+        }
+    }
+
+    fillRefColorGrid();
+}
+
+// Finds the best match on the MacBeth color checker for the continuous block of
+// detected color checkers bounded by row i1, row i2 and column j1 and column j2
+// Assumes that the subsample is less than 4*6.
+void ColorCheckerTest::findBestMatch(int i1, int i2, int j1, int j2) {
+    int numHorizontalGrid = i2 - i1 + 1;
+    int numVerticalGrid = j2 - j1 + 1;
+
+    if (((numHorizontalGrid > 1) || (numVerticalGrid > 1)) &&
+        (numHorizontalGrid <= 4) && (numVerticalGrid <= 6)) {
+        ALOGV("i1, j2, j1, j2 is %d, %d, %d, %d", i1, i2, j1, j2);
+        float minError;
+        float error = 0.f;
+        int horizontalMatch, verticalMatch;
+
+        // Finds the match start point where the error is minimized.
+        for (int i = 0; i < numHorizontalGrid; ++i) {
+            for (int j = 0; j < numVerticalGrid; ++j) {
+                error += mCandidateColors[i1 + i][j1 + j]->squareDistance<int>(
+                        *mReferenceColors[i][j]);
+            }
+        }
+        ALOGV("Error is %f", error);
+        minError = error;
+        horizontalMatch = 0;
+        verticalMatch = 0;
+
+        for (int i = 0; i <= 4 - numHorizontalGrid; ++i) {
+            for (int j = 0; j <= 6 - numVerticalGrid; ++j) {
+                error = 0.f;
+
+                for (int id = 0; id < numHorizontalGrid; ++id) {
+                    for (int jd = 0; jd < numVerticalGrid; ++jd) {
+                        error += mCandidateColors[i1 + id][j1 + jd]->
+                                squareDistance<int>(
+                                        *mReferenceColors[i + id][j + jd]);
+                    }
+                }
+
+                if (error < minError) {
+                    minError = error;
+                    horizontalMatch = i;
+                    verticalMatch = j;
+                }
+                ALOGV("Processed %d, %d and error is %f", i, j, error );
+            }
+        }
+
+        for (int id = 0; id < numHorizontalGrid; ++id) {
+            for (int jd = 0; jd < numVerticalGrid; ++jd) {
+                mMatchPositions[horizontalMatch + id][verticalMatch + jd] =
+                         new Vec2f(mCandidatePositions[i1 + id][j1 + jd]->x(),
+                                   mCandidatePositions[i1 + id][j1 + jd]->y());
+            }
+        }
+        ALOGV("Grid match starts at %d, %d", horizontalMatch, verticalMatch);
+    }
+}
+
+// Finds the boundary of a color checker by its color similarity to the center.
+// Also predicts the location of unmatched checkers.
+void ColorCheckerTest::fillRefColorGrid() {
+    int rowStart = 0;
+    int columnStart = 0;
+    bool foundStart = true;
+
+    for (int i = 0; (i < 4) && foundStart; ++i) {
+        for (int j = 0; (j < 6) && foundStart; ++j) {
+            if (mMatchPositions[i][j] != NULL) {
+                rowStart = i;
+                columnStart = j;
+                foundStart = false;
+            }
+        }
+    }
+    ALOGV("First match found at (%d, %d)", rowStart, columnStart);
+
+    float rowDistance, columnDistance;
+    rowDistance = 0;
+    columnDistance = 0;
+    int numRowGrids = 0;
+    int numColumnGrids = 0;
+
+    for (int i = rowStart; i < 4; ++i) {
+        for (int j = columnStart; j < 6; ++j) {
+            if (mMatchPositions[i][j] != NULL) {
+                if (i > rowStart) {
+                    ++numRowGrids;
+                    rowDistance += (mMatchPositions[i][j]->x() -
+                                mMatchPositions[rowStart][columnStart]->x()) /
+                                static_cast<float>(i - rowStart);
+                }
+                if (j > columnStart) {
+                    ++numColumnGrids;
+                    columnDistance += (mMatchPositions[i][j]->y() -
+                                mMatchPositions[rowStart][columnStart]->y()) /
+                                static_cast<float>(j - columnStart);
+                }
+            }
+        }
+    }
+
+    if ((numRowGrids > 0) && (numColumnGrids > 0)) {
+        rowDistance = rowDistance / numRowGrids;
+        columnDistance = columnDistance / numColumnGrids;
+        ALOGV("delta is %f, %f", rowDistance, columnDistance);
+
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0 ; j < 6; ++j) {
+                if (mMatchPositions[i][j] == NULL) {
+                    mMatchPositions[i][j] = new Vec2f(
+                            mMatchPositions[rowStart][columnStart]->x() +
+                                    (i - rowStart) * rowDistance,
+                            mMatchPositions[rowStart][columnStart]->y() +
+                                    (j - columnStart) * columnDistance);
+                }
+            }
+        }
+        for (int i = 0; i < 4; ++i) {
+            for (int j = 0; j < 6; ++j) {
+                float radius = 0;
+                Vec3i color = mImage->getPixelValue(*mMatchPositions[i][j]);
+                Vec3f meanColor(0.f , 0.f, 0.f);
+
+                int numPixels = 0;
+                for (int ii  = static_cast<int>(mMatchPositions[i][j]->x() -
+                                                rowDistance/2);
+                     ii <= static_cast<int>(mMatchPositions[i][j]->x() +
+                                            rowDistance/2);
+                     ++ii) {
+                    for (int jj = static_cast<int>(mMatchPositions[i][j]->y() -
+                                                   columnDistance/2);
+                         jj <= static_cast<int>(mMatchPositions[i][j]->y() +
+                                                columnDistance/2);
+                         ++jj) {
+                        Vec3i pixelColor = mImage->getPixelValue(ii,jj);
+                        float error = color.squareDistance<int>(pixelColor);
+
+                        if (error < 200.f) {
+                            drawPoint(ii, jj, *mReferenceColors[i][j]);
+                            meanColor = meanColor + pixelColor;
+                            numPixels++;
+                            Vec2i pixelPosition(ii, jj);
+
+                            if (pixelPosition.squareDistance<float>(
+                                    *mMatchPositions[i][j]) > radius) {
+                                radius = pixelPosition.squareDistance<float>(
+                                        *mMatchPositions[i][j]);
+                            }
+                        }
+                    }
+                }
+
+                /** Computes the radius of the checker.
+                 * The above computed radius is the squared distance to the
+                 * furthest point with a matching color. To be conservative, we
+                 * only consider an area with radius half of the above computed
+                 * value. Since radius is computed as a squared root, the one
+                 * that will be recorded is 1/4 of the above computed value.
+                 */
+                mMatchRadius[i][j] = radius / 4.f;
+                mMatchColors[i][j] = new Vec3f(meanColor / numPixels);
+
+                ALOGV("Reference color at (%d, %d) is (%d, %d, %d)", i, j,
+                     mReferenceColors[i][j]->r(),
+                     mReferenceColors[i][j]->g(),
+                     mReferenceColors[i][j]->b());
+                ALOGV("Average color at (%d, %d) is (%f, %f, %f)", i, j,
+                     mMatchColors[i][j]->r(),
+                     mMatchColors[i][j]->g(),
+                     mMatchColors[i][j]->b());
+                ALOGV("Radius is %f", mMatchRadius[i][j]);
+            }
+        }
+
+        mSuccess = true;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
index 8cce754..d31bd4a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/CameraAnalyzerActivity.java
@@ -206,7 +206,8 @@
         }
         mCamera.startPreview();
         CameraTests.setCamera(mCamera);
-        //ColorCheckerTest.getSingletonTest().updateCamera();
+
+        ColorCheckerTest.getSingletonTest().updateCamera();
         //WhiteBalanceTest.getSingletonTest().updateCamera();
         //ExposureCompensationTest.getSingletonTest().updateCamera();
         //MeteringTest.getSingletonTest().updateCamera();
@@ -304,10 +305,10 @@
         @Override
         public void onClick(View v) {
             Log.v(TAG, "Running new color checker finding tests!");
-            /*ColorCheckerTest colorCheckerTest = ColorCheckerTest.getSingletonTest();
+            ColorCheckerTest colorCheckerTest = ColorCheckerTest.getSingletonTest();
 
             mCurrentTest = colorCheckerTest;
-            initializeAdapter();*/
+            initializeAdapter();
         }
     };
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/ColorCheckerTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/ColorCheckerTest.java
new file mode 100644
index 0000000..dd2a966
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/analyzer/ColorCheckerTest.java
@@ -0,0 +1,293 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.analyzer;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.hardware.Camera;
+import android.util.Log;
+import android.widget.ImageView;
+
+import java.util.List;
+
+/** Locates a Xrite Classic Color Checker grid pattern in an image, stores the
+ * center positions and the color checker radius, and provides a function to
+ * get the memory address of these two properties.
+ *
+ * The pattern is a 6x4 grid of square color patches. The detection routine
+ * assumes the pattern is placed roughly facing the camera, with the long size
+ * roughly horizontal. It also assumes that the grey squares are in the bottom
+ * row.
+ */
+public class ColorCheckerTest extends CameraTests {
+
+    private static final String TAG = "ColorCheckerTest";
+
+    /** Memory address of the image class instance that contains the image. */
+    private long mClassAddress;
+    /** Memory address of the image test class instance. */
+    private long mTestHandler;
+    /** Thread lock. */
+    private final Object mProcessingImage = new Object();
+    /** Boolean to tell whether auto focus has succeded.*/
+    private boolean mAutoFocusSuccess;
+    /** Boolean to tell whether auto focus is supported.*/
+    private boolean mAutoFocusEnabled = false;
+    /** Singleton instance of the class.*/
+    private static ColorCheckerTest singletonTest = null;
+
+    private boolean mFindCheckerSuccess = false;
+
+    /**
+     * Constructs a <code>ColorCheckerTest</code> instance with a given
+     * Camera pointer.
+     */
+    private ColorCheckerTest() {
+        super();
+    }
+
+    /**
+     * Updates the camera and parameter when the activity switches camera.
+     */
+    public void updateCamera() {
+        Camera.Parameters params = mTestCamera.getParameters();
+        List<String> supportedFocusModes = params.getSupportedFocusModes();
+
+        // Sets camera focus mode to Auto focus if it is supported.
+        if (supportedFocusModes.contains(params.FOCUS_MODE_AUTO)) {
+            Log.v(TAG, "Auto focus possible");
+            params.setFocusMode(params.FOCUS_MODE_AUTO);
+            mTestCamera.setParameters(params);
+            mAutoFocusEnabled = true;
+        } else {
+            mAutoFocusEnabled = false;
+        }
+    }
+
+    public static synchronized ColorCheckerTest getSingletonTest() {
+        if (singletonTest == null) {
+            Log.v(TAG, "Creating a new ColorCheckerTest instance");
+            singletonTest = new ColorCheckerTest();
+            singletonTest.initializeTest();
+        }
+        return singletonTest;
+    }
+
+    private void initializeTest() {
+        // Creates a native test handler with a 120x160 pixel debug output
+        mTestHandler = createColorCheckerTest(120, 160);
+    }
+
+    @Override
+    public synchronized void run(int index) {
+        Log.v(TAG, "ColorCheckerTest thread started!");
+        mAutoFocusSuccess = false;
+        mFindCheckerSuccess = false;
+        // Sets camera focus mode to Auto focus if it is supported.
+        if (mAutoFocusEnabled) {
+            while (!mAutoFocusSuccess) {
+                // Starts the auto focus process of the camera.
+                mTestCamera.autoFocus(mAutoFocusListener);
+
+                // Locks thread until the camera finishes taking picture.
+                synchronized (mProcessingImage) {
+                    try{
+                        Log.v(TAG, "Start waiting for Image");
+                        mProcessingImage.wait();
+                    } catch (InterruptedException e) {
+                        Log.v(TAG, "Callback wait fails!");
+                    }
+                }
+            }
+        } else {
+            mTestCamera.takePicture(null, null, null, mTestJpegListener);
+            synchronized (mProcessingImage) {
+                try{
+                    Log.v(TAG, "Start waiting for Image");
+                    mProcessingImage.wait();
+                } catch (InterruptedException e) {
+                    Log.v(TAG, "Callback wait fails!");
+                }
+            }
+        }
+
+        // Launches the native method to find the color checker in the image.
+        mFindCheckerSuccess = processColorCheckerTest(mTestHandler);
+        // Displays the debug output from the native test handler instance.
+        displayHandlerDebugOutput(mTestHandler);
+
+        Log.v(TAG, "Callback has returned!");
+    }
+
+    private Camera.AutoFocusCallback mAutoFocusListener = new Camera.AutoFocusCallback() {
+        public void onAutoFocus(boolean mSuccess, Camera mCamera) {
+            if (mSuccess) {
+                mAutoFocusSuccess = true;
+                Log.v(TAG, "Autofocus success!");
+                mCamera.takePicture(null, null, null, mTestJpegListener);
+            } else {
+                try{
+                    Log.v(TAG, "Autofocus failed. Please adjust!");
+                    Thread.sleep(4000);
+                    Log.v(TAG, "END Waiting");
+                } catch (InterruptedException e){}
+
+                synchronized (mProcessingImage) {
+                    mProcessingImage.notifyAll();
+                }
+            }
+        }
+    };
+
+    private Camera.PictureCallback mTestJpegListener = new Camera.PictureCallback() {
+        public void onPictureTaken(byte[] data, Camera mCamera) {
+            Log.v(TAG, "Shutter pressed down!");
+
+            // Changes the focus mode to fixed to avoid focus shift after
+            // auto focus is successful.
+            //Camera.Parameters params = mCamera.getParameters();
+            //params.setFocusMode(params.FOCUS_MODE_FIXED);
+            //mCamera.setParameters(params);
+
+            // Decodes the camera data to Bitmap and creates a native image
+            // class with the Bitmap.
+            Bitmap inputImage;
+            inputImage = BitmapFactory.decodeByteArray(data, 0, data.length);
+            long bufferAddress = findNative(inputImage);
+            Log.v(TAG, "findNative method finishes");
+
+            // Cleans up the Bitmap memory space.
+            inputImage.recycle();
+            data = null;
+            inputImage = null;
+            System.gc();
+
+            // Constructs a test handler class to handle the image.
+            createColorCheckerClass(bufferAddress, mTestHandler);
+
+            mCamera.startPreview();
+
+            // Notifies the thread lock the image capture is done.
+            synchronized (mProcessingImage) {
+                mProcessingImage.notifyAll();
+            }
+        }
+    };
+
+    /**
+     * Overrides the base class method and use the memory addresses of the
+     * checker centers and radius computed by the native test handler class
+     * to update the values stored in the base class.
+     *
+     * @return <code>true</code> indicating a memory address upload is needed.
+     */
+    @Override
+    public boolean copyCheckerAddress() {
+        if (mFindCheckerSuccess) {
+            setCheckerAddress(getColorCheckerCenterAdd(mTestHandler),
+                              getColorCheckerRadiusAdd(mTestHandler));
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    @Override
+    public void cleanUp() {
+        cleanUpHandler(mTestHandler);
+    }
+
+    @Override
+    public String getTestName() {
+        return "Color Checker test: \n";
+    }
+
+    @Override
+    public String getTestName(int index) {
+        return "Find color checker";
+    }
+
+    @Override
+    public String getResult(int index) {
+        if (mFindCheckerSuccess) {
+          return "Passed";
+        } else {
+          return "Failed";
+        }
+    }
+
+    @Override
+    public int getNumTests() {
+        return 1;
+    }
+
+    /**
+     * Gets the memory address of the vector storing the color checker centers
+     * from the native test handler instance.
+     *
+     * @param handlerAddress the memory address of the native test handler
+     * instance
+     *
+     * @return memory address of the native vector storing the color checker
+     * centers' coordinates
+     */
+    private native long getColorCheckerRadiusAdd(long handlerAddress);
+
+    /**
+     * Gets the memory address of the vector storing the color checker radius
+     * from the native test handler instance.
+     *
+     * @param handlerAddress the memory address of the native test handler
+     * instance.
+     *
+     * @return memory address of the native vector storing the color checker
+     * centers' coordinates
+     */
+    private native long getColorCheckerCenterAdd(long handlerAddress);
+
+    /**
+     * Creates a native color checker test handler instance.
+     *
+     * @param outputWidth the desired width for the debug output
+     * @param outputHeight the desired height of the debug output
+     *
+     * @return memory address of the native test handler instance
+     */
+    private native long createColorCheckerTest(int outputWidth, int outputHeight);
+
+    /**
+     * Loads a native image class instances and extracts data from it to add
+     * to the test handler.
+     *
+     * @param bufferAddress the memory address of the image class instance
+     * @param handlerAddress the memory address of the test handler instance
+     */
+    private native void createColorCheckerClass(long bufferAddress, long handlerAddress);
+
+    /**
+     * Processes the data in the native test handler instance. Computes test
+     * results with all the data and construct a debug image or debug text
+     * outputs.
+     *
+     * @param handlerAddress the memory address of the test handler instance
+     */
+    private native boolean processColorCheckerTest(long handlerAddress);
+
+    static {
+        System.loadLibrary("cameraanalyzer");
+    }
+}