Tslib Manual Calibrate On Android
--by ONCE
1.android 的坐标转换处理
This implementation is a linear transformation using 7 parameters
(a, b, c, d, e, f and s) to transform the device coordinates (Xd, Yd)
into screen coordinates (Xs, Ys) using the following equations:
s*Xs = a*Xd + b*Yd + c
s*Ys = d*Xd + e*Yd + f
Xs,Ys:LCD 坐标(也就是所谓的绝对坐标)
Xd,Yd:触摸屏坐标
在编译好了的 ANDROID 根文件系统的 system/etc/pointercal 这个文件
内,存放着 7 个数,
这 7 个数就是对应上面公式的 a,b,c,d,e,f,s
比如我原来的:
+----+-----+--------+------+--+--------+----+
a | b | c |d |e | f |s |
+----+-----+--------+------+--+--------+----+
|-411|37818|-3636780|-51325|39|47065584|6553|
+----+-----+--------+------+--+--------+----+
2.tslib 库的移植
首先下载到一份 tslib 的代码,我用的是 tslib011.tar.gz(该包由
luzhuwei 同学友情赞助)。
将 tslib 解压到 external/下
要将一份代码编译到 android 里面去 其实没什么其他工作 主要就是那
个 Android.mk 文件的编写。网上找找 自己改改也就出来了。我的
Android.mk 文件内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
TSLIB_PLUGINDIR := /system/lib/ts/plugins
LOCAL_SRC_FILES := \
src/ts_attach.c \
src/ts_close.c \
src/ts_config.c \
src/ts_error.c \
src/ts_fd.c \
src/ts_load_module.c \
src/ts_open.c \
src/ts_parse_vars.c \
src/ts_read.c \
src/ts_read_raw.c \
src/ts_read_raw_module.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl
LOCAL_MODULE := libts
include $(BUILD_SHARED_LIBRARY)
#
# plugin: input-raw
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/input-raw.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/input-raw
include $(BUILD_SHARED_LIBRARY)
#
# plugin: pthres
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/pthres.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/pthres
include $(BUILD_SHARED_LIBRARY)
#
# plugin: linear
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/linear.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/linear
include $(BUILD_SHARED_LIBRARY)
#
# plugin: dejitter
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/dejitter.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/dejitter
include $(BUILD_SHARED_LIBRARY)
#
# plugin: variance
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/variance.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/variance
include $(BUILD_SHARED_LIBRARY)
#
# ts_calibrate
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_calibrate.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
tests/ts_calibrate.h \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_calibrate
include $(BUILD_EXECUTABLE)
#
# ts_test
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_test.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_test
include $(BUILD_EXECUTABLE)
#
# ts_print
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_print.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_print
include $(BUILD_EXECUTABLE)
#
# ts_print_raw
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_print_raw.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_print_raw
include $(BUILD_EXECUTABLE)
#
# ts_harvest
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_harvest.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_harvest
include $(BUILD_EXECUTABLE)
我真不愿意粘帖上面的这个文件...太大 不过方便后人拷贝。
这里要注意是我们需要修改一下 tslib 的几个相关文件,因为 android
里的部分设备文件位置比较特殊。还有就是 tslib 里的部分变量他是
定义在 Makefile 里的 我们要把它定义到我们的文件中去。
首先 fb0 的位置变了: tests/fbutils.c
static char *defaultfbdevice = "/dev/fb0";
static char *defaultfbdevice = "/dev/graphics/fb0";
其次 plugins 的目录变了 src/ts_load_module.c
#define PLUGIN_DIR "/system/lib/ts/plugins/"
最后 config 文件的位置变了 src/ts_config.c
#define TS_CONF "/system/etc/ts.conf"
最后的最后不得不提到我们的配置文件 ts.conf。其他都不要动 但是
必须将它的 module_raw input 注释出来。并且 因为我们的 input 模块
编译出来之后我们命名为 input-raw 了 所以这里得改成
module_raw input-raw
下面就是编译了。发现错误就手工拷贝一下编出来的库到需要它在的
目录。
3. ts_calibrate 生成 pointercal
这里要首先不启动 android,然后进命令行手工启动 ts_calibrate 程序。
然后在屏幕上校验,完成后会生成一个正确的 pointercal 文件。
4.Android 读取校验文件 pointercal
如果要让我们的 android 被校验 那莪没我们就要按上面的公式 将
android 读取坐标部分替换掉。
“system/etc/pointercal”这个文件是被 java 程序读取使用的,文件目录:
f rameworks/base/services/java/com/android/server/InputDevice.java
看了一下这个文件的代码 发现要被修改的坐标为
if (device.absX != null) {
//xxw added
if (device.tInfo != null){
scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y +
device.tInfo.z1)/ device.tInfo.s;
Log.i("XXW","x: "+x);
Log.i("XXW","trans x: "+scaledX);
}
else//end
scaledX = ((scaledX-device.absX.minValue)
/ device.absX.range) * w;
}
if (device.absY != null) {
//xxw added
if (device.tInfo != null){
scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y +
device.tInfo.z2) / device.tInfo.s;
Log.i("XXW","y: "+y);
Log.i("XXW","trans y: "+scaledY);
}
else //end
scaledY = ((scaledY-device.absY.minValue)
/ device.absY.range) * h;
}
对照上面的公式其实很容易理解 在注释 xxw 和 end 之间的代码就是
我修改的代码。其中的 device.tInfo 是我定义的一个结构,这个结构
是用来读取 pointercal 文件里的 7 个数字得到的对应的值,具体结构
如下:
static class TransformInfo {
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
float s;
};
读取文件的代码 我放在 InputDevice 的构造函数里:
//xxw added
TransformInfo t = null;
try {Log.i("XXW","InputDevice! try");
FileInputStream is = new FileInputStream(CALIBRATION_FILE);
byte[] mBuffer = new byte[64];
int len = is.read(mBuffer);
is.close();
if (len > 0) {Log.i("XXW","InputDevice! len>0");
int i;
for (i = 0 ; i < len ; i++) {
if (mBuffer == '\n' || mBuffer == 0) {
break;
}
}
len = i;
}Log.i("XXW","InputDevice! len"+len);
StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0,
len));
t = new TransformInfo ();
t.x1 = Integer.parseInt( st.nextToken() ); Log.i("XXW", "t.x1"+t.x1);
t.y1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y1"+t.y1);
t.z1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z1"+t.z1);
t.x2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.x2"+t.x2);
t.y2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y2"+t.y2);
t.z2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z2"+t.z2);
t.s = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.s"+t.s);
} catch (java.io.FileNotFoundException e) {Log.i("XXW",
"FileNotFound!");
} catch (java.io.IOException e) {Log.i("XXW", "IOException");
}
tInfo = t;
Log.i("XXW","InputDevice end!");
}
其中 static final String CALIBRATION_FILE = "/etc/pointercal";
这个时候点击一下屏幕看看是不是准确就知道是不是成功了。这里要
注意的是因为是在 InputDevice 里读取的文件 所以当我们中途替换了
pointercal 文件的时候 android 并不会再去读取这个文件。所以当我们
用 ts_calibrate 程序生成 pointercal 文件之后需要重启一下。同样 当我
们修改或者删除了那个文件 我们也是需要重启的。所以这个文档 的
名字叫手工校验。
PS:
如何自动化?
如何用 android 应用程序校验而不是命令行?
如何不需要重启也能实现?
请听下回分解。
话接上回,我们发现了手工利用tslib校验触摸屏的缺点。那么这一回我们就来一次稍微高级一点的校验吧。
我们其实只需要相对的x,y以及lcd的x,y就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。
其实一切我们打算做的事情可以都在InputDevice里面做完
下面我把完整之后整个的InputDevice贴出来:
/* * Copyright (C) 2007 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.server; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import android.view.WindowManagerPolicy; import java.io.FileInputStream; import java.util.StringTokenizer; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.os.SystemProperties; public class InputDevice { /** Amount that trackball needs to move in order to generate a key event. */ static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; //once edit static final String CALIBRATION_FILE = "/data/etc/pointercal"; //edit ends final int id; final int classes; final String name; final AbsoluteInfo absX; final AbsoluteInfo absY; final AbsoluteInfo absPressure; final AbsoluteInfo absSize; //once edit static TransformInfo tInfo; //edit ends long mDownTime = 0; int mMetaKeysState = 0; static File desFile; final MotionState mAbs = new MotionState(0, 0); final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, TRACKBALL_MOVEMENT_THRESHOLD); static class MotionState { int xPrecision; int yPrecision; float xMoveScale; float yMoveScale; MotionEvent currentMove = null; boolean changed = false; boolean down = false; boolean lastDown = false; long downTime = 0; int x = 0; int y = 0; int pressure = 1; int size = 0; MotionState(int mx, int my) { xPrecision = mx; yPrecision = my; xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; yMoveScale = my != 0 ? (1.0f/my) : 1.0f; } MotionEvent generateMotion(InputDevice device, long curTime, boolean isAbs, Display display, int orientation, int metaState) { if (!changed) { return null; } //once edit //String prop = System.getProperty("ts.config.calibrate", "noset"); String prop = SystemProperties.get("ts.config.calibrate", "noset"); if (prop.equalsIgnoreCase("start")){ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase start"); device.tInfo = null; }else if (prop.equalsIgnoreCase("done")){ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase done"); readCalibrate(); SystemProperties.set("ts.config.calibrate", "end"); }else{ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase else"); } //edit ends float scaledX = x; float scaledY = y; float temp; float scaledPressure = 1.0f; float scaledSize = 0; int edgeFlags = 0; if (isAbs) { int w = display.getWidth()-1; int h = display.getHeight()-1; if (orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270) { int tmp = w; w = h; h = tmp; } if (device.absX != null) { //once edit if (device.tInfo != null){ scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y + device.tInfo.z1)/ device.tInfo.s; Log.i("XXW","x: "+x); Log.i("XXW","trans x: "+scaledX); } else //edit ends scaledX = ((scaledX-device.absX.minValue) / device.absX.range) * w; } if (device.absY != null) { //once edit if (device.tInfo != null){ scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y + device.tInfo.z2) / device.tInfo.s; Log.i("XXW","y: "+y); Log.i("XXW","trans y: "+scaledY); } else //edit ends scaledY = ((scaledY-device.absY.minValue) / device.absY.range) * h; } if (device.absPressure != null) { scaledPressure = ((pressure-device.absPressure.minValue) / (float)device.absPressure.range); } if (device.absSize != null) { scaledSize = ((size-device.absSize.minValue) / (float)device.absSize.range); } switch (orientation) { case Surface.ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = w-temp; break; case Surface.ROTATION_180: scaledX = w-scaledX; scaledY = h-scaledY; break; case Surface.ROTATION_270: temp = scaledX; scaledX = h-scaledY; scaledY = temp; break; } if (scaledX == 0) { edgeFlags += MotionEvent.EDGE_LEFT; } else if (scaledX == display.getWidth() - 1.0f) { edgeFlags += MotionEvent.EDGE_RIGHT; } if (scaledY == 0) { edgeFlags += MotionEvent.EDGE_TOP; } else if (scaledY == display.getHeight() - 1.0f) { edgeFlags += MotionEvent.EDGE_BOTTOM; } } else { scaledX *= xMoveScale; scaledY *= yMoveScale; switch (orientation) { case Surface.ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = -temp; break; case Surface.ROTATION_180: scaledX = -scaledX; scaledY = -scaledY; break; case Surface.ROTATION_270: temp = scaledX; scaledX = -scaledY; scaledY = temp; break; } } changed = false; if (down != lastDown) { int action; lastDown = down; if (down) { action = MotionEvent.ACTION_DOWN; downTime = curTime; } else { action = MotionEvent.ACTION_UP; } currentMove = null; if (!isAbs) { x = y = 0; } return MotionEvent.obtain(downTime, curTime, action, scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device.id, edgeFlags); } else { if (currentMove != null) { if (false) Log.i("InputDevice", "Adding batch x=" + scaledX + " y=" + scaledY + " to " + currentMove); currentMove.addBatch(curTime, scaledX, scaledY, scaledPressure, scaledSize, metaState); if (WindowManagerPolicy.WATCH_POINTER) { Log.i("KeyInputQueue", "Updating: " + currentMove); } return null; } MotionEvent me = MotionEvent.obtain(downTime, curTime, MotionEvent.ACTION_MOVE, scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device.id, edgeFlags); currentMove = me; return me; } } } static class AbsoluteInfo { int minValue; int maxValue; int range; int flat; int fuzz; }; //once edit static class TransformInfo { float x1; float y1; float z1; float x2; float y2; float z2; float s; }; //edit ends InputDevice(int _id, int _classes, String _name, AbsoluteInfo _absX, AbsoluteInfo _absY, AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { id = _id; classes = _classes; name = _name; absX = _absX; absY = _absY; absPressure = _absPressure; absSize = _absSize; //once edit desFile = new File(CALIBRATION_FILE); readCalibrate(); //edit ends } static void readCalibrate(){ //xxw added Log.i("XXW","readCalibrate!"); TransformInfo t = null; try { FileInputStream is = new FileInputStream(CALIBRATION_FILE); byte[] mBuffer = new byte[64]; int len = is.read(mBuffer); is.close(); if (len > 0) { int i; for (i = 0 ; i < len ; i++) { if (mBuffer == '\n' || mBuffer == 0) { break; } } len = i; } StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0, len)); t = new TransformInfo (); t.x1 = Integer.parseInt( st.nextToken() ); t.y1 = Integer.parseInt( st.nextToken() ); t.z1 = Integer.parseInt( st.nextToken() ); t.x2 = Integer.parseInt( st.nextToken() ); t.y2 = Integer.parseInt( st.nextToken() ); t.z2 = Integer.parseInt( st.nextToken() ); t.s = Integer.parseInt( st.nextToken() ); } catch (java.io.FileNotFoundException e) { Log.i("XXW", "FileNotFound!"); } catch (java.io.IOException e) { Log.i("XXW", "IOException"); } tInfo = t; Log.i("XXW","readCalibrate done!"); } }; |
与上一次的那个InputDevice相比我将读取校准文件的代码单独的变成一个函数,之所以这么做是因为我们打算不重启就可以直接让android校准完成。这里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件然后将系统属性变成其他值不再读取文件。
下面我们就要写一个apk来实现校准了。
这里我尝试了2种方法一种是纯java的apk 一种是jni的apk。其实对于校准来说上层已经能拿到x,y那么我们取5个点就已经可以算出来那7个校准值然后利用java存文件了。整个apk的java代码
package com.android.calibrate; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.util.Log; public class Calibrate { private calibration cal; public Calibrate() { cal = new calibration(); } class calibration { int x[] = new int[5]; int xfb[] = new int[5]; int y[] = new int[5]; int yfb[] = new int[5]; int a[] = new int[7]; }; boolean perform_calibration() { Log.i("XXW", "perform_calibration"); int j; float n, x, y, x2, y2, xy, z, zx, zy; float det, a, b, c, e, f, i; float scaling = (float)65536.0; // Get sums for matrix n = x = y = x2 = y2 = xy = 0; for (j = 0; j < 5; j++) { n += 1.0; x += (float)cal.x[j]; y += (float)cal.y[j]; x2 += (float)(cal.x[j] * cal.x[j]); y2 += (float)(cal.y[j] * cal.y[j]); xy += (float)(cal.x[j] * cal.y[j]); } // Get determinant of matrix -- check if determinant is too small det = n * (x2 * y2 - xy * xy) + x * (xy * y - x * y2) + y * (x * xy - y * x2); if (det < 0.1 && det > -0.1) { Log.i("ts_calibrate: determinant is too small -- %f\n", "" + det); return false; } // Get elements of inverse matrix a = (x2 * y2 - xy * xy) / det; b = (xy * y - x * y2) / det; c = (x * xy - y * x2) / det; e = (n * y2 - y * y) / det; f = (x * y - n * xy) / det; i = (n * x2 - x * x) / det; // Get sums for x calibration z = zx = zy = 0; for (j = 0; j < 5; j++) { z += (float)cal.xfb[j]; zx += (float)(cal.xfb[j] * cal.x[j]); zy += (float)(cal.xfb[j] * cal.y[j]); } // Now multiply out to get the calibration for framebuffer x coord cal.a[0] = (int)((a * z + b * zx + c * zy) * (scaling)); cal.a[1] = (int)((b * z + e * zx + f * zy) * (scaling)); cal.a[2] = (int)((c * z + f * zx + i * zy) * (scaling)); System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c * z + f * zx + i * zy)); // Get sums for y calibration z = zx = zy = 0; for (j = 0; j < 5; j++) { z += (float)cal.yfb[j]; zx += (float)(cal.yfb[j] * cal.x[j]); zy += (float)(cal.yfb[j] * cal.y[j]); } // Now multiply out to get the calibration for framebuffer y coord cal.a[3] = (int)((a * z + b * zx + c * zy) * (scaling)); cal.a[4] = (int)((b * z + e * zx + f * zy) * (scaling)); cal.a[5] = (int)((c * z + f * zx + i * zy) * (scaling)); System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c * z + f * zx + i * zy)); // If we got here, we're OK, so assign scaling to a[6] and return cal.a[6] = (int)scaling; return true; /* * // This code was here originally to just insert default values * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1; * return 1; */ } void get_sample(int index, int x1, int y1, int x, int y) { Log.i("XXW", "get_sample"); cal.x[index] = x1; cal.y[index] = y1; cal.xfb[index] = x; cal.yfb[index] = y; } int calibrate_main() { int result = 0; Log.i("XXW", "calibrate_main"); if (perform_calibration()) { String strPara = String.format("%d %d %d %d %d %d %d", cal.a[1], cal.a[2], cal.a[0], cal.a[4], cal.a[5], cal.a[3], cal.a[6]); boolean success = new File("/data/etc").mkdir(); if (!success) { Log.i(this.toString(), "no success"); } File desFile = new File("/data/etc/pointercal"); if (!desFile.exists()) try { desFile.createNewFile(); } catch (IOException e1) { e1.printStackTrace(); } FileOutputStream fos; try { fos = new FileOutputStream(desFile); byte[] buf = strPara.getBytes(); int bytesRead = buf.length; try { fos.write(buf, 0, bytesRead); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } result = 0; } else { result = -1; } return result; } } |
其实这个就是tslib里那个ts_calibrate.cpp的姐妹篇。重要的函数就一个perform_calibration() 。这里要注意的是你必须首先利用get_sample读取5个点的值来初始化cal然后再调用calibrate_main来计算校验系数。而且那5个点的顺序为左上 右上 右下 左下 中间。这4个点基本上应该在屏幕的边缘附近否则计算出来的校验值可能不准。 利用上面的那个类写一个apk出来跑跑看流程应该就可以了。
package com.android.calibrate; import com.android.calibrate.R; import android.app.Activity; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.os.Bundle; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.view.View.OnTouchListener; import android.widget.Toast; import android.os.SystemProperties; public class AndroidCalibrate extends Activity { final String TAG = "ScreenCalibration"; final int UI_SCREEN_WIDTH = 800; final int UI_SCREEN_HEIGHT = 480; CrossView myview; int direction; private Calibrate cal; int xList[] = { 50, UI_SCREEN_WIDTH - 50, UI_SCREEN_WIDTH - 50, 50, UI_SCREEN_WIDTH / 2 }; int yList[] = { 50, UI_SCREEN_HEIGHT - 50, UI_SCREEN_HEIGHT - 50, 50, UI_SCREEN_HEIGHT / 2 }; static void setNotTitle(Activity act) { act.requestWindowFeature(Window.FEATURE_NO_TITLE); } static void setFullScreen(Activity act) { setNotTitle(act); act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); } public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setFullScreen(this); myview = new CrossView(this); setContentView(myview); SystemProperties.set("ts.config.calibrate", "start"); cal = new Calibrate(); direction = 0; myview.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { Log.i("OnTouch", event.getX() + "," + event.getY()); v.invalidate(); if (direction < 4) { Log.i("XXW time onTouchListener", " " + direction); cal.get_sample(direction, (int)event.getX(), (int)event.getY(), xList[direction], yList[direction]); } if (direction == 4) { cal.get_sample(direction, (int)event.getX(), (int)event.getY(), xList[direction], yList[direction]); Log.i("XXW", "calibrate_main"); cal.calibrate_main(); Toast.makeText(getBaseContext(), "Calibrate Done!", Toast.LENGTH_SHORT).show(); SystemProperties.set("ts.config.calibrate", "done"); AndroidCalibrate.this.finish(); } direction++; return false; } }); } public class CrossView extends View { public CrossView(Context context) { super(context); } public void onDraw(Canvas canvas) { Paint paint = new Paint(); paint.setColor(Color.GREEN); if (direction == 0) { canvas.drawLine(40, 50, 60, 50, paint); canvas.drawLine(50, 40, 50, 60, paint); paint.setColor(Color.WHITE); } else if (direction == 1) { canvas.drawLine(UI_SCREEN_WIDTH - 60, 50, UI_SCREEN_WIDTH - 40, 50, paint); canvas.drawLine(UI_SCREEN_WIDTH - 50, 40, UI_SCREEN_WIDTH - 50, 60, paint); paint.setColor(Color.WHITE); } else if (direction == 2) { canvas.drawLine(UI_SCREEN_WIDTH - 60, UI_SCREEN_HEIGHT - 50, UI_SCREEN_WIDTH - 40, UI_SCREEN_HEIGHT - 50, paint); canvas.drawLine(UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 60, UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 40, paint); paint.setColor(Color.WHITE); } else if (direction == 3) { canvas.drawLine(40, UI_SCREEN_HEIGHT - 50, 60, UI_SCREEN_HEIGHT - 50, paint); canvas.drawLine(50, UI_SCREEN_HEIGHT - 60, 50, UI_SCREEN_HEIGHT - 40, paint); paint.setColor(Color.WHITE); } else if (direction == 4) { canvas.drawLine(UI_SCREEN_WIDTH / 2 - 10, UI_SCREEN_HEIGHT / 2, UI_SCREEN_WIDTH / 2 + 10, UI_SCREEN_HEIGHT / 2, paint); canvas.drawLine(UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 - 10, UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 + 10, paint); paint.setColor(Color.WHITE); } else { } // canvas.drawText(getResources().getString(R.string. // screen_calibration_content), // UI_SCREEN_WIDTH / 2 - 50, UI_SCREEN_HEIGHT / 2, paint); super.onDraw(canvas); } } } 原文 http://xxw8393.blog.163.com/blog/static/37256834200988102647150/ 写的十分好,值得学习! |
转:http://blog.163.com/fenglang_200 ... 318200911172427618/
Tslib Manual Calibrate On Android
--by ONCE
1.android 的坐标转换处理
This implementation is a linear transformation using 7 parameters
(a, b, c, d, e, f and s) to transform the device coordinates (Xd, Yd)
into screen coordinates (Xs, Ys) using the following equations:
s*Xs = a*Xd + b*Yd + c
s*Ys = d*Xd + e*Yd + f
Xs,Ys:LCD 坐标(也就是所谓的绝对坐标)
Xd,Yd:触摸屏坐标
在编译好了的 ANDROID 根文件系统的 system/etc/pointercal 这个文件
内,存放着 7 个数,
这 7 个数就是对应上面公式的 a,b,c,d,e,f,s
比如我原来的:
+----+-----+--------+------+--+--------+----+
a | b | c |d |e | f |s |
+----+-----+--------+------+--+--------+----+
|-411|37818|-3636780|-51325|39|47065584|6553|
+----+-----+--------+------+--+--------+----+
2.tslib 库的移植
首先下载到一份 tslib 的代码,我用的是 tslib011.tar.gz(该包由
luzhuwei 同学友情赞助)。
将 tslib 解压到 external/下
要将一份代码编译到 android 里面去 其实没什么其他工作 主要就是那
个 Android.mk 文件的编写。网上找找 自己改改也就出来了。我的
Android.mk 文件内容如下:
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
TSLIB_PLUGINDIR := /system/lib/ts/plugins
LOCAL_SRC_FILES := \
src/ts_attach.c \
src/ts_close.c \
src/ts_config.c \
src/ts_error.c \
src/ts_fd.c \
src/ts_load_module.c \
src/ts_open.c \
src/ts_parse_vars.c \
src/ts_read.c \
src/ts_read_raw.c \
src/ts_read_raw_module.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl
LOCAL_MODULE := libts
include $(BUILD_SHARED_LIBRARY)
#
# plugin: input-raw
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/input-raw.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/input-raw
include $(BUILD_SHARED_LIBRARY)
#
# plugin: pthres
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/pthres.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/pthres
include $(BUILD_SHARED_LIBRARY)
#
# plugin: linear
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/linear.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/linear
include $(BUILD_SHARED_LIBRARY)
#
# plugin: dejitter
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/dejitter.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/dejitter
include $(BUILD_SHARED_LIBRARY)
#
# plugin: variance
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := plugins/variance.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts/plugins/variance
include $(BUILD_SHARED_LIBRARY)
#
# ts_calibrate
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_calibrate.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
tests/ts_calibrate.h \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_calibrate
include $(BUILD_EXECUTABLE)
#
# ts_test
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_test.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_test
include $(BUILD_EXECUTABLE)
#
# ts_print
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_print.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_print
include $(BUILD_EXECUTABLE)
#
# ts_print_raw
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_print_raw.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_print_raw
include $(BUILD_EXECUTABLE)
#
# ts_harvest
#
include $(CLEAR_VARS)
LOCAL_SRC_FILES := tests/testutils.c \
tests/fbutils.c \
tests/font_8x8.c \
tests/ts_harvest.c
LOCAL_C_INCLUDES += $(LOCAL_PATH)/src/ \
/usr/include/
LOCAL_SHARED_LIBRARIES := libdl \
libts
LOCAL_MODULE := ts_harvest
include $(BUILD_EXECUTABLE)
我真不愿意粘帖上面的这个文件...太大 不过方便后人拷贝。
这里要注意是我们需要修改一下 tslib 的几个相关文件,因为 android
里的部分设备文件位置比较特殊。还有就是 tslib 里的部分变量他是
定义在 Makefile 里的 我们要把它定义到我们的文件中去。
首先 fb0 的位置变了: tests/fbutils.c
static char *defaultfbdevice = "/dev/fb0";
static char *defaultfbdevice = "/dev/graphics/fb0";
其次 plugins 的目录变了 src/ts_load_module.c
#define PLUGIN_DIR "/system/lib/ts/plugins/"
最后 config 文件的位置变了 src/ts_config.c
#define TS_CONF "/system/etc/ts.conf"
最后的最后不得不提到我们的配置文件 ts.conf。其他都不要动 但是
必须将它的 module_raw input 注释出来。并且 因为我们的 input 模块
编译出来之后我们命名为 input-raw 了 所以这里得改成
module_raw input-raw
下面就是编译了。发现错误就手工拷贝一下编出来的库到需要它在的
目录。
3. ts_calibrate 生成 pointercal
这里要首先不启动 android,然后进命令行手工启动 ts_calibrate 程序。
然后在屏幕上校验,完成后会生成一个正确的 pointercal 文件。
4.Android 读取校验文件 pointercal
如果要让我们的 android 被校验 那莪没我们就要按上面的公式 将
android 读取坐标部分替换掉。
“system/etc/pointercal”这个文件是被 java 程序读取使用的,文件目录:
f rameworks/base/services/java/com/android/server/InputDevice.java
看了一下这个文件的代码 发现要被修改的坐标为
if (device.absX != null) {
//xxw added
if (device.tInfo != null){
scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y +
device.tInfo.z1)/ device.tInfo.s;
Log.i("XXW","x: "+x);
Log.i("XXW","trans x: "+scaledX);
}
else//end
scaledX = ((scaledX-device.absX.minValue)
/ device.absX.range) * w;
}
if (device.absY != null) {
//xxw added
if (device.tInfo != null){
scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y +
device.tInfo.z2) / device.tInfo.s;
Log.i("XXW","y: "+y);
Log.i("XXW","trans y: "+scaledY);
}
else //end
scaledY = ((scaledY-device.absY.minValue)
/ device.absY.range) * h;
}
对照上面的公式其实很容易理解 在注释 xxw 和 end 之间的代码就是
我修改的代码。其中的 device.tInfo 是我定义的一个结构,这个结构
是用来读取 pointercal 文件里的 7 个数字得到的对应的值,具体结构
如下:
static class TransformInfo {
float x1;
float y1;
float z1;
float x2;
float y2;
float z2;
float s;
};
读取文件的代码 我放在 InputDevice 的构造函数里:
//xxw added
TransformInfo t = null;
try {Log.i("XXW","InputDevice! try");
FileInputStream is = new FileInputStream(CALIBRATION_FILE);
byte[] mBuffer = new byte[64];
int len = is.read(mBuffer);
is.close();
if (len > 0) {Log.i("XXW","InputDevice! len>0");
int i;
for (i = 0 ; i < len ; i++) {
if (mBuffer == '\n' || mBuffer == 0) {
break;
}
}
len = i;
}Log.i("XXW","InputDevice! len"+len);
StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0,
len));
t = new TransformInfo ();
t.x1 = Integer.parseInt( st.nextToken() ); Log.i("XXW", "t.x1"+t.x1);
t.y1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y1"+t.y1);
t.z1 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z1"+t.z1);
t.x2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.x2"+t.x2);
t.y2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.y2"+t.y2);
t.z2 = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.z2"+t.z2);
t.s = Integer.parseInt( st.nextToken() );Log.i("XXW", "t.s"+t.s);
} catch (java.io.FileNotFoundException e) {Log.i("XXW",
"FileNotFound!");
} catch (java.io.IOException e) {Log.i("XXW", "IOException");
}
tInfo = t;
Log.i("XXW","InputDevice end!");
}
其中 static final String CALIBRATION_FILE = "/etc/pointercal";
这个时候点击一下屏幕看看是不是准确就知道是不是成功了。这里要
注意的是因为是在 InputDevice 里读取的文件 所以当我们中途替换了
pointercal 文件的时候 android 并不会再去读取这个文件。所以当我们
用 ts_calibrate 程序生成 pointercal 文件之后需要重启一下。同样 当我
们修改或者删除了那个文件 我们也是需要重启的。所以这个文档 的
名字叫手工校验。
PS:
如何自动化?
如何用 android 应用程序校验而不是命令行?
如何不需要重启也能实现?
请听下回分解。
话接上回,我们发现了手工利用tslib校验触摸屏的缺点。那么这一回我们就来一次稍微高级一点的校验吧。
我们其实只需要相对的x,y以及lcd的x,y就可以把校验系数算出来。这里要说的是lcd的x,y是绝对的准确的 比如我们要在(50,50)画一个十字 那么这个50,50就是我们认为的绝对坐标。我们要的只是从android通过getX()和getY()拿到我们需要的相对坐标。
其实一切我们打算做的事情可以都在InputDevice里面做完
下面我把完整之后整个的InputDevice贴出来:
/* * Copyright (C) 2007 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.server; import android.util.Log; import android.view.Display; import android.view.MotionEvent; import android.view.Surface; import android.view.WindowManagerPolicy; import java.io.FileInputStream; import java.util.StringTokenizer; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.os.SystemProperties; public class InputDevice { /** Amount that trackball needs to move in order to generate a key event. */ static final int TRACKBALL_MOVEMENT_THRESHOLD = 6; //once edit static final String CALIBRATION_FILE = "/data/etc/pointercal"; //edit ends final int id; final int classes; final String name; final AbsoluteInfo absX; final AbsoluteInfo absY; final AbsoluteInfo absPressure; final AbsoluteInfo absSize; //once edit static TransformInfo tInfo; //edit ends long mDownTime = 0; int mMetaKeysState = 0; static File desFile; final MotionState mAbs = new MotionState(0, 0); final MotionState mRel = new MotionState(TRACKBALL_MOVEMENT_THRESHOLD, TRACKBALL_MOVEMENT_THRESHOLD); static class MotionState { int xPrecision; int yPrecision; float xMoveScale; float yMoveScale; MotionEvent currentMove = null; boolean changed = false; boolean down = false; boolean lastDown = false; long downTime = 0; int x = 0; int y = 0; int pressure = 1; int size = 0; MotionState(int mx, int my) { xPrecision = mx; yPrecision = my; xMoveScale = mx != 0 ? (1.0f/mx) : 1.0f; yMoveScale = my != 0 ? (1.0f/my) : 1.0f; } MotionEvent generateMotion(InputDevice device, long curTime, boolean isAbs, Display display, int orientation, int metaState) { if (!changed) { return null; } //once edit //String prop = System.getProperty("ts.config.calibrate", "noset"); String prop = SystemProperties.get("ts.config.calibrate", "noset"); if (prop.equalsIgnoreCase("start")){ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase start"); device.tInfo = null; }else if (prop.equalsIgnoreCase("done")){ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase done"); readCalibrate(); SystemProperties.set("ts.config.calibrate", "end"); }else{ Log.i("XXW prop", prop); Log.i("XXW", "prop.equalsIgnoreCase else"); } //edit ends float scaledX = x; float scaledY = y; float temp; float scaledPressure = 1.0f; float scaledSize = 0; int edgeFlags = 0; if (isAbs) { int w = display.getWidth()-1; int h = display.getHeight()-1; if (orientation == Surface.ROTATION_90 || orientation == Surface.ROTATION_270) { int tmp = w; w = h; h = tmp; } if (device.absX != null) { //once edit if (device.tInfo != null){ scaledX = (device.tInfo.x1 * x + device.tInfo.y1 * y + device.tInfo.z1)/ device.tInfo.s; Log.i("XXW","x: "+x); Log.i("XXW","trans x: "+scaledX); } else //edit ends scaledX = ((scaledX-device.absX.minValue) / device.absX.range) * w; } if (device.absY != null) { //once edit if (device.tInfo != null){ scaledY = (device.tInfo.x2 * x + device.tInfo.y2 * y + device.tInfo.z2) / device.tInfo.s; Log.i("XXW","y: "+y); Log.i("XXW","trans y: "+scaledY); } else //edit ends scaledY = ((scaledY-device.absY.minValue) / device.absY.range) * h; } if (device.absPressure != null) { scaledPressure = ((pressure-device.absPressure.minValue) / (float)device.absPressure.range); } if (device.absSize != null) { scaledSize = ((size-device.absSize.minValue) / (float)device.absSize.range); } switch (orientation) { case Surface.ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = w-temp; break; case Surface.ROTATION_180: scaledX = w-scaledX; scaledY = h-scaledY; break; case Surface.ROTATION_270: temp = scaledX; scaledX = h-scaledY; scaledY = temp; break; } if (scaledX == 0) { edgeFlags += MotionEvent.EDGE_LEFT; } else if (scaledX == display.getWidth() - 1.0f) { edgeFlags += MotionEvent.EDGE_RIGHT; } if (scaledY == 0) { edgeFlags += MotionEvent.EDGE_TOP; } else if (scaledY == display.getHeight() - 1.0f) { edgeFlags += MotionEvent.EDGE_BOTTOM; } } else { scaledX *= xMoveScale; scaledY *= yMoveScale; switch (orientation) { case Surface.ROTATION_90: temp = scaledX; scaledX = scaledY; scaledY = -temp; break; case Surface.ROTATION_180: scaledX = -scaledX; scaledY = -scaledY; break; case Surface.ROTATION_270: temp = scaledX; scaledX = -scaledY; scaledY = temp; break; } } changed = false; if (down != lastDown) { int action; lastDown = down; if (down) { action = MotionEvent.ACTION_DOWN; downTime = curTime; } else { action = MotionEvent.ACTION_UP; } currentMove = null; if (!isAbs) { x = y = 0; } return MotionEvent.obtain(downTime, curTime, action, scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device.id, edgeFlags); } else { if (currentMove != null) { if (false) Log.i("InputDevice", "Adding batch x=" + scaledX + " y=" + scaledY + " to " + currentMove); currentMove.addBatch(curTime, scaledX, scaledY, scaledPressure, scaledSize, metaState); if (WindowManagerPolicy.WATCH_POINTER) { Log.i("KeyInputQueue", "Updating: " + currentMove); } return null; } MotionEvent me = MotionEvent.obtain(downTime, curTime, MotionEvent.ACTION_MOVE, scaledX, scaledY, scaledPressure, scaledSize, metaState, xPrecision, yPrecision, device.id, edgeFlags); currentMove = me; return me; } } } static class AbsoluteInfo { int minValue; int maxValue; int range; int flat; int fuzz; }; //once edit static class TransformInfo { float x1; float y1; float z1; float x2; float y2; float z2; float s; }; //edit ends InputDevice(int _id, int _classes, String _name, AbsoluteInfo _absX, AbsoluteInfo _absY, AbsoluteInfo _absPressure, AbsoluteInfo _absSize) { id = _id; classes = _classes; name = _name; absX = _absX; absY = _absY; absPressure = _absPressure; absSize = _absSize; //once edit desFile = new File(CALIBRATION_FILE); readCalibrate(); //edit ends } static void readCalibrate(){ //xxw added Log.i("XXW","readCalibrate!"); TransformInfo t = null; try { FileInputStream is = new FileInputStream(CALIBRATION_FILE); byte[] mBuffer = new byte[64]; int len = is.read(mBuffer); is.close(); if (len > 0) { int i; for (i = 0 ; i < len ; i++) { if (mBuffer == '\n' || mBuffer == 0) { break; } } len = i; } StringTokenizer st = new StringTokenizer( new String(mBuffer, 0, 0, len)); t = new TransformInfo (); t.x1 = Integer.parseInt( st.nextToken() ); t.y1 = Integer.parseInt( st.nextToken() ); t.z1 = Integer.parseInt( st.nextToken() ); t.x2 = Integer.parseInt( st.nextToken() ); t.y2 = Integer.parseInt( st.nextToken() ); t.z2 = Integer.parseInt( st.nextToken() ); t.s = Integer.parseInt( st.nextToken() ); } catch (java.io.FileNotFoundException e) { Log.i("XXW", "FileNotFound!"); } catch (java.io.IOException e) { Log.i("XXW", "IOException"); } tInfo = t; Log.i("XXW","readCalibrate done!"); } }; |
与上一次的那个InputDevice相比我将读取校准文件的代码单独的变成一个函数,之所以这么做是因为我们打算不重启就可以直接让android校准完成。这里其实也没什么东西 只是读取校验文件 如果读取成功了就用校验公式计算出校准后的坐标。 为了避免重启所以用了一个系统属性ts.config.calibrate来决定重新读取一次文件。当然当ts.config.calibrate值表明正在校验的话 就直接传上来点击的原始坐标而不经过换算。校验完成之后读取一次校验文件然后将系统属性变成其他值不再读取文件。
下面我们就要写一个apk来实现校准了。
这里我尝试了2种方法一种是纯java的apk 一种是jni的apk。其实对于校准来说上层已经能拿到x,y那么我们取5个点就已经可以算出来那7个校准值然后利用java存文件了。整个apk的java代码
package com.android.calibrate; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import android.util.Log; public class Calibrate { private calibration cal; public Calibrate() { cal = new calibration(); } class calibration { int x[] = new int[5]; int xfb[] = new int[5]; int y[] = new int[5]; int yfb[] = new int[5]; int a[] = new int[7]; }; boolean perform_calibration() { Log.i("XXW", "perform_calibration"); int j; float n, x, y, x2, y2, xy, z, zx, zy; float det, a, b, c, e, f, i; float scaling = (float)65536.0; // Get sums for matrix n = x = y = x2 = y2 = xy = 0; for (j = 0; j < 5; j++) { n += 1.0; x += (float)cal.x[j]; y += (float)cal.y[j]; x2 += (float)(cal.x[j] * cal.x[j]); y2 += (float)(cal.y[j] * cal.y[j]); xy += (float)(cal.x[j] * cal.y[j]); } // Get determinant of matrix -- check if determinant is too small det = n * (x2 * y2 - xy * xy) + x * (xy * y - x * y2) + y * (x * xy - y * x2); if (det < 0.1 && det > -0.1) { Log.i("ts_calibrate: determinant is too small -- %f\n", "" + det); return false; } // Get elements of inverse matrix a = (x2 * y2 - xy * xy) / det; b = (xy * y - x * y2) / det; c = (x * xy - y * x2) / det; e = (n * y2 - y * y) / det; f = (x * y - n * xy) / det; i = (n * x2 - x * x) / det; // Get sums for x calibration z = zx = zy = 0; for (j = 0; j < 5; j++) { z += (float)cal.xfb[j]; zx += (float)(cal.xfb[j] * cal.x[j]); zy += (float)(cal.xfb[j] * cal.y[j]); } // Now multiply out to get the calibration for framebuffer x coord cal.a[0] = (int)((a * z + b * zx + c * zy) * (scaling)); cal.a[1] = (int)((b * z + e * zx + f * zy) * (scaling)); cal.a[2] = (int)((c * z + f * zx + i * zy) * (scaling)); System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c * z + f * zx + i * zy)); // Get sums for y calibration z = zx = zy = 0; for (j = 0; j < 5; j++) { z += (float)cal.yfb[j]; zx += (float)(cal.yfb[j] * cal.x[j]); zy += (float)(cal.yfb[j] * cal.y[j]); } // Now multiply out to get the calibration for framebuffer y coord cal.a[3] = (int)((a * z + b * zx + c * zy) * (scaling)); cal.a[4] = (int)((b * z + e * zx + f * zy) * (scaling)); cal.a[5] = (int)((c * z + f * zx + i * zy) * (scaling)); System.out.printf("%f %f %f\n", (a * z + b * zx + c * zy), (b * z + e * zx + f * zy), (c * z + f * zx + i * zy)); // If we got here, we're OK, so assign scaling to a[6] and return cal.a[6] = (int)scaling; return true; /* * // This code was here originally to just insert default values * for(j=0;j<7;j++) { c->a[j]=0; } c->a[1] = c->a[5] = c->a[6] = 1; * return 1; */ } void get_sample(int index, int x1, int y1, int x, int y) { Log.i("XXW", "get_sample"); cal.x[index] = x1; cal.y[index] = y1; cal.xfb[index] = x; cal.yfb[index] = y; } int calibrate_main() { int result = 0; Log.i("XXW", "calibrate_main"); if (perform_calibration()) { String strPara = String.format("%d %d %d %d %d %d %d", cal.a[1], cal.a[2], cal.a[0], cal.a[4], cal.a[5], cal.a[3], cal.a[6]); boolean success = new File("/data/etc").mkdir(); if (!success) { Log.i(this.toString(), "no success"); } File desFile = new File("/data/etc/pointercal"); if (!desFile.exists()) try { desFile.createNewFile(); } catch (IOException e1) { e1.printStackTrace(); } FileOutputStream fos; try { fos = new FileOutputStream(desFile); byte[] buf = strPara.getBytes(); int bytesRead = buf.length; try { fos.write(buf, 0, bytesRead); fos.flush(); fos.close(); } catch (IOException e) { e.printStackTrace(); } } catch (FileNotFoundException e) { e.printStackTrace(); } result = 0; } else { result = -1; } return result; } } |
其实这个就是tslib里那个ts_calibrate.cpp的姐妹篇。重要的函数就一个perform_calibration() 。这里要注意的是你必须首先利用get_sample读取5个点的值来初始化cal然后再调用calibrate_main来计算校验系数。而且那5个点的顺序为左上 右上 右下 左下 中间。这4个点基本上应该在屏幕的边缘附近否则计算出来的校验值可能不准。 利用上面的那个类写一个apk出来跑跑看流程应该就可以了。
package com.android.calibrate;
import com.android.calibrate.R;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Bundle;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.View.OnTouchListener;
import android.widget.Toast;
import android.os.SystemProperties;
public class AndroidCalibrate extends Activity {
final String TAG = "ScreenCalibration";
final int UI_SCREEN_WIDTH = 800;
final int UI_SCREEN_HEIGHT = 480;
CrossView myview;
int direction;
private Calibrate cal;
int xList[] = {
50, UI_SCREEN_WIDTH - 50, UI_SCREEN_WIDTH - 50, 50, UI_SCREEN_WIDTH / 2
};
int yList[] = {
50, UI_SCREEN_HEIGHT - 50, UI_SCREEN_HEIGHT - 50, 50, UI_SCREEN_HEIGHT / 2
};
static void setNotTitle(Activity act) {
act.requestWindowFeature(Window.FEATURE_NO_TITLE);
}
static void setFullScreen(Activity act) {
setNotTitle(act);
act.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
}
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setFullScreen(this);
myview = new CrossView(this);
setContentView(myview);
SystemProperties.set("ts.config.calibrate", "start");
cal = new Calibrate();
direction = 0;
myview.setOnTouchListener(new OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
Log.i("OnTouch", event.getX() + "," + event.getY());
v.invalidate();
if (direction < 4) {
Log.i("XXW time onTouchListener", " " + direction);
cal.get_sample(direction, (int)event.getX(), (int)event.getY(),
xList[direction], yList[direction]);
}
if (direction == 4) {
cal.get_sample(direction, (int)event.getX(), (int)event.getY(),
xList[direction], yList[direction]);
Log.i("XXW", "calibrate_main");
cal.calibrate_main();
Toast.makeText(getBaseContext(), "Calibrate Done!", Toast.LENGTH_SHORT).show();
SystemProperties.set("ts.config.calibrate", "done");
AndroidCalibrate.this.finish();
}
direction++;
return false;
}
});
}
public class CrossView extends View {
public CrossView(Context context) {
super(context);
}
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setColor(Color.GREEN);
if (direction == 0) {
canvas.drawLine(40, 50, 60, 50, paint);
canvas.drawLine(50, 40, 50, 60, paint);
paint.setColor(Color.WHITE);
} else if (direction == 1) {
canvas.drawLine(UI_SCREEN_WIDTH - 60, 50, UI_SCREEN_WIDTH - 40, 50, paint);
canvas.drawLine(UI_SCREEN_WIDTH - 50, 40, UI_SCREEN_WIDTH - 50, 60, paint);
paint.setColor(Color.WHITE);
} else if (direction == 2) {
canvas.drawLine(UI_SCREEN_WIDTH - 60, UI_SCREEN_HEIGHT - 50, UI_SCREEN_WIDTH - 40,
UI_SCREEN_HEIGHT - 50, paint);
canvas.drawLine(UI_SCREEN_WIDTH - 50, UI_SCREEN_HEIGHT - 60, UI_SCREEN_WIDTH - 50,
UI_SCREEN_HEIGHT - 40, paint);
paint.setColor(Color.WHITE);
} else if (direction == 3) {
canvas.drawLine(40, UI_SCREEN_HEIGHT - 50, 60, UI_SCREEN_HEIGHT - 50, paint);
canvas.drawLine(50, UI_SCREEN_HEIGHT - 60, 50, UI_SCREEN_HEIGHT - 40, paint);
paint.setColor(Color.WHITE);
} else if (direction == 4) {
canvas.drawLine(UI_SCREEN_WIDTH / 2 - 10, UI_SCREEN_HEIGHT / 2,
UI_SCREEN_WIDTH / 2 + 10, UI_SCREEN_HEIGHT / 2, paint);
canvas.drawLine(UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 - 10,
UI_SCREEN_WIDTH / 2, UI_SCREEN_HEIGHT / 2 + 10, paint);
paint.setColor(Color.WHITE);
} else {
}
// canvas.drawText(getResources().getString(R.string.
// screen_calibration_content),
// UI_SCREEN_WIDTH / 2 - 50, UI_SCREEN_HEIGHT / 2, paint);
super.onDraw(canvas);
}
}
}