Heim  >  Artikel  >  Web-Frontend  >  So schreiben Sie einen Photoshop-Filter: Fügen Sie dem Dialogfeld eine Miniaturansicht hinzu

So schreiben Sie einen Photoshop-Filter: Fügen Sie dem Dialogfeld eine Miniaturansicht hinzu

高洛峰
高洛峰Original
2017-02-23 09:16:561992Durchsuche

Im vorherigen Artikel haben wir erklärt, wie man Terminologieressourcen zu Filtern hinzufügt, damit unsere Filter vom Skriptsystem von PS wahrgenommen und beschrieben werden können, was eine freundliche Unterstützung für das „Aktion“-Panel von PS darstellt. In diesem Artikel werden wir die vorherige DEMO weiter verfeinern, beispielsweise durch das Hinzufügen einer kleinen Miniaturansicht für die Echtzeitvorschau im Parameterdialogfeld. Die Einführung des Dialogfelds dient hauptsächlich dazu, Benutzern die Möglichkeit und Schnittstelle zum Festlegen oder Anpassen des vom Filter verwendeten Bildverarbeitungsalgorithmus zu bieten. Als Maß für die Benutzerfreundlichkeit der Benutzeroberfläche sollte in der Regel eine Vorschau im Dialogfeld angezeigt werden, damit der Benutzer die Auswirkungen der Parameter auf die Ergebnisse intuitiv erfahren und ihn bei der Anpassung der Parameter unterstützen kann. Anstatt von Benutzern zu verlangen, wiederholt Filterbefehle auszuführen, um den Effekt zu sehen und dann Parameter anzupassen.

Früher dachte ich, dass die Funktion „Miniaturansicht hinzufügen“ nicht sehr schwierig sein sollte, aber als ich es versuchte, stellte ich schnell fest, dass es weitaus schwieriger war, als in den Artikeln beschrieben, die ich zuvor geschrieben hatte. Denn wenn wir versuchen, die von PS bereitgestellte Rückruffunktion zum Anzeigen von Miniaturansichten zu verwenden, müssen wir uns über die Details der von PS bereitgestellten Schnittstelle im Klaren sein, einschließlich Parametereinstellungen, die sich auf Skalierung, Datenverteilung, Scanlinien und andere Details auswirken. Es dürfen keine Fehler vorliegen, da wir sonst möglicherweise eine abnormale Anzeige sehen oder sogar versehentlich dazu führen, dass der Speicher den zulässigen Bereich überschreitet.

Bevor ich Miniaturansichten einführte, habe ich zunächst einige interessante Verbesserungen am Filteralgorithmus vorgenommen und eine kleine Verbesserung vorgenommen, um ihn praktischer zu machen.

                                                                                                                                                                  . Als wir die Pixel zuvor festgelegt haben, waren die Eingabe- und Ausgabepositionen vollständig konsistent, d. h. Dest(i, j) = f (Src(i, j)).

Jetzt überlegen wir, eine kleine Änderung an der obigen Formel vorzunehmen und die Quellpixel zufällig zu dithern, d. h. Dest(i, j) = f (Src(i+dx, j+dy)). Wir stellen den Jitter-Abstand auf den Abstandsparameter (Pixel) ein, sodass wir bei der Aufnahme des Quellpixels zufällig einen Punkt als Quellpixel innerhalb des Quadrats auswählen, wobei das aktuelle Pixel die Mitte bildet und das erweitert wird Abstand zur Peripherie. Dies führt zu einem „auflösenden“ oder „erodierenden“ Effekt im resultierenden Diagramm. Wie im Bild unten gezeigt:

                                                                                                                                                                                                                                                            Wenn wir das Pixel auf diese Weise an der Position (i, j) festlegen, sind die von uns verwendeten Quellpixelkoordinaten:

    

y = j + rand()%(2*distance+1) - Abstand;

怎样编写一个Photoshop滤镜 -- 在对话框上增加缩略图 Bei der tatsächlichen Verarbeitung müssen wir auch das obige Ergebnis berücksichtigen. x, y kann die gültige Datengrenze überschreiten, daher ist es notwendig, x, y auf innerhalb von filterRect zu beschränken.

Da wir eine Fliesenverarbeitungsmethode verwenden, erfordern unsere Anpassungen ein wenig Geschick. Wir werden das von PS angeforderte inRect ändern, das heißt, bei jedem Patch bleibt outRect dasselbe wie zuvor und wir werden versuchen, inRect um Abstandspixel zu allen Seiten zu erweitern. Dadurch wird sichergestellt, dass wir auf allen Seiten gültige Daten erhalten können Zeit, die wir patchen (wenn sich der Patch innerhalb des filterRect befindet), es sei denn, der Patch befindet sich am Rand des filterRect.

Da inRect „größer“ als outRect ist, ist zu beachten, dass die Pixel der beiden Rects nicht mehr gleich groß sind und sich vollständig überlappen, sondern eine gewisse

Verschiebung

! Im Code müssen wir die Versatzbeziehung zwischen den beiden Rechtecken berücksichtigen. Sie können hier auf meinen Quellcode verweisen, daher werde ich die Verarbeitungsmethode nicht im Detail erläutern.

.

                                                                                                                                                                         

Wir haben einen Parameter hinzugefügt und dann einen kleinen Bereich auf der linken Seite des Dialogfelds gelassen, um Miniaturansichten anzuzeigen. Der Einfachheit halber habe ich ein verstecktes STATIC-Steuerelement (Proxy-Banner) an der Miniaturansichtsposition platziert dass ich zur Laufzeit die Grenzen (Clientbereichskoordinaten) des „Miniaturbilds“ ermitteln kann. Das geänderte Dialogfeld sieht wie folgt aus:

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

Bei der Anzeige von Miniaturansichten verwenden wir die DisplayPixels-Rückruffunktion in gFilterRecord. Der Prototyp dieser Funktion ist wie folgt folgt (typedef des Funktionszeigers): typedef MACPASCAL OSErr (*DisplayPixelsProc) ( const PSPixelMap *source, const VRect *srcRect , int32 dstRow,

int32 dstCol,

void * platformContext);

Der erste Parameter ist ein Zeiger auf eine PSPixelMap-Struktur zur Beschreibung eines Pixeldatenbereichs, der dem Quellbild in BitBlt entspricht. Der Parameter srcRect beschreibt das Quellbild. Hier entspricht dstRow dem destY und dstCol dem destX-Parameter, d. h. (dstCol, dstRow) ist die Startkoordinate im Zielbereich.

Der letzte Parameter platformContext ist HDC im Windows-System.

Wir müssen eine kurze Einführung in den ersten Parameter geben, nämlich die Definition von PSPixelMap und die PS-Verteilungsanforderungen für diesen Datenbereich. Die Definition von PSPixelMap lautet wie folgt:

   

◆ Version: Strukturversion.

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;

Für die PS CS-Version müssen wir den Wert auf 1 setzen. Zukünftige Versionen von PS könnten es erweitern und diese Versionsnummer erhöhen.

     ◆ Grenzen: das von den Pixeldaten eingenommene Rechteck. ◆ imageMode: Bildmodus des Datenbereichs.

Es unterstützt die folgenden Modi: Graustufen, RGB, CMYK, Lab.      ◆ rowBytes: Der Abstand in Bytes zwischen benachbarten Zeilen.

Entspricht der Scanlinienbreite (wichtig), in Bytes, muss korrekt eingestellt sein.

     ◆ colBytes: Der Abstand in Bytes benachbarter Spalten von Pixeldaten.

Da die Daten „konzentriert“ sind, hängt der Wert dieses Attributs hauptsächlich von der Farbtiefe des Pixels ab. Für ein normales 24-Bit-Tiefenbild, das ein Byte pro Pixel und Kanal verwendet, beträgt dieser Abstand 1 Byte.

    ◆ planeBytes: Der Abstand in Bytes zwischen benachbarten Kanälen.

Da die Daten „zentral verteilt“ sind, ist dieses Attribut normalerweise rowBytes * Bildhöhe.

    ◆ baseAddr: Startadresse des Datenbereichs.

Ich habe in früheren Artikeln erwähnt, dass die von PS bereitgestellten inData und outData kanalübergreifend verteilt sind (Interleave). Die Anforderungen an die Verteilung der Daten in PSPixelMap sind hier unterschiedlich. Es erfordert eine zentrale Verteilung der Daten in Kanälen.

Für RGB-Bilder lautet die Datenverteilung in inData/outData:

R | B |. ..... (Interleave)

​​​​​​​​​​​​​​Für die Daten in PSPixelMap ist eine Verteilung wie folgt erforderlich: G | |. ... B |. B |. B .... (Konzentrierte Verteilung)

Das heißt, für inData und outData sind alle Kanaldaten kreuzverteilt und die Daten desselben Kanals (Ebene). ) ist in den Daten vorhanden. Der Bereich existiert sprunghaft.

Bei PSPixelMap werden die Daten desselben Kanals (Ebene) zuerst alle Daten des ersten Kanals aufgelistet, dann werden alle Daten des zweiten Kanals aufgelistet und so weiter.

Gleichzeitig müssen wir bei der Pixelpositionierung und Vorhersagepuffergröße, genau wie bei der normalen Bitmap-Pixelpositionierung, das inRowBytes-Attribut (entspricht der Scanlinienbreite) in den Filterparametern verwenden, um die Position zwischen „ Zeilen". Sie können die „Zeilenbreite" nicht selbst annehmen oder berechnen.

【Hinweis】Wir müssen die Details der Datenverteilung kennen, damit wir das Pixel an der angegebenen Position korrekt lokalisieren können.

                                                                                                                      Eines der Probleme, mit denen wir bei der Anzeige von Miniaturansichten konfrontiert sind, ist daher das Skalierungsproblem. Da es sich bei den Miniaturansichten nur um qualitative Darstellungen handelt, können die Anforderungen an die Datengenauigkeit reduziert werden, und unter Berücksichtigung von Leistungsfaktoren kann die Größe der Miniaturansichten im Allgemeinen kleiner eingestellt werden, wenn das Originalbild (filterRect) größer ist als das Wenn das Miniaturbild (bannerRect) groß ist, soll das Bild verkleinert und auf dem Miniaturbild angezeigt werden. Wenn das Originalbild kleiner als das Miniaturbild ist, verwenden wir einfach die tatsächliche Originalbildgröße (d. h. Skalierungsfaktor = 1). Jetzt müssen wir verstehen, wie wir das Originalbild auf Miniaturbildgröße verkleinern können. In GDI wissen wir, dass wir die Funktion

StretchBlt

verwenden können, um die Skalierung abzuschließen. Hier erhalten wir die Quellbilddaten aus den von PS an uns übergebenen inData. Wenn wir das reduzierte Originalbild erhalten möchten, schließen wir die Reduzierung ab, indem wir das Attribut „inputRate“ (Abtastrate) im Parameter „FilterRecord“ festlegen.

                                                                                                               ============================== Es ist in PS als Long-Typ definiert:

typedef long Fixed;

Aber die tatsächliche Bedeutung von Fixed in PS ist eine (Festkomma-)Dezimalzahl. Der sogenannte Fixed ist relativ zum Float (Gleitkomma-Dezimalzahl). Die Position des Dezimalpunkts in Float ist nicht festgelegt und wird daher als Gleitkomma bezeichnet. Fixed zerlegt eine Dezimalzahl in einen ganzzahligen Teil und einen Dezimalteil und speichert sie jeweils in einem oberen 16-Bit- und einem niedrigen 16-Bit-Bereich. Das heißt, seine Bedeutung ist „16.16“. Angenommen, es gibt eine Dezimalzahl 3.00f; die entsprechende feste Zahl ist 0x00030000;

                                                          🎜 >Behoben _fixed = ( Behoben. ) ( _factor * 0x10000

) ;

 Die Methode zur Konvertierung Der feste Typ zum Gleitkommatyp ist (Anmerkungen: 1 / 0x10000 = 0,0000152587890625):

 Fixiert _fixed;

double

_factor = _fixed *

0,0000152587890625 ;.

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

inputRate stellt die Abtastrate dar, bei der es sich um eine logische Dezimalzahl handelt. Wir legen ihren Wert fest, um zu erkennen, dass die erhaltenen inData das Skalierungsergebnis des Originalbilds sind. Standardmäßig ist die von PS festgelegte Eingaberate 1 und es erfolgt keine Skalierung. Wenn wir die Miniaturbilddaten erhalten, müssen wir den Skalierungsfaktor (Faktor) berechnen und dann die Eingaberate auf Faktor setzen (beachten Sie die Datentyp-Konvertierungsmethode). Nachdem wir es so eingestellt haben, sind die tatsächlichen Bildkoordinaten von inData, die wir erhalten, inRect * inputRate, also

Wenn beispielsweise inputRate ist 2.0, ein Punkt wird alle zwei Pixel abgetastet, dann inRect. Die Beziehung von inData ist in der folgenden Abbildung dargestellt: Wir müssen die Koordinaten des gewünschten inRect (rosa rechteckigen Bereichs) durch inputRate dividieren (vorausgesetzt, inputRate = 2 im Bild), um das inRect (blaues Rechteck im Bild oben) zu erhalten, das an PS übermittelt werden muss. Dann verwenden wir die Callback-Funktion „advanceProc“, um Daten als Bilddaten auf der rechten Seite in der obigen Abbildung abzurufen. Es ist ersichtlich, dass ihre Größe in beide Richtungen um die Hälfte reduziert wurde.

【Hinweis 1】Wenn die Miniaturansicht verarbeitet und das Dialogfeld geschlossen wird, muss die Eingaberate auf 0x00010000 (1,0) zurückgesetzt werden. Andernfalls wirkt sich dies weiterhin auf inData in der späteren tatsächlichen Verarbeitung aus! Bewirken, dass die Verarbeitung zu unerwarteten Ergebnissen führt.

【Hinweis 2】Filterparameter müssen die Auswirkungen der Bildskalierung berücksichtigen. Parameter im Zusammenhang mit der Skalierung sollten ebenfalls entsprechend auf die Miniaturbildgröße abgebildet werden (z. B. sollte die zufällige Jitter-Distanz in diesem Beispiel proportional reduziert werden). Parameter, die nichts mit der Skalierung zu tun haben (wie in diesem Beispiel der Deckkraftprozentsatz und die Füllfarbe), können ignoriert werden.

 

(2.2.2) int16 inputPadding;

Wenn PS inData bereitstellt, kann es aufgefüllt werden. Sie können den Wert der aufgefüllten Pixel angeben (0–255) oder ihn auf die folgenden Optionen festlegen (sie werden als negative Zahlen definiert, um sie von den vom Benutzer festgelegten Pixelwerten zu unterscheiden):

            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)调用。

 

            现在总结一下上面四个函数的调用时机,使我们对这四个函数的分工具有一个明确的认识,如下表:

            

Fenstermeldung

Ereignis

Aufgerufene Funktion

Beschreibung

WM_INITDIALOG

Dialog erstellen

ProxyBuffer erstellen

Thumbnail-Puffer beantragen und initialisieren

WM_COMMAND

Parameterwert ändern

Proxy aktualisieren

Miniaturansicht aktualisieren, ruft indirekt PainProxy

WM_PAINT

Fensterzeichnung

PaintProxy

Miniaturansicht malen

WM_DESTROY

Dialog beenden

DeleteProxyBuffer

Thumbnail-Puffer freigeben

 

            【注意】把 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)增加一个我们自己定义的“关于对话框”。

Der Einfachheit halber habe ich unter „Info“ nur eine MessageBox angezeigt. Wir können ein Dialogfeld anpassen. Auch hier habe ich die Vorschläge und den Stil von PS für Dialogfelder übernommen, d obere 1/3) Platz. Der Benutzer drückt die Escape-Taste oder die Eingabetaste oder klickt mit der Maus auf eine beliebige Stelle, um das Dialogfeld zu verlassen. Das Dialogfeld „Info“ meines Filters sieht wie folgt aus (klicken Sie auf das Menü in PS: Hilfe-> Über Plug-in-> FillRed-Filter...): 🎜>

Dies ist ein gewöhnliches Dialogfeld, aber was ich Ich möchte vor allem vorstellen, dass sich der Cursor in eine (IDC_HAND) Handform verwandelt, wenn sich die Maus über die URL meines Blogs bewegt, und Sie können klicken, um die URL mit dem Standardbrowser zu öffnen. Dies erfolgt über die entsprechenden Funktionen im Callback-Funktionssatz von PS. Deshalb werde ich hier eine Standardverwendung von PS-Callback-Suites demonstrieren:

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插件开发的技术细节以及插件种类仍然是非常繁复众多的,有待进一步的研究。

Einer der Hauptgründe, warum wir Photoshop-Plugins entwickeln, ist, dass PS eine wichtige Software im Bereich der Grafikverarbeitung ist und die Plug-in-Erweiterungsschnittstelle für Dritte öffnet. Als Drittentwickler können wir PS in Form von Plug-Ins nach unseren eigenen Bedürfnissen und in Übereinstimmung mit den PS-Konventionen erweitern. Basierend auf der wichtigen Benutzerbasis von PS werden Erweiterung und Forschung praktischer sein.

Die grundlegende Technologie zur Herstellung von Filtern wurde eingeführt, und die verbleibende Arbeit wird sich hauptsächlich auf die Suche und Erforschung von Bildverarbeitungsalgorithmen konzentrieren.

Dieses Beispiel basiert auf der Entwicklung von Windows-Programmen auf Basis des Platform SDK, der Schwerpunkt liegt jedoch auf der PS-Plug-in-Entwicklung, sodass einige technische Details der Windows-Programmentwicklung nicht im Detail erläutert werden.

Weitere Informationen zum Schreiben eines Photoshop-Filters – Hinzufügen von Miniaturansichten zum Dialogfeld finden Sie auf der chinesischen PHP-Website für verwandte Artikel!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn