(接上文《架构设计:系统存储(20)——图片服务器:需求和技术选型(2)》)
之前的两篇文章介绍了图片系统的技术组件选型和技术方案设计,从这篇文章开始我们将搭建工程进行详细的编码开发和效果测试。这里要说明一下,由于文章篇幅的限制不可能贴出所有的代码,这样也不符合读者的阅读习惯。所以笔者的办法是,只通过后续的文章内容介绍详细的设计要点和代码片段,通过这些讲解读者基本可以清楚整个详细设计的思路,而整个图片服务工程代码会上传到了 CSDN 的下载区(http://download.csdn.net/detail/yinwenjie/9740380),如果对工程感兴趣那么读者可以直接下载——免费下载。
由于我们将要进行的是图片处理操作,而图片处理比起读者经常涉及的业务系统来说是一个比较生涩的领域,在一般的业务系统中也就只是需要做到上传或者下载 / 显示图片就 OK 了。而图片服务是专门进行图片处理的,简单的处理包括透明度、旋转、缩放、翻转、重合等,复杂的处理还有背景虚化、人脸识别等等。所以在正式进入编码前,本文有必要向读者介绍一些基本的图片知识,特别是图片颜色模型的知识。当然,如果读者本身就有很丰富的图片处理知识了,则可以直接跳过本节的介绍。
目前在互联网上使用最多的图片都属于位图,例如 JPG、GIF、PNG 和 BMP 图片都属于位图。它们的相同点在于都依靠 RGB 色彩模式描述图片中的每一个点,从而构成一张图片。它们的不同点则体现在文件头结构、压缩算法、扫描方式以及像素深度支持等方面。举个例子,BMP(bitmap)图片使用一种无损压缩算法能够保证还原所有像素点,但是图片大小过大,所以相同像素规模的图片如果使用 BMP 格式,需要的存储容量就更大,而且不利于进行网络传输。再例如,PNG 图片除了可以存储 24 位深,还可以存储额外 8 位甚至 16 位的 Alpha 通道描述来表示每个像素点的透明度,但是 JPG 图片却没有这个特性,它只支持 24 位深度。
那么什么是 GRB 呢?这些图片格式的每一个点都由三个基础色进行描述: 红(Red)、绿(Green)、蓝(Blue),通过三原色之间的比例就可以调制出不同的颜色,将一定规模的颜色点组合起来就是一张图片了。一般来说计算机为每一个原色准备了 8 位 bit 进行描述(就是 1 个 byte),三原色就是 24 位 bit,并且大多数图片的颜色深度就是 24 位。
这样看来 24 位颜色深度的一个图片点,最多可以记录 256 * 256 * 256 = 16777216 种颜色。那么类似 PNG 图片使用的 32 位 / 48 位颜色深度有代表什么意思呢?多出来的 8 位 / 16 位为了记录这个点的可见度(透明度),这个记录范围的数值又称为 Alpha 通道。这样同一个颜色配合不同的可见度就可以满足更丰富颜色展示要求。另外,这也是 JPG 文件不能支持透明度的原因——它的格式规范中不带有对 Alpha 通道的支持。
另外由于一些图片类型也更浅的位深,所以一些图片为了减少极端情况下的使用 / 传输空间,也会使用 16 位 / 8 位的 RGB 描述模式。例如使用 16 位 RGB 模式时,红色位描述为 5 个 bit、绿色位描述为 6bit、蓝色位描述为 5bit。在 Java 原生的图片处理模块中,使用 TYPE_USHORT_565_RGB、TYPE_USHORT_555_RGB 标识对 16 位 RGB 模式进行描述。
JAVA 模块中带有的图片处理功能(Java Image I/O API),能够支持对 JPG、PNG、BMP、WBMP、GIF 格式的文件进行读写操作,而且可以支持到单个像素点级别的操作——也就是说读者可以通过这套 API 对具体的某一个 RBG 描述信息进行操作。通过这套 API 要完成在图片上添加形状、缩放图片、裁剪图片等操作也非常简单,只需要几行代码。首先我们来看一下 java Image I/O API 中会使用的几个概念,以便后续进行代码编写:
从这个类的直观名称可以理解成被缓存的图片信息,它将一张图片经过格式分析后,放置在内存中的一个可访问区域。开发热源可以从这个可访问区域提取到很多关于这张图片有用的信息,例如可以取得 ColorModel 表示的颜色分量,里面包括了每一个像素的 RGB 信息和 Alpha 信息;还可以取得 Raster 表示的像素矩阵,通过它可以读取一个范围内的像素点。开发人员对这个区域进行读写操作,实际上就是对这张图片进行像素级别操作。以下代码可以加载一张位图到 BufferedImage 中:
- ......BufferedImage srcImage = javax.imageio.ImageIO.read(new File("mmexport1444022819048.jpg"));......1 2 3 1 2 3
需要注意的是,BufferedImage 一旦被被加载其像素规模就不能改变了,例如您最初加载了一个 800 * 600 的 JPG 文件到 BufferedImage 中,在操作过程中您就不能将这个 BufferedImage 缩小成 600 * 400 的。如果要这样做,您只能创建一个新的 BufferedImage,并将进行缩放后的计算结果加载到这个新的 BufferedImage 中。以下的方式可以创建一个空的 BufferedImage:
- ......
- // 以下代码创建一个600 * 400的BufferedImage
- BufferedImage outputImage = new BufferedImage(600, 400, BufferedImage.TYPE_INT_RGB);......1 2 3 4 1 2 3 4
注意最有一个参数 BufferedImage.TYPE_INT_RGB,它指定了 BufferedImage 需要支持的 RGB 规则。TYPE_INT_RGB 表示使用一个 int 数值表示 24 位深度的 RGB 规则,TYPE_USHORT_565_RGB 表示,使用一个 short 数值,表示 16 位深度的 RGB 规则,其中红色占 5 位,绿色占 6 位,蓝色占 5 位。再例如 TYPE_INT_ARGB 和 TYPE_4BYTE_ABGR 分别表示以 int 数值或一个 4 位的 byte 数组表示 ARGB 规则,换句话说就是这个 BufferedImage 支持图片透明度的表示。
最直观的理解就是,BufferedImage 相当于在内存区域中的画布,这个画布上可以有一张或者多张图片,也可以没有任何图片。你可以在画布上对每个像素进行读写操作,但是你不能改变画布的大小。
Graphics 类抽象一点说是进行图形操作的上下文控制的类,具体一点说是图形画笔工具。通过这个类(以及它的子类)开发人员可以方便的在画布上绘制不同的规则形状、图片或者文字。
上图中呈现的 Graphics 子类结构可以支持不同的绘图场景,例如 Graphics2D 类基本上可以处理所有主流 2 维位图的画布绘制,在我们的图片服务系统中使用最多的画笔类也是它。以下代码可以在画布上绘制一个规则的矩形,并填充颜色:
- ......
- // 创建一个100 * 80 的画布
- BufferedImage outputImage = new BufferedImage(100, 80, BufferedImage.TYPE_INT_RGB);
- // 获得这个画布的画笔,也可以使用createGraphics创建画笔
- Graphics graphics = outputImage.getGraphics();
- // 从画布上10,10的坐标开始,绘制一个60 * 40的矩形
- graphics.setColor(Color.RED);
- graphics.drawRect(10, 10, 60, 40);
- // 处理
- graphics.dispose();......1 2 3 4 5 6 7 8 9 10 11 1 2 3 4 5 6 7 8 9 10 11
输出到文件,就可以看到如以下所示的效果了。
红色矩形在画布上被绘制出来,但为什么初始化的画布是黑色呢?这是因为我们使用的 BufferedImage 构造方法将使用 RGB=0000 0000 0000 0000 0000 0000 的数值进行每个像素点的初始化,实际上就是黑色的 RGB 值。我们换一种方式进行 BufferedImage 的初始化,就可以将 BufferedImage 中的像素点初始化成白色:
- ......int width = 100,
- height = 80;
- int size = width * height;
- int[] pixels = new int[size];
- // 现在设置每一个像素点的RGB值为白色(整数的表示就是16777215,16进制的表示就是FFFFFF)
- for (int index = 0; index < size; index++) {
- pixels[index] = 0xFFFFFF;
- }
- // size就是像素规模大小
- DataBuffer dataBuffer = new DataBufferInt(pixels, size);
- // 初始化的Raster类,就是像素矩形数组的封装
- WritableRaster raster = Raster.createPackedRaster(dataBuffer, width, height, width, new int[] {
- 0xFF0000,
- 0xFF00,
- 0xFF
- },
- null);
- DirectColorModel directColorModel = new DirectColorModel(24, 0xFF0000, 0xFF00, 0xFF);
- // 生成 BufferedImage, 这样BufferedImage中的每个像素就是白色了
- BufferedImage outputImage = new BufferedImage(directColorModel, raster, true, null);......1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
上文多处已经提到,图片处理操作是计算密集型操作,非常消耗 CPU 资源和内存资源。而 JAVA Image I/O API 又是基于 java 进行的图片像素级操作,其处理性能本身就不及 C/C++。所以对 JVM 的内存优化就显得非常重要了。这里我们假设读者已经知道了 JVM 的基本构造,直接讲解 JVM 的几个优化注意点:
除了 JAVA 提供的 JAVA Image I/O API 以外,还有一些基于 JAVA 开发的第三方图形组件,不过如果您搜索 Goolge 就会发现很多第三方图形组件已经没有再维护了,如果各位读者有兴趣可以进行研究:例如 Java Image Filters、JMagick 等。
JAVA 提供的 JAVA Image I/O API 图片处理工具虽然可以进行像素级别的操作,但是相对于 C/C++ 提供的图片处理性能来说还是较弱,那么要进行图形高效运算的语言基础还是 C/C++ 为宜。目前流行的 2D 和 3D 图像处理软件也多是基于 C/C++ 构建,例如 OpenGL、DirectX 等。另外图形处理都是 CPU 密集型工作,对计算机的运算资源和内存资源要求都比较高,目前的发展趋势是使用专门的 GPU 代替 CPU 进行运算,这也是为什么无论是我们使用 JAVA Image I/O API 还是基于 Nginx 的 Image 模块为系统提供简单的图片处理功能,对 CPU 要求都非常高的原因。
=============================
(接下文)
百度搜索 "就爱阅读", 专业资料, 生活学习, 尽在就爱阅读网 92to.com, 您的在线图书馆!
来源: http://www.92to.com/bangong/2017/07-26/25724890.html