欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果)

程序员文章站 2024-03-16 11:11:28
...

最近POS机项目有个需求,电子签名,就是一个画板上签名。看了一些文章,把贝塞尔曲线算法用上了效果也

不怎么样,然后在Github上也找了些demo,有个brushes还不错,但没有毛笔笔锋那种效果,找啊找,终于找到

ZenBrush(中文名:禅宗画笔,好像是日本人开发的),的确做的非常好,但没有sdk,也没有开源。

想着能否通过反编译的方式,复用里面的核心代码!通过几天尝试可行,但是,如果二次开发的apk挂在了复用里

面的代码时,就没办法通过java代码修复了,只能通过smail修复了,这个需要反编译相关知识了。

来张毛笔带笔锋的效果图,没有书法功底别见笑。

ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果)

现在分析下二次开发思路:

1. 写一个打log的文件ApktoolLog.java,方便在反编译的smail文件中注入代码(通常一行,根据需要在

ApktoolLog加函数,争取注入时只要一行代码),下面贴下ApktoolLog.java的简易代码(但很实用哦)

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);
    }

}
将它反编译成smail文件ApktoolLog.smali拷贝到对应目录。

2.反编译原始的ZenBrush.apk在smail中注入log代码,得到关键参数值,下面详解设置画笔样式的关键参数

$ 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
找到设置画笔样式代码在ZenBrush/smali/jp/co/psoft/zenbrushfree/library/a.smali:209

ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果)

ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果) 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;
        }

    }
效果图如下:

ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果)
打印效果还可以哈ZenBrush(禅宗画笔)反编译后二次开发(电子签名_毛笔带笔锋的效果)


附件下载(包括MyZenBrush(不支持打印),MyZenBrush_ESCPrinter(支持打印),ZenBrush_smail(复用核心代码))