diff options
author | Chris Craik <ccraik@google.com> | 2012-10-01 18:22:38 -0700 |
---|---|---|
committer | Chris Craik <ccraik@google.com> | 2012-11-20 12:41:19 -0800 |
commit | c3683b552f592d8039a466c663f7de8c8286e975 (patch) | |
tree | f7685a25563d42394739ab768e3daa02248b47af /tests/CanvasCompare | |
parent | 74e6489d8910cfa5354b5ac35ffb4ac5968ebe62 (diff) | |
download | frameworks_base-c3683b552f592d8039a466c663f7de8c8286e975.zip frameworks_base-c3683b552f592d8039a466c663f7de8c8286e975.tar.gz frameworks_base-c3683b552f592d8039a466c663f7de8c8286e975.tar.bz2 |
Hardware / Software Canvas comparison tool
Has automated and manual testing modes
Change-Id: I84d27447ad64021540525372022ab13a36ffc116
Diffstat (limited to 'tests/CanvasCompare')
16 files changed, 1685 insertions, 0 deletions
diff --git a/tests/CanvasCompare/Android.mk b/tests/CanvasCompare/Android.mk new file mode 100644 index 0000000..642c9e9 --- /dev/null +++ b/tests/CanvasCompare/Android.mk @@ -0,0 +1,28 @@ +# +# Copyright (C) 2012 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. +# + +LOCAL_PATH:= $(call my-dir) +include $(CLEAR_VARS) + +LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-renderscript-files-under, src) + +LOCAL_PACKAGE_NAME := CanvasCompare + +LOCAL_MODULE_TAGS := tests + +LOCAL_JAVA_LIBRARIES := android.test.runner + +include $(BUILD_PACKAGE) diff --git a/tests/CanvasCompare/AndroidManifest.xml b/tests/CanvasCompare/AndroidManifest.xml new file mode 100644 index 0000000..1cb8c37 --- /dev/null +++ b/tests/CanvasCompare/AndroidManifest.xml @@ -0,0 +1,47 @@ +<!-- Copyright (C) 2012 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. +--> +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.test.hwuicompare" > + + <!-- for perfhud --> + <uses-permission android:name="android.permission.INTERNET" /> + + <application + android:label="@string/app_name" + android:theme="@android:style/Theme.Holo.Light.NoActionBar"> + <activity + android:name="AutomaticActivity" + android:label="CanvasAutoCompare" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <activity + android:name="ManualActivity" + android:label="CanvasManualCompare" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + </activity> + <uses-library android:name="android.test.runner" /> + </application> + <instrumentation + android:name="android.test.InstrumentationTestRunner" + android:targetPackage="com.android.test.hwuicompare" + android:label="HW/SW Canvas comparison tool."/> + +</manifest> diff --git a/tests/CanvasCompare/res/drawable/sunset1.jpg b/tests/CanvasCompare/res/drawable/sunset1.jpg Binary files differnew file mode 100644 index 0000000..92851f3 --- /dev/null +++ b/tests/CanvasCompare/res/drawable/sunset1.jpg diff --git a/tests/CanvasCompare/res/layout/automatic_layout.xml b/tests/CanvasCompare/res/layout/automatic_layout.xml new file mode 100644 index 0000000..e049ec0 --- /dev/null +++ b/tests/CanvasCompare/res/layout/automatic_layout.xml @@ -0,0 +1,38 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" > + + <com.android.test.hwuicompare.MainView + android:id="@+id/hardware_view" + android:layout_width="@dimen/layer_width" + android:layout_height="@dimen/layer_width" /> + + <ImageView + android:id="@+id/software_image_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" /> + + <ImageView + android:id="@+id/hardware_image_view" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:layout_alignParentRight="true" /> + +</RelativeLayout> diff --git a/tests/CanvasCompare/res/layout/manual_layout.xml b/tests/CanvasCompare/res/layout/manual_layout.xml new file mode 100644 index 0000000..1a9288c --- /dev/null +++ b/tests/CanvasCompare/res/layout/manual_layout.xml @@ -0,0 +1,119 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:orientation="vertical" > + + <HorizontalScrollView + android:layout_width="wrap_content" + android:layout_height="wrap_content" > + + <LinearLayout + android:id="@+id/spinner_layout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" /> + </HorizontalScrollView> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="0dip" + android:layout_weight="1" + android:baselineAligned="true" + android:orientation="horizontal" > + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:orientation="horizontal" > + + <com.android.test.hwuicompare.MainView + android:id="@+id/hardware_view" + android:layout_width="@dimen/layer_width" + android:layout_height="@dimen/layer_width" /> + </LinearLayout> + + <LinearLayout + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_weight="1" + android:gravity="center" + android:orientation="horizontal" > + + <com.android.test.hwuicompare.MainView + android:id="@+id/software_view" + android:layout_width="@dimen/layer_width" + android:layout_height="@dimen/layer_width" /> + </LinearLayout> + </LinearLayout> + + <ImageView + android:id="@+id/compare_image_view" + android:layout_width="@dimen/layer_width_double" + android:layout_height="@dimen/layer_height_double" + android:filter="false" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:gravity="center" + android:orientation="horizontal" > + + <ImageButton + android:id="@+id/previous" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/previous_combination" + android:src="@android:drawable/ic_media_previous" /> + + <ImageButton + android:id="@+id/next" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:contentDescription="@string/next_combination" + android:src="@android:drawable/ic_media_next" /> + + <TextView + android:id="@+id/current_error" + android:layout_width="100dp" + android:layout_height="wrap_content" + android:gravity="center" + android:textAppearance="?android:attr/textAppearanceLarge" /> + + <Button + android:id="@+id/show_hardware_version" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/show_hardware_version" /> + + <Button + android:id="@+id/show_software_version" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/show_software_version" /> + + <Button + android:id="@+id/show_error_heatmap" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/show_error_heatmap" /> + </LinearLayout> + +</LinearLayout> diff --git a/tests/CanvasCompare/res/values/strings.xml b/tests/CanvasCompare/res/values/strings.xml new file mode 100644 index 0000000..edd4610 --- /dev/null +++ b/tests/CanvasCompare/res/values/strings.xml @@ -0,0 +1,31 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<resources> + <string name="app_name">Canvas Compare Test</string> + + <!-- show hardware rendered version of the layer --> + <string name="show_hardware_version">Hardware</string> + <!-- show software rendered version of the layer --> + <string name="show_software_version">Software</string> + <!-- show layer error --> + <string name="show_error_values">Error</string> + <!-- show layer error heatmap --> + <string name="show_error_heatmap">Heatmap</string> + <!-- select and display the next combination of painting options--> + <string name="next_combination">Next Combination</string> + <!-- select and display the previous combination of painting options--> + <string name="previous_combination">Previous Combination</string> +</resources> diff --git a/tests/CanvasCompare/res/values/values.xml b/tests/CanvasCompare/res/values/values.xml new file mode 100644 index 0000000..f69378d --- /dev/null +++ b/tests/CanvasCompare/res/values/values.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2012 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. +--> +<resources> + + <!-- NOTE: the below MUST be multiples of 64 --> + <dimen name="layer_height">320px</dimen> + <dimen name="layer_width">320px</dimen> + + <dimen name="layer_height_double">640px</dimen> + <dimen name="layer_width_double">640px</dimen> + +</resources> diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java new file mode 100644 index 0000000..e0ff1dc --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/AutomaticActivity.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import java.util.ArrayList; + +import com.android.test.hwuicompare.R; + +import android.os.Bundle; +import android.os.Trace; +import android.util.Log; +import android.widget.ImageView; +import android.widget.Toast; + +public class AutomaticActivity extends CompareActivity { + private static final String LOG_TAG = "AutomaticActivity"; + private static final float ERROR_DISPLAY_THRESHOLD = 0.01f; + protected static final boolean DRAW_BITMAPS = false; + + private ImageView mSoftwareImageView = null; + private ImageView mHardwareImageView = null; + + private static final float[] ERROR_CUTOFFS = {0, 0.005f, 0.01f, 0.02f, 0.05f, 0.1f, 0.25f, 0.5f, 1f, 2f}; + private float[] mErrorRates = new float[ERROR_CUTOFFS.length]; + private float mTotalTests = 0; + private float mTotalError = 0; + + public abstract static class TestCallback { + abstract void report(String name, float value); + void complete() {} + } + + private ArrayList<TestCallback> mTestCallbacks = new ArrayList<TestCallback>(); + + Runnable mRunnable = new Runnable() { + @Override + public void run() { + loadBitmaps(); + if (mSoftwareBitmap == null || mHardwareBitmap == null) { + Log.e(LOG_TAG, "bitmap is null"); + return; + } + + if (DRAW_BITMAPS) { + mSoftwareImageView.setImageBitmap(mSoftwareBitmap); + mHardwareImageView.setImageBitmap(mHardwareBitmap); + } + + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "calculateError"); + float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap); + Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); + + if (error > ERROR_DISPLAY_THRESHOLD) { + String modname = ""; + for (String s : DisplayModifier.getLastAppliedModifications()) { + modname = modname.concat(s + "."); + } + Log.d(LOG_TAG, String.format("error for %s was %2.9f", modname, error)); + } + for (int i = 0; i < ERROR_CUTOFFS.length; i++) { + if (error <= ERROR_CUTOFFS[i]) break; + mErrorRates[i]++; + } + mTotalError += error; + mTotalTests++; + + if (DisplayModifier.step()) { + for (TestCallback c : mTestCallbacks) { + c.report("averageError", (mTotalError / mTotalTests)); + for (int i = 1; i < ERROR_CUTOFFS.length; i++) { + c.report(String.format("error over %1.3f", ERROR_CUTOFFS[i]), + mErrorRates[i]/mTotalTests); + } + c.complete(); + } + + Toast.makeText(getApplicationContext(), "done!", Toast.LENGTH_SHORT).show(); + finish(); + } else { + mHardwareView.invalidate(); + if (DRAW_BITMAPS) { + mSoftwareImageView.invalidate(); + mHardwareImageView.invalidate(); + } + } + mHandler.removeCallbacks(mRunnable); + } + }; + + @Override + protected void onPause() { + super.onPause(); + mHandler.removeCallbacks(mRunnable); + }; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.automatic_layout); + + mSoftwareImageView = (ImageView) findViewById(R.id.software_image_view); + mHardwareImageView = (ImageView) findViewById(R.id.hardware_image_view); + + onCreateCommon(mRunnable); + mTestCallbacks.add(new TestCallback() { + void report(String name, float value) { + Log.d(LOG_TAG, name + " " + value); + }; + }); + } + + @Override + protected boolean forceRecreateBitmaps() { + // disable, unless needed for drawing into imageviews + return DRAW_BITMAPS; + } + + // FOR TESTING + public void setCallback(TestCallback c) { + mTestCallbacks.add(c); + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java new file mode 100644 index 0000000..0b85189 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/CompareActivity.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; + +import com.android.test.hwuicompare.R; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.drawable.ColorDrawable; +import android.os.Handler; +import android.os.Trace; +import android.util.Log; +import android.view.View; + +abstract public class CompareActivity extends Activity { + private static final String LOG_TAG = "CompareActivity"; + + protected MainView mHardwareView = null; + + protected Bitmap mSoftwareBitmap; + protected Bitmap mHardwareBitmap; + + protected ErrorCalculator mErrorCalculator; + + protected Handler mHandler; + + Runnable mDrawCallback = null; + protected boolean mRedrewFlag = true; + + protected void onCreateCommon(final Runnable postDrawCallback) { + mDrawCallback = new Runnable() { + @Override + public void run() { + mRedrewFlag = true; + mHandler.post(postDrawCallback); + }; + }; + getWindow().setBackgroundDrawable(new ColorDrawable(0xffefefef)); + ResourceModifiers.init(getResources()); + + mHardwareView = (MainView) findViewById(R.id.hardware_view); + mHardwareView.setLayerType(View.LAYER_TYPE_HARDWARE, null); + mHardwareView.setBackgroundColor(Color.WHITE); + mHardwareView.addDrawCallback(mDrawCallback); + + int width = getResources().getDimensionPixelSize(R.dimen.layer_width); + int height = getResources().getDimensionPixelSize(R.dimen.layer_height); + mSoftwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mHardwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + mErrorCalculator = new ErrorCalculator(getApplicationContext(), getResources()); + + mHandler = new Handler(); + } + + protected abstract boolean forceRecreateBitmaps(); + + protected void loadBitmaps() { + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "loadBitmaps"); + if (forceRecreateBitmaps()) { + int width = mSoftwareBitmap.getWidth(); + int height = mSoftwareBitmap.getHeight(); + + mSoftwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + mHardwareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } + + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "softwareDraw"); + mHardwareView.draw(new Canvas(mSoftwareBitmap)); + Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); + + try { + Method getHardwareLayer = View.class.getDeclaredMethod("getHardwareLayer"); + if (!getHardwareLayer.isAccessible()) + getHardwareLayer.setAccessible(true); + Object hardwareLayer = getHardwareLayer.invoke(mHardwareView); + if (hardwareLayer == null) { + Log.d(LOG_TAG, "failure to access hardware layer"); + return; + } + Method copyInto = hardwareLayer.getClass().getSuperclass() + .getDeclaredMethod("copyInto", Bitmap.class); + if (!copyInto.isAccessible()) + copyInto.setAccessible(true); + + Trace.traceBegin(Trace.TRACE_TAG_ALWAYS, "copyInto"); + boolean success = (Boolean) copyInto.invoke(hardwareLayer, mHardwareBitmap); + Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); + if (!success) { + Log.d(LOG_TAG, "failure to copy hardware layer into bitmap"); + } + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + Trace.traceEnd(Trace.TRACE_TAG_ALWAYS); + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java new file mode 100644 index 0000000..6022141 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/DisplayModifier.java @@ -0,0 +1,469 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import java.util.LinkedHashMap; +import java.util.Map.Entry; + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.RectF; +import android.util.Log; + +public abstract class DisplayModifier { + + // automated tests ignore any combination of operations that don't together return TOTAL_MASK + protected final static int TOTAL_MASK = 0x1F; + + // if we're filling, ensure we're not also sweeping over stroke parameters + protected final static int SWEEP_STROKE_WIDTH_BIT = 0x1 << 0; + protected final static int SWEEP_STROKE_CAP_BIT = 0x1 << 1; + protected final static int SWEEP_STROKE_JOIN_BIT = 0x1 << 2; + + protected final static int SWEEP_SHADER_BIT = 0x1 << 3; // only allow non-simple shaders to use rectangle drawing + protected final static int SWEEP_TRANSFORM_BIT = 0x1 << 4; // only sweep over specified transforms + + abstract public void modifyDrawing(Paint paint, Canvas canvas); + protected int mask() { return 0x0; }; + + private static final RectF gRect = new RectF(0, 0, 200, 175); + private static final float[] gLinePts = new float[] { + 100, 0, 200, 200, 200, 200, 0, 200, 0, 200, 100, 0 + }; + private static final float[] gPts = new float[] { + 0, 100, 100, 0, 100, 200, 200, 100 + }; + + @SuppressWarnings("serial") + private static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> gMaps = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>() { + { + put("aa", new LinkedHashMap<String, DisplayModifier>() { + { + put("true", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setAntiAlias(true); + } + }); + put("false", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setAntiAlias(false); + } + }); + } + }); + put("style", new LinkedHashMap<String, DisplayModifier>() { + { + put("fill", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStyle(Paint.Style.FILL); + } + }); + put("stroke", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStyle(Paint.Style.STROKE); + } + @Override + protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } + }); + put("fillAndStroke", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStyle(Paint.Style.FILL_AND_STROKE); + } + + @Override + protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } + }); + } + }); + put("strokeWidth", new LinkedHashMap<String, DisplayModifier>() { + { + put("hair", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeWidth(0); + } + @Override + protected int mask() { return SWEEP_STROKE_WIDTH_BIT; } + }); + put("0.3", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeWidth(0.3f); + } + }); + put("1", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeWidth(1); + } + }); + put("5", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeWidth(5); + } + }); + } + }); + put("strokeCap", new LinkedHashMap<String, DisplayModifier>() { + { + put("butt", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeCap(Paint.Cap.BUTT); + } + @Override + protected int mask() { return SWEEP_STROKE_CAP_BIT; } + }); + put("round", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeCap(Paint.Cap.ROUND); + } + }); + put("square", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeCap(Paint.Cap.SQUARE); + } + }); + } + }); + put("strokeJoin", new LinkedHashMap<String, DisplayModifier>() { + { + put("bevel", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeJoin(Paint.Join.BEVEL); + } + @Override + protected int mask() { return SWEEP_STROKE_JOIN_BIT; } + }); + put("round", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeJoin(Paint.Join.ROUND); + } + }); + put("miter", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setStrokeJoin(Paint.Join.MITER); + } + }); + // TODO: add miter0, miter1 etc to test miter distances + } + }); + + put("transform", new LinkedHashMap<String, DisplayModifier>() { + { + put("noTransform", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) {} + @Override + protected int mask() { return SWEEP_TRANSFORM_BIT; }; + }); + put("rotate5", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.rotate(5); + } + }); + put("rotate45", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.rotate(5); + } + }); + put("rotate90", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.rotate(90); + canvas.translate(0, -200); + } + }); + put("scale2x2", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.scale(2, 2); + } + @Override + protected int mask() { return SWEEP_TRANSFORM_BIT; }; + }); + put("rot20scl1x4", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.rotate(20); + canvas.scale(1, 4); + } + @Override + protected int mask() { return SWEEP_TRANSFORM_BIT; }; + }); + } + }); + + put("shader", new LinkedHashMap<String, DisplayModifier>() { + { + put("noShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) {} + @Override + protected int mask() { return SWEEP_SHADER_BIT; }; + }); + put("repeatShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mRepeatShader); + } + @Override + protected int mask() { return SWEEP_SHADER_BIT; }; + }); + put("translatedShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mTranslatedShader); + } + }); + put("scaledShader", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mScaledShader); + } + }); + put("horGradient", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mHorGradient); + } + }); + put("diagGradient", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mDiagGradient); + } + @Override + protected int mask() { return SWEEP_SHADER_BIT; }; + }); + put("vertGradient", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setShader(ResourceModifiers.instance().mVertGradient); + } + }); + } + }); + + // FINAL MAP: DOES ACTUAL DRAWING + put("drawing", new LinkedHashMap<String, DisplayModifier>() { + { + put("roundRect", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawRoundRect(gRect, 20, 20, paint); + } + }); + put("rect", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawRect(gRect, paint); + } + @Override + protected int mask() { return SWEEP_SHADER_BIT | SWEEP_STROKE_CAP_BIT; }; + }); + put("circle", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawCircle(100, 100, 75, paint); + } + }); + put("oval", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawOval(gRect, paint); + } + }); + put("triLines", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawLines(gLinePts, paint); + } + @Override + protected int mask() { return SWEEP_STROKE_CAP_BIT; }; + }); + put("plusPoints", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawPoints(gPts, paint); + } + }); + put("text", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setTextSize(36); + canvas.drawText("TEXTTEST", 0, 50, paint); + } + }); + put("shadowtext", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + paint.setTextSize(36); + paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff); + canvas.drawText("TEXTTEST", 0, 50, paint); + } + }); + put("bitmapMesh", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawBitmapMesh(ResourceModifiers.instance().mBitmap, 3, 3, + ResourceModifiers.instance().mBitmapVertices, 0, null, 0, null); + } + }); + put("arc", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawArc(gRect, 260, 285, false, paint); + } + @Override + protected int mask() { return SWEEP_STROKE_CAP_BIT; }; + }); + put("arcFromCenter", new DisplayModifier() { + @Override + public void modifyDrawing(Paint paint, Canvas canvas) { + canvas.drawArc(gRect, 260, 285, true, paint); + } + @Override + protected int mask() { return SWEEP_STROKE_JOIN_BIT; }; + }); + } + }); + // WARNING: DON'T PUT MORE MAPS BELOW THIS + } + }; + + private static LinkedHashMap<String, DisplayModifier> getMapAtIndex(int index) { + for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { + if (index == 0) { + return map; + } + index--; + } + return null; + } + + // indices instead of iterators for easier bidirectional traversal + private static final int mIndices[] = new int[gMaps.size()]; + private static final String[] mLastAppliedModifications = new String[gMaps.size()]; + + private static boolean stepInternal(boolean forward) { + int modifierMapIndex = gMaps.size() - 1; + while (modifierMapIndex >= 0) { + LinkedHashMap<String, DisplayModifier> map = getMapAtIndex(modifierMapIndex); + mIndices[modifierMapIndex] += (forward ? 1 : -1); + + if (mIndices[modifierMapIndex] >= 0 && mIndices[modifierMapIndex] < map.size()) { + break; + } + + mIndices[modifierMapIndex] = (forward ? 0 : map.size() - 1); + modifierMapIndex--; + } + return modifierMapIndex < 0; // true if resetting + } + + public static boolean step() { + boolean ret = false; + do { + ret |= stepInternal(true); + } while (!checkModificationStateMask()); + return ret; + } + + public static boolean stepBack() { + boolean ret = false; + do { + ret |= stepInternal(false); + } while (!checkModificationStateMask()); + return ret; + } + + private static boolean checkModificationStateMask() { + int operatorMask = 0x0; + int mapIndex = 0; + for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { + int displayModifierIndex = mIndices[mapIndex]; + for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) { + if (displayModifierIndex == 0) { + mLastAppliedModifications[mapIndex] = modifierEntry.getKey(); + operatorMask |= modifierEntry.getValue().mask(); + break; + } + displayModifierIndex--; + } + mapIndex++; + } + return operatorMask == TOTAL_MASK; + } + + public static void apply(Paint paint, Canvas canvas) { + int mapIndex = 0; + for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { + int displayModifierIndex = mIndices[mapIndex]; + for (Entry<String, DisplayModifier> modifierEntry : map.entrySet()) { + if (displayModifierIndex == 0) { + mLastAppliedModifications[mapIndex] = modifierEntry.getKey(); + modifierEntry.getValue().modifyDrawing(paint, canvas); + break; + } + displayModifierIndex--; + } + mapIndex++; + } + } + + public static String[] getLastAppliedModifications() { + return mLastAppliedModifications.clone(); + } + + public static String[][] getStrings() { + String[][] keys = new String[gMaps.size()][]; + + int i = 0; + for (LinkedHashMap<String, DisplayModifier> map : gMaps.values()) { + keys[i] = new String[map.size()]; + int j = 0; + for (String key : map.keySet()) { + keys[i][j++] = key; + } + i++; + } + + return keys; + } + + public static void setIndex(int mapIndex, int newIndexValue) { + mIndices[mapIndex] = newIndexValue; + } + + public static int[] getIndices() { + return mIndices; + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java new file mode 100644 index 0000000..a08b558 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/ErrorCalculator.java @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import com.android.test.hwuicompare.R; +import com.android.test.hwuicompare.ScriptC_errorCalculator; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.renderscript.Allocation; +import android.renderscript.Element; +import android.renderscript.RenderScript; +import android.util.Log; + +public class ErrorCalculator { + private static final String LOG_TAG = "ErrorCalculator"; + private static final int REGION_SIZE = 8; + + private static final boolean LOG_TIMING = false; + private static final boolean LOG_CALC = false; + + private final RenderScript mRS; + private Allocation mIdealPixelsAllocation; + private Allocation mGivenPixelsAllocation; + private Allocation mOutputPixelsAllocation; + + private final Allocation mInputRowsAllocation; + private final Allocation mOutputRegionsAllocation; + + private final ScriptC_errorCalculator mScript; + + private final int[] mOutputRowRegions; + + public ErrorCalculator(Context c, Resources resources) { + int width = resources.getDimensionPixelSize(R.dimen.layer_width); + int height = resources.getDimensionPixelSize(R.dimen.layer_height); + mOutputRowRegions = new int[height / REGION_SIZE]; + + mRS = RenderScript.create(c); + int[] rowIndices = new int[height / REGION_SIZE]; + for (int i = 0; i < rowIndices.length; i++) + rowIndices[i] = i * REGION_SIZE; + + mScript = new ScriptC_errorCalculator(mRS, resources, R.raw.errorcalculator); + mScript.set_HEIGHT(height); + mScript.set_WIDTH(width); + mScript.set_REGION_SIZE(REGION_SIZE); + + mInputRowsAllocation = Allocation.createSized(mRS, Element.I32(mRS), rowIndices.length, + Allocation.USAGE_SCRIPT); + mInputRowsAllocation.copyFrom(rowIndices); + mOutputRegionsAllocation = Allocation.createSized(mRS, Element.I32(mRS), + mOutputRowRegions.length, Allocation.USAGE_SCRIPT); + } + + + private static long startMillis, middleMillis; + + public float calcErrorRS(Bitmap ideal, Bitmap given) { + if (LOG_TIMING) { + startMillis = System.currentTimeMillis(); + } + + mIdealPixelsAllocation = Allocation.createFromBitmap(mRS, ideal, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + mGivenPixelsAllocation = Allocation.createFromBitmap(mRS, given, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + + mScript.bind_ideal(mIdealPixelsAllocation); + mScript.bind_given(mGivenPixelsAllocation); + + mScript.forEach_countInterestingRegions(mInputRowsAllocation, mOutputRegionsAllocation); + mOutputRegionsAllocation.copyTo(mOutputRowRegions); + + int regionCount = 0; + for (int region : mOutputRowRegions) { + regionCount += region; + } + int interestingPixels = Math.max(1, regionCount) * REGION_SIZE * REGION_SIZE; + + if (LOG_TIMING) { + long startMillis2 = System.currentTimeMillis(); + } + + mScript.forEach_accumulateError(mInputRowsAllocation, mOutputRegionsAllocation); + mOutputRegionsAllocation.copyTo(mOutputRowRegions); + float totalError = 0; + for (int row : mOutputRowRegions) { + totalError += row; + } + totalError /= 1024.0f; + + if (LOG_TIMING) { + long finalMillis = System.currentTimeMillis(); + Log.d(LOG_TAG, "rs: first part took " + (middleMillis - startMillis) + "ms"); + Log.d(LOG_TAG, "rs: last part took " + (finalMillis - middleMillis) + "ms"); + } + if (LOG_CALC) { + Log.d(LOG_TAG, "rs: error " + totalError + ", pixels " + interestingPixels); + } + return totalError / interestingPixels; + } + + public void calcErrorHeatmapRS(Bitmap ideal, Bitmap given, Bitmap output) { + mIdealPixelsAllocation = Allocation.createFromBitmap(mRS, ideal, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + mGivenPixelsAllocation = Allocation.createFromBitmap(mRS, given, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + + mScript.bind_ideal(mIdealPixelsAllocation); + mScript.bind_given(mGivenPixelsAllocation); + + mOutputPixelsAllocation = Allocation.createFromBitmap(mRS, output, + Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT); + mScript.forEach_displayDifference(mOutputPixelsAllocation, mOutputPixelsAllocation); + mOutputPixelsAllocation.copyTo(output); + } + + public static float calcError(Bitmap ideal, Bitmap given) { + if (LOG_TIMING) { + startMillis = System.currentTimeMillis(); + } + + int interestingRegions = 0; + for (int x = 0; x < ideal.getWidth(); x += REGION_SIZE) { + for (int y = 0; y < ideal.getWidth(); y += REGION_SIZE) { + if (inspectRegion(ideal, x, y)) { + interestingRegions++; + } + } + } + + int interestingPixels = Math.max(1, interestingRegions) * REGION_SIZE * REGION_SIZE; + + if (LOG_TIMING) { + long startMillis2 = System.currentTimeMillis(); + } + + float totalError = 0; + for (int x = 0; x < ideal.getWidth(); x++) { + for (int y = 0; y < ideal.getHeight(); y++) { + int idealColor = ideal.getPixel(x, y); + int givenColor = given.getPixel(x, y); + if (idealColor == givenColor) + continue; + totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor)); + totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor)); + totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor)); + totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor)); + } + } + totalError /= 1024.0f; + if (LOG_TIMING) { + long finalMillis = System.currentTimeMillis(); + Log.d(LOG_TAG, "dvk: first part took " + (middleMillis - startMillis) + "ms"); + Log.d(LOG_TAG, "dvk: last part took " + (finalMillis - middleMillis) + "ms"); + } + if (LOG_CALC) { + Log.d(LOG_TAG, "dvk: error " + totalError + ", pixels " + interestingPixels); + } + return totalError / interestingPixels; + } + + private static boolean inspectRegion(Bitmap ideal, int x, int y) { + int regionColor = ideal.getPixel(x, y); + for (int i = 0; i < REGION_SIZE; i++) { + for (int j = 0; j < REGION_SIZE; j++) { + if (ideal.getPixel(x + i, y + j) != regionColor) + return true; + } + } + return false; + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java new file mode 100644 index 0000000..454fe7b --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/MainView.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.util.AttributeSet; +import android.view.View; + +public class MainView extends View { + Paint mPaint = new Paint(); + + public MainView(Context context) { + super(context); + } + + public MainView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public MainView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + mPaint.reset(); + DisplayModifier.apply(mPaint, canvas); + + if (mDrawCallback != null) { + mDrawCallback.run(); + } + } + + private Runnable mDrawCallback; + public void addDrawCallback(Runnable drawCallback) { + mDrawCallback = drawCallback; + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java new file mode 100644 index 0000000..400dff0 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/ManualActivity.java @@ -0,0 +1,211 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import com.android.test.hwuicompare.R; + +import android.graphics.Bitmap; +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +public class ManualActivity extends CompareActivity { + private static final String LOG_TAG = "ManualActivity"; + private ImageView mCompareImageView; + private Bitmap mCompareBitmap; + private TextView mErrorTextView; + private MainView mSoftwareView; + + private static final int COMPARE_VIEW_UNINITIALIZED = -1; + private static final int COMPARE_VIEW_HARDWARE = 0; + private static final int COMPARE_VIEW_SOFTWARE = 1; + private static final int COMPARE_VIEW_HEATMAP = 2; // TODO: add more like this? any ideas? + + private int mCompareImageViewState = COMPARE_VIEW_UNINITIALIZED; + private int mLastCompareImageViewState = COMPARE_VIEW_UNINITIALIZED; + + Runnable mRunnable = new Runnable() { + @Override + public void run() { + Log.d(LOG_TAG, "mRunnable running, mRedrewFlag = " + mRedrewFlag); + + if (mRedrewFlag) { + loadBitmaps(); + // recalculate error + float error = mErrorCalculator.calcErrorRS(mSoftwareBitmap, mHardwareBitmap); + String modname = ""; + for (String s : DisplayModifier.getLastAppliedModifications()) { + modname = modname.concat(s + "."); + } + + Log.d(LOG_TAG, "error for " + modname + " is " + error); + mErrorTextView.setText(String.format("%.4f", error)); + } + + if (mCompareImageViewState != mLastCompareImageViewState || mRedrewFlag) { + switch (mCompareImageViewState) { + case COMPARE_VIEW_UNINITIALIZED: + // set to hardware + case COMPARE_VIEW_HARDWARE: + mCompareImageView.setImageBitmap(mHardwareBitmap); + break; + case COMPARE_VIEW_SOFTWARE: + mCompareImageView.setImageBitmap(mSoftwareBitmap); + break; + case COMPARE_VIEW_HEATMAP: + mErrorCalculator.calcErrorHeatmapRS(mSoftwareBitmap, mHardwareBitmap, + mCompareBitmap); + mCompareImageView.setImageBitmap(mCompareBitmap); + break; + } + mCompareImageView.invalidate(); + } + + mLastCompareImageViewState = mCompareImageViewState; + mRedrewFlag = false; + mHandler.removeCallbacks(mRunnable); + } + }; + + private void redrawViews() { + mHardwareView.invalidate(); + mSoftwareView.invalidate(); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.manual_layout); + onCreateCommon(mRunnable); + + mSoftwareView = (MainView) findViewById(R.id.software_view); + mSoftwareView.setLayerType(View.LAYER_TYPE_SOFTWARE, null); + mSoftwareView.setBackgroundColor(Color.WHITE); + mSoftwareView.addDrawCallback(mDrawCallback); + + mCompareImageView = (ImageView) findViewById(R.id.compare_image_view); + + int width = getResources().getDimensionPixelSize(R.dimen.layer_width); + int height = getResources().getDimensionPixelSize(R.dimen.layer_height); + mCompareBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + + mErrorTextView = (TextView) findViewById(R.id.current_error); + ((ImageButton) findViewById(R.id.next)).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + DisplayModifier.step(); + updateSpinners(); + redrawViews(); + } + }); + ((ImageButton) findViewById(R.id.previous)).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + DisplayModifier.stepBack(); + updateSpinners(); + redrawViews(); + } + }); + ((Button) findViewById(R.id.show_hardware_version)) + .setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mCompareImageViewState = COMPARE_VIEW_HARDWARE; + mHandler.post(mRunnable); + } + }); + ((Button) findViewById(R.id.show_software_version)) + .setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mCompareImageViewState = COMPARE_VIEW_SOFTWARE; + mHandler.post(mRunnable); + } + }); + ((Button) findViewById(R.id.show_error_heatmap)).setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mCompareImageViewState = COMPARE_VIEW_HEATMAP; + mHandler.post(mRunnable); + } + }); + + buildSpinnerLayout(); + } + + private class DisplayModifierSpinner extends Spinner { + private final int mIndex; + + public DisplayModifierSpinner(int index) { + super(ManualActivity.this); + mIndex = index; + setOnItemSelectedListener(new OnItemSelectedListener() { + + @Override + public void onItemSelected(AdapterView<?> parentView, View selectedItem, + int position, long id) { + DisplayModifier.setIndex(mIndex, position); + redrawViews(); + } + + @Override + public void onNothingSelected(AdapterView<?> parentView) { + } + }); + } + } + + private Spinner[] mSpinners; + + private void buildSpinnerLayout() { + LinearLayout layout = (LinearLayout) findViewById(R.id.spinner_layout); + String[][] mapsStrings = DisplayModifier.getStrings(); + mSpinners = new Spinner[mapsStrings.length]; + int index = 0; + for (String[] spinnerValues : mapsStrings) { + mSpinners[index] = new DisplayModifierSpinner(index); + mSpinners[index].setAdapter(new ArrayAdapter<String>(this, + android.R.layout.simple_spinner_dropdown_item, spinnerValues)); + layout.addView(mSpinners[index]); + index++; + } + Log.d(LOG_TAG, "created " + index + " spinners"); + } + + private void updateSpinners() { + int[] indices = DisplayModifier.getIndices(); + for (int i = 0; i < mSpinners.length; i++) { + mSpinners[i].setSelection(indices[i]); + } + } + + @Override + protected boolean forceRecreateBitmaps() { + // continually recreate bitmaps to avoid modifying bitmaps currently being drawn + return true; + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java new file mode 100644 index 0000000..c705443 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/ResourceModifiers.java @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2012 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.test.hwuicompare; + +import com.android.test.hwuicompare.R; + +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.BitmapShader; +import android.graphics.Color; +import android.graphics.LinearGradient; +import android.graphics.Matrix; +import android.graphics.Shader; + +public class ResourceModifiers { + public final BitmapShader mRepeatShader; + public final BitmapShader mTranslatedShader; + public final BitmapShader mScaledShader; + private final int mTexWidth; + private final int mTexHeight; + private final float mDrawWidth; + private final float mDrawHeight; + public final LinearGradient mHorGradient; + public final LinearGradient mDiagGradient; + public final LinearGradient mVertGradient; + public final Bitmap mBitmap; + private final Matrix mMtx1; + private final Matrix mMtx2; + private final Matrix mMtx3; + + public final float[] mBitmapVertices; + public final int[] mBitmapColors; + + private static ResourceModifiers sInstance = null; + public static ResourceModifiers instance() { return sInstance; } + public static void init(Resources resources) { + sInstance = new ResourceModifiers(resources); + } + + public ResourceModifiers(Resources resources) { + mBitmap = BitmapFactory.decodeResource(resources, R.drawable.sunset1); + mTexWidth = mBitmap.getWidth(); + mTexHeight = mBitmap.getHeight(); + + mDrawWidth = resources.getDimensionPixelSize(R.dimen.layer_width); + mDrawHeight = resources.getDimensionPixelSize(R.dimen.layer_height); + + mRepeatShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, + Shader.TileMode.REPEAT); + + mTranslatedShader = new BitmapShader(mBitmap, Shader.TileMode.REPEAT, + Shader.TileMode.REPEAT); + mMtx1 = new Matrix(); + mMtx1.setTranslate(mTexWidth / 2.0f, mTexHeight / 2.0f); + mMtx1.postRotate(45, 0, 0); + mTranslatedShader.setLocalMatrix(mMtx1); + + mScaledShader = new BitmapShader(mBitmap, Shader.TileMode.MIRROR, + Shader.TileMode.MIRROR); + mMtx2 = new Matrix(); + mMtx2.setScale(0.5f, 0.5f); + mScaledShader.setLocalMatrix(mMtx2); + + mHorGradient = new LinearGradient(0.0f, 0.0f, 1.0f, 0.0f, + Color.RED, Color.GREEN, Shader.TileMode.CLAMP); + mMtx3 = new Matrix(); + mMtx3.setScale(mDrawHeight, 1.0f); + mMtx3.postRotate(-90.0f); + mMtx3.postTranslate(0.0f, mDrawHeight); + mHorGradient.setLocalMatrix(mMtx3); + + mDiagGradient = new LinearGradient(0.0f, 0.0f, mDrawWidth / 2.0f, mDrawHeight / 2.0f, + Color.BLUE, Color.RED, Shader.TileMode.CLAMP); + + mVertGradient = new LinearGradient(0.0f, 0.0f, 0.0f, mDrawHeight / 2.0f, + Color.YELLOW, Color.MAGENTA, Shader.TileMode.MIRROR); + + final float width = mBitmap.getWidth() / 8.0f; + final float height = mBitmap.getHeight() / 8.0f; + + mBitmapVertices = new float[] { + 0.0f, 0.0f, width, 0.0f, width * 2, 0.0f, width * 3, 0.0f, + 0.0f, height, width, height, width * 2, height, width * 4, height, + 0.0f, height * 2, width, height * 2, width * 2, height * 2, width * 3, height * 2, + 0.0f, height * 4, width, height * 4, width * 2, height * 4, width * 4, height * 4, + }; + + mBitmapColors = new int[] { + 0xffff0000, 0xff00ff00, 0xff0000ff, 0xffff0000, + 0xff0000ff, 0xffff0000, 0xff00ff00, 0xff00ff00, + 0xff00ff00, 0xff0000ff, 0xffff0000, 0xff00ff00, + 0x00ff0000, 0x0000ff00, 0x000000ff, 0x00ff0000, + }; + } + +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java b/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java new file mode 100644 index 0000000..6ea8237 --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/Test.java @@ -0,0 +1,46 @@ +package com.android.test.hwuicompare; + +import com.android.test.hwuicompare.AutomaticActivity.TestCallback; + +import android.os.Bundle; +import android.test.ActivityInstrumentationTestCase2; + +public class Test extends ActivityInstrumentationTestCase2<AutomaticActivity> { + AutomaticActivity mActivity; + private Bundle mBundle; + + public Test() { + super(AutomaticActivity.class); + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + mBundle = new Bundle(); + mActivity = getActivity(); + mActivity.setCallback(new TestCallback() { + + @Override + void report(String key, float value) { + mBundle.putFloat(key, value); + } + @Override + void complete() { + synchronized(mBundle) { + mBundle.notify(); + } + } + }); + } + + public void testCanvas() { + synchronized(mBundle) { + try { + mBundle.wait(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + getInstrumentation().sendStatus(0, mBundle); + } +} diff --git a/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs new file mode 100644 index 0000000..668f61d --- /dev/null +++ b/tests/CanvasCompare/src/com/android/test/hwuicompare/errorCalculator.rs @@ -0,0 +1,56 @@ +#pragma version(1) +#pragma rs java_package_name(com.android.test.hwuicompare) + +int REGION_SIZE; +int WIDTH; +int HEIGHT; + +const uchar4 *ideal; +const uchar4 *given; + +void countInterestingRegions(const int32_t *v_in, int32_t *v_out) { + int y = v_in[0]; + v_out[0] = 0; + + for (int x = 0; x < HEIGHT; x += REGION_SIZE) { + bool interestingRegion = false; + int regionColor = (int)ideal[y * WIDTH + x]; + for (int i = 0; i < REGION_SIZE && !interestingRegion; i++) { + for (int j = 0; j < REGION_SIZE && !interestingRegion; j++) { + interestingRegion |= (int)(ideal[(y + i) * WIDTH + (x + j)]) != regionColor; + } + } + if (interestingRegion) { + v_out[0]++; + } + } +} + +void accumulateError(const int32_t *v_in, int32_t *v_out) { + int startY = v_in[0]; + int error = 0; + for (int y = startY; y < startY + REGION_SIZE; y++) { + for (int x = 0; x < HEIGHT; x++) { + uchar4 idealPixel = ideal[y * WIDTH + x]; + uchar4 givenPixel = given[y * WIDTH + x]; + error += abs(idealPixel.x - givenPixel.x); + error += abs(idealPixel.y - givenPixel.y); + error += abs(idealPixel.z - givenPixel.z); + error += abs(idealPixel.w - givenPixel.w); + } + } + v_out[0] = error; +} + +void displayDifference(const uchar4 *v_in, uchar4 *v_out, uint32_t x, uint32_t y) { + float4 idealPixel = rsUnpackColor8888(ideal[y * WIDTH + x]); + float4 givenPixel = rsUnpackColor8888(given[y * WIDTH + x]); + + float4 diff = idealPixel - givenPixel; + float totalDiff = diff.x + diff.y + diff.z + diff.w; + if (totalDiff < 0) { + v_out[0] = rsPackColorTo8888(0, 0, clamp(-totalDiff/2, 0, 1)); + } else { + v_out[0] = rsPackColorTo8888(clamp(totalDiff/2, 0, 1), 0, 0); + } +} |