1. 程式人生 > >Android觸控事件(五)-CropBitmapActivity關於裁剪工具的使用

Android觸控事件(五)-CropBitmapActivity關於裁剪工具的使用

目錄

概述

這個Activity是為了裁剪圖片的.使用時需要提供裁剪圖片的路徑,以及圖片裁剪後輸出的路徑.同時如果圖片存在旋轉角度也可以提供,Activity會先將圖片的旋轉角度處理後再進行裁剪.

傳遞資料

  • inputPath:被裁剪圖片的路徑(完整路徑)
  • outputPath:圖片裁剪後輸出的路徑(完整路徑,包括圖片的格式)
  • degree:圖片需要旋轉的角度

在啟動Activity時傳遞以上資料即可顯示並裁剪圖片,圖片會最終儲存到指定的路徑.

裁剪流程

Activity僅僅只是作為一個視窗顯示,實際的裁剪工作是由自定義CropView

進行裁剪的.詳細請見相關文章.
其中所以有裁剪操作由CropView完成,但是儲存操作需要通過Activity通知CropView進行儲存.

其它事項

需要注意的是:

  • 當不存在源圖片(inputPath)資源時會載入預設示例資源圖片.(僅作為demo功能示例)
  • 預設載入圖片的720畫素的縮圖進行裁剪.
  • 裁剪物件理論上可以是任何圖片物件,但是考慮到記憶體因此建議只使用縮圖.
  • 儲存時預設使用的格式為PNG,在具體使用該Activity時需要修改對應的儲存格式.
  • 儲存時需要指定圖片質量,從0-100,預設為50(格式為PNG時,設定質量無效)

建議使用PNG格式,因為當裁剪圖片有透明背景存在時,裁剪後的圖片依然可以保持圖片的透明特性.同時裁剪圖片即使是JPG

也是可以正常顯示.

使用方式

直接通過其靜態方法呼叫即可.

//建立圖片輸出路徑
String outputPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/transformBitmap.png";
//引數1:Context
//引數2:源圖片路徑,源圖片路徑為null,使用內建示例圖片
//引數3:裁剪圖片輸出路徑
//引數4:圖片旋轉角度(糾正圖片的角度)
CropBitmapActivity.startThisActivitySelf(MainActivity.this, null, outputPath, 0
);

圖片輔助功能

在此Activity中需要使用到兩個圖片輔助功能.其中一個是載入圖片的縮圖從而達到減小記憶體使用的目的.
另外一個是獲取圖片的旋轉角度並旋轉圖片.
這兩個功能是比較實用的,特別是第一個功能.下面給出這兩個功能的原始碼.同時在後面將會有專門的一篇文章用來收集類似的實用的功能性的程式碼.歡迎關注.

圖片縮圖載入

圖片縮圖載入的本質是,獲取圖片的寬高大小,然後按比例加載出小尺寸的圖片,從而達到減小圖片佔用記憶體的目的.

關於圖片縮圖及相關的可以見另外的文章,後面會使用一個專用類來說明

/**
 * 載入縮放後的圖片
 *
 * @param in        圖片流資料
 * @param reqSize   預期圖片最長邊允許的最大值
 * @return 返回縮放載入後的圖片, 但圖片的最大邊長度並不一定等於reqSize,只是近似並小於這個值
 */
public static Bitmap decodeBitmapInScale(String filePath, int reqSize) {
    if (TextUtils.isEmpty(filePath) || reqSize <= 0) {
        return null;
    } else {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //僅載入圖片寬高大小(不載入圖片實際二進位制資料)
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        if (reqSize <= 0) {
            throw new RuntimeException("預期邊長不可小於0");
        }
        float bmpWidth = options.outWidth;
        float bmpHeight = options.outHeight;
        float largeSizeInBmp = 0;
        int sampleSize = 1;
        //記錄最大的邊
        if (bmpWidth > bmpHeight) {
            largeSizeInBmp = bmpWidth;
        } else {
            largeSizeInBmp = bmpHeight;
        }
        //將最大邊與預期的邊大小進行比較計算縮放比
        if (largeSizeInBmp < reqSize) {
            //最大邊小於預期,則sampleSize為1
            sampleSize = 1;
        } else {
            //最大邊大於預期邊
            sampleSize = (int) (largeSizeInBmp / reqSize + 0.5);
            //計算所得縮放值為2的幾倍指數,即求 log2(sampleSize)
            double powerNum = Math.log(sampleSize) / Math.log(2);
            int tempPowerNum = (int) powerNum;
            //將所得指數+1,確保儘可能小於指定值
            if (powerNum > tempPowerNum) {
                tempPowerNum += 1;
            }
            //反求sampleSize=2^tempPowerNum
            sampleSize = (int) Math.pow(2, tempPowerNum);
        }
        //完整載入圖片
        options.inJustDecodeBounds = false;
        //圖片縮放比
        options.inSampleSize = sampleSize;
        //圖片是否可修改
        options.inMutable = true;
        return BitmapFactory.decodeFile(filePath, options);
    }
}

圖片旋轉

圖片旋轉角度是基於圖片本身記錄的資訊.如果圖片資訊中不存在旋轉角度,這裡也是獲取不到的(即使圖片本身確實存在旋轉的情況).

以上主要是在相機拍照時使用到.部分相機拍照時會自動旋轉,實際圖片顯示的角度與拍攝時的角度是不同的,此時就會記錄在相片資訊中.
但需要讀取相片並重新儲存為一個新檔案(不作任何修改時),圖片資訊中的旋轉角度會被刪除,此時也不會再取到任何的角度旋轉資訊.

 //讀取圖片屬性:旋轉的角度
 public static int readPictureDegree(String path) {
     int degree = 0;
     try {
         ExifInterface exifInterface = new ExifInterface(path);
         int orientation = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
         switch (orientation) {
             case ExifInterface.ORIENTATION_ROTATE_90:
                 degree = 90;
                 break;
             case ExifInterface.ORIENTATION_ROTATE_180:
                 degree = 180;
                 break;
             case ExifInterface.ORIENTATION_ROTATE_270:
                 degree = 270;
                 break;
         }
     } catch (IOException e) {
         e.printStackTrace();
     }
     return degree;
 }

 //旋轉圖片
 public static Bitmap rotatingBitmap(int angle, Bitmap bitmap) {
    //旋轉圖片動作
    Matrix matrix = new Matrix();
    matrix.postRotate(angle);
    System.out.println("angle2=" + angle);
    // 建立新的圖片
    Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
            bitmap.getWidth(), bitmap.getHeight(), matrix, true);
    return resizedBitmap;
}

原始碼

JAVA程式碼

/**
 * Created by taro on 16/1/11.
 */
public class CropBitmapActivity extends Activity implements View.OnClickListener {
    private Button mBtnLeftRotate;
    private Button mBtnRightRotate;
    private Button mBtnConfirm;
    private Button mBtnCancel;
    private CropView mCropView;

    Handler mHandler = null;
    ProgressDialog mDialog = null;
    Bitmap mPhoto = null;
    String mFilePath = null;
    String mOutputPath = null;

    /**
     * 啟動此Activity
     *
     * @param act
     * @param srcBitmapPath 來源圖片的路徑
     * @param outputPath    裁剪後輸出的圖片路徑
     * @param degree        圖片旋轉了的角度
     */
    public static void startThisActivitySelf(Activity act, String srcBitmapPath, String outputPath, int degree) {
        Intent intent = new Intent(act, CropBitmapActivity.class);
        intent.putExtra("inputPath", srcBitmapPath);
        intent.putExtra("outputPath", outputPath);
        intent.putExtra("degree", degree);
        act.startActivity(intent);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_transform_bitmap);
        mBtnLeftRotate = (Button) findViewById(R.id.transform_left_rotate_btn);
        mBtnRightRotate = (Button) findViewById(R.id.transform_right_rotate_btn);
        mBtnConfirm = (Button) findViewById(R.id.transform_confirm_btn);
        mBtnCancel = (Button) findViewById(R.id.transform_cancel_btn);
        mCropView = (CropView) findViewById(R.id.transform_bitmap_cv);

        mBtnLeftRotate.setOnClickListener(this);
        mBtnRightRotate.setOnClickListener(this);
        mBtnConfirm.setOnClickListener(this);
        mBtnCancel.setOnClickListener(this);

        //輸入地址
        mFilePath = getIntent().getStringExtra("inputPath");
        //輸出地址
        mOutputPath = getIntent().getStringExtra("outputPath");
        int degree = getIntent().getIntExtra("degree", 0);
        InputStream in = null;
        //不存在源圖片路徑時,載入預設的示例圖片資源
        if (mFilePath == null) {
            mPhoto = decodeBitmapInScale(getResources(), R.raw.pkq, 720);
        } else {
            mPhoto = decodeBitmapInScale(mFilePath, 720);
        }

        //存在旋轉角度,對圖片進行旋轉
        if (degree != 0) {
            //旋轉圖片
            Bitmap originalBitmap = rotatingBitmap(degree, mPhoto);
            //回收舊圖片
            mPhoto.recycle();
            mPhoto = originalBitmap;
        }
        mCropView.setImageBitmap(mPhoto);

        mDialog = new ProgressDialog(this);
        mDialog.setTitle("正在處理圖片...");
        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                switch (msg.what) {
                    case 0x1:
                        mDialog.show();
                        break;
                    case 0x2:
                        mDialog.dismiss();
                        finish();
                        break;
                    case 0x3:
                        mDialog.dismiss();
                        break;
                    case 0x4:
                        Toast.makeText(CropBitmapActivity.this, msg
                                .getData().getString("toast"), Toast.LENGTH_LONG).show();
                        break;
                }
            }
        };
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (mPhoto != null && !mPhoto.isRecycled()) {
            mPhoto.recycle();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.transform_confirm_btn:
                //顯示載入對話方塊
                mHandler.sendEmptyMessage(0x1);
                new Thread() {
                    @Override
                    public void run() {
                        if (mCropView.restoreBitmap(mOutputPath, Bitmap.CompressFormat.PNG, true, 50)) {
                            setResult(RESULT_OK);
                            mPhoto.recycle();
                            String toast = "裁剪圖片儲存到: " + mOutputPath;
                            Bundle data = new Bundle();
                            data.putString("toast", toast);
                            Message msg = Message.obtain();
                            msg.what = 0x4;
                            msg.setData(data);
                            mHandler.sendMessage(msg);
                            mHandler.sendEmptyMessageDelayed(0x2, Toast.LENGTH_LONG);
                        } else {
                            //僅取消對話方塊
                            mHandler.sendEmptyMessage(0x3);
                        }
                    }
                }.start();
                break;
            case R.id.transform_cancel_btn:
                //取消時需要回收圖片資源
                mCropView.recycleBitmap();
                setResult(RESULT_CANCELED);
                finish();
                break;
        }
    }


    /**
     * 載入縮放後的圖片
     *
     * @param filePath 圖片路徑
     * @param reqSize  預期圖片最長邊允許的最大值
     * @return 返回縮放載入後的圖片, 但圖片的最大邊長度並不一定等於reqSize,只是近似並小於這個值
     */
    public static Bitmap decodeBitmapInScale(String filePath, int reqSize) {
        if (TextUtils.isEmpty(filePath) || reqSize <= 0) {
            return null;
        } else {
            BitmapFactory.Options options = new BitmapFactory.Options();
            //僅載入圖片寬高大小(不載入圖片實際二進位制資料)
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeFile(filePath, options);
            if (reqSize <= 0) {
                throw new RuntimeException("預期邊長不可小於0");
            }
            float bmpWidth = options.outWidth;
            float bmpHeight = options.outHeight;
            float largeSizeInBmp = 0;
            int sampleSize = 1;
            //記錄最大的邊
            if (bmpWidth > bmpHeight) {
                largeSizeInBmp = bmpWidth;
            } else {
                largeSizeInBmp = bmpHeight;
            }
            //將最大邊與預期的邊大小進行比較計算縮放比
            if (largeSizeInBmp < reqSize) {
                //最大邊小於預期,則sampleSize為1
                sampleSize = 1;
            } else {
                //最大邊大於預期邊
                sampleSize = (int) (largeSizeInBmp / reqSize + 0.5);
                //計算所得縮放值為2的幾倍指數,即求 log2(sampleSize)
                double powerNum = Math.log(sampleSize) / Math.log(2);
                int tempPowerNum = (int) powerNum;
                //將所得指數+1,確保儘可能小於指定值
                if (powerNum > tempPowerNum) {
                    tempPowerNum += 1;
                }
                //反求sampleSize=2^tempPowerNum
                sampleSize = (int) Math.pow(2, tempPowerNum);
            }
            //完整載入圖片
            options.inJustDecodeBounds = false;
            //圖片縮放比
            options.inSampleSize = sampleSize;
            //圖片是否可修改
            options.inMutable = true;
            return BitmapFactory.decodeFile(filePath, options);
        }
    }


    /**
     * 載入縮放後的圖片
     *
     * @param res
     * @param resID   資源ID
     * @param reqSize 預期圖片最長邊允許的最大值
     * @return 返回縮放載入後的圖片, 但圖片的最大邊長度並不一定等於reqSize,只是近似並小於這個值
     */
    public static Bitmap decodeBitmapInScale(Resources res, int resID, int reqSize) {
        if (res == null || resID == 0 || reqSize <= 0) {
            return null;
        } else {
            BitmapFactory.Options options = new BitmapFactory.Options();
            //僅載入圖片寬高大小(不載入圖片實際二進位制資料)
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeResource(res, resID, options);
            if (reqSize <= 0) {
                throw new RuntimeException("預期邊長不可小於0");
            }
            float bmpWidth = options.outWidth;
            float bmpHeight = options.outHeight;
            float largeSizeInBmp = 0;
            int sampleSize = 1;
            //記錄最大的邊
            if (bmpWidth > bmpHeight) {
                largeSizeInBmp = bmpWidth;
            } else {
                largeSizeInBmp = bmpHeight;
            }
            //將最大邊與預期的邊大小進行比較計算縮放比
            if (largeSizeInBmp < reqSize) {
                //最大邊小於預期,則sampleSize為1
                sampleSize = 1;
            } else {
                //最大邊大於預期邊
                sampleSize = (int) (largeSizeInBmp / reqSize + 0.5);
                //計算所得縮放值為2的幾倍指數,即求 log2(sampleSize)
                double powerNum = Math.log(sampleSize) / Math.log(2);
                int tempPowerNum = (int) powerNum;
                //將所得指數+1,確保儘可能小於指定值
                if (powerNum > tempPowerNum) {
                    tempPowerNum += 1;
                }
                //反求sampleSize=2^tempPowerNum
                sampleSize = (int) Math.pow(2, tempPowerNum);
            }
            //完整載入圖片
            options.inJustDecodeBounds = false;
            //圖片縮放比
            options.inSampleSize = sampleSize;
            //圖片是否可修改
            options.inMutable = true;
            return BitmapFactory.decodeResource(res, resID, options);
        }
    }


    /**
     * 旋轉圖片
     *
     * @param angle
     * @param bitmap
     * @return Bitmap
     */
    public static Bitmap rotatingBitmap(int angle, Bitmap bitmap) {
        if (bitmap == null || bitmap.isRecycled()) {
            return null;
        }
        //旋轉圖片 動作
        Matrix matrix = new Matrix();
        matrix.postRotate(angle);
        System.out.println("angle2=" + angle);
        // 建立新的圖片
        Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0,
                bitmap.getWidth(), bitmap.getHeight(), matrix, true);
        return resizedBitmap;
    }
}

XML檔案

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:orientation="vertical">

    <LinearLayout
            android:id="@+id/transform_menu_lin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:visibility="gone"
            android:weightSum="2">

        <Button
                android:id="@+id/transform_left_rotate_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:layout_weight="1"
                android:text="左旋"
                android:textSize="14sp"/>

        <Button
                android:id="@+id/transform_right_rotate_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:layout_weight="1"
                android:text="右旋"
                android:textSize="14sp"/>

    </LinearLayout>

    <LinearLayout
            android:id="@+id/transform_operation_lin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_alignParentBottom="true"
            android:weightSum="2">

        <Button
                android:id="@+id/transform_cancel_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:layout_weight="1"
                android:text="取消"
                android:textSize="14sp"/>

        <Button
                android:id="@+id/transform_confirm_btn"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_margin="5dp"
                android:layout_weight="1"
                android:text="確定"
                android:textSize="14sp"/>


    </LinearLayout>

    <com.henrytaro.ct.ui.CropView
            android:id="@+id/transform_bitmap_cv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_above="@id/transform_operation_lin"
            android:layout_below="@id/transform_menu_lin"
            android:layout_centerInParent="true"/>


</RelativeLayout>

GitHub地址

示例GIF

裁剪圖片示例

回到目錄