ZenBrush(禪宗畫筆)反編譯後二次開發(電子簽名_毛筆帶筆鋒的效果)
最近POS機專案有個需求,電子簽名,就是一個畫板上簽名。看了一些文章,把貝塞爾曲線演算法用上了效果也
不怎麼樣,然後在Github上也找了些demo,有個brushes還不錯,但沒有毛筆筆鋒那種效果,找啊找,終於找到
ZenBrush(中文名:禪宗畫筆,好像是日本人開發的),的確做的非常好,但沒有sdk,也沒有開源。
想著能否通過反編譯的方式,複用裡面的核心程式碼!通過幾天嘗試可行,但是,如果二次開發的apk掛在了複用裡
面的程式碼時,就沒辦法通過java程式碼修復了,只能通過smail修復了,這個需要反編譯相關知識了。
來張毛筆帶筆鋒的效果圖,沒有書法功底別見笑。
現在分析下二次開發思路:
1. 寫一個打log的檔案ApktoolLog.java,方便在反編譯的smail檔案中注入程式碼(通常一行,根據需要在
ApktoolLog加函式,爭取注入時只要一行程式碼),下面貼下ApktoolLog.java的簡易程式碼(但很實用哦)
將它反編譯成smail檔案ApktoolLog.smali拷貝到對應目錄。package com.example.helloworld; import android.util.Log; public class ApktoolLog { private static final String TAG = "ApktoolLog"; public static void printStackTrace() { for (StackTraceElement i : Thread.currentThread().getStackTrace()) { Log.e(TAG, "" + i); } } public static void e(String info) { Log.e(TAG, "String:" + info); } public static void e(int info) { Log.e(TAG, "int:" + info); } public static void e(long info) { Log.e(TAG, "long:" + info); } public static void e(float info) { Log.e(TAG, "float:" + info); } public static void e(double info) { Log.e(TAG, "double:" + info); } public static void e(float paramFloat1, float paramFloat2, float paramFloat3, float paramFloat4, float paramFloat5, float paramFloat6, float paramFloat7, float paramFloat8) { Log.e(TAG, "paramFloat1:" + paramFloat1 + ", paramFloat2:" + paramFloat2 + ", paramFloat3:" + paramFloat3 + ", paramFloat4:" + paramFloat4 + ", paramFloat5:" + paramFloat5 + ", paramFloat6:" + paramFloat6 + ", paramFloat7:" + paramFloat7 + ", paramFloat8:" + paramFloat8); } }
2.反編譯原始的ZenBrush.apk在smail中注入log程式碼,得到關鍵引數值,下面詳解設定畫筆樣式的關鍵引數
找到設定畫筆樣式程式碼在ZenBrush/smali/jp/co/psoft/zenbrushfree/library/a.smali:209$ grep -rn "setBrushType" ZenBrush 匹配到二進位制檔案 ZenBrush/build/apk/lib/armeabi-v7a/libZenBrushRenderer.so 匹配到二進位制檔案 ZenBrush/build/apk/lib/armeabi/libZenBrushRenderer.so ZenBrush/smali/jp/co/psoft/zenbrushfree/library/a.smali:209: invoke-virtual {v0, v1}, Ljp/co/psoft/ZenBrushLib/ZenBrushRenderer;->setBrushType(I)V ZenBrush/smali/jp/co/psoft/ZenBrushLib/ZenBrushRenderer.smali:528:.method public native setBrushType(I)V
smail和Java Decompiler對比,然後在smail的209行上面注入log程式碼把v1的值打印出來,就知道ZenBrush.apk
當前設定樣式的引數了
.method private c(Ljp/co/psoft/zenbrushfree/library/b;)V
.locals 2
iget-object v0, p0, Ljp/co/psoft/zenbrushfree/library/a;->g:Ljp/co/psoft/ZenBrushLib/ZenBrushRenderer;
invoke-virtual {p1}, Ljp/co/psoft/zenbrushfree/library/b;->a()I
move-result v1
invoke-static {v1}, Lcom/example/helloworld/ApktoolLog;->e(I)V
invoke-virtual {v0, v1}, Ljp/co/psoft/ZenBrushLib/ZenBrushRenderer;->setBrushType(I)V
iget-object v0, p0, Ljp/co/psoft/zenbrushfree/library/a;->g:Ljp/co/psoft/ZenBrushLib/ZenBrushRenderer;
iget v1, p1, Ljp/co/psoft/zenbrushfree/library/b;->g:F
invoke-virtual {v0, v1}, Ljp/co/psoft/ZenBrushLib/ZenBrushRenderer;->setBrushAlpha(F)V
iput-object p1, p0, Ljp/co/psoft/zenbrushfree/library/a;->d:Ljp/co/psoft/zenbrushfree/library/b;
iget-object v0, p0, Ljp/co/psoft/zenbrushfree/library/a;->h:[I
invoke-virtual {p1}, Ljp/co/psoft/zenbrushfree/library/b;->ordinal()I
move-result v1
aget v0, v0, v1
invoke-direct {p0, v0}, Ljp/co/psoft/zenbrushfree/library/a;->b(I)V
return-void
.end method
然後回編,簽名,安裝執行抓log,過濾關鍵字,就能一一找到關鍵值的引數。
3. 在自己的工程裡建同名的包名,類名,函式,可以在Java Decompiler裡直接拷貝,將函式體去掉,如果有
返回值的,隨便return一個對應值即可。如核心檔案ZenBrushRenderer程式碼
package jp.co.psoft.ZenBrushLib;
import android.opengl.GLSurfaceView;
import android.view.MotionEvent;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
public class ZenBrushRenderer implements GLSurfaceView.Renderer {
static {
System.loadLibrary("ZenBrushRenderer");
}
public ZenBrushRenderer() {
}
private native void onNdkDrawFrame();
private native void onNdkSurfaceChanged(int paramInt1, int paramInt2);
public final int a() {
return 0;
}
public final boolean a(MotionEvent paramMotionEvent) {
return true;
}
public final int b() {
return 0;
}
public native boolean canClearInk();
public native boolean canRedo();
public native boolean canUndo();
public native boolean canvasInitialize(int paramInt1, int paramInt2);
public native boolean canvasUpdate(int paramInt, float paramFloat1,
float paramFloat2, float paramFloat3, double paramDouble);
public native void clearInk();
public native boolean getImageARGB8888(int[] paramArrayOfInt);
public native boolean getInkImageARGB8888(int[] paramArrayOfInt);
public void onDrawFrame(GL10 paramGL10) {
}
public void onSurfaceChanged(GL10 paramGL10, int paramInt1, int paramInt2) {
}
public void onSurfaceCreated(GL10 paramGL10, EGLConfig paramEGLConfig) {
}
public native void redo();
public native void setBackgroundColor(float paramFloat1, float paramFloat2,
float paramFloat3);
public native boolean setBackgroundImageARGB8888(int[] paramArrayOfInt,
int paramInt1, int paramInt2);
public native void setBrushAlpha(float paramFloat);
public native void setBrushSize(float paramFloat);
public native void setBrushTintColor(float paramFloat1, float paramFloat2,
float paramFloat3, float paramFloat4, float paramFloat5,
float paramFloat6, float paramFloat7, float paramFloat8);
public native void setBrushType(int paramInt);
public native boolean setInkImageARGB8888(int[] paramArrayOfInt);
public native void undo();
}
jni的相關函式宣告及部分成員函式
4. 編寫自己的檔案,並呼叫複用的檔案及函式,這一步很複雜,需要對比smail及Java Decompiler的java程式碼,
並找到呼叫關係,這個需要反編譯相關知識,不懂得略過吧,下面貼出我自己的MainActivity
package com.example.helloworld;
import java.util.concurrent.FutureTask;
import com.example.helloworld.utils.BitmapUtils;
import jp.co.psoft.ZenBrushLib.ZenBrushGLSurfaceView;
import jp.co.psoft.ZenBrushLib.ZenBrushRenderer;
import jp.co.psoft.zenbrushfree.library.e;
import jp.co.psoft.zenbrushfree.library.i;
import jp.co.psoft.zenbrushfree.library.n;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;
public class MainActivity extends Activity implements OnClickListener, i {
private ZenBrushGLSurfaceView mBrushGLSurfaceView;
private Button mBtnClear;
private Button mBtnUndo;
private Button mBtnRedo;
private float mBrushSize = 10.0f;
private TYPE_CALLBACK mTypeCallback = TYPE_CALLBACK.NONE;
private enum TYPE_CALLBACK {
NONE, SAVE, PRINT
}
private static final int EVENT_SHOW_TOAST = 1;
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SHOW_TOAST:
Toast.makeText(MainActivity.this, (String) msg.obj,
Toast.LENGTH_LONG).show();
break;
}
}
};
@Override
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
setContentView(R.layout.activity_main);
try {
getWindow().addFlags(
WindowManager.LayoutParams.class.getField(
"FLAG_NEEDS_MENU_KEY").getInt(null));
} catch (NoSuchFieldException e) {
} catch (IllegalAccessException e) {
}
mBrushGLSurfaceView = (ZenBrushGLSurfaceView) findViewById(R.id.view);
// 設定畫布背景顏色
mBrushGLSurfaceView.a.setBackgroundColor(255F, 255F, 255F);
// 設定畫筆顏色
mBrushGLSurfaceView.a.setBrushTintColor(0f, 0f, 0f, 1.0f, 0f, 0f, 0f,
1.0f);
// 設定畫筆型別
mBrushGLSurfaceView.a.setBrushType(0);
mBrushGLSurfaceView.a.setBrushAlpha(1.0f);
// 設定畫筆大小
mBrushGLSurfaceView.a.setBrushSize(mBrushSize);
mBtnClear = (Button) findViewById(R.id.clear_lnk);
mBtnUndo = (Button) findViewById(R.id.undo);
mBtnRedo = (Button) findViewById(R.id.redo);
mBtnClear.setOnClickListener(this);
mBtnUndo.setOnClickListener(this);
mBtnRedo.setOnClickListener(this);
}
@Override
public void onClick(View view) {
// TODO Auto-generated method stub
if (view == mBtnClear) {
mBrushGLSurfaceView.a.clearInk();
} else if (view == mBtnUndo) {
mBrushGLSurfaceView.a.undo();
} else if (view == mBtnRedo) {
mBrushGLSurfaceView.a.redo();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// TODO Auto-generated method stub
getMenuInflater().inflate(R.menu.main_menu, menu);
return super.onCreateOptionsMenu(menu);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// TODO Auto-generated method stub
switch (item.getItemId()) {
case R.id.action_save:
mTypeCallback = TYPE_CALLBACK.SAVE;
new e(mBrushGLSurfaceView).a(this);
break;
case R.id.action_print:
mTypeCallback = TYPE_CALLBACK.PRINT;
new e(mBrushGLSurfaceView).a(this);
break;
case R.id.action_brush_size_increase:
mBrushSize += 1;
mBrushGLSurfaceView.a.setBrushSize(mBrushSize);
Toast.makeText(this, "Set brush size : " + mBrushSize,
Toast.LENGTH_SHORT).show();
break;
case R.id.action_brush_size_decrease:
mBrushSize -= 1;
mBrushGLSurfaceView.a.setBrushSize(mBrushSize);
Toast.makeText(this, "Set brush size : " + mBrushSize,
Toast.LENGTH_SHORT).show();
break;
}
return super.onOptionsItemSelected(item);
}
private Bitmap getGLBitmap() {
int width = mBrushGLSurfaceView.getWidth();
int height = mBrushGLSurfaceView.getHeight();
Bitmap bitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
int[] pixels = new int[width * height];
mBrushGLSurfaceView.a.getImageARGB8888(pixels);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
@Override
public void a(n paramn) {
// TODO Auto-generated method stub
Bitmap bitmap = BitmapUtils.createBitmap(paramn);
switch (mTypeCallback) {
case SAVE:
String fileName = BitmapUtils.saveBitmapAsPng(BitmapUtils
.adjustPhotoRotation(bitmap, 90));
if (!TextUtils.isEmpty(fileName)) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SHOW_TOAST,
fileName));
}
break;
case PRINT:
break;
}
}
}
拓展,POS機列印電子簽名,有響應的SDK列印Bitmap(可能需要旋轉,放縮等動作)即可
@Override
public void a(n paramn) {
// TODO Auto-generated method stub
Bitmap bitmap = BitmapUtils.createBitmap(paramn);
switch (mTypeCallback) {
case SAVE:
String fileName = BitmapUtils.saveBitmapAsPng(BitmapUtils
.adjustPhotoRotation(bitmap, 90));
if (!TextUtils.isEmpty(fileName)) {
mHandler.sendMessage(mHandler.obtainMessage(EVENT_SHOW_TOAST,
fileName));
}
break;
case PRINT:
mESCPrinter.sendBitmap(
0,
BitmapUtils.scaleBitmap(bitmap, 1.0f
* ESCPrinter.PAGE_WIDTH / bitmap.getWidth()));
break;
}
}
效果圖如下:
列印效果還可以哈
附件下載(包括MyZenBrush(不支援列印),MyZenBrush_ESCPrinter(支援列印),ZenBrush_smail(複用核心程式碼))