清代诗人钱大昕在《十驾斋养新录·改过》中写道:“圣贤以改过为能,不以无过为贵”。我感受这句话说的颇有道理。是啊,没有人是天生什么都会的。第一次作出错了,把错误改正,把不会的弥补上,下一次不就会了嘛!因此说关键仍是在于敢于尝试和勤于改正呀!java
好了,又扯远了。言归正传,上一周发了一篇笑话结果浏览量惨不忍睹,看来你们仍是喜欢技术类的文章。好吧,本文将使用JNI技术来实现一个山寨版的美图软件。须要的文件有有关美图的动态连接库.so文件及对应的JNI类,没有的话能够来这里下载。android
简单介绍一下JNI。JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其余语言的通讯(主要是C&C++)。从Java1.1开始,JNI标准成为java平台的一部分,它容许Java代码和其余语言写的代码进行交互。因为篇幅缘由搭建JNI开发环境就不介绍了,这方面的资料网上有不少,我们直接进入正题。算法
第一步,建立工程,没什么好说的,别忘了勾选“Include C++ support”选项。数组
第二步,把对应的.so文件拷贝到java/main/jniLibs目录,并建立JNI.java。app
以后,在加载动态连接库,在JNI类中添加以下代码:ide
{ 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: liushilin520@foxmail.com * 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; }
最后是实验环节,选取中国科学院大学雁栖湖校区东区操场的一张照片为实验材料:
通过高亮处理:
通过怀旧处理:
通过饱和处理:
通过黑白处理:
因为核心算法并非个人,因此处理结果不做评论。本文及相关材料只做学习交流使用,不作其余用途。对了,这个是能够混合叠加处理的。你们有什么想说的欢迎评论,若是想要相关文件资料,在评论区留下邮箱便可!