最近项目需求实现一个类似的图片堆叠效果,用于相册的封面展示效果,让用户一眼就可以看出里面其实涵盖了多张图片。原理当然是图片随着中心点旋转,多次随机旋转并绘制到Canvas上,如果直接旋转bitmap你会发现有黑色背景,因此根本不能满足预期效果,所以需要先建立一块空的画布,容纳后面旋转角度后绘制到画布上的所有图片,这里引出一个问题,建立多大的空白画布呢?如果使用矩形的对角线做为画布的长宽那自然是可以满足的,不管图片如何旋转总是可以容纳这些旋转的图片,不会产生相应的剪切显示不全,但这不是最优的,作为程序员我们应该追求最优的效果,由于旋转的角度这个我们是已知的,其实根据三角换算关系我们是可以求出空白图片所需要的最大高度和宽度的,下面的程序中具体展示了如何计算这个最佳的宽高。

先给每张需要堆叠展示的图片进行一个加入白色边框效果,然后继续加上阴影显示效果合成一张新的bitmap,最后进行随机角度堆叠,就出现图中的显示效果了,具体参见StackPic类。

先来看一下如何计算最大长度和宽度:

以下算法只是计算出旋转小于90度时的公式。当旋转大于90时,可以先把问题域换算到锐角的情况,再进行计算即可。

如下图所示,需要计算出来的是len_delta的长度,就是有双竖线的位置,它是新图片要增加的宽。(要增加的高度同理可得。)

其实只要知道len的长度,还有len和len_delta的夹角,就可以算出len_delta的长度了。

1. len的长度。注意到它是等腰三角形的底边,顶角为angel, 容易得到len=2*R*sin(angel/2)

2. len和len_delta的夹角。先可以计算出angel_alpha,也就是等腰三角形的底角 angel_alpha = (PI - angel) / 2

然后是R和原图像的底边的夹角angel_delta,显然其tan值是原图片的高宽比(注意计算增加的高度时是宽高比)。用arctan求出其角度。

len和len_delta的夹角 = PI - angel_alpha - angel_delta

3. len_delta = len * cos(len和len_delta的夹角)

demo效果如下所示:

具体StackPIc类如下:

package com.example.stackpicdemo;import java.util.ArrayList;
import java.util.List;
import java.util.Random;import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.util.Log;/*** @Description TODO 堆叠显示图片,并加入背景框和阴影效果*/
public class StackPic {private final static String TAG = "StackPic";private final static boolean DEBUG = true;// 上下文private Context context;// 图片偏移角度(角度制非弧度)private int offsetDegree;// 图片集合private Bitmap[] bitmaps;// 最大宽度private int maxWidth = 0;// 最大高度private int maxHeight = 0;// 图片中心x坐标private int centerX = 0;// 图片中心y坐标private int centerY = 0;// 空白的 Bitmapprivate Bitmap bitmap;// 画布private Canvas mCanvas;// 画笔private Paint paint;// bitmap简单信息对象List<SimpleBitmapInfo> simpleBitmapInfoList;/*** @param context*            上下文* @param bitmaps*            需要加载的Bitmap[]* @param offsetDegree*            此参数必须小于90度生效,大于90度没有换算*/public StackPic(Context context, Bitmap[] bitmaps, int offsetDegree) {this.context = context;this.bitmaps = bitmaps;this.offsetDegree = offsetDegree;init();}/*** @Description TODO 初始化*/private void init() {Random random = new Random();simpleBitmapInfoList = new ArrayList<SimpleBitmapInfo>();for (int i = 0; i < bitmaps.length; i++) {bitmaps[i] = drawImageDropShadow(addFrame(bitmaps[i], DisplayUtil.dip2px(context, 6)));if (0 == i) {// 最顶层的图片不涉及旋转simpleBitmapInfoList.add(calculateMaxWidthAndHeight(0, bitmaps[i].getWidth(), bitmaps[i].getHeight()));} else {simpleBitmapInfoList.add(calculateMaxWidthAndHeight(random.nextInt(offsetDegree) + 1, bitmaps[i].getWidth(), bitmaps[i].getHeight()));}}// 取得最大的高度和宽度for (SimpleBitmapInfo sbi : simpleBitmapInfoList) {if (sbi.getWidth() >= maxWidth) {maxWidth = sbi.getWidth();}if (sbi.getHeight() >= maxHeight) {maxHeight = sbi.getHeight();}}centerX = (int) (maxWidth / 2);centerY = (int) (maxHeight / 2);// 创建空白的最大bitmap可恰好容纳所有bitmapbitmap = Bitmap.createBitmap(maxWidth, maxHeight, Bitmap.Config.ARGB_8888);mCanvas = new Canvas(bitmap);// 抗锯齿PaintFlagsDrawFilter filter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);mCanvas.setDrawFilter(filter);paint = new Paint();paint.setAntiAlias(true);}/*** @Description TODO 计算bitmap旋转对应角度后需要的宽高* @param angle*            bitmap需要旋转的角度* @param w*            bitmap宽* @param h*            bitmap高* @return 包含bitmap宽度和高度的简单对象*/private SimpleBitmapInfo calculateMaxWidthAndHeight(int angle, int w, int h) {// 矩形对角线半径double r = Math.sqrt(h * h + w * w) / 2;// 偏移角度对应的等腰三角形底边长double len = 2 * Math.sin(Math.toRadians(angle) / 2) * r;// 偏移角度对应的等腰三角形底角double angel_alpha = (Math.PI - Math.toRadians(angle)) / 2;// 对角线和矩形边形成的角度double angel_dalta_width = Math.atan((double) h / w);double angel_dalta_height = Math.atan((double) w / h);// 偏移的宽度int len_dalta_width = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_width));// 偏移的高度int len_dalta_height = (int) (len * Math.cos(Math.PI - angel_alpha - angel_dalta_height));// 最终宽度int des_width = w + len_dalta_width * 2;// 最终高度int des_height = h + len_dalta_height * 2;return new SimpleBitmapInfo(des_width, des_height, angle);}/*** @Description TODO 获取最后生成的bitmap* @return 叠加后生成的bitmap*/public Bitmap getStackPicBitmap() {int size = bitmaps.length;// 倒序取出图片,逐个绘制,保证顶层的图片显示在最前面for (int i = size - 1; i  >= 0; i--) {Bitmap bt = bitmaps[i];int w = bt.getWidth();int h = bt.getHeight();mCanvas.save();mCanvas.translate(centerX, centerY);mCanvas.rotate(simpleBitmapInfoList.get(i).getDegree());if (DEBUG) {Log.d(TAG, i + " bt.height=" + bt.getHeight() + ",bt.weight=" + bt.getWidth());}mCanvas.drawBitmap(bt, -w / 2, -h / 2, paint);mCanvas.restore();}return bitmap;}/*** @Description TODO 给Bitmap添加边框* @param bitmap*            传入源bitmap* @param offset*            边框距离* @return 带有边框的Bitmap*/private Bitmap addFrame(Bitmap bitmap, int offset) {int h = bitmap.getHeight();int w = bitmap.getWidth();Bitmap b = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);Canvas c = new Canvas(b);PaintFlagsDrawFilter filter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);c.setDrawFilter(filter);Paint paint = new Paint();paint.setAntiAlias(true);c.save();float sx = (float) (w - offset) / w;float sy = (float) (h - offset) / h;c.scale(sx, sy);c.drawColor(Color.WHITE);c.drawBitmap(bitmap, (float) offset / 2, (float) offset / 2, paint);c.restore();return b;}/*** @Description TODO 给Bitmap绘制阴影* @param originalBitmap*            源Bitmap* @return 返回绘制阴影的bitmap*/private Bitmap drawImageDropShadow(Bitmap originalBitmap) {BlurMaskFilter blurFilter = new BlurMaskFilter(1, BlurMaskFilter.Blur.NORMAL);Paint shadowPaint = new Paint();shadowPaint.setAlpha(50);shadowPaint.setColor(context.getResources().getColor(R.color.gray));shadowPaint.setMaskFilter(blurFilter);int[] offsetXY = new int[] { 0, 0 };Bitmap shadowBitmap = originalBitmap.extractAlpha(shadowPaint, offsetXY);Bitmap shadowImage32 = shadowBitmap.copy(Bitmap.Config.ARGB_8888, true);Canvas c = new Canvas(shadowImage32);c.drawBitmap(originalBitmap, offsetXY[0], offsetXY[1], null);return shadowImage32;}public int getOffsetDegree() {return offsetDegree;}public void setOffsetDegree(int offsetDegree) {this.offsetDegree = offsetDegree;init();}public Bitmap[] getBitmaps() {return bitmaps;}public void setBitmaps(Bitmap[] bitmaps) {this.bitmaps = bitmaps;init();}/*** @Description TODO bitmap简单信息对象*/class SimpleBitmapInfo {private int width;// 改变后宽度private int height;// 改变后高度private int degree;// 改变后角度public SimpleBitmapInfo(int width, int height, int degree) {super();this.width = width;this.height = height;this.degree = degree;}public int getWidth() {return width;}public void setWidth(int width) {this.width = width;}public int getHeight() {return height;}public void setHeight(int height) {this.height = height;}public int getDegree() {return degree;}public void setDegree(int degree) {this.degree = degree;}}
}

使用方法如下:

ImageView iv = (ImageView)findViewById(R.id.iv);
Bitmap bimtmap = CommonUtil.getImageFromAssetsFile(this, "ddffg.jpg");
Bitmap[] bitmaps = new Bitmap[]{bimtmap,bimtmap,bimtmap,bimtmap,bimtmap};
StackPic sp = new StackPic(this, bitmaps, 20);
iv.setImageBitmap(sp.getStackPicBitmap());

完整demo下载:

点此下载

图片随机小角度堆叠效果,可用于相册缩略图叠加样式【长宽最优化】相关推荐

  1. 前端图片压缩上传(纯js的质量压缩,非长宽压缩)

    前端图片压缩上传(纯js的质量压缩,非长宽压缩) 此demo为大于1M对图片进行压缩上传 若小于1M则原图上传,可以根据自己实际需求更改. demo源码如下: <!DOCTYPE html> ...

  2. php 图片扭曲,把一张图片变形扭曲成各种不同的长宽

    把一张图片变形扭曲成各种不同的长宽 第一步:把如下代码加入 区域中 var b = 1; var c = true; function fade(){ if(document.all); if(c = ...

  3. 简洁风个人主页(3) js背景图片随机切换

    静态页面做完了,现在用js做一个背景图片随机切换的效果. 1.点击'个人网站'这个字样,实现body背景的切换.所以,首先获取这两个节点. var body = document.body; var ...

  4. 仿朋友圈相册图片选择以及画廊效果

    仿朋友圈相册图片选择以及画廊效果 1.效果展示 2.导入相关第三方库依赖 3.编写选择图片页面 a.编写布局 b.编写Activity c.相册选择工具类部分代码 d.相册4宫图适配器 4.编写画廊页 ...

  5. html 图片重叠效果,CSS实现照片堆叠效果的实例代码

    实现效果 步骤 1.初始index.html 为了建立第一张照片,也就是最上面的那张.我们只需要添加一个div,里面包含照片的img.就这么多,剩下的效果都是通过CSS来实现的.确保div的class ...

  6. html5如何将图片堆叠代码,css实现图片堆叠效果的方法介绍

    css实现图片堆叠效果的方法介绍 发布时间:2020-04-03 13:49:30 来源:亿速云 阅读:51 作者:小新 今天小编给大家分享的是css实现图片堆叠效果的方法介绍,很多人都不太了解,今天 ...

  7. 推荐一款jquery前端插件(zoomify),灯箱效果插件(用于图片看大图的效果),简单易用!

    前段时间项目中,需要使用图片看大图的效果,主要是用于上传的企业工商执照等信息.这个时候的需求是希望放大图片,看到大图.在进行认真调研基础上发现了一款极好用.极简单的前端插件 zoomify,下面简单介 ...

  8. layui下的图片轮播图效果代码收藏

    以下展示在layui table表格列表中展示图片集,并使用layui图片轮播效果. <script> layui.use(['table', 'tree', 'layer','jquer ...

  9. html随机出现一张图片,图片随机飘动用html怎么做

    公告: 为响应国家净网行动,部分内容已经删除,感谢读者理解. 话题:图片随机飘动用html怎么做 回答:建立一个html,下面的内容,打开就可以看到效果了var xPos = 300;var yPos ...

最新文章

  1. 数组, 数组的初始化
  2. oracle递归查询子节点
  3. dev gridcontrol简单的动态设置动态表头
  4. 引用 看下面图片是向左转还是向右转呢?
  5. ElementUI如何将当前组件的所有属性快速传递给子组件
  6. ssh(Spring+Spring mvc+hibernate)——EmpServiceImpl.java
  7. Codeforces Round #493 (Div. 2):C. Convert to Ones
  8. 任意文件夹下打开cmd窗口
  9. POJ 3678 2-SAT简单题
  10. HashMap的put过程
  11. 十字军之王3Crusader Kings III mac中文
  12. 在c语言程序中main函数的位置,在C语言程序中,main函数的位置_________。
  13. 倍福--授权等级的区别
  14. V4L2采集视频数据
  15. GROUP BY clause and contains nonaggregated 报错处理
  16. sklearn中的决策树(回归)
  17. 路由器刷第三方固件,宽带免费提速
  18. 多线程py爬虫实现邮编,区号查询小程序
  19. iOS系统整体框架及类继承框架图
  20. 系统错误 由于找不到MSVCP1.40.ddl,无法继续执行代码,重新安装程序可能会解决此问题 解决办法

热门文章

  1. Web应用的性能测试
  2. unity中如何锁定运行帧率
  3. Linux从0到1:安装Linux操作系统(超级详细版)
  4. 微调Hugging Face中图像分类模型
  5. C语言createthread函数详解,CreateThread()使用实例
  6. bat批处理的基本命令和使用方法总结
  7. mac电脑清理垃圾软件CleanMyMacX2023
  8. 5G的高精度时间同步服务器网络实现方案
  9. Ubuntu 系统设置同步NTP服务器
  10. django进入admin报错ORA-00918:column ambiguously defined