图片裁剪


Android中图片的选取与裁剪是非常常见的功能,由于手机型号众多,难免在兼容性上出现问题。常 见情况就是出现闪退,无法选取图片。这方面的问题在以前的博客中曾经有过介绍,具体可以参考以下两篇博文。



关于拍照闪退的一种情况:


关于上传图片闪退的一种情况的:



这两篇文章分别描述了由于内存溢出和uri格式转换问题导致的选取图片出错。



现在我们仔细分析一下Android拍照,选取图片的流程。下文主要分析选取图片时uri的相关格式与路径的转换,图片存储位置,图片命名等问题,了解了这些问题,图片才能真正为我们所用。


下面代码主要来源于开源中国的安卓App,对其进行了摘取和注释,写成Moudle的形式,方便我们的使用,可以从下面的地址进行下载







路径设置


//拍照相关
    private final static int CROP = 200;

    //保存裁剪后照片的路径
    private final static String FILE_SAVEPATH = Environment
            .getExternalStorageDirectory().getAbsolutePath()
            + "/vonchenchen/Portrait/";

    //保存直接拍照后未经剪裁照片的路径
    private final static String FILE_SAVEPATH_CAMERA = Environment
            .getExternalStorageDirectory().getAbsolutePath()
            + "/vonchenchen/Camera/";

    //裁剪后照片的前缀名  文件名:FILE_SAVENAME + timeStamp + ".jpg"
    private final static String FILE_SAVENAME_CROP = "vonchenchen";
    //直接拍照后照片的前缀名   文件名:FILE_SAVENAME_CAMERA + timeStamp + ".jpg"/other
    private final static String FILE_SAVENAME_CAMERA = "vonchenchen";



主要设置文件名前缀和存储路径,存储路径分为拍照后照片的存储路径和裁剪后照片的存储路径。


拍照过程


拍照的原理是通过intent开启系统相机的拍照程序,我们可以指定拍照后照片存储的位置,并使用onActivityResult在拍照完毕后得到照片。



/**
     * 拍照
     */
    private void startTakePhoto() {
        Intent intent;
        // 判断是否挂载了SD卡
        String savePath = "";
        String storageState = Environment.getExternalStorageState();
        if (storageState.equals(Environment.MEDIA_MOUNTED)) {
            savePath = FILE_SAVEPATH_CAMERA;
            File savedir = new File(savePath);
            if (!savedir.exists()) {
                savedir.mkdirs();
            }
        }

        // 没有挂载SD卡,无法保存文件
        if (StringUtils.isEmpty(savePath)) {
            Toast.makeText(this, "无法保存照片,请检查SD卡是否挂载", Toast.LENGTH_SHORT);
            return;
        }

        String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss")
                .format(new Date());
        String fileName = FILE_SAVENAME_CAMERA + timeStamp + ".jpg";// 照片命名
        File out = new File(savePath, fileName);
        Uri uri = Uri.fromFile(out);
        origUri = uri;                           //***********************指定拍摄照片后,得到照片的Uri*************************

        theLarge = savePath + fileName;// 该照片的绝对路径

        intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
        startActivityForResult(intent,
                ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA);
    }


选取照片


如果使用选取本地照片,那么uri就是本地的了。同样也是开启一个intent,调用系统的照片列表,选择后为我们返回一个Uri,这个uri就是照片的路径。注意,这个uri的头



可能有多种,如果得到的是content:// ,我们可以通过系统的Media获取这个相片的路径,如果返回的头是file://,则直接取其后半部分,就是照片的路径。我们可以通过发送一



个系统广播,指定某个file://的照片进入Media,这样就可以使其得到标准的uri。由于这种方式有延迟,此处直接使用截去file重新拼凑路径的方法。



/**
     * 选择图片
     */
    private void startImagePick() {
        Intent intent;
        if (Build.VERSION.SDK_INT < 19) {
            intent = new Intent();
            intent.setAction(Intent.ACTION_GET_CONTENT);
            intent.setType("image/*");
            startActivityForResult(Intent.createChooser(intent, "选择图片"),
                    ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP);
        } else {
            //ACTION_PICK 可以显示详细的选择样式
            intent = new Intent(Intent.ACTION_PICK,
                    MediaStore.Images.Media.EXTERNAL_CONTENT_URI);

            intent.setType("image/*");
            startActivityForResult(Intent.createChooser(intent, "选择图片"),
                    ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP);
        }
    }


裁剪图片


当完成拍照或者选取照片时,系统程序会返回我们的程序,并执行onActivityResult。此时我们可以获取目标图片的uri,如果是拍照的话,那么我们拍摄的图片已经存入了


我们设定的路径中。这样,我们就可以拿着选取图片的uri,进行裁剪操作,同样,裁剪也是一个系统应用,需要我们使用startActivity开启这个应用。


/**
     * 拍照后裁剪
     * @param data 原始图片Uri
     */
    private void startActionCrop(Uri data) {
        Intent intent = new Intent("com.android.camera.action.CROP");
        //data 这个uri必须经过new File出来
        intent.setDataAndType(data, "image/*");
        //指定裁剪后的图片的保存路径
        intent.putExtra("output", <span style="color:#ff0000;">getUploadTempFile</span>(data));
        intent.putExtra("crop", "true");
        intent.putExtra("aspectX", 1);// 裁剪框比例
        intent.putExtra("aspectY", 1);
        intent.putExtra("outputX", CROP);// 输出图片大小
        intent.putExtra("outputY", CROP);
        intent.putExtra("scale", true);// 去黑边
        intent.putExtra("scaleUpIfNeeded", true);// 去黑边
        startActivityForResult(intent,
                ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD);
    }



getUploadTempFile函数分析


这个函数指定了裁剪完图片后的存储位置和文件名,通过传入已经获取到的图片Uri,对这个Uri进行处理,并根据这些信息在 FILE_SAVENAME_CROP指定的路径中创建


图片Uri。这个Uri最终被设置给用来开启剪裁图片的intent。 


/**
     * 在指定裁剪
     * @param uri
     * @return
     */
    private Uri getUploadTempFile(Uri uri) {
        String storageState = Environment.getExternalStorageState();
        if (storageState.equals(Environment.MEDIA_MOUNTED)) {
            File savedir = new File(FILE_SAVEPATH);
            if (!savedir.exists()) {
                savedir.mkdirs();
            }
        } else {
            Toast.makeText(this, "无法保存上传的头像,请检查SD卡是否挂载", Toast.LENGTH_SHORT).show();
            return null;
        }
        String timeStamp = new SimpleDateFormat("yyyyMMddHHmmss")
                .format(new Date());

        //将uri转换为绝对路径,  如果是file:// 开头的uri,获取路径
        String thePath = ImageUtils.getAbsolutePathFromNoStandardUri(uri);

        // 如果是标准Uri
        if (StringUtils.isEmpty(thePath)) {
            //将uri转换为绝对路径,  如果是content:// 开头的uri,获取路径
            thePath = ImageUtils.getAbsoluteImagePath(this, uri);
        }
        String ext = StringUtils.getFileFormat(thePath);
        ext = StringUtils.isEmpty(ext) ? "jpg" : ext;
        // 裁剪完毕的照片命名
        String cropFileName = FILE_SAVENAME_CROP + timeStamp + "." + ext;
        // 裁剪头像的绝对路径
        protraitPath = FILE_SAVEPATH + cropFileName;
        protraitFile = new File(protraitPath);

        Uri cropUri = Uri.fromFile(protraitFile);
        return cropUri;
    }



下面我们看看我们所拼凑的路径,下图为非标准的uri, file:// 后面的字符串就是我们的路径




Android 根据路径计算图片大小 安卓 照片路径_图片选取



获取结果


onActivityResult相当于一个中转站,用于 获取选择图片的结果 以及 拿着这些结果(图片的uri)开启图片裁剪的应用。代码如下:


@Override
    protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnIntent) {

        switch (requestCode) {
            case ImageUtils.REQUEST_CODE_GETIMAGE_BYCAMERA: //开启拍照

                //origUri 是我们指定的拍照完毕后存储的uri
                startActionCrop(origUri);                        // 拍照后裁剪
                break;
            case ImageUtils.REQUEST_CODE_GETIMAGE_BYCROP:   //开启选取本地图片
                if(imageReturnIntent != null) {
                    //我们选择完图片后返回其Uri
                    startActionCrop(imageReturnIntent.getData());// 选图后裁剪
                }
                break;
            case ImageUtils.REQUEST_CODE_GETIMAGE_BYSDCARD:  //开启图片剪裁

                boolean b = protraitFile.exists();
                if (!StringUtils.isEmpty(protraitPath) && protraitFile.exists()) {
                    //protraitPath 是我们为裁剪后指定的图片的Uri
                    protraitBitmap = ImageUtils.convertToBitmap(protraitPath, 200, 200);
                    mImageView.setImageBitmap(protraitBitmap);
                }
                break;
        }
    }