CorelDraw插件开发教程(9):读图
Category:COREL插件CorelDraw插件开发教程(9):读图
刘肖健
浙江工业大学工业设计研究院
1. 图形与图像
从技术的角度,“图形”和“图像”是两个概念。图形主要处理矢量元素,点线面体,有2D也有3D;图像主要处理像素,RGB、HSB、CMYK等,一般是2D。CorelDraw是图形处理软件,Photoshop是图像处理软件。但有时CorelDraw也会做一些最基本的图像处理,如调整色彩、矢量化等。在创意设计领域,图像则经常被用作概念实施的参考来源,如下述福田繁雄的两件以《蒙娜丽莎》为参考源的平面设计作品:
这两件作品的思路其实并不复杂,核心技术就是把蒙娜丽莎源图的RGB像素值读出来,然后做一些简单的算法处理,生成设计图形。上述两图现在来做毫无困难,但福田繁雄在那个没有电脑的时代用纯手工方式做出来还是非常惊艳的。
要想利用图像做设计,首要问题是把图像的每一个像素的RGB(或CMYK、HSB等其他色彩模式)值取出来。这节就来处理这个问题,把一幅图像的RGB值取出来保存在二维数组里,供后续设计使用。
图像有很多种格式,这里只处理bmp格式,因为bmp格式的图像数据结构最简单。jpg等类型的格式由于有压缩,读像素色彩时还要进行解压处理,算法要复杂一些。如果需要读jpg图,我们的办法是将其转化成bmp图再进行处理。
本节的图像处理方法包含四个步骤:
1)把待处理的图像放在cdr文档的页面中;
2)将图像导出到硬盘上,保存为24位色彩的标准bmp文档;
3)打开bmp文档,获取像素色彩,存入二维数组;
4)利用色彩二维数组的信息进行设计。
下面就通过一个完整的实例来看看上述步骤如何实现。
2. 生成像素矩阵
由于图像处理功能使用频繁,我们将其定义为一个单独的模块。当然,定义成一个Function也行,但是为了以后加入更多的通用图像处理功能,作为一个集中的模块更好些。
创建一个模块的方法跟创建一个对话框差不多:在工程资源管理器右击项目标题,选“插入/模块”:
产生的新模块会出现在“模块”文件夹下,将其改为我们所需的名称。本例用的名称是OpenBMP。
模块不像对话框有个图形界面,它只有代码页。所以在工程资源管理器的左上角三个按钮中,中间的“查看对象”按钮是灰色不可用的,只有“查看代码”按钮可用。
在右边的代码工作区写入如下代码:
Public bmpBlue() As Byte
Public bmpGreen() As Byte
Public bmpRed() As Byte
Public biWidth As Long ‘图像宽度
Public biHeight As Long ‘图像高度
Public biBitCount As Integer
Public Function OpenBitmap(filepath As String)
‘打开文件
Open filepath For Binary As #1
‘读BMP信息
Get #1, 19, biWidth ‘获取图像宽度
Get #1, 23, biHeight ‘获取图像高度
Get #1, 29, biBitCount ‘获取位数
‘只能读24位BMP,不是则退出
If biBitCount <> 24 Then
MsgBox”Not 24-bit BMP.”
Close #1
Exit Function
End If
‘重定义记录RGB值的二维数组
ReDim bmpBlue(biHeight – 1, biWidth – 1) As Byte
ReDim bmpGreen(biHeight – 1, biWidth – 1) As Byte
ReDim bmpRed(biHeight – 1, biWidth – 1) As Byte
‘RGB数组赋值
Dim n As Long
n = 0
Dim i As Integer, j As Integer
For i = 0 To biHeight – 1
For j = 0 TobiWidth – 1
DoEvents ‘控制权交给系统(防死机)
Get #1, 55+ n, bmpBlue(i, j)
n = n + 1
Get #1, 55+ n, bmpGreen(i, j)
n = n + 1
Get #1, 55+ n, bmpRed(i, j)
n = n + 1
Next
‘补行尾空位(图形文档每行的存储空间字节数为4的整数倍,不足则补空位,因此每行首字节位置都是从4的整数倍开始)
n = n +biWidth Mod 4
Next
Close #1 ‘关闭文档
End Function
上述代码包含一个名为OpenBitmap的函数,其功能是打开一个指定位置的bmp图像文档(在参数filepath中指定),读出其全部像素的RGB值,保存在三个二维数组bmpRed、bmpGreen、bmpBlue中——它们分别用来保存RGB三个色彩分量。简单说,就是OpenBitmap函数执行完以后,三个数组内就写进了RGB值,可以用了。
模块定义了6个Public型的变量,它们在别的模块(如对话框)中也可以使用。
打开文档,从指定位置读出了图像的宽度(biWidth)和高度(biHeight)信息后,就可以定义(ReDim)三个二维数组的确切长度了。二维数组的第一个参数表示行,第二个参数表示列,如bmpRed(10,20)中的值代表图像第10行第20列的红色分量,一个0~255之间的整数。
bmp文档从第55个字节开始存放像素色彩信息,前面的都是文件头,存储像素行列数、文件大小、色彩位数等信息。RGB三个色值每个占一字节,依次存放,不过顺序是先Blue再Green再Red,所以读出的时候也要注意顺序不要弄错。
读像素语句Get #1,55 + n, bmpBlue(i, j)表示把#1号文件的第55+n个字节的数据写入bmpBlue(i, j)。完了以后,n的值加上1,作为下个数据的位置。
计数器n定义为Long型而不是Integer型,是因为通常图像的像素数十分巨大,Integer型整数占两个存储字节,能表达的最大整数是32767,只够表达一个200×163尺寸的一个bmp图像。Long型整数占4个字节,能表达的最大整数是2147483647,能表达50000×42949那么大的bmp图像,对一般图片足够用了。
读像素的双循环中有个DoEvents语句,用于向Windows系统移交控制权。这是因为读像素的工作量很大很耗时,容易造成“假死机”现象,移交控制权后,系统还可以响应其他程序,不会将该程序判定为死机。
循环里的最后一句n= n+biWidthMod 4,作用是跳过行末尾的空位。bmp图像的存储规则是,留给每行像素的存储空间是4的整数倍。如果实际的存储空间不是4的整数倍,比如每行27个像素,则用掉存储空间27×3=81字节,而实际分配的空间是84(4的整数倍)个字节,所以下一行第一个像素是从第85个字节开始存储。因此一行像素读完后要判断一下行末是否有空位,有的话就跳过去。Mod是一个运算符,作用是“整除后取余数”,如81 Mod 4的运算结果是1。每一行补上biWidth Mod 4个字节后刚好是4的倍数(biWidth是每行像素数)。
3. 图像加载与导出
这节给出一个简单的例子,来看看怎样在程序中使用bmpRed、bmpGreen、bmpBlue三个像素色彩数组来做设计。
建立一个对话框,做两个按钮和一个图片框:
按钮“Load图像”的作用是,把用户选中的图片导出为bmp图像文件到硬盘上,记下它的地址和文件名,然后把图片加载的对话框上的图片框里(主要是向用户展示执行结果的反馈)。
按钮“Mapping”的作用是,打开硬盘上刚才导出的bmp图像,生成三个像素色彩二维数组bmpRed、bmpGreen、bmpBlue。然后画一整个页面的圆,用三个数组中保存的色彩给各个圆上色,使页面中呈现出点阵图像。
“Load图像”按钮的单击事件子程序如下:
Dim exportPicPathName As String ‘导出图片文件名
Private Sub SaveRefImage3_Click()
‘选定图片
Dim myRange As ShapeRange ‘当前被选物体集
Set myRange = ActiveDocument.SelectionRange
If myRange.Shapes.Count <> 1 Then
MsgBox “请选一个图片。”
Exit Sub
End If
‘设置图片处理对象(当前选择集里的第一个物体)
Dim sImage As Shape
Set sImage = myRange.Shapes(1)
‘定义输出图像宽高
Dim W As Integer, H As Integer
W = 200
H = Int(W * sImage.SizeHeight / sImage.SizeWidth)
‘设定文档输出位置、文件名、扩展名
exportPicPathName = Application.UserDataPath &sImage.StaticID & “.bmp”
‘输出图片到硬盘
Dim ex As ExportFilter
Set ex = ActiveDocument.ExportBitmap(exportPicPathName,cdrBMP, cdrSelection, cdrRGBColorImage, W, H)
ex.Finish
‘临时图片加载到图片框里
RefImage.Picture = LoadPicture(exportPicPathName)
‘设置图片框适配模式(完整显示)
RefImage.PictureSizeMode = fmPictureSizeModeZoom
‘设置图片框边框色为背景色(即去边框)
RefImage.BorderColor = RefImage.BackColor
End Sub
为对话框定义了一个变量exportPicPathName,它用于存放导出的图片文档的带路径全名。因为还有另一个按钮需要用到这个变量中的文件名,所以它必须在定义在所有子程序的外面,这样所有子程序都可以使用它。如果另一个模块或对话框中的子程序也要用到它,则必须用Public定义为全局公用变量。
程序允许用户从页面中选择(且只能选择)一个图片作为处理对象。所有当前被选对象被保存在myRange中,用它的count属性来判断选择的对象数量是不是一个,如果不是,则给出错误提示后退出。选择正确的话,待处理图片对象被保存在Shape型对象sImage中。
输出到硬盘上的bmp图像的宽高尺寸W和H可以自行指定,以像素计。W=200是给定的,对图形设计,这个分辨率够用了。H是根据图像的宽高计算出来的。
导出图像的路径和文件名自行指定。为了确保程序在任何一台电脑上都能正确运行,指定的路径必须是万无一失的。有人会指定“c:\”根目录作为输出位置,因为这个地址几乎在每台计算机上都存在。但是有个问题,有些系统锁住了C区根目录,不允许应用程序在这里创建临时文件。如果不能成功建立导出文件,后面的步骤就会出错。
这里给出的解决方案是用Application.UserDataPath获取CorelDraw安装时指定的用户数据存放地址,作为临时文档的导出地址。我们不必知道这个文件夹在哪里,知道有这么一个保险的地方可用就行了。真想知道也可以,用一个MsgBoxApplication.UserDataPath语句就可以把它显示出来,我的用户数据文件夹如下……藏的很深:
解决的地址,还要给bmp图片一个文件名。理论上讲,任何名字都可以,不过我们也有简单的方法。sImage.StaticID是Shape对象sImage在cdr文档内部的代号,这个代号是唯一的(不同Shape对象的Name属性可以是相同的),用它来做文件名最合适。
我们用MsgBox exportPicPathName可以看到导出文档的全名:
用ActiveDocument.ExportBitmap函数可以把指定图片导出到硬盘上,有很多参数,请用F1参阅帮助文档中的说明,这里就不啰嗦了。
用LoadPicture(exportPicPathName)可以把导出的bmp图片加载到对话框上的图片框里,给用户一个反馈,表明图片导出成功了。RefImage是图片框的名字。
4. 使用像素数组做设计
“Mapping”按钮首先是画满一页的圆,按点阵方式整齐排列。然后用图像色彩给每个圆上色。
“Mapping”按钮的单击事件子程序如下:
Private Sub ColorMapping_Click()
Dim i As Integer, j As Integer, m As Integer, n AsInteger
‘打开图像,像素色彩写入bmpRed、bmpGreen、bmpBlue三个数组
OpenBitmap exportPicPathName
Dim mm As Integer, nn As Integer ‘图片数组的行列数
mm = UBound(bmpRed, 1) ‘行数
nn = UBound(bmpRed, 2) ‘列数
‘————– 画圆圈填满页面 ————–
m = 22 ‘行数
n = 16 ‘列数
Dim p() As Shape
ReDim p(m – 1, n – 1) As Shape
Dim dSpan As Double ‘圆心间隔
dSpan = ActivePage.SizeWidth / n
Dim x As Double, y As Double
For i = 0 To m – 1 ‘行数循环
y =ActivePage.BottomY + 0.5 * dSpan + i * dSpan
For j = 0 Ton – 1 ‘列数循环
x =ActivePage.LeftX + 0.5 * dSpan + j * dSpan
Set p(i, j)= ActiveLayer.CreateEllipse2(x, y, dSpan / 2)
Next
Next
‘————— 给圆圈填色 —————–
ActiveDocument.ReferencePoint = cdrCenter ‘中心定位
Dim nRow As Integer, nCol As Integer ‘行列数
Dim r As Integer, g As Integer, b As Integer ‘RGB值
For i = 0 To m – 1 ‘行数循环
For j = 0 Ton – 1 ‘列数循环
x = p(i,j).PositionX
y = p(i,j).PositionY
nCol =Int(nn * (x / ActivePage.SizeWidth))
nRow =Int(mm * (y / ActivePage.SizeHeight))
r =bmpRed(nRow, nCol)
g =bmpGreen(nRow, nCol)
b =bmpBlue(nRow, nCol)
p(i,j).Fill.UniformColor.RGBAssign r, g, b ‘填色
Next
Next
End Sub
运行OpenBitmapexportPicPathName后,得到三个像素色彩二维数组bmpRed、bmpGreen、bmpBlue。用它们给圆上色的关键是找到每个圆对应像素,这里用的是坐标定位法:把圆心在页面中的坐标位置换算成图像中对应的像素点位置,即可锁定该圆的色彩。
mm和nn分别为图片的行列数,通过Ubound函数获取二维数组的上限得到。x和y是圆心坐标,用x/ActivePage.SizeWidth可以得到一个0~1之间的数(因为所有圆都在页面内),这个数乘以nn即可得到圆心位置对应的像素列号nCol,同理可以得到行号nRow。用nRow和nCol即可锁定圆对应的二维数组成员的位置,然后把RGB三个色彩分量取出来为圆上色。
还有另外一种不带色彩的点阵图:全部的圆都是黑色,像素色彩被转化成灰度值,然后根据灰度对圆进行缩放,圆对应的像素越黑,直径就越大,反之越小,形成明暗效果。
完成上述任务只需对程序做不大的改动即可:
……
‘————— 给圆圈填色 —————–
ActiveDocument.ReferencePoint = cdrCenter ‘中心定位
Dim nRow As Integer, nCol As Integer ‘行列数
Dim r As Integer, g As Integer, b As Integer ‘RGB值
Dim gray AsDouble ‘灰度值
For i = 0 To m – 1 ‘行数循环
For j = 0 Ton – 1 ‘列数循环
x = p(i,j).PositionX
y = p(i,j).PositionY
nCol =Int(nn * (x / ActivePage.SizeWidth))
nRow =Int(mm * (y / ActivePage.SizeHeight))
r =bmpRed(nRow, nCol)
g =bmpGreen(nRow, nCol)
b =bmpBlue(nRow, nCol)
‘计算灰度:0~1之间的比值
gray = (r * 0.299 + g * 0.587 + b * 0.114)/ 255
gray = 1 – 0.8 * gray
‘根据灰度值对圆进行缩放
p(i, j).Stretch gray
p(i,j).Fill.UniformColor.RGBAssign 0,0,0 ‘填色
Next
Next
……
增加了一个表达灰度的变量gray,它是一个0~1之间的实数。
基于RGB三个色彩分量计算灰度值的公式是r*0.299+g*0.587+b*0.114,算出来是一个0~255之间的数,除以255即可得到0~1之间的灰度值。gray设置为0~1是为了可以将其用作缩放因子。按上述算法,越暗的像素灰度值gray越小,但其对应的圆则越大。缩放因子设置为1-0.8*gray,即最大的圆(对应纯黑像素,gray=0)保持原尺寸,最小的圆(对应纯白像素,gray=1)缩为原大的0.2倍。
注意,图形尺寸不能缩为零,会出错。但是可以加一个判断语句,如果像素为纯白,则把圆删掉了事,不用上色了。
感谢关注创意之代码。
http://mip.i3geek.com