怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图
在上一篇文章里,我们讲解了为滤镜添加术语资源,从而使我们的滤镜可以被PS的scripting system感知和描述,这样即友好支持了PS的“动作”面板。在这一篇文章中,我们将对此前的DEMO进行进一步的细化,例如在参数对话框上增加实时预览的小缩略图等。对话框的引入主要是给用户一个机会和接口,设置或调节滤镜使用的图像处理算法。通常作为UI的友好性,在对话框上应该提供预览图,这样可以直观的把参数对结果产生的影响反馈给用户,指导他们调整参数。而不是要用户必须反复执行滤镜命令才能看到效果然后去调节参数。
此前我觉得“添加缩略图”这样的功能应该不是很困难,但当我尝试这样去做,我很快发现它的难度远远超过了以往我写的文章中的讲解。因为当我们尝试使用PS提供的回调函数去显示缩略图时,我们必须对PS提供的接口细节完全清楚,包括影响缩放的参数设置,数据分布,扫描行等细节。不能够有一分一毫的差错,否则我们就可能看到不正常的显示,甚至会不小心的使内存越界。
在引入缩略图之前,我先对滤镜算法做一些有趣的改进,进行了一点增强,使它更加实用化。
(1)引入“像素随机抖动”参数和算法。
此前我们设置像素时,输入和输出的位置本来是完全一致的,即 Dest(i, j) = f (Src(i, j))。
现在我们考虑对上式进行一点改动,把源像素进行随机抖动,即Dest(i, j) = f (Src(i+dx, j+dy))。
我们设置抖动距离为distance(像素)参数,这样我们取源像素时,在以当前像素为中心向外围扩展distance的正方形内随机选取某个点作为源像素。从而使他们在结果图中具有一种“溶解”或“腐蚀”效果。如下图所示:
因此我们在滤镜参数中增加了distance参数,表示上图中的随机抖动距离。这样当我们设置位于 (i,j)位置的像素时,我们取的源像素坐标是:
x = i + rand()%(2*distance+1) - distance;
y = j + rand()%(2*distance+1) - distance;
在实际处理时,我们还需要考虑上述结果x,y可能会超出有效数据边界,因此需要把x,y限定在 filterRect 内部。
而由于我们采用的是贴片(Tile)处理方法,因此我们的调整需要一点技巧性。我们将改动我们向PS请求的inRect,即每次贴片时,outRect依然保持和此前一致,而把 inRect 尝试向四周扩张distance像素距离,这样可以保证我们每次贴片时都能拿到有效数据(当贴片位于filterRect内部时),除非贴片位于filterRect的边缘。
必须注意的是,由于 inRect 比 outRect 要“大一圈”,所以这时候两个Rect的像素已经不是大小一致完全重叠关系了,而是有一定偏移的!在代码中我们必须考虑两个矩形之间的偏移关系。这里可以参考我的源代码,就不详细讲解该处理方法了。
(2)为对话框增加“缩略图”(Proxy)。
我们增加了一个参数,然后在对话框左侧空出一个较小区域用于显示缩略图,为了方便,我在缩略图位置放置了一个隐藏的STATIC 控件(Proxy Banner),它的主要用处是使我可以在运行时获取到 “缩略图”的边界(客户区坐标)。修改后的对话框如下图所示:
(2.1) displayPixles 回调函数 和 PSPixelMap 结构;
在显示缩略图时,我们使用的是 gFilterRecord 中的 displayPixels 回调函数,这个函数的原型如下(函数指针的typedef):
typedef MACPASCAL OSErr (*DisplayPixelsProc) (
const PSPixelMap *source,
const VRect *srcRect,
int32 dstRow,
int32 dstCol,
void *platformContext);
第一个参数是一个 PSPixelMap 结构体的指针用以描述一块像素数据区,它相当于BitBlt中的源图,srcRect参数描述的是源图矩形;
dstRow 和 dstCol 描述的是目标区域的“目标行”,“目标列”坐标,请注意其逻辑意义与我们通常使用的参数的区别。这里dstRow相当于 destY, dstCol就相当于 destX 参数,即(dstCol, dstRow) 是在目标区域中的起始坐标,这一点需要注意。
最后一个参数 platformContext 在 windows系统中也就是 HDC 。
其中第一个参数我们还需要再做简单介绍,即 PSPixelMap 的定义和 PS 对该数据区的分布要求。PSPixelMap的定义如下:
typedef struct PSPixelMap { int32 version; VRect bounds; int32 imageMode; int32 rowBytes; int32 colBytes; int32 planeBytes; void *baseAddr; //--------------------------------------------------------------------------- // Fields new in version 1: //--------------------------------------------------------------------------- PSPixelMask *mat; PSPixelMask *masks; // Use to set the phase of the checkerboard: int32 maskPhaseRow; int32 maskPhaseCol; //--------------------------------------------------------------------------- // Fields new in version 2: //--------------------------------------------------------------------------- PSPixelOverlay *pixelOverlays; unsigned32 colorManagementOptions; } PSPixelMap;
◆ version: 结构体版本。
对于PS CS版本来说,它要求我们把它设置为1。PS的未来版本可能会扩展它并提升此版本号。
◆ bounds:像素数据所占据的矩形。
◆ imageMode:数据区的图像模式。
它支持以下模式: grayscale, RGB, CMYK, Lab。
◆ rowBytes: 相邻行之间的字节数距离。
相当于扫描行宽度(重要),以字节为单位,必须设置正确。
◆ colBytes: 相邻列像素数据的字节数距离。
由于数据是“集中分布”的,所以这个属性的值主要取决于像素的色深度。对于每个通道每个像素使用一个字节的普通24位深度的图像来说,这个距离为1byte。
◆ planeBytes: 相邻通道的字节数距离。
由于数据是“集中分布”的,所以这个属性通常是 rowBytes * 图像高度。
◆ baseAddr:数据区起始地址。
我曾在此前的文章中讲过,PS提供给我们的 inData 和 outData 是通道交叉分布的(interleave)。而这里PSPixelMap中的数据的分布要求则不同,它要求数据是通道集中分布的。
例如对于RGB图像来说, 当我们请求所有通道时,inData/outData中的数据分布是:
R | G | B | R | G | B | R | G | B | ..... (interleave)
而对于 PSPixelMap 中的数据来说,则要求按如下分布:
R | R | R | .... G | G | G | ... B | B | B .... (集中分布)
也就是说对inData 和 outData, 所有通道数据交叉分布,同一个通道(plane)的数据在数据区中是跳跃式存在的。
而对于 PSPixelMap 来说,同一个通道(plane)的数据是集中在一起的,先列出所有第一个通道数据,再列出所有第二个通道数据,等等。
同时我们进行像素定位和预测缓冲区大小时,和普通的Bitmap像素定位一样,必须使用滤镜参数中的 inRowBytes 属性(相当于扫描行宽度)进行在“行”间定位,而不能假设或自行计算“行宽度”。
【注意】我们必须清楚数据分布的细节,这样才能正确定位到指定位置的像素。
(2.2)控制缩放:FilterRecord中的 inputRate 和 inputPadding 属性;
由于PS处理的图像大小是多种多样的,因此我们显示缩略图时必然面对的一个问题是缩放问题。大多数情况下,由于缩略图只是定性展示,对数据精确性要求可以有所降低,并且考虑到性能因素,因此一般缩略图的尺寸可以设置的较小,当原图(filterRect)比缩略图(bannerRect)大时,我们希望图像缩小显示在缩略图上。而当原图比缩略图小时,我们就采用实际原图大小(即缩放因子=1)即可。因此现在我们需要了解如何把原图缩小到缩略图大小。在GDI中我们知道我们可以使用 StretchBlt 函数来完成缩放的。而在这里我们获取源图数据是从PS传递给我们的inData得到的,当我们希望得到缩小的原图时,我们即通过设置 FilterRecord 参数中的 inputRate (采样率)属性来完成缩小。
(2.2.1)Fixed inputRate;
==================================================
【关于 Fixed 类型】
Fixed在PS中被定义为 long 类型:
typedef long Fixed;
但是Fixed在PS中的实际意义是一个(定点)小数。所谓Fixed是相对 float(浮点小数)来说的。在float中小数点的位置是不固定的,因此称为浮动点。而 Fixed 则把一个小数拆解为整数部分和小数部分,分别存储到一个高16位和低16位。即其含义是 "16.16"。
例如假设有一个小数是3.00f;则相应的Fixed数是 0x00030000;
从浮点数转换成 Fixed 类型的方法是:
double _factor;
Fixed _fixed = ( Fixed ) ( _factor * 0x10000 ) ;
从 Fixed 类型转换成浮点类型的方法是 ( 备注:1 / 0x10000 = 0.0000152587890625 ) :
Fixed _fixed;
double _factor = _fixed * 0.0000152587890625 ;
==================================================
inputRate表示采样率,在逻辑意义上是一个小数,我们通过设置它的值来实现得到的inData是对原图的缩放结果。在默认情况下,PS设置的inputRate就是1,也就没有任何缩放。我们在获取缩略图数据时,需要计算缩放因子(factor),然后把inputRate设置为factor(注意数据类型的转换方法)。
这样设置后,我们得到的 inData 的实际图像坐标将是 inRect * inputRate,即
(inRect.left * inputRate, inRect.top* inputRate,
inRect.right * inputRate, inRect.bottom * inputRate)
例如,当 inputRate 为 2.0 时,则每两个像素采样一个点,则 inRect, inData 的关系如下图所示:
在上面这幅图中,展示了在缩放时应该如何设置inRect,请注意为了获取我们需要的矩形,我们需要把我们希望的inRect(粉红色矩形区域)的坐标除以 inputRate(图中假设inputRate = 2),才是需要提交给PS的 inRect(上图中的蓝色矩形)。然后我们使用advanceProc回调函数,即可得到 inData 为上图中右侧的图像数据,可见它的尺寸在两个方向上都缩小了一半。
【注意1】在处理完毕缩略图并关闭对话框时,必须把inputRate恢复为 0x00010000 (1.0)。否则它将会继续影响后续的实际处理中的 inData!使处理结果产生意料外的结果。
【注意2】滤镜参数必须考虑图像缩放所带来的影响。和缩放有关的参数也要相应的映射到缩略图尺寸上(例如本例中的随机抖动距离要同比例缩小)。和缩放无关的参数(例如本例中的不透明度百分比,填充色)可不考虑缩放影响。
(2.2.2)int16 inputPadding;
当PS提供 inData 时,它可以被补齐。可以指定补齐的像素的值(0~255),也可以设置为以下选项(它们被定义为负数,以和用户设置像素值区别):
plugInWantsEdgeReplication: 复制边缘像素。
plugInDoesNotWantPadding:随机值(不确定的值)。
plugInWantsErrorOnBoundsException:(默认值)请求边界外数据时标记一个错误。
当请求区域超出边界,PS将使用上述选项设置 inData 的数据。
(2.2.3)显示缩略图。
为了显示缩略图,我们需要请求PS为我们分配缓冲区。我们首先需要预测我们的缓冲区的大小,并在Prepare调用时通知 PS 我们的需求。
考虑到当用户在对话框上进行参数调整时,我们应该实时的更新缩略图显示,以反馈当前参数效果。所以我们需要两份缩略图数据,一份是缩略图的原始数据,它作为算法的输入,在创建对话框时获取到源图数据,然后在整个对话框生命期间保持数据不会改变。另一份是我们用于处理 WM_PAINT 消息时使用的绘制数据,即可以实时改变的缩略图实际显示数据。
因此我们评估缩略图的尺寸,然后使用以下估计值:
bufferSize = 缩略图最大宽度 * 缩略图最大高度 * 通道数 * 2;
在 Prepare 调用期间,我们把这个值(bufferSize)设置到 FilterRecord 的 bufferSpace 和 maxSpace 属性中,这表示我们(PlugIn)和PS(Host)进行内存需求“协商”,使 PS 了解到我们预期的内存开销,然后尝试准备足够内存以供我们后续的申请。
真正显示对话框是在 start 调用中,我们在对话框的初始化消息时准备请PS为我们申请缓冲区。基本方式如下:
//获取 buffer 回调函数集指针 BufferProcs *bufferProcs = gFilterRecord->bufferProcs; //请PS为我们申请内存 bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId0); //请PS为我们锁定内存(禁止内存整理) //[ 1 ]函数返回被锁定的内存起始地址。 //[ 2 ]第二个参数是BOOL moveHigth,对windows平台将被忽略。 m_ProxyData.data0 = bufferProcs->lockProc(m_ProxyData.bufferId0, TRUE); //============================= // 这里是处理和更新缓冲区的期间 //============================= //使用结束后,释放和解锁缓冲区。 //解锁 gFilterRecord->bufferProcs->unlockProc(m_ProxyData.bufferId0); //释放内存 gFilterRecord->bufferProcs->freeProc(m_ProxyData.bufferId0);
我们使用 lockProc 锁定缓冲区这块内存,主要是防止操作系统在我们处理数据期间进行内存整理,从而破坏缓冲区资源。
【注意】这里加锁和解锁使用的是“引用计数”机制,即解锁次数 必须 匹配加锁次数才能使缓冲区真正得到解锁。
为了显示缩略图,并能够实时反馈用户的调节,我们准备了下面的四个函数(其中CreateProxyBuffer 和 UpdateProxy 难度最大):
● CreateProxyBuffer
计算缩略图实际大小和缩放因子,委托PS为我们申请缓冲区,同时也初始化了原始数据(即把inData拷贝到PsPixelMap中),在处理 WM_INITDIALOG 时调用。
● UpdateProxy
当用户在对话框上修改了某个参数时(WM_COMMAND)被调用,用于更新缩略图显示数据,并刷新缩略图显示。会引起对 PaintProxy 函数的间接调用。
● PaintProxy
绘制缩略图,通过 displayPixels 回调函数完成,在处理 WM_PAINT 消息时调用。
● DeleteProxyBuffer
释放我们申请的缓冲区,在对话框退出前(WM_DESTROY)调用。
现在总结一下上面四个函数的调用时机,使我们对这四个函数的分工具有一个明确的认识,如下表:
窗口消息 | 事件 | 被调用的函数 | 说明 |
WM_INITDIALOG | 创建对话框 | CreateProxyBuffer | 申请缩略图缓冲区并初始化 |
WM_COMMAND | 修改参数值 | UpdateProxy | 更新缩略图,将间接调用PainProxy |
WM_PAINT | 窗口绘制 | PaintProxy | 绘制缩略图 |
WM_DESTROY | 退出对话框 | DeleteProxyBuffer | 释放缩略图缓冲区 |
【注意】把 inData 拷贝到 PSPixelMap, 是一个难度很大,并且特别需要注意的地方。两块数据的通道数据的分布不同,因此像素定位方式也完全不同。并且涉及到缓冲区大小的计算和申请。 复制缓冲区时是使用指针进行访问的,而这非常容易因为引发错误(将导致PS进程崩溃)。
在CreateProxyBuffer中,我们的主要任务是分配缓冲区,然后把源图数据(inData)相应的拷贝到我们的缓冲区(绘制时设置给PSPixelMap结构)。由于这是一个有难度的地方,因此我特别把这个函数代码放在此处展示,代码如下:
//定义描述缩略图数据的结构(在CommonDefine.h中定义) typedef struct _PROXYDATA { int left;//缩略图左上角客户区坐标 int top; int width;//缩略图实际尺寸(像素) int height; int rowbytes; //扫描行宽度(bytes) int planebytes; //通道间的距离(bytes) float factor; //原图和缩略图之间的缩放因子 Ptr data0; //缩放后的原始数据块(即inData的一份拷贝),通过设置inputRate。 Ptr data1; //缩放后的显示数据块(用于即时性更新缩略图) BufferID bufferId0; //data0的bufferId BufferID bufferId1; //data1的bufferId } PROXYDATA; //用于缩略图缓冲区数据的参数 PROXYDATA m_ProxyData; //申请缩略图内存,并申请缩略图数据 void CreateProxyBuffer() { int filterWidth = gFilterRecord->filterRect.right - gFilterRecord->filterRect.left; int filterHeight = gFilterRecord->filterRect.bottom - gFilterRecord->filterRect.top; int bannerWidth = m_RectBanner.right - m_RectBanner.left; int bannerHeight = m_RectBanner.bottom - m_RectBanner.top; float f1 = (float)filterWidth / bannerWidth; float f2 = (float)filterHeight / bannerWidth; m_ProxyData.factor = max(f1, f2); //如果原图比缩略图小 if(m_ProxyData.factor < 1.0f) { m_ProxyData.factor = 1.0f; m_ProxyData.width = filterWidth; m_ProxyData.height = filterHeight; } else { //原图比缩略图大,则计算缩略图的实际尺寸 //把factor去除小数部分!因为我们不知道怎么把小数部分转换到Fixed的LOWORD。 m_ProxyData.factor = (int)(m_ProxyData.factor + 1.0f); m_ProxyData.width = (int)(filterWidth / m_ProxyData.factor); m_ProxyData.height = (int)(filterHeight / m_ProxyData.factor); } //设置缩略图左上角坐标(居中显示) m_ProxyData.left = m_RectBanner.left + (bannerWidth - m_ProxyData.width)/2; m_ProxyData.top = m_RectBanner.top + (bannerHeight - m_ProxyData.height)/2; //想PS请求原始数据,用于填充data0 gFilterRecord->inRect.left = (int)(gFilterRecord->filterRect.left / m_ProxyData.factor); gFilterRecord->inRect.top = (int)(gFilterRecord->filterRect.top / m_ProxyData.factor); gFilterRecord->inRect.right = (int)(gFilterRecord->filterRect.right / m_ProxyData.factor); gFilterRecord->inRect.bottom = (int)(gFilterRecord->filterRect.bottom / m_ProxyData.factor); //通知 P S我们希望的补充数据(未知区域的填充数据) gFilterRecord->inputPadding = 255; //plugInWantsEdgeReplication; //通知 PS 输入采样率 //PS中,Fixed数字是用DWORD表示小数,HIWORDF表示整数部分,LOWORD表示小数部分。即 "ffff.ffff" WORD hiword = (WORD)(m_ProxyData.factor); gFilterRecord->inputRate = (hiword << 16); //现在我们请求第一个通道的数据,以从PS那里获取一些必须的信息 gFilterRecord->inLoPlane = 0; gFilterRecord->inHiPlane = 0; //请求PS为我们更新InData gFilterRecord->advanceState(); //现在我们委托PS申请缓存空间,为了简单,我们假设内存充裕,不会失败 int inHeight = gFilterRecord->inRect.bottom - gFilterRecord->inRect.top; //扫描行宽度 * inRect高度 * 通道数 int bufferSize = gFilterRecord->inRowBytes * inHeight * gFilterRecord->planes; //获取 buffer 回调函数集指针 BufferProcs *bufferProcs = gFilterRecord->bufferProcs; //请PS为我们申请内存 bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId0); bufferProcs->allocateProc(bufferSize, &m_ProxyData.bufferId1); //请PS为我们锁定内存(禁止内存整理) //[ 1 ]函数返回被锁定的内存起始地址。 //[ 2 ]第二个参数是BOOL moveHigth,对windows平台将被忽略。 m_ProxyData.data0 = bufferProcs->lockProc(m_ProxyData.bufferId0, TRUE); m_ProxyData.data1 = bufferProcs->lockProc(m_ProxyData.bufferId1, TRUE); //注意提供给displayPixels函数的数据不是interleave分布,而是一个通道0,通道1,通道2(集中分布)! //也就是 R R R R | G G G G | B B B B | //现在我们把得到的通道interleave分布的数据转换为通道集中分布 uint8* p0=(uint8*)m_ProxyData.data0; //我们复制第一个通道的数据到data0起始处 m_ProxyData.planebytes = gFilterRecord->inRowBytes * inHeight; memcpy(p0,(void*)gFilterRecord->inData, m_ProxyData.planebytes); //复制其他通道 for(int i=1; i<gFilterRecord->planes; i++) { gFilterRecord->inLoPlane = i; gFilterRecord->inHiPlane = i; //请求PS为我们更新InData gFilterRecord->advanceState(); memcpy(p0 + i * m_ProxyData.planebytes,(void*)gFilterRecord->inData, m_ProxyData.planebytes); } //设置扫描行宽度 m_ProxyData.rowbytes = gFilterRecord->inRowBytes; }
在上面的函数(CreateProxyBuffer)中,我们首先按照下面的方法计算出缩略图的缩放因子:
factor = ceiling (max(原图宽度 / 缩略图宽度, 原图高度 / 缩略图高度));
然后我们计算了缩略图的起始点坐标(m_ProxyData.left, m_ProxyData.top)和采用上述缩放因子后的缩略图实际尺寸(m_ProxyData.width, m_ProxyData.height)。请注意,我们把 factor 向上取整(ceiling),这会使缩略图的实际尺寸是小于等于其 BANNER 尺寸的。通过设置左上角坐标,我们使缩略图的位置在 BANNER 矩形中居中。
然后我们委托 PS 为我们分配两块同样大小的缓冲区 data0 和 data1(一个原图数据拷贝,一个是用于即时显示)并锁定它们。我们使用了PS提供的 advanceState 回调去请求原图数据,我在此前的文章中已经介绍过这个最重要的回调函数之一,它的作用是请求 Photoshop 立即更新滤镜参数(FilterRecord)结构中的相关数据,包括inData,outData等等。请注意在上面的代码中,我们是逐个通道进行复制的,即我们每次请求PS为我们发送一个通道的数据,然后我们把这批数据一次性的完全拷贝到缓冲区(使用memcpy),这样就完成了通道数据的“集中分布”。其中每个通道字节数(planeBytes)计算方法如下:
每个通道字节数(planeBytes) = 单一通道的扫描行宽度(inRowBytes) * 缩略图的图像高度(inRect高度);
我们把缩略图数据的信息并保存在m_ProxyData参数中。在 PaintProxy 中,我们只需要把这些信息再设置并提交给 displayProxy 回调函数即可。显示缩略图(PaintProxy,UpdateProxy)的主要逻辑和代码原理,限于篇幅这里不详细讲述,可参考附件中的源代码。最后我们可以看下滤镜的对话框运行效果如下:
当在上面的滤镜对话框中使用鼠标拖动或者键盘改变文本框数值时,左侧缩略图将会实时更新以反应当前的参数效果。在参数设置对话框中,我模拟了一个Photoshop中常见的UI特性,当你把鼠标悬停在数值文本框的左侧标签上时,光标变为一个拖动箭头的形状,这时按下鼠标,左右拖动,可以看到相应文本框的数据发生变化(这和操作滑杆控件非常类似)。在上面这个对话框中,你能够看到我如何模拟了PS的这种UI效果(在Photoshop看似朴素的外表下,隐藏着非常多让人惊叹的 UI 效果,而这只是它们中的其中一个,向强大的Photoshop致敬!)。
(3)增加一个我们自己定义的“关于对话框”。
在此前为了简单起见,在“关于”中我仅仅弹出了一个MessageBox。我们可以自定义一个关于对话框,同样这里我吸取了 PS 的关于对话框的建议和风格,即没有标题栏,没有任何按钮,对话框初始位置在其父窗口的中等偏上(上1/3)处。用户按Escape,回车键 或用鼠标点击任何位置即退出对话框。我的滤镜的关于对话框如下(在PS中点击菜单:帮助 -> 关于增效工具 -> FillRed Filter... ):
这是一个普通的对话框,但我主要想介绍是当鼠标移动到我的博客的网址上时,光标变成(IDC_HAND)手形,点击即可使用默认浏览器打开网址。它是用过使用PS的回调函数集中的相应函数来完成的。因此这里我将示范 PS callback suites 的一种标准用法:
char url[256]; //函数集指针 PSGetFileListSuite4 *suite; //获取GetFileList 回调函数集(callback suite)的指针 SPErr err = sSPBasic->AcquireSuite( kPSGetFileListSuite, //suite name kPSGetFileListSuiteVersion4, //suite version (const void**)&suite //suite pointer ); if(err) return TRUE; //获取网址 GetDlgItemText(hDlg, IDC_STATIC_MYBLOG, url, sizeof(url)); //用默认浏览器打开网址 suite->BrowseUrl(url); //释放suite sSPBasic->ReleaseSuite(kPSGetFileListSuite, kPSGetFileListSuiteVersion4);
在上面的代码中我们可以看到, PS CALLBACK Suites的用法 和 COM 组件的 QueryInterface 的使用方法是完全类似的:先声明想获取的回调函数集(callback Suite,一个含有一组PS内部的函数指针的struct)的一个指针,然后把该指针的地址传递给 BasicSuite 的 AcquireSuite 函数,成功以后我们就可以通过该指针去调用PS提供给插件的相应回调函数。
(4)总结。
到目前为止,我们已经完整的讲解了有关制作一个Photoshop滤镜的主要技术环节,从(1)创建项目,到(2)添加UI资源,再到(3)使Photoshop Scripting System知道我们的滤镜,并支持“动作”面板的对话框选项,以及本篇重点讲述的添加在对话框上的缩略图。涵盖了制作 Photoshop 滤镜插件的流程和重要知识,而Photoshop插件开发的技术细节以及插件种类仍然是非常繁复众多的,有待进一步的研究。
我们开发Photoshop插件的一个主要原因是,PS是图形处理领域的重要软件,为第三方开放了插件扩展的接口。作为第三方开发者我们可以根据自己的需求,遵照PS的约定去以插件形式扩展PS。在PS的重要用户基础上,扩展和研究将会更有实际意义。
制作滤镜的基本技术已经介绍完成,剩下的其他工作将主要是对图像处理算法的寻求和发掘。
本例是以使用基于Platform SDK的Windows程序开发为基础的,但重点在于讲解PS插件开发,因此没有详细讲解Windows程序开发中的一些技术细节。
更多怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图 相关文章请关注PHP中文网!

Alat AI Hot

Undresser.AI Undress
Apl berkuasa AI untuk mencipta foto bogel yang realistik

AI Clothes Remover
Alat AI dalam talian untuk mengeluarkan pakaian daripada foto.

Undress AI Tool
Gambar buka pakaian secara percuma

Clothoff.io
Penyingkiran pakaian AI

AI Hentai Generator
Menjana ai hentai secara percuma.

Artikel Panas

Alat panas

Notepad++7.3.1
Editor kod yang mudah digunakan dan percuma

SublimeText3 versi Cina
Versi Cina, sangat mudah digunakan

Hantar Studio 13.0.1
Persekitaran pembangunan bersepadu PHP yang berkuasa

Dreamweaver CS6
Alat pembangunan web visual

SublimeText3 versi Mac
Perisian penyuntingan kod peringkat Tuhan (SublimeText3)

Topik panas

Butiran artikel menggunakan Photoshop untuk grafik media sosial, meliputi persediaan, alat reka bentuk, dan teknik pengoptimuman. Ia menekankan kecekapan dan kualiti dalam penciptaan grafik.

Artikel membincangkan penyediaan imej untuk kegunaan web di Photoshop, memberi tumpuan kepada mengoptimumkan saiz fail, resolusi, dan ruang warna. Isu utama adalah mengimbangi kualiti imej dengan masa pemuatan cepat.

Artikel membincangkan menggunakan alat pengisian kandungan dan memindahkan kandungan Photoshop dengan berkesan, menawarkan petua untuk memilih kawasan sumber, mengelakkan kesilapan, dan menyesuaikan tetapan untuk hasil yang optimum.

Artikel membincangkan pemantauan menentukur untuk warna yang tepat dalam Photoshop, alat untuk penentukuran, kesan penentukuran yang tidak wajar, dan kekerapan semula. Isu utama adalah memastikan ketepatan warna.

Artikel ini menerangkan cara menggunakan Photoshop untuk penyuntingan video, memperincikan langkah -langkah untuk mengimport, mengedit, dan mengeksport video, dan menonjolkan ciri -ciri utama seperti panel garis masa, lapisan video, dan kesan.

Artikel membincangkan membuat dan mengoptimumkan GIF animasi di Photoshop, termasuk menambah bingkai kepada GIF yang sedia ada. Fokus utama adalah untuk mengimbangi kualiti dan saiz fail.

Artikel membincangkan mengoptimumkan imej untuk web menggunakan Photoshop, memberi tumpuan kepada saiz fail dan resolusi. Isu utama adalah mengimbangi kualiti dan masa beban.

Artikel ini membimbing untuk menyediakan imej untuk dicetak di Photoshop, memberi tumpuan kepada resolusi, profil warna, dan ketajaman. Ia berpendapat bahawa profil 300 ppi dan CMYK adalah penting untuk cetakan yang berkualiti.
