前言:

图片的操作我相信大家都操作过,在算法层面大家往往都是把图片转成MAT矩阵处理的,而Android 开发层面大多数都是bitmap位图操作。接下来我将分算法层面以及android层面来讲解一下图片的操作。

好了,废话不多说,先将android层面的图像处理,这里我将分俩篇介绍,一个是基础篇,另外一篇是实战演练篇。

基本概念

什么是Bitmap

Bitmap位图包括像素以及长、宽、颜色等描述信息。长宽和像素位数是用来描述图片的,可以通过这些信息计算出图片的像素占用内存的大小。

Config:图片像素类型

图片像素类型包括ALPHA_8、RGB_565、ARGB_4444、ARGB_8888。其中,A:透明度;RGB分别是Red、Green、Blue,三种原色。

  1. ARGB_8888
    四个通道都是8位,每个像素占用4个字节,图片质量是最高的,但是占用的内存也是最大的。
  2. ARGB_4444
    四个通道都是4位,每个像素占用2个字节,图片的失真比较严重 。
  3. RGB_565
    没有A通道,每个像素占用2个字节,图片失真小,但是没有透明度。
  4. ARGB_4444
    失真严重,基本不用;ALPHA_8使用场景特殊,比如设置遮盖效果等;不需要设置透明度,RGB_565是个不错的选择;既要设置透明度,对图片质量要求又高,就用ARGB_8888

CompressFormat:压缩格式

Bitmap.CompressFormat.JPEG、Bitmap.CompressFormat.PNG、Bitmap.CompressFormat.WEBP
  1. JPEG
    一种有损压缩(JPEG2000既可以有损也可以无损),".jpg"或者".jpeg"; 优点:采用了直接色,有丰富的色彩,适合存储照片和生动图像效果;缺点:有损,不适合用来存储logo、线框类图。
  2. PNG:
    一种无损压缩,".png"; 优点:支持透明、无损,主要用于小图标,透明背景等;缺点:若色彩复杂,则图片生成后文件很大;
  3. WEBP
    以WebP算法进行压缩;Google开发的新的图片格式,同时支持无损和有损压缩,使用直接色。无损压缩,相同质量的webp比PNG小大约26%;有损压缩,相同质量的webp比JPEG小25%-34% 支持动图,基本取代gif

相关API方法

常用方法:

public void recycle()——回收位图占用的内存空间,把位图标记为Deadpublic final boolean isRecycled() ——判断位图内存是否已释放public final int getWidth()——获取位图的宽度public final int getHeight()——获取位图的高度public final boolean isMutable()——图片是否可修改public int getScaledWidth(Canvas canvas)——获取指定密度转换后的图像的宽度public int getScaledHeight(Canvas canvas)——获取指定密度转换后的图像的高度public boolean compress(CompressFormat format, int quality, OutputStream stream)——按指定的图片格式以及画质,将图片转换为输出流。format:Bitmap.CompressFormat.PNG或Bitmap.CompressFormat.JPEG
quality:画质,0-100.0表示最低画质压缩,100以最高画质压缩。对于PNG等无损格式的图片,会忽略此项设置。

常用的静态方法

public static Bitmap createBitmap(Bitmap src) ——以src为原图生成不可变得新图像public static Bitmap createScaledBitmap(Bitmap src, int dstWidth,
int dstHeight, boolean filter)——以src为原图,创建新的图像,指定新图像的高宽以及是否可变。public static Bitmap createBitmap(int width, int height, Config config)——创建指定格式、大小的位图public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height)以source为原图,创建新的图片,指定起始坐标以及新图像的高宽。

BitmapFactory常用方法

BitmapFactory提供静态方法有 decodeFile、decodeResource,decodeStream、decodeByteArray

这几个方法最后一个参数都可以添加一个BitmapFactory.Options 对象,表示位创建Bitmap设置一些参数,比如:

BitmapFactory.Options options = new BitmapFactory.Options();
//inJustDecodeBounds为true,不返回bitmap,只返回这个bitmap的尺寸
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), images[position], options);

Option 参数类:

public boolean inJustDecodeBounds——如果设置为true,不获取图片,不分配内存,但会返回图片的高度宽度信息。public int inSampleSize——图片缩放的倍数。如果设为4,则宽和高都为原来的1/4,则图是原来的1/16。public int outWidth——获取图片的宽度值public int outHeight——获取图片的高度值public int inDensity——用于位图的像素压缩比public int inTargetDensity——用于目标位图的像素压缩比(要生成的位图)public boolean inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity。

BitmapDrawable

BitmapDrawable可以强转Bitmap,常用构造函数如下:

public BitmapDrawable(Resources res) ——创建一个空的drawable。(Response用来指定初始时所用的像素密度)替代** public BitmapDrawable()** 方法(此方法不处理像素密度)
public BitmapDrawable(Resources res, Bitmap bitmap) ——创建以BitmapDrawable通过Bitmap
public BitmapDrawable(Resources res, String filepath) ——创建以BitmapDrawable通过文件路径
public BitmapDrawable(Resources res, java.io.InputStream is) ——创建以BitmapDrawable通过输入流

API常用操作

1:将Bitmap转换成圆角

public Bitmap toRoundCorner(Bitmap bitmap, int pixels) {Bitmap roundCornerBitmap = Bitmap.createBitmap(bitmap.getWidth(), bitmap.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(roundCornerBitmap);int color = 0xff424242;//int color = 0xff424242;Paint paint = new Paint();paint.setColor(color);//防止锯齿paint.setAntiAlias(true);Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());RectF rectF = new RectF(rect);float roundPx = pixels;//相当于清屏canvas.drawARGB(0, 0, 0, 0);//先画了一个带圆角的矩形canvas.drawRoundRect(rectF, roundPx, roundPx, paint);paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));//再把原来的bitmap画到现在的bitmap!!!注意这个理解canvas.drawBitmap(bitmap, rect, rect, paint);return roundCornerBitmap;}

2:获取图片的缩略图

private Bitmap getBitmapThumbnail(String filePath) {BitmapFactory.Options options = new BitmapFactory.Options();//true那么将不返回实际的bitmap对象,不给其分配内存空间但是可以得到一些解码边界信息即图片大小等信息options.inJustDecodeBounds = true;//此时rawBitmap为nullBitmap rawBitmap = BitmapFactory.decodeFile(filePath, options);if (rawBitmap == null) {System.out.println("此时rawBitmap为null");//inSampleSize表示缩略图大小为原始图片大小的几分之一,若该值为3//则取出的缩略图的宽和高都是原始图片的1/3,图片大小就为原始大小的1/9//计算sampleSizeint sampleSize = computeSampleSize(options, 150, 200 * 200);//为了读到图片,必须把options.inJustDecodeBounds设回falseoptions.inJustDecodeBounds = false;options.inSampleSize = sampleSize;//原图大小为625x690 90.2kB//测试调用computeSampleSize(options, 100, 200*100);//得到sampleSize=8//得到宽和高位79和87//79*8=632 87*8=696Bitmap thumbnailBitmap = BitmapFactory.decodeFile(filePath, options);//保存到SD卡方便比较this.compressAndSaveBitmapToSDCard(thumbnailBitmap, "15.jpg", 80);return thumbnailBitmap;}//第一个参数:原本Bitmap的options//第二个参数:希望生成的缩略图的宽高中的较小的值//第三个参数:希望生成的缩量图的总像素public static int computeSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {int initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels);int roundedSize;if (initialSize <= 8) {roundedSize = 1;while (roundedSize < initialSize) {roundedSize <<= 1;}} else {roundedSize = (initialSize + 7) / 8 * 8;}return roundedSize;}private static int computeInitialSampleSize(BitmapFactory.Options options, int minSideLength, int maxNumOfPixels) {//原始图片的宽double w = options.outWidth;//原始图片的高double h = options.outHeight;System.out.println("========== w=" + w + ",h=" + h);int lowerBound = (maxNumOfPixels == -1) ? 1 : (int) Math.ceil(Math.sqrt(w * h / maxNumOfPixels));int upperBound = (minSideLength == -1) ? 128 : (int) Math.min(Math.floor(w / minSideLength), Math.floor(h / minSideLength));if (upperBound < lowerBound) {// return the larger one when there is no overlapping zone.return lowerBound;}if ((maxNumOfPixels == -1) && (minSideLength == -1)) {return 1;} else if (minSideLength == -1) {return lowerBound;} else {return upperBound;}}

3:压缩且保存图片到SDCard

private void compressAndSaveBitmapToSDCard(Bitmap rawBitmap, String fileName, int quality) {String saveFilePaht = this.getSDCardPath() + File.separator + fileName;File saveFile = new File(saveFilePaht);if (!saveFile.exists()) {try {saveFile.createNewFile();FileOutputStream fileOutputStream = new FileOutputStream(saveFile);if (fileOutputStream != null) {//imageBitmap.compress(format, quality, stream);//把位图的压缩信息写入到一个指定的输出流中//第一个参数format为压缩的格式//第二个参数quality为图像压缩比的值,0-100.0 意味着小尺寸压缩,100意味着高质量压缩//第三个参数stream为输出流rawBitmap.compress(Bitmap.CompressFormat.JPEG, quality, fileOutputStream);}fileOutputStream.flush();fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}}

4:Bitmap裁剪图像

Bitmap裁剪图像有两种方式:

  1. Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height)

    根据源Bitmap对象source,创建出source对象裁剪后的图像的Bitmap。x,y分别代表裁剪时,x轴和y轴的第一个像素,width,height分别表示裁剪后的图像的宽度和高度。

注意:

x+width要小于等于source的宽度,y+height要小于等于source的高度
  1. Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)

    这个方法只比上面的方法多了m和filter这两个参数,m是一个Matrix(矩阵)对象,可以进行缩放,旋转,移动等动作,filter为true时表示source会被过滤,仅仅当m操作不仅包含移动操作,还包含别的操作时才适用。其实上面的方法本质上就是调用这个方法而已。

    public static Bitmap createBitmap(Bitmap source, int x, int y, int width, int height) {return createBitmap(source, x, y, width, height, null, false);}

5:变换Bitmap

变换bitmap包括缩放,旋转,移动图像,基本都是基于Matrix 矩阵,如下所示:

        // 新建立矩阵Matrix matrix = new Matrix();matrix.postScale(heightScale, widthScale);// 设置图片的旋转角度//matrix.postRotate(-30);// 设置图片的倾斜//matrix.postSkew(0.1f, 0.1f);Bitmap newBitmap = Bitmap.createBitmap(rawBitmap, 0, 0, rawWidth, rawWidth, matrix, true);

6:高效压缩图片

为什么要高效压缩图片了,主要是为了解决使用Bitmap时防止OOM。

    /*** 谷歌推荐使用方法,从资源中加载图像,并高效压缩,有效降低OOM的概率* @param res 资源* @param resId 图像资源的资源id* @param reqWidth 要求图像压缩后的宽度* @param reqHeight 要求图像压缩后的高度* @return*/public Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) {// 设置inJustDecodeBounds = true ,表示获取图像信息,但是不将图像的像素加入内存final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(res, resId, options);// 调用方法计算合适的 inSampleSizeoptions.inSampleSize = calculateInSampleSize(options, reqWidth,reqHeight);// inJustDecodeBounds 置为 false 真正开始加载图片options.inJustDecodeBounds = false;//将options.inPreferredConfig改成Bitmap.Config.RGB_565,// 是默认情况Bitmap.Config.ARGB_8888占用内存的一般options.inPreferredConfig= Bitmap.Config.RGB_565;return BitmapFactory.decodeResource(res, resId, options);}// 计算 BitmapFactpry 的 inSimpleSize的值的方法public int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {if (reqWidth == 0 || reqHeight == 0) {return 1;}// 获取图片原生的宽和高final int height = options.outHeight;final int width = options.outWidth;Log.d(TAG, "origin, w= " + width + " h=" + height);int inSampleSize = 1;// 如果原生的宽高大于请求的宽高,那么将原生的宽和高都置为原来的一半if (height > reqHeight || width > reqWidth) {final int halfHeight = height / 2;final int halfWidth = width / 2;// 主要计算逻辑// Calculate the largest inSampleSize value that is a power of 2 and// keeps both// height and width larger than the requested height and width.while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) {inSampleSize *= 2;}}Log.d(TAG, "sampleSize:" + inSampleSize);return inSampleSize;}

注意使用bitmap需要注意以下问题:

使用Bitmap时要防止OOM,而防止OOM的有效方法还可以使用缓存。常用的缓存有内存缓存LruCache和磁盘缓存DiskLruCache,下面简单的介绍一下这俩种缓存。

8:内存缓存LruCache

LruCache中很重要的两个成员变量size和maxSize,因为清理缓存的是在size>maxSize时触发的,因此在初始化的时候要传入maxSize定义缓存的大小,然后重写sizeOf方法,因为LruCache是通过sizeOf方法来计算每个元素的大小。这里我们是使用LruCache来缓存图片,所以sizeOf方法需要计算Bitmap的大小并返回。

LruCache对其缓存对象采用的是强引用关系,采用maxSize来控制缓存空间大小以避免OOM错误。而且LruCache类在Android SDK中已经提供了,在实际使用中我们只需要完成以下几步即可:

  • 设计LruCache的最大缓存大小:一般是通过计算当前可用的内存大小继而来获取到应该设置的缓存大小
  • 创建LruCache对象:传入最大缓存大小的参数,同时重写sizeOf方法来设置存在LruCache里的每个对象的大小
  • 封装对LruCache的数据访问和添加操作并对外提供接口以供调用
//初始化LruCache对象
public void initLruCache()
{//获取当前进程的可用内存,转换成KB单位int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//分配缓存的大小int maxSize = maxMemory / 8;//创建LruCache对象并重写sizeOf方法lruCache = new LruCache<String, Bitmap>(maxSize){@Overrideprotected int sizeOf(String key, Bitmap value) {// TODO Auto-generated method stubreturn value.getWidth() * value.getHeight() / 1024;}};
}/*** 封装将图片存入缓存的方法* @param key 图片的url转化成的key* @param bitmap对象*/
private void addBitmapToMemoryCache(String key, Bitmap bitmap)
{if(getBitmapFromMemoryCache(key) == null){mLruCache.put(key, bitmap);}
}//封装从LruCache中访问数据的方法
private Bitmap getBitmapFromMemoryCache(String key)
{return mLruCache.get(key);
}/*** 因为外界一般获取到的是url而不是key,因此为了方便再做一层封装* @param url http url* @return bitmap*/
private Bitmap loadBitmapFromMemoryCache(String url)
{final String key = hashKeyFromUrl(url);return getBitmapFromMemoryCache(key);
}

9:磁盘缓存DiskLruCache

由于DiskLruCache并不属于Android SDK的一部分,需要自行设计。与LruCache实现LRU算法的思路基本上是一致的,但是有很多不一样的地方:LruCache是内存缓存,其键对应的值类型直接为Bitmap;而DiskLruCache是磁盘缓存,所以其键对应的值类型应该是一个代表图片文件的类。其次,前者访问或添加元素时,查找成功可以直接使用该Bitmap对象;后者访问或添加元素时,查找到指定图片文件后还需要通过文件的读取和Bitmap的加载过程才能使用。另外,前者是在内存中的数据读写操作所以不需要异步;后者涉及到文件操作必须开启子线程实现异步处理。

具体DiskLruCache的设计方案和使用方式可以参考这篇博客:Bitmap的加载和Cache

有了LruCache类和DiskLruCache类,可以实现完整的Android图片二级缓存策略:在具体的图片加载时:先尝试在LruCache中查找Bitmap对象,如果有直接拿来使用。如果没有再尝试在DiskLruCache中查找图片文件,如果有将其加载为Bitmap对象再使用,并将其添加至LruCache中;如果没有查找到指定的图片文件,则发送网络请求获取图片资源并加载为Bitmap对象再使用,并将其添加DiskLruCache中。

10:存储

Bitmap 2.3之前的像素存储需要的内存是在native上分配的,并且生命周期不太可控,可能需要用户自己回收。 2.3-7.1之间,Bitmap的像素存储在Dalvik的Java堆上,当然,4.4之前的甚至能在匿名共享内存上分配(Fresco采用),而8.0之后的像素内存又重新回到native上去分配,不需要用户主动回收,8.0之后图像资源的管理更加优秀,极大降低了OOM。native的内存不影响java虚拟机的OOM我们知道Java虚拟机一般是有一个上限,但是由于Android同时能运行多个APP,这个上限一般不会太高,如果没有在AndroidManifest中启用largeheap,那么Java 堆内存达到192M的时候就会崩溃,但是将Bitmap保存在native中,Bitmap的大小几乎可以使用系统可用的所有内存。不过,内存无限增长的情况下,也会导致APP崩溃,但是这种崩溃已经不是OOM崩溃了,Java虚拟机也不会捕获,按道理说,应该属于linux的OOM了。如下图所示:


注意:

NativeAllocationRegistry是Android 8.0引入的一种辅助自动回收native内存的一种机制,当Java对象因为GC被回收后,NativeAllocationRegistry可以辅助回收Java对象所申请的native内存

总结:

好了,支持android关于bitmap的基本概念和常用的API基本介绍完毕,通常你掌握了这些基本的概念和常用API已经符合基本的日常开发了,但是如果涉及到更高升的滤镜,美颜一类的图像操作,那就需要进一步对图像,也就是对bitmap进行像素级别的操作了,接下我将详解bitmap像素操作。

Bitmap详解(上)常用概念和常用API相关推荐

  1. pillow属于python标准库吗_详解Python图像处理库Pillow常用使用方法

    PIL(Python Image Library)是python的第三方图像处理库,但是由于其强大的功能与众多的使用人数,几乎已经被认为是python官方图像处理库了. 其官方主页为:PIL. PIL ...

  2. 详解STS(SpringToolSuite)常用设置

    详解STS(SpringToolSuite)常用设置大全 STS常用设置详解 一.快捷键类设置 1.1.STS常用快捷键 1.2.常用编辑快捷键 1.3.查找和定位快捷键 1.4.调试快捷键 二.字体 ...

  3. ansible自动化运维详解(三)ansible常用模块续

    文章目录 ansible自动化运维详解(三)ansible常用模块续 四.ansible常用模块(2) 4.10.yum_repository 4.11.dnf 4.12.service 及 fire ...

  4. android拍照保存照片方向,Android:Camera2开发详解(上):实现预览、拍照、保存照片等功能...

    android.jpg 前言 在前几篇文章中介绍了如何调用系统相机拍照和使用Camera1的实现自定义相机拍照.人脸检测等功能 文章传送门: 接下来的几篇文章中,我将给大家介绍如何使用Camera2实 ...

  5. android开发自动拍照,Android:Camera2开发详解(上):实现预览、拍照、保存照片等功能...

    android.jpg 前言 在前几篇文章中介绍了如何调用系统相机拍照和使用Camera1的实现自定义相机拍照.人脸检测等功能 文章传送门: 接下来的几篇文章中,我将给大家介绍如何使用Camera2实 ...

  6. Python全栈开发-数据分析-02 Pandas详解 (上)

    Pandas详解 (上) 一. 安装pandas 1.按Win+R,输入CMD确定, 输入 pip install pandas 回车 还要安装xlrd,否则你打不开Excel文件 pip insta ...

  7. IPv6技术详解:基本概念、应用现状、技术实践(上篇)

    本文来自微信技术架构部的原创技术分享. 1.前言 普及IPV6喊了多少年了,连苹果的APP上架App Store也早已强制IPV6的支持,然并卵,因为历史遗留问题,即使在IPV4地址如果饥荒的情况下, ...

  8. 【破解教程】PE文件格式详解(上)

    PE文件格式详解(上) 摘要 Windows NT 3.1引入了一种名为PE文件格式的新可执行文件格式.PE文件格式的规范包含在了MSDN的CD中(Specs and Strategy, Specif ...

  9. IPv6技术详解:基本概念、应用现状、技术实践(上篇)(转)

    最近在搞IPV6的项目,百度搜了下,这个还是写的很清楚,转载下, 原文是这里,https://www.cnblogs.com/imstudy/p/9056334.html 严禁转载,请告知 本文来自微 ...

最新文章

  1. fastp: 极速全能的FASTQ文件自动质控+过滤+校正+预处理软件
  2. 误差分析是什么?如何进行误差分析?分析为了获得什么知识?
  3. 人工智能正在推动芯片的复兴
  4. C++知识点33——使用C++标准库(无序关联容器unordered_(multi)map,unordered_(multi)set)
  5. 单机部署zookeeper、kafka
  6. Linux NTP服务配置 for Oracle RAC
  7. 因唯一缺点惨被吐槽!小米9升级版来了:8GB+256GB卖3299
  8. 一、计算二进制中1的个数
  9. linux-dd命令,dd命令_Linux dd 命令用法详解:复制文件并对原文件的内容进行转换和格式化处理...
  10. A problem occurred configuring project ‘:app‘.
  11. 《计算机工程》投稿经验分享
  12. [设备驱动] 最简单的内核设备驱动--字符驱动
  13. OS学习笔记-8(清华大学慕课)虚拟存储管理
  14. 基于医疗RFID手术用品智能柜管理应用方案
  15. C#合并多个pdf到一个pdf文件;不使用Aspose.pdf.dll,避免水印
  16. 基于微信小程序的驾校报名管理系统
  17. MOSS SDK学习(2)
  18. 一.图像处理系统MATLAB实现(GUI界面)
  19. java的平均年龄怎么算,C#接收5个年龄和计算平均年龄
  20. C 定义了 7 种变量类别 静态变量 实例变量 数组元素 值参数 引用参数 输出参数和局部变量

热门文章

  1. Windows 10安装TensorFlow-gpu1.4 及CUDA8.0,cuDNN6.0,搞定了,包含安装方法和下载路径
  2. weihan talk
  3. 《2021多多阅读报告》发布,95后、00后图书消费潜力攀升,大学生群体拼单量同比增长387%...
  4. 刘强东卸任京东集团CEO!接任人是他...
  5. 苹果试图将AirPods打造成健康产品 可监测体温和姿势
  6. 国庆档首日票房破2亿 长津湖票房占比过半
  7. 晨光文具卖出去的笔能绕地球几圈?
  8. AMD CEO苏姿丰称芯片短缺还将持续 今年相当紧缺
  9. 小鹏汽车拟挂牌港交所 披露了一些有意思的数据
  10. “光棍节”变“购物节”的第12年:4982亿+2715亿!