>  기사  >  웹 프론트엔드  >  Photoshop 필터 작성 방법 - 대화 상자에 축소판 추가

Photoshop 필터 작성 방법 - 대화 상자에 축소판 추가

高洛峰
高洛峰원래의
2017-02-23 09:16:561919검색

이전 기사에서는 필터에 용어 리소스를 추가하여 PS의 스크립팅 시스템에서 필터를 인식하고 설명할 수 있도록 하여 PS의 "Action" 패널을 친숙하게 지원하는 방법을 설명했습니다. 이 기사에서는 매개변수 대화 상자에 실시간 미리보기를 위한 작은 썸네일을 추가하는 등 이전 DEMO를 더욱 개선해 보겠습니다. 대화 상자의 도입은 주로 사용자에게 필터에서 사용되는 이미지 처리 알고리즘을 설정하거나 조정할 수 있는 기회와 인터페이스를 제공하는 것입니다. 일반적으로 UI 친화성을 측정하기 위해 매개변수가 결과에 미치는 영향을 사용자에게 직관적으로 피드백하고 매개변수 조정을 안내할 수 있도록 대화 상자에 미리보기를 제공해야 합니다. 사용자가 효과를 확인한 다음 매개변수를 조정하기 위해 필터 명령을 반복적으로 실행하도록 요구하는 대신.

예전에는 '썸네일 추가' 기능이 크게 어렵지 않을 거라 생각했는데 막상 막상 해보니 이전에 썼던 글에서 설명한 것보다 훨씬 어렵다는 걸 금방 깨달았습니다. PS에서 제공하는 콜백 기능을 사용하여 썸네일을 표시하려고 할 때 크기 조정, 데이터 배포, 스캔 라인 및 기타 세부 사항에 영향을 미치는 매개 변수 설정을 포함하여 PS에서 제공하는 인터페이스 세부 사항을 완전히 명확하게 설명해야 하기 때문입니다. 실수가 있을 수 없습니다. 그렇지 않으면 비정상적인 표시가 나타나거나 실수로 메모리가 범위를 벗어날 수도 있습니다.

썸네일을 소개하기 전에 먼저 필터 알고리즘에 몇 가지 흥미로운 개선 사항을 적용하고 좀 더 실용적으로 만들기 위해 약간의 개선을 했습니다.

                                                                                                                                                 이전에 픽셀을 설정했을 때 입력과 출력 위치가 완전히 일치했습니다. 즉, Dest(i, j) = f(Src(i, j)).

이제 위 공식을 약간 변경하고 소스 픽셀을 무작위로 디더링하는 것, 즉 Dest(i, j) = f(Src(i+dx, j+dy))를 고려해 보겠습니다. 지터 거리를 거리(픽셀) 매개변수로 설정하여 소스 픽셀을 가져올 때 현재 픽셀을 중심으로 정사각형 내의 한 점을 소스 픽셀로 무작위로 선택하고 주변까지의 거리. 이로 인해 결과 그래프에 "용해" 또는 "침식" 효과가 나타납니다. 아래 그림과 같이

                                                                                        ~                                    이러한 방식으로 (i, j) 위치에 픽셀을 설정할 때 우리가 취하는 소스 픽셀 좌표는 다음과 같습니다.

x = i + rand()%(2*distance+1) - distance;

y = j + rand()%(2*distance+1) - distance;怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图

실제 처리에서는 위의 결과 x, y가 유효한 데이터 경계를 초과할 수도 있다는 점도 고려해야 합니다. , 따라서 x와 y를 filterRect 내부로 제한해야 합니다.

타일 처리 방식을 사용하기 때문에 조정에는 약간의 기술이 필요합니다. PS에서 요청하는 inRect를 변경할 것입니다. 즉, 패치할 때마다 outRect는 이전과 동일하게 유지되고 inRect를 거리만큼 픽셀을 모든 방향으로 확장하려고 시도합니다. 이를 통해 모든 면에서 유효한 데이터를 얻을 수 있습니다. (패치가 filterRect 내부에 있을 때) 패치가 filterRect의 가장자리에 있지 않은 경우.

inRect가 outRect보다 "더 크므로" 두 Rect의 픽셀은 더 이상 동일한 크기가 아니고 완전히 겹치지만 특정

이동

! 코드에서는 두 직사각형 사이의 오프셋 관계를 ​​고려해야 합니다. 여기에서 제 소스코드를 참고하실 수 있으므로 처리 방법에 대해서는 자세히 설명하지 않겠습니다.

                                                                                    ~

매개변수를 추가한 다음 썸네일을 표시하기 위해 대화 상자 왼쪽에 작은 영역을 남겨 두었습니다. 편의상 썸네일 위치에 숨겨진 정적 컨트롤(프록시 배너)을 배치하는 것이 목적입니다. 런타임 시 "썸네일"의 경계(클라이언트 영역 좌표)를 얻을 수 있습니다. 수정된 대화 상자는 다음과 같습니다.

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

썸네일을 표시할 때 gFilterRecord의 displayPixels 콜백 함수를 사용합니다. 이 함수의 프로토타입은 다음과 같습니다. 다음(함수 포인터의 typedef): typedef MACPASCAL OSErr(*DisplayPixelsProc) ( const PSPixelMap *source, const VRect *srcRect , int32 dstRow,

int32 dstCol,

void * platformContext);

첫 번째 매개변수는 BitBlt의 소스 이미지와 동일한 픽셀 데이터 영역을 설명하는 PSPixelMap 구조에 대한 포인터입니다. srcRect 매개변수는 Rectangle을 설명합니다. 여기서 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;

    

◆ 버전: 구조 버전.

PS CS 버전의 경우 1로 설정해야 합니다. PS의 향후 버전에서는 이를 확장하여 이 버전 번호를 높일 수 있습니다.

     ◆ 경계: 픽셀 데이터가 차지하는 직사각형. ◆ imageMode : 데이터 영역의 이미지 모드입니다.

Grayscale, RGB, CMYK, Lab 모드를 지원합니다.      ◆ rowBytes: 인접한 행 사이의 거리(바이트)입니다.

스캔 라인 너비(중요)와 동일하며 바이트 단위로 올바르게 설정되어야 합니다.

     ◆ colBytes: 픽셀 데이터의 인접한 열 사이의 거리(바이트)입니다.

데이터가 "집중"되어 있으므로 이 속성의 값은 주로 픽셀의 색 농도에 따라 달라집니다. 채널당 픽셀당 1바이트를 사용하는 일반 24비트 깊이 이미지의 경우 이 거리는 1바이트입니다.

    ◆ planeBytes: 인접한 채널 간의 거리(바이트)입니다.

데이터가 "중앙 분산"되므로 이 속성은 일반적으로 rowBytes * 이미지 높이입니다.

    ◆ baseAddr: 데이터 영역의 시작 주소.

이전 기사에서 PS가 제공하는 inData와 outData가 채널 교차 분산(인터리브)이라고 언급한 바 있습니다. 여기서 PSPixelMap의 데이터 배포 요구 사항은 데이터가 채널에 중앙 집중식으로 배포되어야 합니다.

예를 들어 RGB 이미지의 경우 inData/outData의 데이터 분포는 다음과 같습니다.

R | G | B | B | ..... (인터리브)

​​​​​​​​​​​​​​​PSPixelMap의 데이터는 다음과 같이 배포되어야 합니다. G | | ... B | B | B .... (집중 분포)

즉, inData와 outData의 경우 모든 채널 데이터가 교차 분산되어 있으며 동일한 채널(평면) )가 데이터에 있습니다. 영역이 비약적으로 존재합니다.

PSPixelMap의 경우 동일한 채널(평면)의 데이터가 함께 집중되어 첫 번째 채널 데이터가 모두 나열되고 두 번째 채널 데이터가 모두 나열되는 식입니다.

동시에, 일반적인 비트맵 픽셀 위치 지정과 마찬가지로 픽셀 위치 지정 및 예측 버퍼 크기를 수행할 때 필터 매개변수의 inRowBytes 속성(스캔선 너비와 동일)을 사용하여 " 행". "행 너비"를 직접 가정하거나 계산할 수 없습니다.

【참고】지정된 위치에서 픽셀을 올바르게 찾을 수 있도록 데이터 분포의 세부 사항을 알아야 합니다.

                                                                                                                                                             ​​따라서 썸네일을 표시할 때 직면해야 하는 문제 중 하나는 스케일링 문제. 대부분의 경우 썸네일은 정성적 표시일 뿐이므로 데이터 정확도에 대한 요구 사항이 줄어들 수 있으며, 성능 요소를 고려하여 썸네일의 크기는 일반적으로 원본 이미지(filterRect)가 원본 이미지보다 더 작게 설정될 수 있습니다. 썸네일(bannerRect)이 크기 때문에 이미지를 축소하여 썸네일에 표시하려고 합니다. 원본 이미지가 썸네일보다 작은 경우 실제 원본 이미지 크기만 사용합니다(예: 배율 = 1). 이제 원본 이미지를 축소판 크기로 줄이는 방법을 이해해야 합니다. GDI에서는

StretchBlt

함수를 사용하여 크기 조정을 완료할 수 있다는 것을 알고 있습니다. 여기에서는 PS가 전달한 inData에서 소스 이미지 데이터를 얻습니다. 축소된 원본 이미지를 얻으려면 FilterRecord 매개변수에 inputRate(샘플링 속도) 속성을 설정하여 축소를 완료합니다.

                                                                                                       =============================== PS에서는 long 유형으로 정의됩니다.

typedef long 고정;

그러나 PS에서 고정의 실제 의미는 (고정 소수점) 십진수입니다. 소위 고정은 부동 소수점(부동 소수점 소수점)을 기준으로 합니다. float의 소수점 위치는 고정되어 있지 않으므로 부동소수점이라고 합니다. 고정(Fixed)은 10진수를 정수부와 소수부로 분해하여 각각 상위 16비트와 하위 16비트에 저장합니다. 즉, 그 의미는 "16.16"입니다. 예를 들어 10진수 3.00f가 있다고 가정하면 해당 고정 숫자는 0x00030000입니다.

부동 소수점 숫자를 고정 유형으로 변환하는 방법은 다음과 같습니다.

                                                               🎜 >Fixed _fixed = ( Fixed ) ( _factor * 0x10000

)

 에서 변환하는 방법 부동 소수점 유형에 대한 고정 유형은 (비고: 1 / 0x10000 = 0.0000152587890625 ):

 고정 _fixed;

더블

_factor = _fixed *

0.0000152587890625 ;

============ ===============

inputRate는 샘플링 속도를 나타내며, 이는 논리적 십진수이며, 얻은 inData가 다음과 같다는 것을 인식하도록 값을 설정합니다. 원본 이미지의 크기 조정 결과. 기본적으로 PS에서 설정한 inputRate는 1이며 스케일링이 없습니다. 썸네일 데이터를 얻을 때 스케일링 인자(인수)를 계산한 다음 inputRate를 인자로 설정해야 합니다(데이터 유형 변환 방법 참고). 이렇게 설정하면 우리가 얻는 inData의 실제 이미지 좌표는 inRect * inputRate가 됩니다. 즉,

예를 들어 inputRate가 2.0에서는 두 픽셀마다 하나의 포인트가 샘플링된 다음 inRect가 됩니다. inData의 관계는 아래 그림과 같습니다. 원하는 inRect(분홍색 직사각형 영역)의 좌표를 inputRate로 나누어야 합니다(inRect에서 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

대화상자 만들기

프록시 버퍼 만들기

썸네일 버퍼 적용 및 초기화

WM_COMMAND

매개변수 값 수정

UpdateProxy

썸네일 업데이트, PainProxy

WM_PAINT

창 그리기

PaintProxy

페인트 썸네일

WM_DESTROY

종료 대화 상자

프록시 버퍼 삭제

썸네일 버퍼 출시

 

            【注意】把 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, Enter 키를 누르거나 마우스로 아무 곳이나 클릭하여 대화 상자를 종료합니다. 내 필터의 정보 대화 상자는 다음과 같습니다(PS에서 메뉴 클릭: 도움말-> 플러그인 정보-> FillRed 필터... ): 🎜>

이것은 일반적인 대화 상자이지만 내가 주로 소개하고 싶은 점은 내 블로그의 URL에 마우스를 올리면 커서가 (IDC_HAND) 손 모양으로 바뀌고 클릭하면 기본 브라우저를 이용해 URL을 열 수 있다는 점입니다. 이는 PS의 콜백 함수 세트에 있는 해당 함수를 사용하여 수행됩니다. 여기서는 PS 콜백 제품군의 표준 사용법을 보여드리겠습니다.

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가 그래픽 처리 분야에서 중요한 소프트웨어이며 타사를 위한 플러그인 확장 인터페이스를 개방한다는 것입니다. 제3자 개발자로서 우리는 필요에 따라 그리고 PS의 규칙에 따라 플러그인 형태로 PS를 확장할 수 있습니다. PS의 중요한 사용자 기반을 기반으로 확장과 연구가 더욱 실용적이 될 것입니다.

필터를 만드는 기본 기술은 소개되었으며, 남은 작업은 주로 이미지 처리 알고리즘의 검색 및 탐색에 중점을 둘 예정입니다.

본 예시는 Platform SDK를 기반으로 한 Windows 프로그램 개발을 기반으로 하고 있으나, PS Plug-in 개발에 중점을 두고 있기 때문에 Windows 프로그램 개발에 관한 일부 기술적 내용은 자세히 설명하지 않습니다.

Photoshop 필터 작성 방법(대화 상자에 썸네일 추가)에 대한 자세한 내용은 PHP 중국어 웹사이트에서 관련 기사를 참고하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.