android:照片涂画功能实现过程及原理详解
这个功能可以帮你实现,在图片上进行随意的涂抹,可以用于sns产品。
绘图本身很简单,但是要实现在图片上指定的部分精确(位置,缩放)的绘图,就有点麻烦了。
下面讲讲实现过程及原理:
ui构图
这个ui,看似简单,还是有点复杂的,下面需要一个底图,上面再来一个透明的图片控件,画图的时候要同步两个控件的变形。
ui层次简图
为什么,需要上面一个canvas image与back保持一致?因为,canvas image会被画到canvas上,它是canvas的宿主,即imageview被变成了一块画布,原来imageview上的内容会被擦除。如果只有back image那么一旦开启画布,你什么也看不到。
神奇的matrix
imageview控件是常用的android控件之一,主要用于图片展示。基本上所有的高级语言中,都有类似控件。但是,有一样东西让他化腐朽为神奇,那就是matrix。有了matrix我们就可以实现图片平移、放大、旋转、扭曲等常用的特效。matrix本身是一个9*9的矩阵,里面存放的是平移坐标、放大系数、sin/cos角度值。我们可以通过getmatrix()来获取一个iv的矩阵,或者通过setmatrix()来设置它的值。
上面的东西拿来有什么用?试想一下,当我们打开相册,查看一张照片的时候,可以通过触摸,平移或者放大图片。我们,要在上面绘图,先把canvas image 变为canvas,但是,canvas image没有经过变化。必须,至少确保两个img控件拥有相同的变形,否则无法对齐画的坐标点。这个时候,要么当back image变的时候,canvas image立即同步操作,要么,最画到canvas上的时候,同步变形。前一种方案是没有必要的,果断使用后一种。这个时候你就要问,怎么得到iv的变形参数?iv提供了一些方法来单独的获取和设置某种变形,当时折腾了很久,不但繁琐,而且达不到效果。这时候,上面的matrix就派上用场了。当时,可没人这么愉快的告诉我这个。
坐标映射
上面完成了图形的变换,现在终于可以再上面作画了。但是一画,你就会发现一个问题,画不到指定位置上。这是什么问题呢?坐标系偏移。(0,0)点默认为屏幕的左上角。但是,想一下当我们的图片不满一个屏幕,很小的时候,canvas的坐标系在什么位置?我在屏幕(0,0)坐标画一个点,canvas上就会出现一个点,即使两者的位置相差很远。
这个时候,我们需要将两个坐标系进行映射,通过偏移对齐坐标系。偏移多少?这时候该使用矩阵的translate值了。这样我们就可以知道图片在坐标系上的偏移了,随边你怎么移动坐标都能准确对齐。
private hashmap getimageviewineersize(imageview iv){
hashmap size=new hashmap();
//获得imageview中image的变换矩阵
matrix m = iv.getimagematrix();
float[] values = new float[10];
m.getvalues(values);
//image在绘制过程中的变换矩阵,从中获得x和y方向的缩放系数
float sx = values[0];
float sy = values[4];
//计算image在屏幕上实际绘制的宽高
size.put("scalex", 1/sx);
size.put("scaley", 1/sy);
size.put("offsetx", values[2]); //x轴的translate的值
size.put("offsety",values[5]);
return size;
}
其中width=backimage.getdrawable().getbounds().width();
你会发现有个scalex,这是干什么的?我们假设现在图片经过缩放后的宽度恰好等于屏幕宽度,图片的实际宽度是960px。但是我们在x=480px的地方画一个点,这个点应该显示在图片的什么地方呢?我们的意图是要在图片的最后面,即x=960px的地方画一个点,但是现在却跑到了480处,明显不满足要求。这时,就需要乘上上面的scalex了。
画线的最终代码:
/*根据两点坐标,绘制连线
*startx、stopx 为触摸事件开始、结束的地方
*offsetx,为图片在x轴的位移值
*scalex,为图片在x轴的缩放值的倒数
*/
if((starty-offsety)>=0&&(stopy-offsety)>=0)
canvas.drawline((startx-offsetx)*scalex, (starty-offsety)*scaley, (stopx-offsetx)*scalex, (stopy-offsety)*scaley, pen);
【注】
imageview的实际大小等于屏幕的大小,canvas的实际大小由图片实际大小决定。
imageview的宽高很容易取得,但是它里面的图片是变过形的,怎么获取它的当前大小呢?用(原始大小*缩放系数)。
合并
最后一步就是将两个图层合并为一张图片。参考代码如下:
/**
* 合并两张bitmap为一张
* @param background
* @param foreground
* @return bitmap
*/
public static bitmap combinebitmap(bitmap background, bitmap foreground) {
if (background == null) {
return null;
}
int bgwidth = background.getwidth();
int bgheight = background.getheight();
int fgwidth = foreground.getwidth();
int fgheight = foreground.getheight();
bitmap newmap = bitmap.createbitmap(bgwidth, bgheight, config.argb_8888);
canvas canvas = new canvas(newmap);
canvas.drawbitmap(background, 0, 0, null);
canvas.drawbitmap(foreground, (bgwidth - fgwidth) / 2,
(bgheight - fgheight) / 2, null);
canvas.save(canvas.all_save_flag);
canvas.restore();
return newmap;
} //end of combinebitmap
通过canvas来合并和改变bitmap的大小,由于两个图层的大小、位置完全一致,故坐标对齐(0,0)点就可以了。
如果,没有前面的工作,你是很难精确的进行图片合并的。
下一篇: 备孕吃芹菜对于身体好吗