JNI实战(一)实现山寨版美图软件

    清代诗人钱大昕在《十驾斋养新录·改过》中写道:“圣贤以改过为能,不以无过为贵”。我感觉这句话说的很有道理。是啊,没有人是天生什么都会的。第一次做出错了,把错误改正,把不会的弥补上,下一次不就会了嘛!所以说关键还是在于勇于尝试和勤于改正呀!

    好了,又扯远了。言归正传,上一周发了一篇笑话结果浏览量惨不忍睹,看来大家还是喜欢技术类的文章。好吧,本文将使用JNI技术来实现一个山寨版的美图软件。需要的文件有有关美图的动态链接库.so文件及对应的JNI类,没有的话可以来这里下载。

    简单介绍一下JNI。JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。由于篇幅原因搭建JNI开发环境就不介绍了,这方面的资料网上有很多,咱们直接进入正题。

    第一步,创建工程,没什么好说的,别忘了勾选“Include C++ support”选项。

    第二步,把对应的.so文件拷贝到java/main/jniLibs目录,并创建JNI.java。

    之后,在加载动态链接库,在JNI类中添加如下代码:

{
        System.loadLibrary("mtimage-jni");
}

    第四步,写布局文件,很简单,不解释。代码如下:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.example.data.jni001.MainActivity">

    <ScrollView
        android:layout_width="wrap_content"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <ImageView
                android:id="@+id/iv_icon"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:src="@drawable/pic001" />

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content">

                <Button
                    android:id="@+id/choose"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="选择图片" />

                <Button
                    android:id="@+id/save"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="保存图片" />

                <Button
                    android:id="@+id/reset"
                    android:layout_width="0dp"
                    android:layout_weight="1"
                    android:layout_height="wrap_content"
                    android:text="原始图片"/>

            </LinearLayout>

            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                app:layout_constraintEnd_toEndOf="@id/iv_icon">

                <Button
                    android:id="@+id/gaoliang"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="高亮" />

                <Button
                    android:id="@+id/huaijiu"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="怀旧" />

                <Button
                    android:id="@+id/baohe"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="饱和" />

                <Button
                    android:id="@+id/heibai"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="黑白" />

                <Button
                    android:id="@+id/huanyuan"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_weight="1"
                    android:text="还原" />
            </LinearLayout>
        </LinearLayout>
    </ScrollView>
</LinearLayout>

    程序界面如下:

    第五步,也是最重要的,编写Java代码。 

    初始化代码如下:

bitMap = BitmapFactory.decodeResource(getResources(),R.drawable.pic001);
pixels = new int[bitMap.getWidth()*bitMap.getHeight()];
pic = new int[bitMap.getWidth()*bitMap.getHeight()];
bitMap.getPixels(pixels,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());
bitMap.getPixels(pic,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());

    定义两个数组,分别pic用来存储原始图片pixel用来存储之后加载的图片。用原始图片生成Bitmap,并存入数组方便后续处理。getPixels()函数的几个参数含义分别为:接收位图颜色值的数组、写入到pixels[]中的第一个像素索引值、pixels[]中的行间距个数值(必须大于等于位图宽度,可以为负数)、从位图中读取的第一个像素的x坐标值、从位图中读取的第一个像素的y坐标值、从每一行中读取的像素宽度和读取的行数。

    设置几个按钮的点击事件:

        gaoLiang.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                jni.StyleLomoHDR(pixels,bitMap.getWidth(),bitMap.getHeight());
                bitMap = Bitmap.createBitmap(pixels,bitMap.getWidth(),bitMap.getHeight(),Bitmap.Config.ARGB_8888);
                ivIcon.setImageBitmap(bitMap);
            }
        });
        huaiJiu.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                jni.StyleLomoB(pixels, bitMap.getWidth(), bitMap.getHeight());
                bitMap =  Bitmap.createBitmap(pixels,bitMap.getWidth(),bitMap.getHeight(), Bitmap.Config.ARGB_8888);
                ivIcon.setImageBitmap(bitMap);
            }
        });
        baoHe.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                jni.StyleBaoColor(pixels, bitMap.getWidth(), bitMap.getHeight());
                bitMap =  Bitmap.createBitmap(pixels,bitMap.getWidth(),bitMap.getHeight(), Bitmap.Config.ARGB_8888);
                ivIcon.setImageBitmap(bitMap);
            }
        });
        heiBai.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                jni.StyleLomoC(pixels, bitMap.getWidth(), bitMap.getHeight());
                bitMap =  Bitmap.createBitmap(pixels,bitMap.getWidth(),bitMap.getHeight(), Bitmap.Config.ARGB_8888);
                ivIcon.setImageBitmap(bitMap);
            }
        });
        huanYuan.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bitMap =  Bitmap.createBitmap(pic,bitMap.getWidth(),bitMap.getHeight(), Bitmap.Config.ARGB_8888);
                ivIcon.setImageBitmap(bitMap);
                pixels = new int[bitMap.getWidth()*bitMap.getHeight()];
                bitMap.getPixels(pixels,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());
            }
        });
        reset.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bitMap = BitmapFactory.decodeResource(getResources(),R.drawable.pic001);
                pixels = new int[bitMap.getWidth()*bitMap.getHeight()];
                bitMap.getPixels(pixels,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());
                ivIcon.setImageBitmap(bitMap);
            }
        });

    在选择照片时提供从相册选取和拍照两种模式:

choose.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                List<String> names = new ArrayList<>();
                names.add("拍照");
                names.add("相册");
                showDialog(new SelectDialog.SelectDialogListener() {
                    @Override
                    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                        switch (position){
                            case 0:
                                pictureName = "MLGJing" + System.currentTimeMillis() + ".jpg";
                                takePhoto();
                                break;
                            case 1:
                                choosePicture();
                        }
                    }
                },names);
            }
        });
    private SelectDialog showDialog(SelectDialog.SelectDialogListener listener, List<String> names) {
        SelectDialog dialog = new SelectDialog(MainActivity.this, R.style.transparentFrameWindowStyle,listener, names);
        if (!MainActivity.this.isFinishing()) {
            dialog.show();
        }
        return dialog;
    }

SelectDialog工具类: 

package com.example.data.jni001;

import android.app.Activity;
import android.app.Dialog;
import android.os.Bundle;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ListView;
import android.widget.TextView;

import java.util.List;

/**
 * 选择对话框
 *
 * Author: nanchen
 * Email: [email protected]
 * Date: 2017-03-22  11:38
 */

public class SelectDialog extends Dialog implements OnClickListener,OnItemClickListener {
    private SelectDialogListener mListener;
    private Activity mActivity;
    private Button mMBtn_Cancel;
    private TextView mTv_Title;
    private List<String> mName;
    private String mTitle;
    private boolean mUseCustomColor = false;
    private int mFirstItemColor;
    private int mOtherItemColor;

    public interface SelectDialogListener {
        public void onItemClick(AdapterView<?> parent, View view, int position, long id);
    }


    /**
     * 取消事件监听接口
     *
     */
    private SelectDialogCancelListener mCancelListener;

    public interface SelectDialogCancelListener {
        public void onCancelClick(View v);
    }

    public SelectDialog(Activity activity, int theme,
                        SelectDialogListener listener, List<String> names) {
        super(activity, theme);
        mActivity = activity;
        mListener = listener;
        this.mName=names;

        setCanceledOnTouchOutside(true);
    }

    /**
     * @param activity 调用弹出菜单的activity
     * @param theme 主题
     * @param listener 菜单项单击事件
     * @param cancelListener 取消事件
     * @param names 菜单项名称
     *
     */
    public SelectDialog(Activity activity, int theme, SelectDialogListener listener, SelectDialogCancelListener cancelListener , List<String> names) {
        super(activity, theme);
        mActivity = activity;
        mListener = listener;
        mCancelListener = cancelListener;
        this.mName=names;

        // 设置是否点击外围不解散
        setCanceledOnTouchOutside(false);
    }

    /**
     * @param activity 调用弹出菜单的activity
     * @param theme 主题
     * @param listener 菜单项单击事件
     * @param names 菜单项名称
     * @param title 菜单标题文字
     *
     */
    public SelectDialog(Activity activity, int theme, SelectDialogListener listener, List<String> names, String title) {
        super(activity, theme);
        mActivity = activity;
        mListener = listener;
        this.mName=names;
        mTitle = title;

        // 设置是否点击外围可解散
        setCanceledOnTouchOutside(true);
    }

    public SelectDialog(Activity activity, int theme, SelectDialogListener listener, SelectDialogCancelListener cancelListener, List<String> names, String title) {
        super(activity, theme);
        mActivity = activity;
        mListener = listener;
        mCancelListener = cancelListener;
        this.mName=names;
        mTitle = title;

        // 设置是否点击外围可解散
        setCanceledOnTouchOutside(true);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        View view = getLayoutInflater().inflate(R.layout.view_dialog_select,
                null);
        setContentView(view, new LayoutParams(LayoutParams.FILL_PARENT,
                LayoutParams.WRAP_CONTENT));
        Window window = getWindow();
        // 设置显示动画
        window.setWindowAnimations(R.style.main_menu_animstyle);
        WindowManager.LayoutParams wl = window.getAttributes();
        wl.x = 0;
        wl.y = mActivity.getWindowManager().getDefaultDisplay().getHeight();
        // 以下这两句是为了保证按钮可以水平满屏
        wl.width = LayoutParams.MATCH_PARENT;
        wl.height = LayoutParams.WRAP_CONTENT;

        // 设置显示位置
        onWindowAttributesChanged(wl);

        initViews();
    }

    private void initViews() {
        DialogAdapter dialogAdapter=new DialogAdapter(mName);
        ListView dialogList=(ListView) findViewById(R.id.dialog_list);
        dialogList.setOnItemClickListener(this);
        dialogList.setAdapter(dialogAdapter);
        mMBtn_Cancel = (Button) findViewById(R.id.mBtn_Cancel);
        mTv_Title = (TextView) findViewById(R.id.mTv_Title);


        mMBtn_Cancel.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                if(mCancelListener != null){
                    mCancelListener.onCancelClick(v);
                }
                dismiss();
            }
        });

        if(!TextUtils.isEmpty(mTitle) && mTv_Title != null){
            mTv_Title.setVisibility(View.VISIBLE);
            mTv_Title.setText(mTitle);
        }else{
            mTv_Title.setVisibility(View.GONE);
        }
    }

    @Override
    public void onClick(View v) {
        dismiss();

    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position,
                            long id) {

        mListener.onItemClick(parent, view, position, id);
        dismiss();
    }
    private class DialogAdapter extends BaseAdapter {
        private List<String> mStrings;
        private Viewholder viewholder;
        private LayoutInflater layoutInflater;
        public DialogAdapter(List<String> strings) {
            this.mStrings = strings;
            this.layoutInflater=mActivity.getLayoutInflater();
        }

        @Override
        public int getCount() {
            // TODO Auto-generated method stub
            return mStrings.size();
        }

        @Override
        public Object getItem(int position) {
            // TODO Auto-generated method stub
            return mStrings.get(position);
        }

        @Override
        public long getItemId(int position) {
            // TODO Auto-generated method stub
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            if (null == convertView) {
                viewholder=new Viewholder();
                convertView=layoutInflater.inflate(R.layout.view_dialog_item, null);
                viewholder.dialogItemButton=(TextView) convertView.findViewById(R.id.dialog_item_bt);
                convertView.setTag(viewholder);
            }else{
                viewholder=(Viewholder) convertView.getTag();
            }
            viewholder.dialogItemButton.setText(mStrings.get(position));
            if (!mUseCustomColor) {
                mFirstItemColor = mActivity.getResources().getColor(R.color.blue);
                mOtherItemColor = mActivity.getResources().getColor(R.color.blue);
            }
            if (1 == mStrings.size()) {
                viewholder.dialogItemButton.setTextColor(mFirstItemColor);
                viewholder.dialogItemButton.setBackgroundResource(R.drawable.dialog_item_bg_only);
            } else if (position == 0) {
                viewholder.dialogItemButton.setTextColor(mFirstItemColor);
                viewholder.dialogItemButton.setBackgroundResource(R.drawable.select_dialog_item_bg_top);
            } else if (position == mStrings.size() - 1) {
                viewholder.dialogItemButton.setTextColor(mOtherItemColor);
                viewholder.dialogItemButton.setBackgroundResource(R.drawable.select_dialog_item_bg_buttom);
            } else {
                viewholder.dialogItemButton.setTextColor(mOtherItemColor);
                viewholder.dialogItemButton.setBackgroundResource(R.drawable.select_dialog_item_bg_center);
            }
            return convertView;
        }

    }

    public static class Viewholder {
        public TextView dialogItemButton;
    }

    /**
     * 设置列表项的文本颜色
     */
    public void setItemColor(int firstItemColor, int otherItemColor) {
        mFirstItemColor = firstItemColor;
        mOtherItemColor = otherItemColor;
        mUseCustomColor = true;
    }
}
    private void takePhoto() {
        // 实例化一个Intent对象,将Intent
        // action设置为[MediaStore.ACTION_IMAGE_CAPTURE],即指向系统相机
        Intent intentFromCapture = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        // 设置拍照后图片的保存路径及图片名称(IMAGE_FILE_NAME)
        intentFromCapture.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(new File(Environment.getExternalStorageDirectory(), pictureName)));
        // 启动Intent并获取返回值,requestCode[CAMERA_REQUEST_CODE]为返回的代号,自定义
        startActivityForResult(intentFromCapture, CAMERA_REQUEST_CODE);
    }

    private void choosePicture() {
        Intent intentFromGallery = new Intent();
        intentFromGallery.setType("image/*");
        intentFromGallery.setAction(Intent.ACTION_GET_CONTENT);
        startActivityForResult(intentFromGallery, IMAGE_REQUEST_CODE);
    }

     /**
     * onActivityResult 接收相机或者相册的返回值 requestCode 请求的代号,即在启动intent时设置的requestCode
     * resultCode 返回的结果代号 data 返回的intent数据
     */
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        // TODO Auto-generated method stub
        super.onActivityResult(requestCode, resultCode, data);
        if (resultCode != RESULT_CANCELED) {// 筛掉用户中途取消的操作
            switch (requestCode) {// 根据requestCode分别执行不同代码段
                case CAMERA_REQUEST_CODE:
                    File tempFile = new File(Environment.getExternalStorageDirectory() + "/" + pictureName);
                    loadImage(Uri.fromFile(tempFile));
                    break;
                case IMAGE_REQUEST_CODE:
                    loadImage(data.getData());
                    break;
                default:
                    break;
            }
        }
    }
    private void loadImage(Uri uri) {
        try {
            bitMap = MediaStore.Images.Media.getBitmap(this.getContentResolver(), uri);
            pic = new int[bitMap.getWidth()*bitMap.getHeight()];
            pixels = new int[bitMap.getWidth()*bitMap.getHeight()];
            bitMap.getPixels(pixels,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());
            bitMap.getPixels(pic,0,bitMap.getWidth(),0,0,bitMap.getWidth(),bitMap.getHeight());
            ivIcon.setImageBitmap(bitMap);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

保存图片:

     save.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String savePath;
                File filePic;
                if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
                    savePath = "/sdcard/WayOfDragon;/pic/";
                } else {
                    savePath = MainActivity.this.getApplicationContext().getFilesDir().getAbsolutePath() + "/WayOfDragon;/pic/";
                }
                try {
                    filePic = new File(savePath + "MLGJing" + System.currentTimeMillis() + ".jpg");
                    if (!filePic.exists()) {
                        filePic.getParentFile().mkdirs();
                        filePic.createNewFile();
                    }
                    FileOutputStream fos = new FileOutputStream(filePic);
                    bitMap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                    fos.flush();
                    fos.close();
                    Toast.makeText(MainActivity.this,"保存成功!",Toast.LENGTH_SHORT).show();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        });

    实现再按一次退出功能:

    @Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        keyDown = true;
        return super.onKeyDown(keyCode, event);
    }

    @Override
    public boolean onKeyUp(int keyCode, KeyEvent event) {
        if (!keyDown) {
            return false;
        }
        keyDown = false;
        if (keyCode == KeyEvent.KEYCODE_BACK && !judgeExit()) {
            return false;
        }
        return super.onKeyUp(keyCode, event);
    }

    private boolean judgeExit() {
        long now = SystemClock.elapsedRealtime();
        if (now - lastClickTimeStamp < 3000) {
            return true;
        }
        lastClickTimeStamp = now;
        Toast.makeText(this, "再按一次退出程序", Toast.LENGTH_SHORT).show();
        return false;
    }

    最后是实验环节,选取中国科学院大学雁栖湖校区东区操场的一张照片为实验材料:

    经过高亮处理:

    经过怀旧处理:

    经过饱和处理:

    经过黑白处理:

    由于核心算法并不是我的,所以处理结果不作评论。本文及相关材料只作学习交流使用,不做其他用途。对了,这个是可以混合叠加处理的。大家有什么想说的欢迎评论,如果想要相关文件资料,在评论区留下邮箱即可!

原创文章 19 获赞 34 访问量 1万+

猜你喜欢

转载自blog.csdn.net/weixin_38141453/article/details/84171893
今日推荐