• 技术文章 >web前端 >PS教程

    怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

    高洛峰高洛峰2017-02-23 09:16:56原创1092
    在上一篇文章里,我们讲解了为滤镜添加术语资源,从而使我们的滤镜可以被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的正方形内随机选取某个点作为源像素。从而使他们在结果图中具有一种“溶解”或“腐蚀”效果。如下图所示:

    怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

    因此我们在滤镜参数中增加了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),它的主要用处是使我可以在运行时获取到 “缩略图”的边界(客户区坐标)。修改后的对话框如下图所示:

    怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

    (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 的关系如下图所示:

    怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

    在上面这幅图中,展示了在缩放时应该如何设置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滤镜 -- 在对话框上增加缩略图

    当在上面的滤镜对话框中使用鼠标拖动或者键盘改变文本框数值时,左侧缩略图将会实时更新以反应当前的参数效果。在参数设置对话框中,我模拟了一个Photoshop中常见的UI特性,当你把鼠标悬停在数值文本框的左侧标签上时,光标变为一个拖动箭头的形状,这时按下鼠标,左右拖动,可以看到相应文本框的数据发生变化(这和操作滑杆控件非常类似)。在上面这个对话框中,你能够看到我如何模拟了PS的这种UI效果(在Photoshop看似朴素的外表下,隐藏着非常多让人惊叹的 UI 效果,而这只是它们中的其中一个,向强大的Photoshop致敬!)。

    (3)增加一个我们自己定义的“关于对话框”。

    在此前为了简单起见,在“关于”中我仅仅弹出了一个MessageBox。我们可以自定义一个关于对话框,同样这里我吸取了 PS 的关于对话框的建议和风格,即没有标题栏,没有任何按钮,对话框初始位置在其父窗口的中等偏上(上1/3)处。用户按Escape,回车键 或用鼠标点击任何位置即退出对话框。我的滤镜的关于对话框如下(在PS中点击菜单:帮助 -> 关于增效工具 -> FillRed Filter... ):

    怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

    这是一个普通的对话框,但我主要想介绍是当鼠标移动到我的博客的网址上时,光标变成(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);


    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中文网!

    声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。
    专题推荐:Photoshop滤镜
    上一篇:Photoshop中裁切圆形图片 下一篇: Photoshop 入门十战
    20期PHP线上班

    相关文章推荐

    精选22门好课,价值3725元,开通VIP免费学习!• Photoshop设计制作出漂亮的彩色五角星光束• Photoshop打造一只非常可爱的新年吉祥兔• Photoshop滤镜制作漂亮的抽象效果• 用PS制作唯美的水下人像特效• Photoshop设计制作简洁的双11电商促销横幅
    1/1

    PHP中文网