Android开发实践:自己动手编写图片剪裁应用(3)

前面两篇文章分别介绍了我编写的开源项目ImageCropper库,以及如何调用系统的图片剪裁模块,本文则继续分析一下开发Android图片剪裁应用中需要用到的Bitmap操作。

在Android系统中,对图片的操作主要是通过Bitmap类和Matrix类来完成,本文就介绍一下图片剪裁应用中对Bitmap的一些操作,包括:打开、保存、剪裁、旋转等,我已经将这些操作都封装到了一个BitmapHelper.java类中,放到GitHub上了(点击这里),大家可以方便地集成到自己的项目中。

  1. 打开图片

图片的打开主要是把各种格式的图片转换为Bitmap对象,Android通过BitmapFactory类提供了一系列的静态方法来协助完成这个操作,如下所示:

public class BitmapFactory {
    public static Bitmap decodeFile(String pathName, Options opts);
    public static Bitmap decodeFile(String pathName);
    public static Bitmap decodeResourceStream(Resources res, TypedValue value,
            InputStream is, Rect pad, Options opts) ;
    public static Bitmap decodeResource(Resources res, int id, Options opts) ;
    public static Bitmap decodeResource(Resources res, int id);
    public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts);
    public static Bitmap decodeByteArray(byte[] data, int offset, int length);
    public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts);
    public static Bitmap decodeStream(InputStream is) ;
    public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts);
    public static Bitmap decodeFileDescriptor(FileDescriptor fd) ;
}

通过这些静态方法,我们可以方便地从文件、资源、字节流等各种途径打开图片,生成Bitmap对象。下面给出一个从文件中打开图片的函数封装:

    public static Bitmap load( String filepath ) {

        Bitmap bitmap = null;
        try {
            FileInputStream fin = new FileInputStream(filepath);
            bitmap = BitmapFactory.decodeStream(fin);
            fin.close();
        }
        catch (FileNotFoundException e) {
            
        } 
        catch (IOException e) {
                
        }
        return bitmap;
    }


2. 保存图片


图片的保存则主要通过Bitmap的compress方法,该方法的原型如下:

/**
  * Write a compressed version of the bitmap to the specified outputstream.    
  * @param format   The format of the compressed image
  * @param quality  Hint to the compressor, 0-100. 0 meaning compress for
  *                 small size, 100 meaning compress for max quality. Some
  *                 formats, like PNG which is lossless, will ignore the
  *                 quality setting
  * @param stream   The outputstream to write the compressed data.
  * @return true if successfully compressed to the specified stream.
  */
public boolean compress(CompressFormat format, int quality, OutputStream stream)

第一个参数是图片格式,只有JPEG、PNG和WEBP三种,第二个参数是压缩质量(0~100),数值越大图片信息损失越小,第三个参数则是文件流对象。

同样,这里给出一个保存图片的函数封装:

public static void save( Bitmap bitmap, String filepath ) {
    try {
        FileOutputStream fos = new FileOutputStream(filepath);
        bitmap.compress(CompressFormat.JPEG, 100, fos);              
        bitmap.recycle();            
        fos.close();            
     }
     catch (FileNotFoundException e) {
           
     } 
     catch (IOException e) {      
            
     }   
 }

3. 剪裁图片


Android中剪裁图片主要有2种方法,一种通过Bitmap的createBitmap方法来生成剪裁的图片,另一种则是通过Canvas对象来“绘制”新的图片,这里先给出代码,再分析:

public static Bitmap crop( Bitmap bitmap, Rect cropRect ) {
    return Bitmap.createBitmap(bitmap,cropRect.left,cropRect.top,cropRect.width(),cropRect.height());
}
    
public static Bitmap cropWithCanvas( Bitmap bitmap, Rect cropRect ) {
    Rect destRect = new Rect(0,0,cropRect.width(),cropRect.height());
    Bitmap cropped = Bitmap.createBitmap(cropRect.width(),cropRect.height(),Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(cropped);        
    canvas.drawBitmap(bitmap,cropRect,destRect,null);
    return cropped;
}

其实第一种方法内部实现也是利用了Canvas对象来“绘制”新的图片的,Canvas对象通过一个Bitmap对象来构建,该Bitmap即为“画布”,drawBitmap则是将源bitmap对象“画”到“画布”之中,这样就实现了数据的搬移,实现了图片的剪裁。

4. 旋转图片


Android中旋转图片同样是通过Bitmap的createBitmap方法来生成旋转后的图片,不过图片的旋转需要借助Matrix对象来协助完成,代码如下:

public static Bitmap rotate( Bitmap bitmap, int degrees  ) {
    Matrix matrix = new Matrix();
    matrix.postRotate(degrees);            
    return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
}

当然,图片的旋转也是可以通过Canvas来“绘制”,由于图片旋转会导致边界坐标发生变化,所以需要以图片中心点坐标为中心来旋转,具体实现见如下代码:

public static Bitmap rotateWithCanvas( Bitmap bitmap, int degrees  ) {
        
    int destWidth,destHeight;
        
    float centerX = bitmap.getWidth()/2;
    float centerY = bitmap.getHeight()/2;        
        
    // We want to do the rotation at origin, but since the bounding
    // rectangle will be changed after rotation, so the delta values
    // are based on old & new width/height respectively.
    Matrix matrix = new Matrix();        
    matrix.preTranslate(-centerX,-centerY);
    matrix.postRotate(degrees);        
    if( degrees/90%2 == 0 ) { 
        destWidth  = bitmap.getWidth();
        destHeight = bitmap.getHeight();
        matrix.postTranslate(centerX,centerY);
    }        
    else {            
        destWidth  = bitmap.getHeight();
        destHeight = bitmap.getWidth();
        matrix.postTranslate(centerY,centerX);            
    }
    Bitmap cropped = Bitmap.createBitmap(destWidth,destHeight,Bitmap.Config.RGB_565);
    Canvas canvas = new Canvas(cropped);       
    canvas.drawBitmap(bitmap, matrix, null);
    return cropped;
}

5. 小结


关于Bitmap的相关操作就介绍到这里了,更多的代码示例和实现可以参考我的开源项目ImageCropper,

该项目的GitHub地址:https://github.com/Jhuster/ImageCropper,有任何疑问欢迎留言讨论或者来信[email protected]交流。

时间: 2024-10-26 19:57:51

Android开发实践:自己动手编写图片剪裁应用(3)的相关文章

Android开发实践:自己动手编写图片剪裁应用(2)

上篇文章主要介绍了我开源在Github上的图片剪裁库(ImageCropper)的基本特性和用法,从本文开始,慢慢介绍一些开发图片剪裁应用中涉及的知识点和技术. 其实Android系统本身也提供了图片剪裁的模块,我们可以直接通过Intent来调用系统的图片剪裁功能,本文我们就先了解一下系统自带的图片剪裁功能是如何调用的吧. 得到被剪裁图片的URL地址 既然是图片剪裁,就一定要有被剪裁的图片,由于图片数据一般很大,为了防止内存溢出,普通APP与Android系统图片剪裁应用之间是通过URL来传递图

Android开发实践:自己动手编写图片剪裁应用(1)

最近利用一周左右的业余时间,终于完成了一个Android图片剪裁库,核心功能是根据自己的理解实现的,部分代码参考了Android源码的图片剪裁应用.现在将该代码开源在Github上以供大家学习和使用,地址:https://github.com/Jhuster/ImageCropper,效果如下所示: 我的大致计划是首先介绍一下这个库的用法,然后再写几篇文章介绍一下其中的一些原理和关键技术,希望对Android开发新手有所帮助. [特性] 支持通过手势移动和缩放剪裁窗口 支持固定剪裁窗口大小.固定

Android开发实践:利用ProGuard进行代码混淆

由于Android的代码大都是Java代码,所以挺容易被反编译的,好在Android ADT为我们集成了混淆代码的工具,一来可以混淆我们的代码,让程序被反编译后基本看不懂,另外还能起到代码优化的作用.发布项目前,建议打开Android的代码混淆功能. Android ADT主要通过ProGuard工具来提供代码混淆,网上也有挺多博客文章讲这个的,但感觉很多都介绍得太过于复杂,这里我就以问答的方式来更加简洁地介绍下ProGuard吧. 1. ProGuard是什么 ProGuard是一个工具,用来

Android开发实践:Android交叉编译工具链的使用

前面2篇文章分别介绍了Android NDK编译的命令行参数,以及如何在任意目录使用Android.mk来编译本地c/c++代码,Andriod.mk和ndk-build只不过是Android官方提供了一套封装过的Android交叉编译环境而已,其实,你可以不用它,而直接通过传统的Makefile文件来编译你的c/c++代码的,本文即介绍如何直接通过传统的Makefile文件来编译可用于Android平台的库文件. 经常搞嵌入式开发的朋友对于交叉编译环境应该并不陌生,说白了,就是一组运行在x86

Android开发实践:为什么要继承onMeasure()

Android开发中偶尔会用到自定义View,一般情况下,自定义View都需要继承View类的onMeasure方法,那么,为什么要继承onMeasure()函数呢?什么情况下要继承onMeasure()?系统默认的onMeasure()函数行为是怎样的 ?本文就探究探究这些问题. 首先,我们写一个自定义View,直接调用系统默认的onMeasure函数,看看会是怎样的现象: package com.titcktick.customview; import android.content.Con

Android开发实践:WIFI连接功能的封装

在上一篇文章<Android开发实践:WIFI扫描功能的封装>介绍了如何利用Andriod的API实现WIFI的扫描,本文则重点讲述一下如何连接WIFI吧,在此,也给出一个封装WIFI连接过程的类,提供简单的接口以供在各个代码工程中复用. 与WIFI扫描类似,WIFI的连接同样是一个耗时的过程,所以需要放到线程中执行,通过回调来通知调用者连接结果.该回调接口的定义如下: public interface WifiConnectListener { public void OnWifiConne

Android开发实践:由new Handler()说开去

最近面试一些Android开发的应聘者,除了基本的Activity生命周期等基础问题以外,我一般还会问如下两个问题: (1) Service与Thread有什么区别? (2) 在Activity里new Handler()和在自己创建的Thread中new Handler()有什么区别? 第一个问题其实是一个伪命令,因为Service是Android四大组件之一,而Thread只是Java提供的一个封装了线程管理的工具类,无论是Activity还是Service,都可以通过Thread来创建一个

Android开发实践:Java层与Jni层的数组传递

Android开发中,经常会在Java代码与Jni层之间传递数组(byte[]),一个典型的应用是Java层把需要发送给客户端的数据流传递到Jni层,由Jni层的Socket代码发送出去,当然,Jni层也需要把从Socket接收到的数据流返回给Java层.我简单地总结了一下,从Java层到Jni层,从Jni层到JAVA层,各有3种传递方式,下面用代码示例简单地介绍一下. 示例代码的主要文件有两个,一个是Native.java,是Java层的类:另一个是Native.c,是JNI层的文件,关键的地

Android开发实践:以“专业”的态度处理多线程

刚开始学一门编程语言的时候,我总是会有一种困惑,怎样让自己的代码看起来更"专业"?很多时候,我们可以照着教材实现一些基本的功能,比如用Socket发送/接收几个字符,写一个线程完成某个异步任务,但是在实际的项目中,往往不那么简单,比如需要设计Socket通信协议,需要处理Socket的连接异常断开,需要考虑在线程阻塞的情况下如何正常退出和释放资源等等,关于这些"实战经验",前面的文章也有所涉及,以后有空准备再开个专题跟大家分享探讨一下,今天先简单地说说怎样更&quo