返回顶部
首页 > 资讯 > 后端开发 > JAVA >ImageCombiner设计源码详解
  • 917
分享到

ImageCombiner设计源码详解

java自定义海报Poweredby金山文档 2023-09-04 11:09:43 917人浏览 独家记忆
摘要

前言 在前面的博客中介绍了一款Java的海报生成器ImageCombiner,原文地址: 拿来就用的Java海报生成器ImageCombiner(一),在博文中简单介绍了一下代码以及一个真实的生成案例。但是对源码的介绍不多,本文

前言

在前面的博客中介绍了一款Java的海报生成器ImageCombiner,原文地址: 拿来就用的Java海报生成器ImageCombiner(一),在博文中简单介绍了一下代码以及一个真实的生成案例。但是对源码的介绍不多,本文就针对源码进行深入的讲解,便于用户在使用的过程当中可以知其然,知其所以然,了解它的内部架构,程序设计理念,相关类的具体实现,现有的不足,再此基础上可以扩展出符合自己业务的逻辑,进行二次开发改造。

一、源码分析

1、整体设计

ImageCombiner的源码比较简洁,主要包含以下三个包,element 海报组合要素,enums 海报样式、输出格式枚举,painter 具体的绘制器。

包图说明

整体类图

2、绘制器设计

从上面的类图可以清晰得看到,兑现绘制器和绘制工厂共同完成图像、文本、矩形绘制器三种。

图片绘制器与文本绘制器和矩形绘制器不一样的是多了两个make的方法,一个是用于对图片进行高斯模糊处理的方法。高斯模糊的源码如下:

    private BufferedImage makeBlur(BufferedImage srcImage, int radius) {        if (radius < 1) {            return srcImage;        }        int w = srcImage.getWidth();        int h = srcImage.getHeight();        int[] pix = new int[w * h];        srcImage.getRGB(0, 0, w, h, pix, 0, w);        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++) {                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;            }        }        srcImage.setRGB(0, 0, w, h, pix, 0, w);        return srcImage;    }

在上述的代码,尤其是绘制器工厂的实现,信心的朋友通过阅读代码可以看到,工厂实现方式还是可以进行改造,由于目前的绘制对象也不多,不是有代码洁癖的也能接受。

package com.freewayso.image.combiner.painter;import com.freewayso.image.combiner.element.CombineElement;import com.freewayso.image.combiner.element.ImageElement;import com.freewayso.image.combiner.element.RectangleElement;import com.freewayso.image.combiner.element.TextElement;public class PainterFactory {    private static ImagePainter imagePainter;               //图片绘制器    private static TextPainter textPainter;                 //文本绘制器    private static RectanglePainter rectanglePainter;       //矩形绘制器    public static IPainter createInstance(CombineElement element) throws Exception {        //考虑到性能,这里用单件,先不lock了        if (element instanceof ImageElement) {            if (imagePainter == null) {                imagePainter = new ImagePainter();            }            return imagePainter;        } else if (element instanceof TextElement) {            if (textPainter == null) {                textPainter = new TextPainter();            }            return textPainter;        } else if (element instanceof RectangleElement) {            if (rectanglePainter == null) {                rectanglePainter = new RectanglePainter();            }            return rectanglePainter;        } else {            throw new Exception("不支持的Painter类型");        }    }}

在上述的代码中,通过if循环来进行具体的绘制图形实例生成,可以想象一下,针对上述的代码,如果未来要扩展一种新的绘制对象,该怎么进行代码优化

3、海报元素类设计

海报元素可以分为三种元素,与绘制器一一对应,分别是图片元素、文本元素、矩形元素。这几个类都是CombineElement的子类。后面结合时序图来说明具体的元素绑定与注册及绘制过程。

4、海报生成器

ImageCombiner是最重要的海报生成器类,在ImageCombiner中,定义了一个海报元素类的集合用来保存需要在海报中添加的元素。同时定义了很多的以add开头的添加海报元素的方法,用以往List集合中添加新的元素对象。

    public ImageElement addImageElement(String imgUrl, int x, int y, int width, int height, ZoomMode zoomMode) {        ImageElement imageElement = new ImageElement(imgUrl, x, y, width, height, zoomMode);        this.combineElements.add(imageElement);        return imageElement;    }

二、生成时序解析

1、海报组装

public void test1() throws Exception {        try {            String bgImageUrl = "https://img.thebeastshop.com/combine_image/funny_topic/resource/bg_3x4.png";                       //背景图(测试url形式)            String qrCodeUrl = "Http://imgtest.thebeastshop.com/file/combine_image/qrcodef3D132b46b474fe7a9cc6e76a511dfd5.jpg";     //二维码            String productImageUrl = "https://img.thebeastshop.com/combine_image/funny_topic/resource/product_3x4.png";             //商品图            BufferedImage waterMark = ImageIO.read(new URL("https://img.thebeastshop.com/combine_image/funny_topic/resource/water_mark.png"));  //水印图(测试BufferedImage形式)            BufferedImage avatar = ImageIO.read(new URL("https://img.thebeastshop.com/member/privilege/level-icon/level-three.jpg"));           //头像            String title = "# 最爱的家居";           //标题文本            String content = "苏格拉底说:“如果没有那个桌子,可能就没有那个水壶”";  //内容文本            //合成器和背景图(整个图片的宽高和相关计算依赖于背景图,所以背景图的大小是个基准)            ImageCombiner combiner = new ImageCombiner(bgImageUrl, OutputFORMat.PNG);            combiner.setBackgroundBlur(30);     //设置背景高斯模糊(毛玻璃效果)            combiner.setcanvasRoundCorner(100); //设置整图圆角(输出格式必须为PNG)            //商品图(设置坐标、宽高和缩放模式,若按宽度缩放,则高度按比例自动计算)            combiner.addImageElement(productImageUrl, 0, 160, 837, 0, ZoomMode.Width)                    .setRoundCorner(46)     //设置圆角                    .setCenter(true);       //居中绘制,会忽略x坐标参数,改为自动计算            //标题(默认字体为“阿里巴巴普惠体”,也可以自己指定字体名称或Font对象)            combiner.addTextElement(title, 55, 150, 1400);            //内容(设置文本自动换行,需要指定最大宽度(超出则换行)、最大行数(超出则丢弃)、行高)            combiner.addTextElement(content, "微软雅黑", 40, 150, 1480)                    .setAutoBreakLine(837, 2, 60);            //头像(圆角设置一定的大小,可以把头像变成圆的)            combiner.addImageElement(avatar, 200, 1200, 130, 130, ZoomMode.WidthHeight)                    .setRoundCorner(200)                    .setBlur(5);       //高斯模糊,毛玻璃效果            //水印(设置透明度,0.0~1.0)            combiner.addImageElement(waterMark, 630, 1200)                    .setAlpha(.8f)      //透明度,0.0~1.0                    .setRotate(15);     //旋转,0~360,按中心点旋转            //二维码(强制按指定宽度、高度缩放)            combiner.addImageElement(qrCodeUrl, 138, 1707, 186, 186, ZoomMode.WidthHeight);            //元素对象也可以直接new,然后手动加入待绘制列表            TextElement textPrice = new TextElement("¥1290", 40, 600, 1400);            textPrice.setStrikeThrough(true);       //删除线            combiner.addElement(textPrice);         //加入待绘制集合            //动态计算位置            int offsetPrice = textPrice.getX() + textPrice.getWidth() + 10;            combiner.addTextElement("¥999", 60, offsetPrice, 1400)                    .setColor(Color.red);            //执行图片合并            combiner.combine();            //保存文件            combiner.save("d://fullTest.png");            System.out.println(1/0);            System.out.println("end...");            //或者获取流(并上传oss等)            //InputStream is = combiner.getCombinedImageStream();            //String url = ossUtil.upload(is);        } catch (Exception e) {            e.printStackTrace();        }            }

通过以上的时序图可以清晰的看到系统的调用过程,一共包含了24个步骤。通过时序图和代码,相信您可以很直观的看到相关的调用方式,这里不做过多的赘述。

2、combine海报合成

    public BufferedImage combine() throws Exception {        combinedImage = new BufferedImage(canvasWidth, canvasHeight, BufferedImage.TYPE_INT_RGB);        Graphics2D g = combinedImage.createGraphics();        //PNG要做透明度处理,否则背景图透明部分会变黑        if (outputFormat == OutputFormat.PNG) {            combinedImage = g.getDeviceConfiguration().createCompatibleImage(canvasWidth, canvasHeight, Transparency.TRANSLUCENT);            g = combinedImage.createGraphics();        }        //抗锯齿        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);        g.setColor(Color.white);        //循环绘制各元素        for (CombineElement element : combineElements) {            IPainter painter = PainterFactory.createInstance(element);            if (element.isRepeat()) {                //平铺绘制                painter.drawRepeat(g, element, canvasWidth, canvasHeight);            } else {                //正常绘制                painter.draw(g, element, canvasWidth);            }        }        g.dispose();        //处理整图圆角        if (roundCorner != null) {            combinedImage = this.makeRoundCorner(combinedImage, canvasWidth, canvasHeight, roundCorner);        }        return combinedImage;    }

combine方法是最核心的生成方法,在第6步进入循环,将页面设置的不同的海报元素添加到生成器中,并调用绘制器工程生成相应的绘制器,通过Graphics2D来进行海报的生成。至此,海报生成器的相关类就全部讲解完毕,工具虽小,五脏俱全。

//循环绘制各元素for (CombineElement element : combineElements) {      IPainter painter = PainterFactory.createInstance(element);       if (element.isRepeat()) {            //平铺绘制             painter.drawRepeat(g, element, canvasWidth, canvasHeight);       } else {           //正常绘制            painter.draw(g, element, canvasWidth);       } }

三、总结

以上就是本文的主要内容,本文主要从海报生成器的源码和生成时序解析两个方面进行深度解析,使用UML的分析方法对类图、时序图结合源码进行说明,将海报生成器的核心代码做完整的剖析,想研究的朋友可以深入的学习,对于源码中不合理的地方可以进行修改。

来源地址:https://blog.csdn.net/yelangkingwuzuhu/article/details/129144254

--结束END--

本文标题: ImageCombiner设计源码详解

本文链接: https://lsjlt.com/news/393427.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • ImageCombiner设计源码详解
    前言 在前面的博客中介绍了一款Java的海报生成器ImageCombiner,原文地址: 拿来就用的Java海报生成器ImageCombiner(一),在博文中简单介绍了一下代码以及一个真实的生成案例。但是对源码的介绍不多,本文...
    99+
    2023-09-04
    java 自定义海报 Powered by 金山文档
  • SpringAware源码设计示例解析
    目录1. Aware介绍2. Aware类别2.1 BeanClassLoaderAware2.2 BeanFactoryAware2.3 BeanNameAware2.4 Appl...
    99+
    2023-01-15
    Spring Aware源码设计 Spring Aware
  • Redis源码设计剖析之事件处理示例详解
    目录1. Redis事件介绍2. 事件的抽象2.1 文件事件结构2.2 时间事件结构2.3 事件状态结构3. 事件的实现1. Redis事件介绍 Redis服务器是一个事件驱动程序,所谓事件驱动就是输入一条命令并且按下回...
    99+
    2024-04-02
  • 工具 | pg_recovery 设计原理与源码解读
    作者:张连壮 PostgreSQL 研发工程师 从事多年 PostgreSQL 数据库内核开发,对 citus 有非常深入的研究。 上一期 我们介绍了 PostgreSQL 数据找回工具:pg_reconvery 本文将带大家了解 p...
    99+
    2014-11-21
    工具 | pg_recovery 设计原理与源码解读
  • Mybatis源码详解
    Mybatis源码详解 Mybatis相关全览一、JDBC与Mybatis对比JDBC调用Mybatis调用两者对比 二、Mybatis资源加载数据源获取SqlSessionFactory...
    99+
    2023-09-29
    mybatis java mysql
  • 基于Android设计模式之--SDK源码之策略模式的详解
    策略模式其实特别简单(听到这句话,大家是不是心里一下子放松了?)。比如排序,官方告诉大家我这里有一个排序的接口ISort的sort()方法,然后民间各尽其能,实现这个排序的方法...
    99+
    2022-06-06
    策略模式 sdk Android
  • Android Matrix源码详解
    Matrix的数学原理 在Android中,如果你用Matrix进行过图像处理,那么一定知道Matrix这个类。Android中的Matrix是一个3 x 3的矩阵,其内容如下...
    99+
    2022-06-06
    matrix Android
  • Android DownloadProvider 源码详解
    Android DownloadProvider 源码分析: Download的源码编译分为两个部分,一个是DownloadProvider.apk, 一个是DownloadP...
    99+
    2022-06-06
    源码 Android
  • Spring源码BeanFactoryPostProcessor详解
    Spring源码分析-BeanFactoryPostProcessor BeanFactoryPostProcessor接口是Spring提供的对Bean的扩展点,它的子接口是Bea...
    99+
    2024-04-02
  • Go泛型generic设计源码分析
    这篇文章主要讲解了“Go泛型generic设计源码分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Go泛型generic设计源码分析”吧!Go 泛型的设计融入了现代语言的风格,比如类型限制...
    99+
    2023-07-06
  • java TreeMap源码解析详解
    java TreeMap源码解析详解 在介绍TreeMap之前,我们来了解一种数据结构:排序二叉树。相信学过数据结构的同学知道,这种结构的数据存储形式在查找的时候效率非常高。如图所示,这种数据结构是以二叉树为基础的,所有的左孩子的...
    99+
    2023-05-31
    java treemap 源码
  • 计算机毕设题目设计与实现(论文+源码)_kaic
                                    毕业设计(论文)题目 高校图书馆座位预约选座微信小程序设计与实现 基于防火墙的访问控制系统的设计与实现 基于区块链的农产品追溯系统设计与实现 学生公寓楼改造布线系统规划与设计...
    99+
    2023-08-31
    tomcat java c# php r语言
  • 如何从设计模式看OkHttp源码
    今天就跟大家聊聊有关如何从设计模式看OkHttp源码,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。使用读源码,首先就要从它的使用方法开始:val&n...
    99+
    2024-04-02
  • (附源码)订餐app 毕业设计190711
      摘 要 随着现在网络的快速发展,网络的应用在各行各业当中它很快融入到了许多学校的眼球之中,他们利用网络来做这个职位推荐的网站,随之就产生了“订餐app ”,这样就让用户订餐app更加方便简单。 对于本订餐app的设计来...
    99+
    2023-09-07
    mysql spring boot python php html Powered by 金山文档
  • Vue.use的原理和设计源码探究
    目录前言基本使用源码解析控制反转前言 这段时间打算回顾一下Vue的全局方法,脑海里第一个跳出来的方法就是Vue.use,之所以会首先想到它,我觉得和我平时看的面试题相关~~~ Vu...
    99+
    2023-02-09
    Vue.use原理设计 Vue.use设计
  • Reactcommit源码分析详解
    目录总览commitBeforeMutationEffectscommitMutationEffects插入 dom 节点获取父节点及插入位置判断当前节点是否为单节点在对应位置插入节...
    99+
    2022-11-13
    React commit React commit源码
  • Java源码解析重写锁的设计结构和细节
    目录引导语1、需求2、详细设计2.1、定义锁 2.2、定义同步器Sync2.3、通过能否获得锁来决定能否得到链接3、测试4、总结 引导语 有的面试官喜欢让同学在说...
    99+
    2024-04-02
  • 【计算机毕业设计】个人交友网站源码
    一、系统截图(需要演示视频可以私聊) 摘 要 本论文主要论述了如何使用JAVA语言开发一个个人交友网站,本系统将严格按照软件开发流程进行各个阶段的工作,采用B/S架构,面向对象编程思想进行项目开发。在引言中,作者将论述个人交友网站的当前...
    99+
    2023-10-24
    java spring 源码软件 mysql
  • 详解go中panic源码解读
    panic源码解读 前言 本文是在go version go1.13.15 darwin/amd64上进行的 panic的作用 panic能够改变程序的控制流,调用pa...
    99+
    2022-06-07
    panic GO
  • OpenJDK源码解析之System.out.println详解
    目录一、前戏二、JVM源码分析三、坑?四、总结一、前戏 可能不少小伙伴习惯在代码中使用sout打印一些信息,就像这样: System.out.println("hello wor...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作