Android基础笔记(十五)- 图形、解决大图OOM、绘画工具的使用和练习

  • 计算机图形表示的原理
  • 加载大图出现OOM
  • 缩放加载大的图片资源
  • 创建一个原图的副本
  • 图形处理的常用的API
  • 傻瓜版美图秀秀
  • 画画版

计算机图形表示的原理

首先要明确的一点是,一张图片的在内存中存储所需的大小和图片在屏幕设备上完整显示所需的内存大小是有非常大的差异的。

如下图,从中我们可以清晰的看出这张图片,在硬盘上所占的存储空间是303KB,也就是310272个字节。但是如果想把这样图片完整的展示到屏幕设备上,所需的内存空间远远不止这些。

有这样一个计算公式:图片展示所需内存 = 图片的宽度像素 × 图片的高度像素 × 每个像素的大小

那么,像素大小怎么知道呢?当我们点击图片另存为时,会出现下图:

依次来介绍一下其中的含义。

单色位图:
要么黑、要么白,只需要一bit就可以表示,需要1/8个字节来表示一个像素值。在这种情况下上面的图片显示到加载入内存中需要:1920 * 1200 * 1/8 / 1024 / 1024= 0.2M。
16位图:
需要1/2一个字节来表示一个像素值。在这种情况下图片加载入内存准备显示需要:1920 * 1200 * 1 / 2 / 1024 / 1024 = 1.09M。
256位图:
表示一个像素值需要用1个字节来表示,在这种情况下图片加载入内存准备显示需要:1920 * 1200 * 1 / 1024 / 1024 = 2.19M。
24位位图:
表示一个像素值需要3个字节来表示,在这种情况下图片加载入内存准备显示需要:1920 * 1200 * 3 / 1024 / 1024 = 6.59M。
而在Android操作系统中,使用的是ARGB来表示一个像素值
其中A代表的是透明度;那么在这种情况下,在这种情况下图片加载入内存准备显示需要:1920 * 1200 * 4 / 1024 / 1024 = 8.79M。

如果在Andorid中,系统为应用默认提供的VM Heap是16M,在不对图片进行压缩处理的情况下,一定会出现OOM异常。

加载大图出现OOM

让我们加载一张大图试试吧,图片资源如下,大小为1.7M。

创建一个工程,在布局中加一个ImageView控件,并在MainActivity中找到控件,设置图片。

ImageView iv = (ImageView) findViewById(R.id.iv);
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg");
iv.setImageBitmap(bitmap );

代码很简单,当运行时就出现了错误,让我们看看错误是什么:

可以看到,应用程序向系统申请了30720012个字节,然后就直接出现了OutOfMemoryError错误。

在下一节中将讲述如何解决加载大图而不出现OOM的方法。

缩放加载大的图片资源

我们可以看到,这样狗狗的图片是2400*3200的,而我们的手机只是320*480。如果完全直接放上去,一来是会出现异常;二来是浪费资源。

Google工程师已经我们准备好了解决办法,再使用BitmapFactory去解析资源时,先获取被加载图片的宽高,并结合手机设备的屏幕宽高计算出缩放比例,然后再去使用这个缩放比例加载图片资源到内存中。

代码也比较简单,请看:

ImageView iv = (ImageView) findViewById(R.id.iv);

// ★1. 使用窗口管理者,获取手机屏幕的宽高
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
int screenHeight = wm.getDefaultDisplay().getHeight();
int screenWidth = wm.getDefaultDisplay().getWidth();

// ★2. 在不把图片加载入内存的情况下,获取图片的属性和配置
BitmapFactory.Options options = new Options();

// 此参数设置为true是,使用BitmapFactory解析资源并不会返回Bitmap,但是资源的相关配置却会被设置:例如图片的宽高
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg", options);

// 拿到图片的宽高
int outHeight = options.outHeight;
int outWidth = options.outWidth;

// ★3. 根据屏幕宽高和图片宽高计算出缩放比例
int scale = 0;
int scaleH = outHeight / screenHeight;
int scaleW =  outWidth / screenWidth;
scale = scaleH > scaleW ? scaleH : scaleW;

// ★4. 根据缩放比去解析图片资源,并返回Bitmap
options.inJustDecodeBounds = false;
options.inSampleSize = scale;
Bitmap bitmap = BitmapFactory.decodeFile("/mnt/sdcard/dog.jpg", options);

iv.setImageBitmap(bitmap);

创建一个原图的副本

在Android加载到内存的Bitmap是不允许修改的,只能够在其副本上修改和作画。那么如何创建一个原图的副本呢?

需要以下这些步骤:
①准备一个和原图宽高及配置完全一样的白纸
②白纸放在画布上
③准备一支笔
④准备一个矩阵

代码也比较简单,其中涉及了CanvasPaintMatrix等类,下面是简单的拷贝原图的代码:

ImageView srcImageView = (ImageView) findViewById(R.id.iv_src);
ImageView copyImageView = (ImageView) findViewById(R.id.iv_copy);

// 原图
Bitmap srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
srcImageView.setImageBitmap(srcBitmap);

// 拷贝

// 1. 准备一个和原图宽高完全一样的白纸
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 2. 把白纸放在画布上
Canvas canvas = new Canvas(copyBitmap);
// 3. 准备一支笔
Paint paint = new Paint();
// 4. 准备一个矩阵
Matrix matrix = new Matrix();
// 使用指定的矩阵绘图
canvas.drawBitmap(srcBitmap, matrix, paint);

copyImageView.setImageBitmap(copyBitmap);

测试结果如下:

图形处理的常用的API

就如在学动画的时候,图形的处理操作也分为以下种类,操作的关键步骤是使用矩阵进行变化;Google工程师已经帮我们把这些操作封装的很完善了。

平移:

代码如下:

// 原图
Bitmap srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
srcImageView.setImageBitmap(srcBitmap);

// 拷贝
Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
Canvas canvas = new Canvas(copyBitmap);
Paint paint = new Paint();
Matrix matrix = new Matrix();

// ★使用矩阵平移图形
matrix.setTranslate(150, 50);

canvas.drawBitmap(srcBitmap, matrix, paint);
copyImageView.setImageBitmap(copyBitmap);

测试图如下:

缩放:

缩放代码如下:

// ★使用矩阵缩放图形
// 以图片中心点为原点,缩小0.5倍
matrix.setScale(0.5f, 0.5f, srcBitmap.getWidth() / 2,
srcBitmap.getHeight() / 2);

测试图:

旋转:

旋转代码如下:

// ★使用矩阵图形
// 以图片中心点为原点,旋转30度
matrix.setRotate(30, srcBitmap.getWidth()/2,
srcBitmap.getHeight()/2);

测试图:

镜像:

镜像代码如下:

// ★使用矩阵把图片镜像
matrix.setScale(-1.0f, 1.0f);
matrix.postTranslate(srcBitmap.getWidth(), 0);

测试图:

倒影:


倒影代码如下:

// ★使用矩阵把图片倒影
matrix.setScale(1.0f, -1.0f);
matrix.postTranslate(0 , srcBitmap.getHeight());

测试图:

傻瓜版美图秀秀

目的,使用使用颜色矩阵,修改图片的色值(简单+简陋=练练手):

界面布局如下:

布局下方是三个<SeekBar>布局,用于修改图片的色值。

代码如下:

public class MainActivity extends Activity implements OnSeekBarChangeListener {

    private Bitmap srcBitmap;
    private SeekBar sb_red;
    private SeekBar sb_green;
    private SeekBar sb_blue;
    private ImageView iv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        sb_red = (SeekBar) findViewById(R.id.sb_red);
        sb_green = (SeekBar) findViewById(R.id.sb_green);
        sb_blue = (SeekBar) findViewById(R.id.sb_blue);

        iv = (ImageView) findViewById(R.id.iv);
        srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/meinv.jpg");
        iv.setImageBitmap(srcBitmap);

        sb_blue.setOnSeekBarChangeListener(this);
        sb_red.setOnSeekBarChangeListener(this);
        sb_green.setOnSeekBarChangeListener(this);

    }

    // 停止滑动时
    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {
        int currentPosition = seekBar.getProgress();

        Bitmap copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
        Canvas canvas = new Canvas(copyBitmap);
        Paint paint = new Paint();

        // ★使用颜色矩阵绘制绘制图片
        ColorMatrix colorMatrix = new ColorMatrix();

        float rf = 0;
        float gf = 0;
        float bf = 0;

        // 根据滑动的值修改ARGB的颜色值
        switch (seekBar.getId()) {
        case R.id.sb_red:
            rf = currentPosition / 10f;
            break;
        case R.id.sb_blue:
            bf = currentPosition / 10f;
            break;
        case R.id.sb_green:
            gf = currentPosition / 10f;
            break;
        }

        colorMatrix.set(new float[] { //
                        rf, 0, 0, 0, 0,//
                        0, gf, 0, 0, 0,//
                        0, 0, bf, 0, 0,//
                        0, 0, 0, 1, 0 });

        ColorFilter filter = new ColorMatrixColorFilter(colorMatrix);
        // ★为画笔设置颜色矩阵过滤器
        paint.setColorFilter(filter);

        canvas.drawBitmap(srcBitmap, new Matrix(), paint);
        iv.setImageBitmap(copyBitmap);
    }

    // 滑动的过程中
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
    }

    // 开始滑动时
    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {
    }
}

测试结果:

没什么很重要的东西,知道一下颜色矩阵就行。

画画版

先看一些成型后什么样子吧:

自己做出来后还是挺逗的。

做画画板有以下这些步骤:
①准备一个背景图
②做一个背景图的拷贝,以便在其上面进行绘画
③注册一个ImageView的一个触摸事件;在按下和移动时,记录触摸点,并绘制图画和设置到Imageview上。
④增加“加粗画笔”和“改变颜色的功能”

那么下面就一步一步来吧

第一步,准备一个背景图,并存入到SD卡中。

设置背景图片的代码:

iv = (ImageView) findViewById(R.id.iv);
srcBitmap = BitmapFactory.decodeFile("/mnt/sdcard/bk.png");
iv.setImageBitmap(srcBitmap);

第二步,做一个背景图的拷贝,以便在其上面进行绘画

// 获取一个拷贝
copyBitmap = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), srcBitmap.getConfig());
// 获取画布
canvas = new Canvas(copyBitmap);
// 获取画笔
paint = new Paint();
// 获取一个矩阵
matrix = new Matrix();

canvas.drawBitmap(srcBitmap, matrix, paint);

// 画一条直线
// canvas.drawLine(0, 0, 100, 100, paint);

iv.setImageBitmap(copyBitmap);

// 注册触摸事件回调
iv.setOnTouchListener(this);

第三步,注册一个ImageView的一个触摸事件;在按下和移动时,记录触摸点,并绘制图画和设置到Imageview上。

// 起始坐标点
private int startX;
private int startY;
@Override
public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction()) {
    case MotionEvent.ACTION_DOWN:
        startX = (int) event.getX();
        startY = (int) event.getY();
        System.out.println("按下的坐标点(" + startX + "," + startY + ")");
        break;
    case MotionEvent.ACTION_MOVE:
        int nowX = (int) event.getX();
        int nowY = (int) event.getY();
        System.out.println("移动起始的坐标点(" + startX + "," + startY + ")");
        System.out.println("移动结束的坐标点(" + nowX + "," + nowY + ")");
        canvas.drawLine(startX, startY, nowX, nowY, paint);
        iv.setImageBitmap(copyBitmap);
        startX = nowX;
        startY = nowY;

        break;
    case MotionEvent.ACTION_UP:

        break;

    default:
        break;
    }

    // 返回值为true,事件被消耗掉,并且不在向下传递;
    // 返回值为false,事件不被消耗掉,并且向下继续传递;
    return true;
}

第四步,增加“加粗画笔”和“改变颜色的功能”

// 颜色内容
int[] colors = { Color.RED, Color.GREEN, Color.BLUE };
private int colorIndex;

// 修改颜色
public void changecolor(View v) {
    paint.setColor(colors[colorIndex++ % 3]);
}

// 画笔宽度
private int paintWidth;

// 加粗画笔
public void overstriking(View v) {
    paint.setStrokeWidth(++paintWidth);

}
时间: 2025-01-31 09:04:40

Android基础笔记(十五)- 图形、解决大图OOM、绘画工具的使用和练习的相关文章

Android基础笔记(五)

Android下访问网络资源和一些注意事项 Android下异步消息处理线程技术 Android下异步消息处理线程技术的基本原理 模仿新闻客户端小案例 GET方式提交数据到服务器 POST方式提交数据到服务器 Android下访问网络资源和一些注意事项 这里只说明了最简单的网络访问资源的方式,与服务端交互在后面讲述. Andriod中访问网络中资源的基本步骤 ① 通过调用URL.openConnection()获取一个HttpURLConnection连接对象 ② 设置一些链接对象的配置参数,比

Android学习笔记十五.深入理解fragment(三) 之《兼容多分辨率的应用》实战

深入理解fragment(三) 之<兼容多分辨率的应用>实战 在上一篇博文中介绍了如何使用Android Fragment开发适用于大屏幕应用,现在我们在上一个应用的基础上继续学习如何使用Fragment开发兼容多分辨率的应用. 1.建立/res/values-large/refs.xml引用资源文件 为了开发兼顾屏幕分辨率的应用,我们需要建立一个引用资源文件,专门用于定义各种引用项.refs.xml引用资源文件中定义了一项引用,其作用就是标明activity_book_list实际引用(@)

Android修行笔记(五):ListView 良好编程指南

构成Android应用的一个重要的UI构件就要算ListView了,我们项目中用得很多,所以总结了以下的经验进行分享探讨 1.重用Adapter getView函数中的ConvertView 列表的每一行Item通常都是可复用的,重用可以避免多次创建,让滚动更加顺滑. (注意:2.3系统在复用View的时候有些状态会被保留,比如GONE的状态,所以在getView中重用convertView时要对Visibility属性重新进行设置) 2.使用ViewHolder来保存通过findViewByI

Bootstrap &lt;基础二十五&gt;警告(Alerts)

警告(Alerts)以及 Bootstrap 所提供的用于警告的 class.警告(Alerts)向用户提供了一种定义消息样式的方式.它们为典型的用户操作提供了上下文信息反馈. 您可以为警告框添加一个可选的关闭按钮.为了创建一个内联的可取消的警告框,请使用 警告(Alerts) jQuery 插件. 您可以通过创建一个 <div>,并向其添加一个 .alert class 和四个上下文 class(即 .alert-success..alert-info..alert-warning..ale

Java for Android 基础笔记-数据类型

Java的基本数据类型 布尔类型 boolean true | false java是一个强类型的语言,与JS中的布尔类型的自由转换相比,JAVA的Boolean类型只有两种true和false,JS中相应的只可以自行转换. 字符类型 char 可以存储一个汉字,和其他语言非常类似不再赘述. 整型 byte, 占用一个字节 -128`-127 short 2字节 -2^15~2^15-1 int 4字节 -2^31~2^31-1 long 8字节  -2^63~2^63-1 整型常量默认为int

Android之旅十五 android中的网络操作

android中的网络操作和java里面没有什么区别,java里面的很多网络操作方法都可以搬到android中去使用,主要几个点: 1.post和get请求的区别,大家可以在网上查阅有关资料进行了解,get主要以向地址中拼接字符串参数发送到服务器,长度有限制,并且请求参数暴露在地址栏中,不怎么安全:post则主要是将请求参数转换为相应的http协议请求体发送到服务器,相比get方式,参数的长度没有限制,并且参数信息不会暴露给用户: 2.我们在java web里面通过浏览器以post方式发送数据,

Android基础之十四数据存储 之 SQLite数据库详解

Android基础之十四数据存储 之 SQLite数据库详解 SQLite 是一款 轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百 K 的内存就足够了,因而特别适合在移动设备上使用. SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID( 原子性(Atomicity) .一致性(Consistency) . 隔离性(Isolation) . 持久性(Durability))事务,所以只要你以前使用过其他的关系型数据库,就可以很快地上手 SQLite.而

Swift 学习笔记十五:扩展

扩展就是向一个已有的类.结构体或枚举类型添加新功能(functionality).扩展和 Objective-C 中的分类(categories)类似.(不过与Objective-C不同的是,Swift 的扩展没有名字.) Swift 中的扩展可以: 1.添加计算型属性和计算静态属性 2.定义实例方法和类型方法 3.提供新的构造器 4.定义下标 5.定义和使用新的嵌套类型 6.使一个已有类型符合某个协议 一.扩展属性,构造器,方法 class Human{ var name:String? va

laravel3学习笔记(十五)

原作者博客:ieqi.net ==================================================================================================== 异常与日志 在应用中,我们总会遇到各种问题.各种异常,这时,记录异常发生时的状态就很重要,所以异常与日志是有着天然的关系的. 关于异常与日志的配置在文件 application/config/error.php 中. 文件中有四个配置项: 'ignore' => ar

【转】 Pro Android学习笔记(五二):ActionBar(5):list模式

可以在action bar中加入spinner的下来菜单,有关spinner,可以参考Pro Android学习笔记(二十):用户界面和控制(8):GridView和Spinner. list的样式和theme有关,如果theme设置不正确,还可能会出现异常. 相关的代码如下: public class ListActionBarDemo extends SearchTestCase3 implements OnNavigationListener{ //List触发的回调函数接口    @Ov