备注:本文关键内容是“OOM(Out Of Memory)异常",跟 “移动时的截图起点规则”内容,其他部分没看也可以看的懂
写此程序背景
看到微信的图片浏览的强大功能,于是自己写了一个。原则上可以无限放大,但是放大部分 像素必须有原图片的1个像素,最小也不能小于1*1像素。
思路:
功能解剖:
缩放:微信的缩放能缩放到用户满意的范围。(放大不会超过max倍,缩小不会超过min)
移动:当图片高与宽小于屏幕时,能移动图片到任意位置。当高或宽大于屏幕时,移动图片则会截取图片某一模块放大满屏显示。
最重要的一点就是图片放大时看不出来图片变模糊
解剖雏形:
假设用系统自带Matrix函数来控制放大缩小。
缩小: 可以缩小很小倍,当不易控制倍数(如1.25倍,但Matrix不会那么精确)
放大:但放大超出屏幕时,Bitmap.createBitmap会在内存中创建一个很大的图(或内存超出系统设定的值或宽高超出屏幕),导致显存或内存不足。
因为上条放大会出现问题所以本方案绝对不行。
解剖过度:
那么要有那么一种缩放方法满足下面条件
一、能几乎精确的缩小到某一个倍数
二、放大时内存不会溢出
基于缩放的截取方法想出以下移动方案
一、当放大时移动时计算某个参考点在图上移动的位置所占比列(x,y),高宽为屏幕高度/倍数
二、当缩小的图在屏幕范围之内,那么移动的效果通过移动ImageView的位置实现
方案:因为缩放的关键是放大,所以可以考虑放大时用截取一段图*n倍不会溢出的图
截图方案: 一、看到截取就想到用画布canvas解决(于是创建了一个MyBitMap类)能截永远不会内存溢出的放大图。并且图像不会模糊(canvas优秀之处)。
补充:OOM(Out Of Memory)异常
1.放大时不模糊的实现:利用canvas获取放大后的图,就能解决安常规放大后模糊的现状。
2.图片不溢出的实现:在canvas放大时限制图片的大小不超出屏幕就行。
溢出的情况
一、图片的高或宽超出了屏幕。所以在canvas放大时限制图片的大小不超出屏幕就行。
二、某一个bitmap超出了系统对单张图片的限制大小5MB(5MB根据系统不同会有差别)。利用canve截图到的图才几十kb。如果利用常规的Matrix在原来的图基础上来放大(放大后的图片大小为原图大小*当前倍数的平方),就会有超限(5MB)溢出。
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.widget.ImageView.ScaleType;
/**
*此处函数是得到剪切的图片
* @author ZhangJianLin
*
*/
public class MyBitMap {
public MyBitMap() {
// TODO Auto-generated constructor stub
}
/**
*
* @param unscaledBitmap the bitmap of source
* @param dstWidth what width you want to set
* @param dstHeight What width you want to set
* @param scalingLogic it is ScaleType
* @return the scaled bitmap
*/
public static Bitmap createScaledBitmap(Bitmap unscaledBitmap, int dstWidth, int dstHeight, ScaleType scalingLogic) {
Rect srcRect = calculateSrcRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic);
Rect dstRect = calculateDstRect(unscaledBitmap.getWidth(), unscaledBitmap.getHeight(), dstWidth, dstHeight, scalingLogic);
Bitmap scaledBitmap = Bitmap.createBitmap(dstRect.width(), dstRect.height(), Config.ARGB_8888);
Canvas canvas = new Canvas(scaledBitmap);
canvas.drawBitmap(unscaledBitmap, srcRect, dstRect, new Paint(Paint.FILTER_BITMAP_FLAG));
return scaledBitmap;
}
//根据dstWOrH计算原图应该截取的截图合适的高宽比例图
public static Rect calculateSrcRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScaleType scalingLogic) {
if (scalingLogic == ScaleType.CENTER_CROP) {
final float srcAspect = (float)srcWidth / (float)srcHeight;
final float dstAspect = (float)dstWidth / (float)dstHeight;
if (srcAspect > dstAspect) {
final int srcRectWidth = (int)(srcHeight * dstAspect);
final int srcRectLeft = (srcWidth - srcRectWidth) / 2;
return new Rect(srcRectLeft, 0, srcRectLeft + srcRectWidth, srcHeight);
} else {
final int srcRectHeight = (int)(srcWidth / dstAspect);
final int scrRectTop = (int)(srcHeight - srcRectHeight) / 2;
return new Rect(0, scrRectTop, srcWidth, scrRectTop + srcRectHeight);
}
} else {
return new Rect(0, 0, srcWidth, srcHeight);
}
}
//根据dstWOrH计算原图应该截取的期望图合适的高宽比例图
public static Rect calculateDstRect(int srcWidth, int srcHeight, int dstWidth, int dstHeight, ScaleType scalingLogic) {
if (scalingLogic == ScaleType.FIT_XY) {
final float srcAspect = (float)srcWidth / (float)srcHeight;
final float dstAspect = (float)dstWidth / (float)dstHeight;
if (srcAspect > dstAspect) {
return new Rect(0, 0, dstWidth, (int)(dstWidth / srcAspect));
} else {
return new Rect(0, 0, (int)(dstHeight * srcAspect), dstHeight);
}
} else {
return new Rect(0, 0, dstWidth, dstHeight);
}
}
/**
*
* @param unscaledBitmap the bitmap of source
* @param scale the scale you want
* @param scalingLogic it is ScaleType
* @return the scaled bitmap
*/
//根据放大倍数获得截取图安scale放大的图
public static Bitmap createBMScaleBitmap(Bitmap unscaledBitmap, Double scale, ScaleType scalingLogic){
int dstWidth = (int)(unscaledBitmap.getWidth()* scale);
int dstHeight = (int)(unscaledBitmap.getHeight()*scale);
return createScaledBitmap(unscaledBitmap, dstWidth, dstHeight, scalingLogic);
}
}
移动时的截图起点规则
每缩放一次,就以图的中心为截获图的中心(Ox,Oy),起点为(Ox-needwidth/2,Oy - needhight/2)。
在缩放时,把放大后的图在逻辑上的坐标划分为m份,(1单位为屏幕的宽或高),同时每移动一次,就移动1/4份(即每次移动1/4屏幕)。 (单次移动的宽或高像素为 (bitmapWidth/m*n或bitmapHight/m*n,其中n是缩放的倍数,m为计算缩放后的图片高宽跟屏幕对应的宽高的比例,以此得到的值作为x或y的坐标最大值,坐标单位为一个屏幕,每滑次移动半个屏幕或1/3屏幕
结合安卓的滑动或移动的灵敏度,能完美的模拟出效果图。(亲测,如果移动规则用跟踪手的移动位移来移动图片是行不通的,不知道市面上能作出这样的效果是什么样的一个算法,还有待探究)
核心实现代码及注释如下
package com.p_w_picpathopen;
import android.app.Activity;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnTouchListener;
import android.view.Window;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
public class BigzoonImage extends Activity {
ImageView myImageView;
Button bigButton;//放大按钮
Button smallButton;//缩小按钮
View myButtons;
private Bitmap myBitmap;
private double bigSize = 1.25;//每次放大的比列
private double smallSize = 0.8;//每次缩小的比例
double size = 1;//当前放大的倍数
double pixel = 30.00;//限制图片缩小时的最小像素
int bmpWidth;//图片宽度
int bmpHight;//图片高度
int bmpSizeWidth;//放大后的图片宽度bmpwidth*size
int bmpSizeHight;
int x ;//
int y ;//
int screenWidth; // 屏幕宽(像素,如:480px)
int screenHeight; // 屏幕高(像素,如:800px)
int dstHeight;
int dstWidth;
@Override
protected void onCreate(Bundle savedInstanceState) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
super.onCreate(savedInstanceState);
setContentView(R.layout.bigzoonp_w_picpath_main);
init();
}
private void init(){//初始化各参数的值
myImageView = (ImageView)findViewById(R.id.bitmap_p_w_picpath);
bigButton = (Button)findViewById(R.id.button_big);
smallButton = (Button)findViewById(R.id.button_small);
myButtons = (View)findViewById(R.id.bitmap_button);
MyBitmapFactory myBitmapFactory = new MyBitmapFactory(this);
myBitmap = myBitmapFactory.getDrawBmp(R.drawable.bitmap_test);//事先加载一张图片
myImageView.setImageBitmap(myBitmap);
myImageView.setOnTouchListener(ImageOpenListener);
bigButton.setOnClickListener(sizeButton);//添加按钮触发事件
smallButton.setOnClickListener(sizeButton);//同上
DisplayMetrics dm = new DisplayMetrics(); //声明一个屏幕像素的类屏幕像素
dm = getResources().getDisplayMetrics(); //得到屏幕像素
screenWidth = dm.widthPixels; // 屏幕宽(像素,如:480px)
screenHeight = dm.heightPixels; // 屏幕高(像素,如:800px)
}
private void big(){//放大时图片的变化
size = bigSize * size;
Bitmap newBitmap = myBitmap;
newBitmap = bigCal(myBitmap);
newBitmap = MyBitMap.createScaledBitmap(newBitmap, dstWidth, dstHeight, ScaleType.FIT_XY);
myImageView.setImageBitmap(newBitmap);
}
private void small(){//缩小时图片的变化
size = smallSize * size;
Bitmap newBitmap = myBitmap;//得到原图的截取图
newBitmap = bigCal(newBitmap);
newBitmap = MyBitMap.createScaledBitmap(newBitmap, dstWidth, dstHeight, ScaleType.FIT_XY);
myImageView.setImageBitmap(newBitmap);
}
private OnClickListener sizeButton = new OnClickListener() {//放大缩小的事件
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(v == bigButton){
big();
}
else small();
}
};
//每缩放一次,就以图的中心为截获图的中心(Ox,Oy),起点为(Ox-needwidth/2,Oy - needhight/2),宽高为屏幕宽高的图。
public Bitmap bigCal(Bitmap bitmap){//缩放得到原图的截取图
bmpWidth = bitmap.getWidth();
bmpHight = bitmap.getHeight();
//放大的size最小值,16是指限制的截取的图片最小像素,要是缩小的很小,那么一丁点没有意义,这里的16看个人意思
int sizeMax = Math.min(myBitmap.getWidth()/16, myBitmap.getHeight()/16);//限定放大的最大倍数
double sizeMin = Math.max(pixel/myBitmap.getWidth(), pixel/myBitmap.getHeight());//限制缩小的size最小倍数
if(size > sizeMax){
size = sizeMax;
}
if(size < sizeMin){
size = sizeMin;
}
bmpSizeWidth = (int)(bmpWidth*size);
bmpSizeHight = (int)(bmpHight*size);
if(screenWidth > bmpSizeWidth){
x = 0;
dstWidth = bmpSizeWidth;
}else{
x = (int)((bmpSizeWidth - screenWidth)/(2*size));
bmpWidth = (int)(screenWidth / size);
dstWidth = screenWidth;
}
if(screenHeight > bmpSizeHight){
y = 0;
dstHeight = bmpSizeHight;
}else{
y = (int)((bmpSizeHight - screenHeight)/(2*size));//放大时计算以中心的为截取中心的所要截图的左上点坐标(x,y)
bmpHight = (int)(screenHeight / size);
dstHeight = screenHeight;
}
bitmap = Bitmap.createBitmap(bitmap, x, y, bmpWidth, bmpHight);
return bitmap;
}
//计算缩放的倍数跟屏幕的比例,以得到的值作为x或y的坐标最大值,坐标单位为一个屏幕,每滑动次移动半个屏幕或1/3屏幕
public double rowOrCowNum(int sizeBitmapWH, int screenWH){
double num = (sizeBitmapWH * size)/screenWH;
return num;
}
public Bitmap movCal(Bitmap bitmap, int dx, int dy){//计算移动后的要截取的图
double coordinateX = rowOrCowNum(myBitmap.getWidth() , screenWidth);//缩放后x坐标,
double coordinateY = rowOrCowNum(myBitmap.getHeight(), screenHeight);//缩放后y的坐标
if(coordinateX > 1){
if(dx > 0){
x -= (myBitmap.getWidth()/(coordinateX * 4));
if(x < 0){
x = 0;
}
}
if(dx < 0){
x += (myBitmap.getWidth()/(coordinateX *4));
if(x > (myBitmap.getWidth() - bmpWidth)){
x = myBitmap.getWidth() - bmpWidth;
}
}
}
if(coordinateY > 1){
if(dy > 0){
y -= (myBitmap.getHeight()/(coordinateY * 4));
if(y < 0){
y = 0;
}
}
if(dy < 0){
y += (myBitmap.getHeight()/(coordinateY *4));
if(y > (myBitmap.getHeight() - bmpHight)){
y = myBitmap.getHeight() - bmpHight;
}
}
}
bitmap = Bitmap.createBitmap(bitmap, x, y, bmpWidth, bmpHight);
bitmap = MyBitMap.createScaledBitmap(bitmap, dstWidth, dstHeight, ScaleType.FIT_XY);
return bitmap;
}
private OnTouchListener ImageOpenListener = new OnTouchListener() {//移动监听
int lastX;
int lastY;
int left;//图片的左边界的坐标
int right;//图片右边界坐标
int top;//图片上边界的坐标
int bottom;//图片下边界的坐标
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
switch(event.getAction()){
case MotionEvent.ACTION_DOWN:
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int dx = (int)event.getRawX() - lastX;//dx为在屏幕的x轴上移动的距离
int dy = (int)event.getRawY() - lastY;//dy为在屏幕的y轴上移动的距离
Bitmap newBitmap;
//计算缩放的图片是否找出屏幕范围,如果是
if(bmpSizeWidth > screenWidth||bmpSizeHight > screenHeight){
if(bmpSizeWidth > screenWidth){
left = 0;
right = screenWidth;
}else{
left = v.getLeft() + dx;
right = v.getRight() + dx;
}
if(bmpSizeHight > screenHeight){
top = 0;
bottom = screenHeight;
}else {
top = v.getTop() + dy;
bottom = v.getBottom() + dy;
}
if((dx > 3 || dx < -3) && (dy > 3 ||dy < -3)){//设置灵敏度,一定要设置
newBitmap = movCal(myBitmap, dx, dy);
myImageView.setImageBitmap(newBitmap);
}
}
else{//如果没有超出则移动ImageView
left = v.getLeft() + dx;
top = v.getTop() + dy;
bottom = v.getBottom() + dy;
right = v.getRight() + dx;
}
v.layout(left, top, right, bottom);
lastX = (int)event.getRawX();
lastY = (int)event.getRawY();
break;
}
return true;
}
};
@Override
protected void onPause() {
// TODO Auto-generated method stub
System.exit(0);
super.onPause();
}
@Override
protected void onStop() {
// TODO Auto-generated method stub
System.exit(0);
super.onStop();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
}
另外有一个辅助类,就是获取各类渠道图片的封装类
import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import android.content.Context; import android.graphics.Bitmap; import android.graphics.Bitmap.CompressFormat; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.LinearGradient; import android.graphics.Matrix; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; import android.graphics.PorterDuffXfermode; import android.graphics.Shader.TileMode; import android.util.Base64; /** * 功能:得到原始的bitmap,就是unscaledbitmap;将得到bitmap字节流 * @author ZhangJianLin * */ public class MyBitmapFactory { Context context; public MyBitmapFactory(Context context) { // TODO Auto-generated constructor stub this.context = context; } public Bitmap getFileBmp(String path){//通过路径获得图片 Bitmap bm = BitmapFactory.decodeFile(path); return bm; } public Bitmap getDrawBmp(int id){//通过本项目id获得图片 Bitmap bm = BitmapFactory.decodeResource(context.getResources(), id); return bm; } public Bitmap getStringBmp(InputStream inputstring){//从流中获取图片 Bitmap bm = BitmapFactory.decodeStream(inputstring); return bm; } public Bitmap getArrayBmp(byte[] data, int offset, int length){//从字节转化成图片 Bitmap bm = BitmapFactory.decodeByteArray(data, offset, length); return bm; } //获得带倒影的图片方法 public static Bitmap createReflectionImageWithOrigin(Bitmap bitmap){ final int reflectionGap = 4; int width = bitmap.getWidth(); int height = bitmap.getHeight(); Matrix matrix = new Matrix(); matrix.preScale(1, -1); Bitmap reflectionImage = Bitmap.createBitmap(bitmap, 0, height/2, width, height/2, matrix, false); Bitmap bitmapWithReflection = Bitmap.createBitmap(width, (height + height/2), Config.ARGB_8888); Canvas canvas = new Canvas(bitmapWithReflection); canvas.drawBitmap(bitmap, 0, 0, null); Paint deafalutPaint = new Paint(); canvas.drawRect(0, height,width,height + reflectionGap, deafalutPaint); canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, null); Paint paint = new Paint(); LinearGradient shader = new LinearGradient(0, bitmap.getHeight(), 0, bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, TileMode.CLAMP); paint.setShader(shader); // Set the Transfer mode to be porter duff and destination in paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); // Draw a rectangle using the paint with our linear gradient canvas.drawRect(0, height, width, bitmapWithReflection.getHeight() + reflectionGap, paint); return bitmapWithReflection; } public Bitmap stringtoBitmap(String string){//从string到bitmap //将字符串转换成Bitmap类型 Bitmap bitmap=null; try { byte[]bitmapArray; bitmapArray=Base64.decode(string, Base64.DEFAULT); bitmap=BitmapFactory.decodeByteArray(bitmapArray, 0, bitmapArray.length); } catch (Exception e) { e.printStackTrace(); } return bitmap; } public String bitmaptoString(Bitmap bitmap){ //将Bitmap转换成字符串 String string=null; ByteArrayOutputStream bStream=new ByteArrayOutputStream(); bitmap.compress(CompressFormat.PNG,100,bStream); byte[]bytes=bStream.toByteArray(); string=Base64.encodeToString(bytes,Base64.DEFAULT); return string; } public Bitmap returnBitMap(String url) {//从网络中获得图片 URL myFileUrl = null; Bitmap bitmap = null; try { myFileUrl = new URL(url); } catch (MalformedURLException e) { e.printStackTrace(); } try { HttpURLConnection conn = (HttpURLConnection) myFileUrl .openConnection(); conn.setDoInput(true); conn.connect(); InputStream is = conn.getInputStream(); bitmap = BitmapFactory.decodeStream(is); is.close(); } catch (IOException e) { e.printStackTrace(); } return bitmap; } }
本实验中所用到的布局如下
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <ImageView android:id="@+id/bitmap_p_w_picpath" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_centerVertical="true" android:src="@drawable/ic_launcher" /> <LinearLayout android:id="@+id/bitmap_button" android:layout_width="80dp" android:layout_height="40dp" android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" android:layout_marginBottom="16dp" android:layout_marginLeft="14dp" android:orientation="horizontal" > <Button android:id="@+id/button_small" android:layout_width="40dp" android:layout_height="40dp" android:background="@drawable/bitmap_small" /> <Button android:id="@+id/button_big" android:layout_width="40dp" android:layout_height="40dp" android:background="@drawable/bitmap_big" /> </LinearLayout> </RelativeLayout>
效果如下图
原图:
本实验有如下缺陷:(很容易改进)
1、移动时给用户的体验还不错。有一点点缺陷
2、放大时p_w_picpath边上有空白,这是因为计算截图时的误差
改进思路:
对于一、改进移动时的x,y及每次移动的算法规则;每次移动的距离为屏幕的1/m,提高m值,或用算法动态改变m值
对于二、当放大后的图像的宽高都大于屏幕的宽高,截获放大后的图设为全屏背景。
源码地址http://pan.baidu.com/share/link?shareid=480398&uk=2065228996,编码是utf-8
转载于:https://blog.51cto.com/7071976/1208469