Android 触摸屏校准
--by ONCE
话接上回,我们发现了手工利用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[i] == '/n' || mBuffer[i] == 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出来跑跑看 流程应该就可以了。