图片处理
本文主要介绍Android平台下图片的获取和处理,欢迎点赞,拍砖,留言
图片的加载和创造
- 图片的加载
图片的来源十分的丰富,可以是本地资源文件,也是可以是本地文件,同样也支持对于流的加载。实例如下:
本地资源:BitmapFactory.decodeResource(this.getResources(),R.id.XX,opts)
本地文件:BitmapFactory.decodeFile(“文件路径”)
本地流:BitmapFactory.decodeStream(流);
其中需要重点关注的是它们都可以带有参数来进行加载,这个参数就是BitmapFactory.Options,它可以决定加载的时候是采用什么样的模式!最明显是例子就是在很多的情况下,并不会直接把一张图片加载到内存里面,而是才用先查看图片的大小,然后设定一定的缩放比例进行加载。
查看图片的宽高,需要设置options.inJustDecodeBounds = true;通过options.outHeight;options.outWidth;来获取宽高
缩放是通过option.inJustDecodeBounds = false 并且,ptions.inSampleSize = 缩放的比例
这个地方的option很有点值得考虑的,在Android2.3之前有,设置inPuraeable 参数可以放到匿名的内存区来存放图片。后来就不行了,但是有个inBitmap的参数可以重复利用之前的图片空间。图片加载经典的三级缓存,其中用到了软引用和inBitmap来极大程序的缓解图片的加载问题。
https://developer.android.google.cn/topic/performance/graphics/manage-memory.html
加载出来的图片一般是不可变,就是没法做下面的变换动作。
- 图片的创建
创造一张图片使用的Bitmap.createBitmap()函数,里面需要一些参数指定图片的宽高之类的,其中需要注意是Config这个参数,拿Bitmap.Config.ARGB_8888举例子,它表示图片的每个像素是由A(透明度值),R(红色),G(绿色),B(蓝色)组成,并且每个值用八位表示!其它的参数也大概是这样的意思。
图片的变换
- 利用系统矩阵转换
首先介绍下矩阵的基础知识,Android中用Matrix这个来表示,它是一个3X3的矩阵
public static final int MSCALE_X = 0;
public static final int MSKEW_X = 1;
public static final int MTRANS_X = 2;
public static final int MSKEW_Y = 3;
public static final int MSCALE_Y = 4;
public static final int MTRANS_Y = 5;
public static final int MPERSP_0 = 6;
public static final int MPERSP_1 = 7;
public static final int MPERSP_2 = 8;
其每个值的意义如上,其实这个是没什么必要的,知道具体的概念后自己可以任意构造。每个像素点是一个3X1的一维矩阵来表示,也许很奇怪,为什么是三维,其实X坐标,Y坐标,以及透视值。需要注意是就是这个透视值,在视觉中,即时是同一张图片你看的远近不一样,其形成的视觉效果也是不一样的。具体的知识可以查看透视图相关。
理论上,我们有了这个矩阵后可以做任意操作,但是Matrix帮你封装了一些基本的操作,这里进行一些演示:
首先需要建立一个可以绘制的位图,如果下所示:
bitmap = Bitmap.createBitmap(具体宽,具体高, Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
Paint paint = new Paint();
canvas.drawBitmap(temp,0f,0f,paint);
imageView.setImageBitmap(bitmap);
进行旋转操作:
//先进行清理,如果是一次性的可以不用
Paint paint2 = new Paint();
paint2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPaint(paint2);
paint2.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Matrix matrix = new Matrix();
matrix.setRotate(50);
paint2.setAntiAlias(true);
canvas.drawBitmap(temp,matrix,paint2);
imageView.postInvalidate();
进行镜像操作:
Paint paint3 = new Paint();
paint3.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPaint(paint3);
paint3.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Matrix matrixXY = new Matrix();
matrixXY.setScale(-1,1);//这里进行处理
matrixXY.postTranslate((float) temp.getWidth(),0);
paint3.setAntiAlias(true);
canvas.drawBitmap(temp,matrixXY,paint3);
imageView.postInvalidate();
修改透视值,形成视觉上的缩放:
Paint paint4 = new Paint();
paint4.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
canvas.drawPaint(paint4);
paint4.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
Matrix matrixX = new Matrix();
matrixX.setValues(new float[]{
1,0,0,
0,1,0,
0,0,2
});
paint4.setAntiAlias(true);
canvas.drawBitmap(temp,matrixX,paint4);
imageView.postInvalidate();
以上的的矩阵变化都是指的空间变换,其实还有一个ColorMatrix用来控制RGBA的属性变换,挺好玩的,大家可以琢磨下,掌握了这些简单的东西后就可以完全的控制一张图片的表示了。(因为后面要黏代码,原理的有时间搁在其它文章里面讲)
- 手动处理图片的像素点
具体到每个像素点的操作,这更应该像C程序来做的事哈!其实为了效率,一般是也是扔给C来做的,不过也可以使用java代码,下面来看两个有意思的代码
高斯模糊:
public static Bitmap doBlur(Bitmap sentBitmap, int radius,
boolean canReuseInBitmap) {
Bitmap bitmap;
if (canReuseInBitmap) {
bitmap = sentBitmap;
} else {
bitmap = sentBitmap.copy(sentBitmap.getConfig(), true);
}
if (radius < 1) {
return (null);
}
int w = bitmap.getWidth();
int h = bitmap.getHeight();
int[] pix = new int[w * h];
bitmap.getPixels(pix, 0, w, 0, 0, w, h);
int wm = w - 1;
int hm = h - 1;
int wh = w * h;
int div = radius + radius + 1;
int r[] = new int[wh];
int g[] = new int[wh];
int b[] = new int[wh];
int rsum, gsum, bsum, x, y, i, p, yp, yi, yw;
int vmin[] = new int[Math.max(w, h)];
int divsum = (div + 1) >> 1;
divsum *= divsum;
int dv[] = new int[256 * divsum];
for (i = 0; i < 256 * divsum; i++) {
dv[i] = (i / divsum);
}
yw = yi = 0;
int[][] stack = new int[div][3];
int stackpointer;
int stackstart;
int[] sir;
int rbs;
int r1 = radius + 1;
int routsum, goutsum, boutsum;
int rinsum, ginsum, binsum;
for (y = 0; y < h; y++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
for (i = -radius; i <= radius; i++) {
p = pix[yi + Math.min(wm, Math.max(i, 0))];
sir = stack[i + radius];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rbs = r1 - Math.abs(i);
rsum += sir[0] * rbs;
gsum += sir[1] * rbs;
bsum += sir[2] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
}
stackpointer = radius;
for (x = 0; x < w; x++) {
r[yi] = dv[rsum];
g[yi] = dv[gsum];
b[yi] = dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (y == 0) {
vmin[x] = Math.min(x + radius + 1, wm);
}
p = pix[yw + vmin[x]];
sir[0] = (p & 0xff0000) >> 16;
sir[1] = (p & 0x00ff00) >> 8;
sir[2] = (p & 0x0000ff);
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[(stackpointer) % div];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi++;
}
yw += w;
}
for (x = 0; x < w; x++) {
rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
yp = -radius * w;
for (i = -radius; i <= radius; i++) {
yi = Math.max(0, yp) + x;
sir = stack[i + radius];
sir[0] = r[yi];
sir[1] = g[yi];
sir[2] = b[yi];
rbs = r1 - Math.abs(i);
rsum += r[yi] * rbs;
gsum += g[yi] * rbs;
bsum += b[yi] * rbs;
if (i > 0) {
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
} else {
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
}
if (i < hm) {
yp += w;
}
}
yi = x;
stackpointer = radius;
for (y = 0; y < h; y++) {
// Preserve alpha channel: ( 0xff000000 & pix[yi] )
pix[yi] = (0xff000000 & pix[yi]) | (dv[rsum] << 16)
| (dv[gsum] << 8) | dv[bsum];
rsum -= routsum;
gsum -= goutsum;
bsum -= boutsum;
stackstart = stackpointer - radius + div;
sir = stack[stackstart % div];
routsum -= sir[0];
goutsum -= sir[1];
boutsum -= sir[2];
if (x == 0) {
vmin[y] = Math.min(y + r1, hm) * w;
}
p = x + vmin[y];
sir[0] = r[p];
sir[1] = g[p];
sir[2] = b[p];
rinsum += sir[0];
ginsum += sir[1];
binsum += sir[2];
rsum += rinsum;
gsum += ginsum;
bsum += binsum;
stackpointer = (stackpointer + 1) % div;
sir = stack[stackpointer];
routsum += sir[0];
goutsum += sir[1];
boutsum += sir[2];
rinsum -= sir[0];
ginsum -= sir[1];
binsum -= sir[2];
yi += w;
}
}
bitmap.setPixels(pix, 0, w, 0, 0, w, h);
return (bitmap);
}
打马赛克:
/**
*
* @param bitmap
* @param targetRect
* @param blockSize
* @return
* @throws Exception
*/
public static Bitmap makeMosaic(Bitmap bitmap, Rect targetRect,
int blockSize) throws OutOfMemoryError {
if (bitmap == null || bitmap.getWidth() == 0 || bitmap.getHeight() == 0
|| bitmap.isRecycled()) {
throw new RuntimeException("bad bitmap to add mosaic");
}
if (blockSize < 4) {
blockSize = 4;
}
if (targetRect == null) {
targetRect = new Rect();
}
int bw = bitmap.getWidth();
int bh = bitmap.getHeight();
if (targetRect.isEmpty()) {
targetRect.set(0, 0, bw, bh);
}
//
int rectW = targetRect.width();
int rectH = targetRect.height();
int[] bitmapPxs = new int[bw * bh];
// 获取图片像素
bitmap.getPixels(bitmapPxs, 0, bw, 0, 0, bw, bh);
//目标矩形区域根据块的大小切割出来的行数与列数
int rowCount = (int) Math.ceil((float) rectH / blockSize);
int columnCount = (int) Math.ceil((float) rectW / blockSize);
//最大的X,Y为图片的宽高
int maxX = bw;
int maxY = bh;
for (int r = 0; r < rowCount; r++) { // row loop
for (int c = 0; c < columnCount; c++) {// column loop
int startX = targetRect.left + c * blockSize + 1;
int startY = targetRect.top + r * blockSize + 1;
dimBlock(bitmapPxs, startX, startY, blockSize, maxX, maxY);
}
}
return Bitmap.createBitmap(bitmapPxs, bw, bh, Bitmap.Config.ARGB_8888);
}
/**
* 从块内取样,并放大,从而达到马赛克的模糊效果
*
* @param pxs
* @param startX
* @param startY
* @param blockSize
* @param maxX
* @param maxY
*/
private static void dimBlock(int[] pxs, int startX, int startY,
int blockSize, int maxX, int maxY) {
int stopX = startX + blockSize - 1;
int stopY = startY + blockSize - 1;
if (stopX > maxX) {
stopX = maxX;
}
if (stopY > maxY) {
stopY = maxY;
}
//
int sampleColorX = startX + blockSize / 2;
int sampleColorY = startY + blockSize / 2;
//
if (sampleColorX > maxX) {
sampleColorX = maxX;
}
if (sampleColorY > maxY) {
sampleColorY = maxY;
}
//在像素点数组中所处的行数
int colorLinePosition = (sampleColorY - 1) * maxX;
//在像素点数所处的位置
int sampleColor = pxs[colorLinePosition + sampleColorX - 1];// 像素从1开始,但是数组层0开始
//把块中的颜色都已取样的像素点来填充,用块中的像素点取样
for (int y = startY; y <= stopY; y++) {
int p = (y - 1) * maxX;
for (int x = startX; x <= stopX; x++) {
// 像素从1开始,但是数组层0开始
pxs[p + x - 1] = sampleColor;
}
}
}
图片的存储
图片的缓存的三级结构
- 内存缓存* LruCache*(提供map的功能,可以通过key获取和存储图片)
- 文件缓存 内部存储的Cache和外部存储的Cache,或者直接就放在外置SDCard的公共目录下
- 网络 最后的选择才是从网路上请求
一般的使用顺序就是,当想要获取一张图片的时候,先去内存缓存中查找,再去文件查找,都没有的情况下才去请求网络,网络请求的图片保存在文件缓存中。以上内存不是我们关注的重点。跳过,有兴趣的可以交流下!
图片的保存
图片可以压缩成PNG,JPG等格式,在压缩成JPG的时候提供其压缩率,这个值只对JPG有效,因为PNG是无所压缩的。使用的函数:
compress (Bitmap.CompressFormat format,int quality,OutputStream stream),我们可以看到起格式有JPG,PNG,WEBP三种,后面的质量只对JPG有效,一般设置为90即可。输出流一般是用于保存文件路径。
详情请见下回分晓,哇哈哈哈~~