Android实用工具类-GrallyAndPhotoUtils图片处理工具

目录

概述

此类是用于简便调用系统拍照及打开相册选择图片.通用于多种机型.(亲测魅族MX4,三星note 2,三星note 3)


前言

在执行拍照和打开相册之前,我们需要注意一下.
由于Android开放的系统,很多手机厂商是定制自己的系统的,那么就存在一个问题.尽管大部分时候拍照或者打开相册都有一个基本的标准,但可能不同的系统操作方式或者是细节表现是不同的.
可能存在以下的情况:

  • 拍照时相机会自动旋转一定的角度(出现在三星机型上)
  • 相册图片返回路径存在URI中(原生系统)

以上只是遇到过的情况,包括魅族也存在一些问题.不管是拍照还是相册获取图片,我们都希望能正确获取到图片.因此,这里采用的方式是:

  • 拍照时将图片保存到指定位置,加载图片时通过图片路径加载
  • 获取相册图片时通过返回URI图片路径加载图片

拍照

拍照调用的是系统的拍照程序而不是自己实现的拍照程序.通用性更广泛,也不需要自己去处理各种情况从而实现拍照程序,节省了大量的时间.
但是系统拍照程序可能会存在的问题是,系统的拍照程序是我们无法监视和控制的.有可能某些系统的拍照程序会造成一些问题.比如部分机型会自动旋转相片角度.
所以我们在拍照后获取图片时,可以检测图片是否存在旋转角度从而修正图片的方向.


创建存储拍照图片的文件

存储拍照图片的路径从外部提供,原因后面会解释到.
检测路径下的文件是否存在,不存在则创建一个新的空文件.文件不存在的情况下无法将拍照所得的图片写入到文件中.
该路径应该为完整路径,带图片格式后缀.

 //尝试创建文件夹及用于存储拍照后的文件
File outputImage = new File(filePath);
if (!outputImage.exists()) {
    try {
        outputImage.createNewFile();
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    }
}

调用系统拍照程序

调用系统拍照程序的Action是通用的.我们需要注意的是调用时需要设置拍照参数为图片输出到指定路径

//将File对象转换为Uri并启动照相程序
Uri photoUri = Uri.fromFile(outputImage);
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //照相
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); //指定图片输出地址
try {
    act.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); //启动照相
} catch (SecurityException se) {
    se.printStackTrace();
    return null;
}

其中启动拍照程序使用的requestCode默认使用自定义值REQUEST_CODE_TAKE_PHOTO.因为后面会提供统一的处理方法.


读取相册图片

读取相册图片使用的也是系统的相册工具.

//设置选择Action
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType("image/*");
//存在多个相册类型的应用时,显示给用户选择的打开相册的应用界面
act.startActivityForResult(Intet.createChooser(intent,"请选择"),REQUEST_CODE_OPEN_GRALLY);

获取返回URI中的图片路径

相册打开图片后返回的往往是一个URI,存放的是图片的路径.但有时又不一定是绝对路径,即我们无法通过这个URI去读取到图片.可能返回的是一个如下的URI:

content://media/external/images/media/45928

这并不是一个文件的绝对路径,我们也无法从该路径直接获取到图片,但是此URI确实指向了某个资源文件.
因此我们需要将该URI转化成我们能获取到的图片的路径.

public static String getBmpPathFromContent(Context context, Intent bmpIntent) {
    if (bmpIntent != null && bmpIntent.getData() != null) {
        Uri contentUri = bmpIntent.getData();
        String uriPath = contentUri.getPath();
        //content存在时
        if (!TextUtils.isEmpty(uriPath)) {
            String[] filePathColumn = {MediaStore.Images.Media.DATA};
            //加载系统数据库
            Cursor cursor = context.getContentResolver().query(contentUri,
                    filePathColumn, null, null, null);
            //移动到第一行,不移动将越界
            cursor.moveToFirst();
            //加载查询数据的列号
            int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
            //获取图片路径
            String picturePath = cursor.getString(columnIndex);
            //游标应该关闭
            cursor.close();
            return picturePath;
        } else {
            return null;
        }
    } else {
        return null;
    }
}

通过ContentResolver查询该URI对应的图片的信息,从而我们得到可以直接读取加载的图片路径.


统一的回调处理

以上不管是拍照还是打开相册,都是通过startActivityForResult()的方式打开并获取数据的.
因此可以使用统一的方式进行处理拍照或者是相册获取图片的回调.

/**
 * 处理拍照/打开相册后进入裁剪工作
 *
 * @param requestCode 连接
 * @param resultCode
 * @param data        Intent数据
 * @param act         启动裁剪Activity的act
 * @param inputPath   裁剪图片的路径(针对拍照保存的路径,相册获取的路径在data中)
 * @param outputPath  图片裁剪后输出的路径(提供给裁剪Activity)
 * @return
 */
public static boolean onActivityResult(int requestCode, int resultCode, Intent data, Activity act, String inputPath, String outputPath) {
    switch (requestCode) {
        case REQUEST_CODE_TAKE_PHOTO:
        case REQUEST_CODE_OPEN_GRALLY:
            //当启动使用的activity不存在或者是拍照/打开相册失败时(resultCode不为RESULT_OK),不处理
            if (act == null || resultCode != Activity.RESULT_OK) {
                return false;
            }
            //若当前为拍照回调,尝试读取图片的旋转角度
            int degree = requestCode == REQUEST_CODE_TAKE_PHOTO ? readPictureDegree(inputPath) : 0;
            //获取intent中返回的图片路径(如果是打开相册部分机型可能将图片路径存放在intent中返回)
            String tempPath = getBmpPathFromContent(act, data);
            //若intent路径存在,使用该路径,否则使用inputPath
            inputPath = TextUtils.isEmpty(tempPath) ? inputPath : tempPath;

            //源图片路径或者输出路径为无效时,不处理
            if (TextUtils.isEmpty(inputPath) || TextUtils.isEmpty(outputPath)) {
                return false;
            } else {
                Intent cropIntent = new Intent(act, CropBitmapActivity.class);
                cropIntent.putExtra("inputPath", inputPath);
                cropIntent.putExtra("outputPath", outputPath);
                cropIntent.putExtra("degree", degree);
                //启动裁剪工具
                act.startActivityForResult(cropIntent, REQUEST_CODE_CROP_PHOTO);
                return true;
            }
        default:
            return false;
    }
}

以上需要注意的是,同一时间onActivityResult()仅会回调一次,也就是说任何时候只会处理拍照或者是相册图片中的一种情况.其中

//若当前为拍照回调,尝试读取图片的旋转角度
int degree = requestCode == REQUEST_CODE_TAKE_PHOTO ? readPictureDegree(inputPath) : 0;

为处理拍照后的图片的角度旋转问题.

//获取intent中返回的图片路径(如果是打开相册部分机型可能将图片路径存放在intent中返回)
String tempPath = getBmpPathFromContent(act, data);

处理相册图片路径的问题.
inputPath仅为拍照图片存储的路径,而与相册图片路径无关.所以最后只会保留一个.

//若intent路径存在,使用该路径,否则使用inputPath
inputPath = TextUtils.isEmpty(tempPath) ? inputPath : tempPath;

当存在相册图片路径时,说明当前不是拍照回调,使用相册图片路径;当不存在相册图片路径时,使用拍照存储的图片路径,即处理为拍照回调.

最后调用自定义的裁剪CropBitmapActivity进行裁剪图片.关于裁剪详细可见另外的文章.

图片旋转角度的检测仅针对拍照后的相片.如果需要对相册图片及相片都进行检测则可以对最终的inputPath路径检测.

int degree = readPictureDegree(inputPath);

图片缩略图加载

在裁剪图片时可能会需要用到加载图片的缩略图.这里因为很多情况下裁剪的图片并不需要很高的清晰度(往往作为头像等小图显示).
此时使用加载一张高清图占用的内存是可观的,在内存紧张时甚至可能导致程序因为内存问题而崩溃.
图片缩略图加载的原理是:

通过按比例加载出更小比例的图片从而减少图片占用的内存.

实际上BitmapFactory系统图片加载工具本身就可以指定比例加载图片.但是只能加载缩略图而无法加载大于图片本身比例的图片.


加载流程

  • 加载图片的宽高(仅图片信息,不加载图片数据)
  • 计算缩略图比例
  • 按缩略图比例加载图片

计算缩略图比例

加载缩略图最重要的一个部分是,计算出需要加载的缩略图与原图的比例.在BitmapFactory.Options中有一个变量是用于记录加载图片与原图的比例的.

options.inSampleSize//int类型

该变量只能是大于1的数,1表示按原图比例加载,大于1表示长宽缩小为1/inSampleSize进行加载.

除此之外,该值必须为2的N次方.即使给定的值不是2的N次方的时候,BitmapFactory加载时也会取该值最接近的2的N次方(非常有可能是向下取值)

/**
 * 计算图片的缩放比例;</br>
 * 此值是指图片的宽高的缩放比例,图片面积的缩放比例为返回值的2次方;
 * 返回值必定大于0且为2的N次方(这是由于系统决定了返回值必须是2的N次方)
 *
 * @param options 加载图片所得的配置信息
 * @param reqSize 预期希望的的图片最长边大小,最长边由图片本身决定,可能是宽也可能是高,加载后的图片最长边不会超过此参数值
 * @return
 */
public static int calculateInSampleSize(BitmapFactory.Options options, float reqSize) {
    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);
    }
    return sampleSize;
}

缩略图加载

缩略图的加载的有多种方式,可以加载图片数据流,图片资源,图片路径.
但本质都是通过加载图片数据流加载的.图片资源与图片路径都是加载成流文件再加载的.
我们可以看一下系统的路径加载与资源加载的源代码:

//路径加载
//省略了非重点代码
Bitmap bm = null;
InputStream stream = null;
//路径加载时实质上也是加载成流文件再从流中加载出数据
stream = new FileInputStream(pathName);
bm = decodeStream(stream, null, opts);


//资源加载
//打开资源流文件,加载出资源相关信息到value中,value的类型是TypedValue
is = res.openRawResource(id, value);
bm = decodeResourceStream(res, value, is, null, opts);
//进一步查看该加载方法 decodeResourceStream
//最终返回时还是通过加载流的形式返回bitmap,上述的value仅仅是进行一些opts的设置
return decodeStream(is, pad, opts);

所以这里提供的是针对流的加载.

/**
 * 获取图片加载配置
 *
 * @param in      图片流,此方法运行后该流会被关闭
 * @param reqSize 预期图片最长边的最大值
 * @return
 */
public static BitmapFactory.Options getStreamBitmapOptions(InputStream in, float reqSize) {
    try {
        if (in == null || reqSize <= 0) {
            return null;
        }
        //仅加载图片信息(不加载图片数据),获取配置文件
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(in, null, options);

        //计算图片缩放比例
        int sampleSize = calculateInSampleSize(options, reqSize);
        //正常加载图片
        options.inJustDecodeBounds = false;
        options.inSampleSize = sampleSize;
        return options;
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

/**
 * 从图片流数据中加载图片
 *
 * @param in      图片流数据,此参数值建议从文件重新加载出来(不要使用过的流)
 * @param options 加载配置,此参数应该来自于{@link #getStreamBitmapOptions(InputStream, float)}
 * @return
 */
public static Bitmap decodeBitmapInScale(InputStream in, BitmapFactory.Options options) {
    try {
        if (in == null || options == null || in.available() <= 0) {
            return null;
        }
        Log.i("bmp", "sampleSize = " + options.inSampleSize + "\nsrcWidth = " + options.outWidth + "\nsrcHeight = " + options.outHeight);
        return BitmapFactory.decodeStream(in, null, options);
    } catch (IOException e) {
        e.printStackTrace();
        return null;
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

以上图片加载时是分为两部分的.一部分是加载出图片的信息并计算缩略图比例后返回缩略图加载的配置对象BitmapFactory.Options
另一部分是根据新的缩略图配置加载出缩略图对象.
这里使用的是inputStream流加载,分为两次的原因是:

当使用该流in加载出配置信息后,再重复使用该流加载图片时,很可能会加载不出图片

以上问题为亲测确定的.当使用BitmapFactory.decodeFile()BitmapFactory.decodeResource()都是可以正常加载出图片.
但使用BitmapFactory.decodeStream()并使用同一个流(之前加载配置信息的流)加载时,就会出现无法成功加载出图片的问题.

查询一下BitmapFactory.decodeFile()BitmapFactory.decodeResource()方法的源代码可以确定,实际上此两个方法也都是将文件及资源文件加载成流文件再加载图片的.
而在加载BitmapFactory.Options配置时,调用一次decodeFile()即打开一次流;再次重新加载出图片时,再调用一次会重新打开一次流.

因此一次缩放加载图片至少会打开两次流;不管是通过哪种方式(流或者路径或者资源)进行加载.
所以,在加载配置信息及加载图片时,需要使用同一个文件的两个全新的流对象,而不能复用同一个流对象.

类似地提供了两个便捷地加载路径及资源的方法,但本质都是通过流加载出图片,并且每个文件都是被打开了两次流.一次用于加载配置,一次用于加载图片:

/**
 * 加载缩略图
 *
 * @param filePath 文件路径
 * @param reqSize  预期图片最长边的最大值,加载所有图片的最大值不会超过此值
 * @return
 */
public static Bitmap decodeBitmapInScale(String filePath, int reqSize) {
    InputStream in = null;
    in = getBmpInputStream(filePath);
    BitmapFactory.Options options = getStreamBitmapOptions(in, reqSize);
    in = getBmpInputStream(filePath);
    return decodeBitmapInScale(in, options);
}

/**
 * 加载图片(适用于资源文件)
 *
 * @param res
 * @param resID   资源ID
 * @param reqSize 预期图片最长边的最大值,加载所有图片的最大值不会超过此值
 * @return
 */
public static Bitmap decodeBitmapInScale(Resources res, int resID, int reqSize) {
    InputStream in = null;
    in = getBmpInputStream(res, resID);
    BitmapFactory.Options options = getStreamBitmapOptions(in, reqSize);
    in = getBmpInputStream(res, resID);
    return decodeBitmapInScale(in, options);
}

本质是加载两次流文件并使用流加载出图片.(与BitmapFactory的加载本质是相同的)


源码

源码包括了图片的旋转,图片的缩略加载,拍照/相册调用,打开自定义裁剪CropBitmapActivity

/**
 * Created by taro on 16/4/11.
 */
public class GrallyAndPhotoUtils {
    /**
     * 拍照请求
     */
    public static final int REQUEST_CODE_TAKE_PHOTO = 0x1;
    /**
     * 打开相册请求
     */
    public static final int REQUEST_CODE_OPEN_GRALLY = 0x2;
    /**
     * 裁剪图片请求
     */
    public static final int REQUEST_CODE_CROP_PHOTO = 0x3;

    /**
     * 加载缩略图
     *
     * @param filePath 文件路径
     * @param reqSize  预期图片最长边的最大值,加载所有图片的最大值不会超过此值
     * @return
     */
    public static Bitmap decodeBitmapInScale(String filePath, int reqSize) {
        InputStream in = null;
        in = getBmpInputStream(filePath);
        BitmapFactory.Options options = getStreamBitmapOptions(in, reqSize);
        in = getBmpInputStream(filePath);
        return decodeBitmapInScale(in, options);
    }

    /**
     * 加载图片(适用于资源文件)
     *
     * @param res
     * @param resID   资源ID
     * @param reqSize 预期图片最长边的最大值,加载所有图片的最大值不会超过此值
     * @return
     */
    public static Bitmap decodeBitmapInScale(Resources res, int resID, int reqSize) {
        InputStream in = null;
        in = getBmpInputStream(res, resID);
        BitmapFactory.Options options = getStreamBitmapOptions(in, reqSize);
        in = getBmpInputStream(res, resID);
        return decodeBitmapInScale(in, options);
    }

    /**
     * 获取图片加载配置
     *
     * @param in      图片流,此方法运行后该流会被关闭
     * @param reqSize 预期图片最长边的最大值
     * @return
     */
    public static BitmapFactory.Options getStreamBitmapOptions(InputStream in, float reqSize) {
        try {
            if (in == null || reqSize <= 0) {
                return null;
            }
            //仅加载图片信息(不加载图片数据),获取配置文件
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(in, null, options);

            //计算图片缩放比例
            int sampleSize = calculateInSampleSize(options, reqSize);
            //正常加载图片
            options.inJustDecodeBounds = false;
            options.inSampleSize = sampleSize;
            return options;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 从图片流数据中加载图片
     *
     * @param in      图片流数据,此参数值建议从文件重新加载出来(不要使用过的流)
     * @param options 加载配置,此参数应该来自于{@link #getStreamBitmapOptions(InputStream, float)}
     * @return
     */
    public static Bitmap decodeBitmapInScale(InputStream in, BitmapFactory.Options options) {
        try {
            if (in == null || options == null || in.available() <= 0) {
                return null;
            }
            Log.i("bmp", "sampleSize = " + options.inSampleSize + "\nsrcWidth = " + options.outWidth + "\nsrcHeight = " + options.outHeight);
            return BitmapFactory.decodeStream(in, null, options);
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 计算图片的缩放比例;</br>
     * 此值是指图片的宽高的缩放比例,图片面积的缩放比例为返回值的2次方;
     * 返回值必定大于0且为2的N次方(这是由于系统决定了返回值必须是2的N次方)
     *
     * @param options 加载图片所得的配置信息
     * @param reqSize 预期希望的的图片最长边大小,最长边由图片本身决定,可能是宽也可能是高,加载后的图片最长边不会超过此参数值
     * @return
     */
    public static int calculateInSampleSize(BitmapFactory.Options options, float reqSize) {
        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);
        }
        return sampleSize;
    }

    /**
     * 从图片路径获取图片输入流
     *
     * @param filePath 图片路径
     * @return 获取失败返回null
     */
    public static InputStream getBmpInputStream(String filePath) {
        try {
            return new FileInputStream(new File(filePath));
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 从资源文件获取图片输入流
     *
     * @param res
     * @param resID 资源ID
     * @return 获取失败null
     */
    public static InputStream getBmpInputStream(Resources res, int resID) {
        if (res == null || resID == 0) {
            return null;
        }
        return res.openRawResource(resID);
    }


    /**
     * 读取图片属性:旋转的角度
     *
     * @param path 图片绝对路径
     * @return degree旋转的角度
     */
    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 (Exception e) {
            e.printStackTrace();
        }
        return degree;
    }

    /**
     * 旋转图片
     *
     * @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;
    }

    /**
     * 将图片保存到文件中
     *
     * @param outputPath 图片输出地址,完整的地址包括图片格式
     * @param bitmap     保存的图片对象
     * @param format     图片格式类型
     * @param quality    图片质量,0-100,默认使用50.当类型为PNG时,此参数无效.
     * @return
     */
    public boolean saveBitmapToFile(String outputPath, Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
        if (bitmap == null || TextUtils.isEmpty(outputPath)) {
            return false;
        }
        if (quality < 0 || quality > 100) {
            quality = 50;
        }
        try {
            File file = new File(outputPath);
            //文件不存在,创建空文件
            if (!file.exists()) {
                file.createNewFile();
            }
            FileOutputStream out = new FileOutputStream(file);
            bitmap.compress(format, quality, out);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 从系统content资源路径中获取图片的真实地址路径
     *
     * @param context
     * @param bmpIntent 系统返回的Intent数据,包含图片的content Uri
     * @return
     */
    public static String getBmpPathFromContent(Context context, Intent bmpIntent) {
        if (bmpIntent != null && bmpIntent.getData() != null) {
            Uri contentUri = bmpIntent.getData();
            String uriPath = contentUri.getPath();
            //content存在时
            if (!TextUtils.isEmpty(uriPath)) {
                String[] filePathColumn = {MediaStore.Images.Media.DATA};
                //加载系统数据库
                Cursor cursor = context.getContentResolver().query(contentUri,
                        filePathColumn, null, null, null);
                //移动到第一行,不移动将越界
                cursor.moveToFirst();
                //加载查询数据的列号
                int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
                //获取图片路径
                String picturePath = cursor.getString(columnIndex);
                //游标应该关闭
                cursor.close();
                return picturePath;
            } else {
                return null;
            }
        } else {
            return null;
        }
    }

    /**
     * 打开照相机并拍照(默认将照片存放到相册中)
     *
     * @param act 启动照相机的activity
     * @return 返回用于存放拍照完的图像的Uri
     */
    public static Uri openCamera(Activity act, String filePath) {
        if (TextUtils.isEmpty(filePath)) {
            throw new RuntimeException("filePath can not be null");
        }
        //尝试创建文件夹及用于存储拍照后的文件
        File outputImage = new File(filePath);
        if (!outputImage.exists()) {
            try {
                outputImage.createNewFile();
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }
        //将File对象转换为Uri并启动照相程序
        Uri photoUri = Uri.fromFile(outputImage);
        Intent intent = new Intent("android.media.action.IMAGE_CAPTURE"); //照相
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); //指定图片输出地址
        try {
            act.startActivityForResult(intent, REQUEST_CODE_TAKE_PHOTO); //启动照相
        } catch (SecurityException se) {
            se.printStackTrace();
            return null;
        }
        return photoUri;
    }

    /**
     * 打开相册
     *
     * @param act 用于启用系统相册的Activity
     * @return
     */
    public static void openGrally(Activity act) {
        if (act == null) {
            return;
        }
//        //此action也可以使用,此action是选择任何指定类型的文件
//        Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
        Intent intent = new Intent(Intent.ACTION_PICK);
        intent.setType("image/*");
        //存在多个相册类型的应用时,显示给用户选择的打个相册的应用界面
        act.startActivityForResult(Intent.createChooser(intent, "请选择"), REQUEST_CODE_OPEN_GRALLY);
        return;
    }

    /**
     * 调用系统裁剪功能裁剪,部分机型可能不适用.
     *
     * @param act          启动裁剪功能的activity
     * @param bitmapIntent
     * @param photoUri     拍照所得的uri
     * @param width        头像宽度
     * @param height       头像高度
     * @deprecated
     */
    public static void cropPhoto(Activity act, Intent bitmapIntent, Uri photoUri, int width, int height) {
        if (photoUri == null) {
            //若当前uri不存在(可能被系统清除了)
            return;
        }

        if (width <= 0) {
            width = 300;
        }
        if (height <= 0) {
            height = 300;
        }

        Uri inputUri = photoUri;
        if (bitmapIntent != null && bitmapIntent.getData() != null) {
            inputUri = bitmapIntent.getData();
        } else {
            inputUri = photoUri;
        }

        //广播刷新相册
        Intent intentBc = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
        intentBc.setData(inputUri);
        act.sendBroadcast(intentBc);

        Intent intent = new Intent("com.android.camera.action.CROP"); //剪裁
        intent.setDataAndType(inputUri, "image/*");
        intent.putExtra("crop", true);
        intent.putExtra("scale", true);
        //设置宽高比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        //设置裁剪图片宽高
        intent.putExtra("outputX", width);
        intent.putExtra("outputY", height);
        intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
        intent.putExtra("return-data", false);
        intent.putExtra("outputFormat", Bitmap.CompressFormat.PNG.toString());
        act.startActivityForResult(intent, REQUEST_CODE_CROP_PHOTO); //设置裁剪参数显示图片至ImageView
    }

    /**
     * 处理拍照/打开相册后进入裁剪工作
     *
     * @param requestCode 连接
     * @param resultCode
     * @param data        Intent数据
     * @param act         启动裁剪Activity的act
     * @param inputPath   裁剪图片的路径(针对拍照保存的路径,相册获取的路径在data中)
     * @param outputPath  图片裁剪后输出的路径(提供给裁剪Activity)
     * @return
     */
    public static boolean onActivityResult(int requestCode, int resultCode, Intent data, Activity act, String inputPath, String outputPath) {
        switch (requestCode) {
            case REQUEST_CODE_TAKE_PHOTO:
            case REQUEST_CODE_OPEN_GRALLY:
                //当启动使用的activity不存在或者是拍照/打开相册失败时(resultCode不为RESULT_OK),不处理
                if (act == null || resultCode != Activity.RESULT_OK) {
                    return false;
                }
                //若当前为拍照回调,尝试读取图片的旋转角度
                int degree = requestCode == REQUEST_CODE_TAKE_PHOTO ? readPictureDegree(inputPath) : 0;
                //获取intent中返回的图片路径(如果是打开相册部分机型可能将图片路径存放在intent中返回)
                String tempPath = getBmpPathFromContent(act, data);
                //若intent路径存在,使用该路径,否则使用inputPath
                inputPath = TextUtils.isEmpty(tempPath) ? inputPath : tempPath;

                //源图片路径或者输出路径为无效时,不处理
                if (TextUtils.isEmpty(inputPath) || TextUtils.isEmpty(outputPath)) {
                    return false;
                } else {
                    Intent cropIntent = new Intent(act, CropBitmapActivity.class);
                    cropIntent.putExtra("inputPath", inputPath);
                    cropIntent.putExtra("outputPath", outputPath);
                    cropIntent.putExtra("degree", degree);
                    //启动裁剪工具
                    act.startActivityForResult(cropIntent, REQUEST_CODE_CROP_PHOTO);
                    return true;
                }
            default:
                return false;
        }
    }
}

图片加载结果

04-15 16:30:26.850    1644-1644/com.henrytaro.ct I/bmp﹕ 
    sampleSize = 4//长度缩小为1/4,全图缩小为1/16
    srcWidth = 1152
    srcHeight = 1920
04-15 16:30:26.895    1644-1644/com.henrytaro.ct I/bmp﹕ 
    scaleWidth = 288
    scaleHeight = 480

由以上打印数据可见,全图缩小为原来的1/16,长宽由原本的1152*1920缩小为288*480
ARGB.8888格式计算,每个像素使用4个字节保存.原图占用空间为:
1152*1920*4/1024/1024 约 8M
缩小后占用空间为:
288*480*4/1024/1024 约 0.5M

所以在可以使用缩略图的情况下,尽可能使用缩略图才不会加重程序的内存负担

至于保存图片时,如果非PNG格式时,可以降低保存图片的质量从而降低图片的大小.(大部分情况下用于JPG)


示例GIF

以上工具类可以打开相册及相机,此处只示例相册打开并裁剪图片.
此工具类不包括裁剪功能,仅为图片处理方法而已.
详细裁剪实现请参考相关文章CropViewCropBitmapActivity

选择图片剪裁

回到目录

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值