Heim  >  Artikel  >  Web-Frontend  >  Photoshop-Ölgemälde-Effektfilter

Photoshop-Ölgemälde-Effektfilter

高洛峰
高洛峰Original
2017-02-18 13:33:162421Durchsuche

Dieser Filter wurde von mir mit dem PS SDK entwickelt. Ich habe mich auf den Quellcode von FilterExplorer (VC 6) verwiesen , von Jason Waltman (18. April 2001). Darüber hinaus sollte auch der Algorithmus des Ölgemäldefilters in PhotoSprite (Version 3.0, 2006, geschrieben von Lian Jun), einer anderen in der Sprache C# geschriebenen inländischen Software, aus dem ersteren (oder einem anderen homologen Code) zitiert werden. Beim Studium dieses Filteralgorithmus habe ich mich hauptsächlich auf den C++-Code des ersteren bezogen. Die konzeptionelle Beschreibung des Algorithmus in diesem Artikel gehört zu meinem Verständnis und meiner Interpretation. Die Effizienz dieses Algorithmus ist jedoch nicht hoch. Die zeitliche Komplexität bezüglich der Vorlagengröße wurde von O (n^2) auf lineare Komplexität O (n) verbessert Die Anzahl der Pixel ist eine Konstante, und die Verarbeitungsgeschwindigkeit desselben Parameters für dieselbe Testprobe (ein bestimmtes RGB-Bild mit 1920 * 1200 Pixeln) wird von etwa 35 Sekunden auf etwa 3 Sekunden reduziert Die Geschwindigkeit wird auf etwa das 10- bis 12-fache erhöht (grobe Schätzung).

In diesem Artikel wird hauptsächlich der Photoshop-Ölgemälde-Effektfilter (OilPaint) veröffentlicht. Der Algorithmus wird nicht von mir vorgeschlagen, Sie können auf die Referenzen in diesem Artikel verweisen. Dieser Filter ist in der in C# entwickelten heimischen Software PhotoSprite zu sehen. Ich wurde 2010 gebeten, bei der Entwicklung dieses Filters mitzuhelfen, und jetzt habe ich etwa ein paar Tage damit verbracht, ihn zu entwickeln und kostenlos zur Verfügung zu stellen.

(1) Konzeptionelle Beschreibung des Algorithmus des Ölgemäldefilters

Dies ist das Verständnis, das ich durch das Lesen des FilterExplorer-Quellcodes gewonnen habe. Dieser Filter hat zwei Parameter, einer ist der Vorlagenradius (Radius), dann ist die Vorlagengröße (Radius * 2 + 1) * (Radius * 2 + 1) Größe, dh mit dem aktuellen Pixel als Mittelpunkt wird nach außen erweitert nach Radius Ein rechteckiger Bereich von Pixeln, als Suchbereich, wir nennen ihn vorübergehend „Vorlage“ (eigentlich handelt es sich bei diesem Algorithmus nicht um eine Standardvorlagenmethode wie Gaußsche Unschärfe oder benutzerdefinierte Filter, der Verarbeitungsprozess ist lediglich ähnlich , damit ich die später eingeführten Optimierungen implementieren kann).

Ein weiterer Parameter ist die Glätte, bei der es sich eigentlich um die Anzahl der Graustufen-Buckets handelt. Wir gehen davon aus, dass die Graustufen/Helligkeit der Pixel (0 ~ 255) gleichmäßig in Glätteintervalle unterteilt sind. Dann nennen wir hier jedes Intervall einen Bucket, sodass wir viele Buckets haben, die vorübergehend als Buckets-Array (Buckets) bezeichnet werden.

Dieser Algorithmus durchläuft jedes Pixel im Bild und wandelt für das aktuelle Positionspixel (x, y) alle Pixel innerhalb des Vorlagenbereichs in Graustufen um, d. h. wandelt das Bild in ein Graustufenbild um und ändert dann das Pixelwert Eine weitere Diskretisierung besteht darin, die Pixel in der Vorlage der Reihe nach entsprechend dem Intervall, in das die Graustufe des Pixels fällt, in die entsprechenden Buckets einzuordnen. Suchen Sie dann den Eimer, in den die größte Anzahl an Pixeln fällt, und ermitteln Sie den Mittelwert der Farbe aller Pixel in diesem Eimer als resultierenden Wert an der Position (x, y).

Die obige Algorithmusbeschreibung wird durch das folgende schematische Diagramm dargestellt. Das Bild in der Mitte ist das Ergebnis von Graustufen + Diskretisierung vom Originalbild (entspricht der Tonwerttrennung in Photoshop), und das kleine Kästchen stellt die Vorlage dar. Was unten gezeigt wird, ist das Bucket-Array (8 Buckets, d. h. der Grauwert von 0 bis 255 wird in 8 Intervalle diskretisiert).

  Photoshop 油画效果滤镜

  (2) Verbesserung der Effizienz der bestehenden Ausländercodes

Es ist nicht schwierig, den vorhandenen Code in den PS-Filter zu übertragen. Ich habe etwa 1 bis 2 Tage Freizeit gebraucht, um ihn grundsätzlich erfolgreich zu debuggen. Aber als ich den Quellcode von Ausländern las, hatte ich eindeutig das Gefühl, dass der Originalcode nicht effizient genug war. Dieser Algorithmus kann durch einmaliges Durchlaufen des Bildes vervollständigt werden, und die Verarbeitung jedes Pixels ist eine konstante Zeit, sodass die Komplexität O(n) für die Anzahl der Pixel (Bildlänge * Bildbreite) ist, aber der konstante Koeffizient des Originalcodes ist beispielsweise größer. Jedes Mal, wenn das Pixelergebnis berechnet wird, muss die Graustufe des Pixels innerhalb des Vorlagenbereichs neu berechnet und in den Bucket gelegt werden, was tatsächlich zu einer großen Anzahl sich wiederholender Berechnungen führt.

 2.1 Zu diesem Zweck besteht meine erste Verbesserung darin, den aktuellen gesamten Bildpatch in PS grau zu skalieren und zu diskretisieren (in einen Eimer zu legen), sodass bei Verwendung der Vorlage Beim Durchlaufen des Patches Es ist nicht erforderlich, die Graustufen wiederholt zu berechnen und zu diskretisieren. Dadurch wurde die Laufgeschwindigkeit des Algorithmus ungefähr verdoppelt (für eine bestimmte Probe erhöhte sich die Verarbeitungsgeschwindigkeit von mehr als 20 Sekunden auf etwa 10 Sekunden).

 2.2 Aber die Geschwindigkeitsverbesserung ist immer noch nicht signifikant genug. Deshalb habe ich eine weitere, wichtigere Optimierung vorgenommen, die darin bestand, die Komplexität für die Vorlagengröße von quadratischer auf lineare Komplexität zu reduzieren. Dies basiert auf der Tatsache, dass unter Berücksichtigung der Tatsache, dass sich die Vorlage zwischen den aktuellen Zeilen von links nach rechts bewegt, die Statistik der Pixel in der Mitte der Vorlage (dem Schnittpunkt zweier benachbarter Vorlagen) im Ergebnis unverändert bleibt. Nur die Spalte ganz links verlässt die Vorlage und die Spalte ganz rechts tritt in die Vorlage ein, sodass wir uns beim Durchlaufen des Bildes nicht um die Pixel in der Mitte der Vorlage kümmern müssen, sondern nur die beiden Kanten verarbeiten müssen Vorlage. Wie unten gezeigt (Radius ist 2, Vorlagengröße ist 5 * 5 Pixel):

 Photoshop 油画效果滤镜

Wenn wir den rechten Rand des Patches erreichen, setzen wir ihn nicht wie ein Wagenrücklauf oder Zeilenvorschub an den Zeilenanfang zurück, sondern verschieben ihn Vorlage eine Zeile nach unten und geben Sie das Ende der nächsten Zeile ein und verschieben Sie es dann nach links, sodass die Flugbahn der Vorlage zu einer schlangenförmigen Schrittflugbahn wird. Nach dieser Verbesserung müssen wir beim Durchlaufen der Pixel nur noch die beiden Kantenpixel der Vorlage verarbeiten. Auf diese Weise wird die Vorlagengröße (Radius im Parameter) von O(n^2) auf O(n) reduziert, wodurch die Betriebsgeschwindigkeit des Algorithmus erheblich verbessert wird. In Kombination mit der Optimierung 2.1 wird schließlich die Betriebsgeschwindigkeit des Algorithmus verbessert Zeiten (dieser Wert ist nur eine grobe Schätzung und wurde nicht an einer großen Anzahl von Proben getestet). Die Verarbeitungszeit des optimierten Algorithmus für große Bilder ist ebenfalls akzeptabel.

[Hinweis] Der Grund, warum ich eine solche Optimierung erreichen kann, ist, dass der Filteralgorithmus kein Standardvorlagenalgorithmus ist. Sein Wesen besteht darin, statistische Informationen innerhalb des Vorlagenbereichs zu erhalten, d. h. das Ergebnis Es hat nichts mit den Vorlagenkoordinaten des Pixels zu tun. Genauso wollen wir Informationen wie die Bevölkerungszahl und das Verhältnis von Männern zu Frauen in einem bestimmten lokalen Gebiet erhalten. Daher optimieren wir nach der oben genannten Methode.

Die Bewegungsbahn der Vorlage ist ein Serpentinenschritt, zum Beispiel:

→ → → → → → →

unten Der Code des Kernalgorithmus des Filters, alle Codes befinden sich in algorithm.cpp:

code_FilterData_OilPaint

 2.3 Der ursprüngliche Code begrenzt den Bereich des Radius auf (1 ~ 5). Da ich den Code optimiert habe, kann ich den Bereich des Radius I erheblich vergrößern Als ich den Radius auf 100 einstellte, stellte ich fest, dass es keinen Sinn machte, den Radius zu groß zu wählen, da es fast unmöglich war, das Originalbild zu erkennen.

[Zusammenfassung] Der verbesserte Code ist technischer und anspruchsvoller, einschließlich einer großen Anzahl von Zeigeroperationen auf niedriger Ebene und zwischen verschiedenen Rechtecken (Eingabe-Patches, Ausgabe-Patches, Vorlagen) Die Koordinatenpositionierung kann die Lesbarkeit des Codes leicht beeinträchtigen, aber solange die oben genannten Prinzipien verstanden werden, ist der Code immer noch gut lesbar. Darüber hinaus habe ich auch darüber nachgedacht, den Algorithmus zu verbessern, die Vorlage von einem Rechteck in einen „Kreis“ zu ändern und die beiden Parameter Vorlagenradius und Anzahl der Buckets beim Durchlaufen des Bildes zufällig zu zittern, aber diese Verbesserungen werden dazu führen Verbesserung in 2.2 Wenn die Optimierung fehlschlägt, sinkt die Geschwindigkeit des Algorithmus wieder auf ein niedrigeres Niveau.
#include "Algorithm.h"

//=========================================
//    缩略图和实际处理共享的滤镜算法
//=========================================
//
// 默认把数据当作是RGB, GrayData 是单通道数据,矩形和 InRect 一致
//
// bInitGray: 是否需要算法重新计算灰度数据
// rowBytes: inData/outData, 扫描行宽度
// colBytes: inData/outData, 对于interleave分布,等于通道数,集中分布时该为1
// planeBytes: 每个通道的字节数(对于interleave分布,该参数的值为1)
// grayData: 由于仅一个通道,所以grayColumnBytes一定是1;
// buckets: 灰度桶; 每个灰度占据4个UINT,0-count,1-redSum,2-greenSum,3-blueSum
// abortProc: 用于测试是否取消的回调函数(在滤镜处理过程中,即测试用户是否按了Escape)
//              在缩略图中用于测试是否已经产生了后续的Trackbar拖动事件
// retVal:如果没有被打断,返回TRUE,否则返回FALSE(说明被用户取消或后续UI事件打断)
//

BOOL FilterData_OilPaint(
    uint8* pDataIn, Rect& inRect, int inRowBytes, int inColumnBytes, int inPlaneBytes,
    uint8* pDataOut, Rect& outRect, int outRowBytes, int outColumnBytes, int outPlaneBytes,
    uint8* pDataGray, int grayRowBytes, BOOL bInitGray,
int radius, 
int smoothness,
    UINT* buckets,
    TestAbortProc abortProc
    )
{
int indexIn, indexOut, indexGray, x, y, i, j, i2, j2, k;    //像素索引
     uint8 red, green, blue;

//设置边界
     int imaxOut = (outRect.right - outRect.left);
int jmaxOut = (outRect.bottom - outRect.top);
int imaxIn = (inRect.right - inRect.left);
int jmaxIn = (inRect.bottom - inRect.top);

//获取两个矩形(inRect和outRect)之间的偏移,即 outRect 左上角在 inRect 区中的坐标
     int x0 = outRect.left - inRect.left;
int y0 = outRect.top - inRect.top;

// 灰度离散化应该作为原子性操作,不应该分割
     if(bInitGray)
    {
//把 In 贴片灰度化并离散化
         double scale = smoothness /255.0;

for(j =0; j < jmaxIn; j++)
        {
for(i =0; i < imaxIn; i++)
            {
                indexIn = i * inColumnBytes + j * inRowBytes; //源像素[x, y]
                
                red = pDataIn[indexIn];
                green = pDataIn[indexIn + inPlaneBytes];
                blue = pDataIn[indexIn + inPlaneBytes*2];

                pDataGray[grayRowBytes * j + i] = (uint8)(GET_GRAY(red, green, blue) * scale);
            }
        }
    }

if(abortProc != NULL && abortProc())
return FALSE;

// 模板和统计数据
// 灰度桶 count, rSum, gSum, bSum
//
    memset(buckets, 0, (smoothness +1) *sizeof(UINT) *4);

int colLeave, colEnter, yMin, yMax;
int rowLeave, rowEnter, xMin, xMax;
int direction;

//初始化第一个模板位置的数据
     yMin = max(-y0,              -radius);
    yMax = min(-y0 + jmaxIn -1, radius);
    xMin = max(-x0,              -radius);
    xMax = min(-x0 + imaxIn -1, radius);

for(j2 = yMin; j2 <= yMax; j2++)
    {
for(i2 = xMin; i2 <= xMax; i2++)
        {
            indexIn = (j2 + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
            indexGray = (j2 + y0) * grayRowBytes + (i2 + x0);

            buckets[ pDataGray[indexGray] *4 ]++; //count
            buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
            buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
            buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
        }
    }

if(abortProc != NULL && abortProc())
return FALSE;

//进入模板的蛇形迂回循环
    
for(j =0; j < jmaxOut; j++)
    {
if(abortProc != NULL && abortProc())
return FALSE;

//direction:水平移动方向( 1 - 向右移动; 0 - 向左移动)
         direction =1- (j &1);

//找到最大的那个像素
         GetMostFrequentColor(buckets, smoothness, &red, &green, &blue);
if(direction)
        {
            indexOut = j * outRowBytes;
        }
else
        {
            indexOut = j * outRowBytes + (imaxOut -1) * outColumnBytes;
        }
        pDataOut[ indexOut                     ] = red;  
        pDataOut[ indexOut + outPlaneBytes     ] = green;
        pDataOut[ indexOut + outPlaneBytes *2 ] = blue;

        i = direction?1 : (imaxOut -2);

for(k =1; k < imaxOut; k++) //k 是无意义的变量,仅为了在当前行中前进
         {
//每 64 个点测试一次用户取消 ( 在每行中间有一次测试 )
            if((k &0x3F) ==0x3F&& abortProc != NULL && abortProc())
            {
return FALSE;
            }

if(direction) //向右移动
              {
                colLeave = i - radius -1;
                colEnter = i + radius;
            }
else//向左移动
              {
                colLeave = i + radius +1;
                colEnter = i - radius;
            }

            yMin = max(-y0,              j - radius);
            yMax = min(-y0 + jmaxIn -1, j + radius);

//移出当前模板的那一列
              if((colLeave + x0) >=0&& (colLeave + x0) < imaxIn)
            {
for(j2 = yMin; j2 <= yMax; j2++)
                {
                    indexIn = (j2 + y0) * inRowBytes + (colLeave + x0) * inColumnBytes;
                    indexGray = (j2 + y0) * grayRowBytes + (colLeave + x0);

                    buckets[ pDataGray[indexGray] *4     ]--; //count
                    buckets[ pDataGray[indexGray] *4+1 ] -= pDataIn[indexIn]; //redSum
                    buckets[ pDataGray[indexGray] *4+2 ] -= pDataIn[indexIn + inPlaneBytes]; //greenSum
                    buckets[ pDataGray[indexGray] *4+3 ] -= pDataIn[indexIn + inPlaneBytes*2]; //greenSum
                }
            }

//进入当前模板的那一列
              if((colEnter + x0) >=0&& (colEnter + x0) < imaxIn)
            {
for(j2 = yMin; j2 <= yMax; j2++)
                {
                    indexIn = (j2 + y0) * inRowBytes + (colEnter + x0) * inColumnBytes;
                    indexGray = (j2 + y0) * grayRowBytes + (colEnter + x0);

                    buckets[ pDataGray[indexGray] *4     ]++; //count
                    buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
                    buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
                    buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
                }
            }

//找到最大的那个像素
              GetMostFrequentColor(buckets, smoothness, &red, &green, &blue);

//目标像素[i, j]
            indexOut =  j * outRowBytes + i * outColumnBytes; 
            pDataOut[ indexOut                     ] = red;  
            pDataOut[ indexOut + outPlaneBytes     ] = green;
            pDataOut[ indexOut + outPlaneBytes *2 ] = blue;

            i += direction?1 : -1;
        }

//把模板向下移动一行
         rowLeave = j - radius;
        rowEnter = j + radius +1;

if(direction)
        {
            xMin = max(-x0,              (imaxOut -1) - radius);
            xMax = min(-x0 + imaxIn -1, (imaxOut -1) + radius);
            indexOut = (j +1) * outRowBytes + (imaxOut -1) * outColumnBytes; //目标像素[i, j]
        }
else
        {
            xMin = max(-x0,              -radius);
            xMax = min(-x0 + imaxIn -1, radius);
            indexOut = (j +1) * outRowBytes; //目标像素[i, j]
        }

//移出当前模板的那一列
         if((rowLeave + y0) >=0&& (rowLeave + y0) < jmaxIn)
        {
for(i2 = xMin; i2 <= xMax; i2++)
            {
                indexIn = (rowLeave + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
                indexGray = (rowLeave + y0) * grayRowBytes + (i2 + x0);

                buckets[ pDataGray[indexGray] *4     ]--; //count
                buckets[ pDataGray[indexGray] *4+1 ] -= pDataIn[indexIn]; //redSum
                buckets[ pDataGray[indexGray] *4+2 ] -= pDataIn[indexIn + inPlaneBytes]; //greenSum
                buckets[ pDataGray[indexGray] *4+3 ] -= pDataIn[indexIn + inPlaneBytes*2]; //greenSum
            }
        }

//进入当前模板的那一列
         if((rowEnter + y0) >=0&& (rowEnter + y0) < jmaxIn)
        {
for(i2 = xMin; i2 <= xMax; i2++)
            {
                indexIn = (rowEnter + y0) * inRowBytes + (i2 + x0) * inColumnBytes;
                indexGray = (rowEnter + y0) * grayRowBytes + (i2 + x0);

                buckets[ pDataGray[indexGray] *4     ]++; //count
                buckets[ pDataGray[indexGray] *4+1 ] += pDataIn[indexIn]; //redSum
                buckets[ pDataGray[indexGray] *4+2 ] += pDataIn[indexIn + inPlaneBytes]; //greenSum
                buckets[ pDataGray[indexGray] *4+3 ] += pDataIn[indexIn + inPlaneBytes*2]; //greenSum
            }
        }
    }
return TRUE;
}

//从灰度桶阵列中,提取出最多像素的那个桶,并把桶中像素求平均值作为 RGB 结果。
void GetMostFrequentColor(UINT* buckets, int smoothness, uint8* pRed, uint8* pGreen, uint8* pBlue)
{
    UINT maxCount =0;
int i, index =0;
for(i =0; i <= smoothness; i++)
    {
if(buckets[ i *4 ] > maxCount)
        {
            maxCount = buckets[ i *4 ];
            index = i;
        }
    }
if(maxCount >0)
    {
*pRed =   (uint8)(buckets[ index *4+1 ] / maxCount);    //Red  
        *pGreen = (uint8)(buckets[ index *4+2 ] / maxCount);    //Green 
        *pBlue =  (uint8)(buckets[ index *4+3 ] / maxCount);    //Blue
    }
}

(3) Verwenden Sie Multithreading-Technologie, um die Effizienz der Miniaturansichtsanzeige zu verbessern und eine Beeinträchtigung der Interaktivität des UI-Threads zu vermeiden.

Anzeige auf dem Parameter Informationen zur Miniaturbildtechnologie finden Sie im vierten Artikel meines vorherigen Tutorials zum Schreiben von PS-Filtern, den ich hier nicht beschreiben werde. Ich spreche hier von Verbesserungen der UI-Interaktion beim Aktualisieren von Miniaturansichten sowie der Zoom- und Schwenktechnologie.

Das Bild unten zeigt das Parametereinstellungsdialogfeld, das angezeigt wird, wenn dieser Filter in Photoshop aufgerufen wird. Der Benutzer kann das Schieberegler-Steuerelement (TrackBar, auch bekannt als Slider) ziehen oder direkt in das Textfeld dahinter eingeben, um die Parameter zu ändern. Miniaturansichten werden in Echtzeit aktualisiert, um die neuen Parameter widerzuspiegeln. In der ursprünglichen Filterimplementierung habe ich die Verarbeitung der Aktualisierung von Miniaturansichten im selben Thread wie die Dialog-Benutzeroberfläche platziert. Dies führt zu dem folgenden Problem. Wenn der Benutzer den Schieberegler schnell zieht, ändern sich die Parameter schnell, und der UI-Thread ist möglicherweise für kurze Zeit mit der Verarbeitung von Miniaturbilddaten beschäftigt und „blockiert“, sodass er nicht sofort reagieren kann Dies bedeutet, dass das Ziehen des Schiebereglers nicht sanft genug ist, es zu Sprüngen, Frustration und Trägheit kommt und die Rückmeldung beim Ziehen mit der Maus nicht empfindlich genug ist.

 

Um dieses Problem zu beheben und den UI-Thread nicht zu beeinträchtigen, habe ich vor, die zeitaufwändige Miniaturansichtsverarbeitung einzuführen Aufgabe zum Abschließen in einem neuen Thread Wenn der Thread die Miniaturansichtsverarbeitung abschließt, wird das Dialogfeld benachrichtigt, um seine Ansicht zu aktualisieren. Beim Ziehen der Trackbar erhält der UI-Thread sehr häufig Steuerungsbenachrichtigungen, wie z. B. einen „Anstieg“, der erfordert, dass nachfolgende eintreffende UI-Ereignisse dazu führen können, dass die laufende Thread-Aufgabe schnell beendet und beendet wird.

Ich habe den Filteralgorithmus als gemeinsam genutzte Funktion extrahiert, sodass die eigentliche Verarbeitung des Filters und die Aktualisierung des Miniaturbilds diese Funktion gemeinsam nutzen können. Während des eigentlichen Aufrufs des Filters durch PS und der Aktualisierung des Miniaturbilds muss der Filteralgorithmus tatsächlich regelmäßig „Aufgabenabbruch“-Ereignisse erkennen. Wenn PS beispielsweise einen Filter aufruft und der Benutzer die ESC-Taste drückt, wird ein zeitaufwändiger Filtervorgang sofort abgebrochen. Wenn beim Aktualisieren von Miniaturansichten eine Flut von UI-Ereignissen auftritt, muss der Verarbeitungsthread auch schnell beendet werden können.

In der Kernalgorithmusfunktion des Filters erkenne ich regelmäßig „Aufgabenabbruch“-Ereignisse. Da die Testabbruchmethode beim Aufrufen des Filters in PS und beim Aktualisieren der Miniaturansicht unterschiedlich ist, ist der Filteralgorithmus in der Funktion. Ich habe einen Callback-Funktionsparameter (TestAbortProc) hinzugefügt. Wenn PS den Filter für die tatsächliche Verarbeitung aufruft, wird auf diese Weise die integrierte Rückruffunktion von PS verwendet, um das Abbruchereignis zu erkennen. Beim Aktualisieren der Miniaturansicht des Dialogfelds verwende ich eine von mir selbst bereitgestellte Rückruffunktion, um das Abbruchereignis zu erkennen ( Diese Funktion erkennt eine boolesche Variable, um festzustellen, ob neue UI-Ereignisse auf die Verarbeitung warten.

Ich verwende einen einzelnen Thread für die Miniaturansichtsverarbeitung. Das heißt, jedes Mal, wenn ein neues UI-Ereignis eintrifft, muss festgestellt werden, ob der Miniaturbild-Verarbeitungsthread ausgeführt wird. Wenn ja, setze ich eine Markierung für das neue UI-Ereignis, warte dann, bis der Thread beendet wird, und beginne dann ein neuer Thread, nachdem der vorherige Thread beendet wurde, sodass immer nur ein Thread zum Verarbeiten von Miniaturansichten vorhanden ist, anstatt zu viele Threads zu öffnen, wenn UI-Ereignisse kontinuierlich eintreffen. Dies hat den Vorteil, dass die Logik klar und einfach zu steuern ist. und es wird uns nicht in eine unhaltbare Situation mit zu vielen Threads in Schwierigkeiten geraten lassen. Der Nachteil besteht darin, dass der Thread zwar regelmäßig Abbruchereignisse erkennt, das Beenden des Threads jedoch etwas Zeit in Anspruch nimmt. Dies führt dazu, dass der UI-Thread immer noch eine leichte „Pause“ aufweist, die jedoch im Vergleich zum Aktualisieren der Miniaturansicht trivial ist Erzielen Sie wesentliche Verbesserungen im UI-Thread.

Nach der Verbesserung können Sie die beiden Schieberegler im Parameterdialogfeld sehr schnell ziehen. Obwohl der Kernalgorithmus dieses Filters eine große Menge an Berechnungen erfordert, können wir sehen, dass das Parameterdialogfeld immer noch funktioniert hat viele Funktionen.

(4) Miniaturbild-Zoom- und Schwenkfunktionen

Tatsächlich ist es unabhängig vom Zoomen oder Schwenken nicht schwierig, die Miniaturbilddaten zu aktualisieren sich selbst. Die Schwierigkeit liegt vor allem im Schwenken von Miniaturansichten, da es sich dabei um eine Mausinteraktion handelt, die sehr solide Windows-Programmierkenntnisse und ein Verständnis der zugrunde liegenden Mechanismen von Windows-Programmen erfordert.

Es gibt zwei Möglichkeiten, Miniaturansichten zu ziehen:

4.1 Ziehen Sie das Ergebnisbild direkt.

Dies ist in zwei Methoden unterteilt. Einer davon ist ein relativ perfekter Drag-Effekt, aber er geht mit einer gewissen Platz- und Zeitverschwendung einher, und auch die Codierung ist eine Herausforderung. Das heißt, die Eingabedaten des Miniaturbilds werden auf die 9-fache Größe erweitert und das Ergebnisbild wird im Speicher abgerufen. Bei der Anzeige wird nur der mittlere Teil des Ergebnisdiagramms angezeigt. Beim Ziehen erscheint kein Leerraum in der Miniaturansicht.

Eine andere Methode besteht darin, beim Ziehen einen Schnappschuss (Screenshot) des aktuellen Ergebnisbilds zu machen und dann beim Ziehen einfach das Screenshot-Ergebnis an der entsprechenden Position auf dem Bildschirm einzufügen. Dies ist effizienter, hat jedoch den Nachteil, dass beim Ziehen ein leerer Bereich neben der Miniaturansicht angezeigt wird. Diese Methode wird häufig verwendet, wenn die Kosten für die Aktualisierung der Ansicht hoch sind, beispielsweise beim Vektorzeichnen. Die Methode, die ich in diesem Filter implementiert habe, fällt in diese Kategorie.

 4.2 Ziehen Sie das Bild auf das ursprüngliche Eingabebild.

Das heißt, beim Ziehen werden als Bild die Originaldaten und nicht das Ergebnisbild verwendet. Dies ist auch ein Kompromiss, um die Kosten für die Aktualisierung der Daten zu senken. Diese Methode wird beispielsweise im in Photoshop integrierten Filter Gaußscher Weichzeichner verwendet. Beim Ziehen der Miniaturansicht wird als Miniaturansicht das Originalbild angezeigt und der Vorschaueffekt wird erst angezeigt, wenn die Maus losgelassen wird. Das ist wirtschaftlicher und effektiver. Da die Kosten für die Anforderung der Originaldaten für uns nicht hoch sind, sind die Kosten für die einmalige Verarbeitung des Miniaturbilds mit einem Filter jedoch hoch.

Hier sind einige zusätzliche technische Details. Bitte beachten Sie, dass Sie LOWORD (lParam) und HIWORD (lParam) nicht direkt verwenden können, um den Client-Bereich zu erhalten, da sich die Maus möglicherweise außerhalb des Client-Bereichs bewegt (eine negative Zahl wird). Koordinaten (Da WORD eine vorzeichenlose Zahl ist), sollten sie vor der Verwendung in vorzeichenbehaftete Zahlen (kurz) umgewandelt werden. Der richtige Weg besteht darin, die Makros in der Header-Datei „windowsx.h“ zu verwenden: GET_X_LPARAM und GET_Y_LPARAM.

(5) Download-Link für diesen Filter (der Anhang enthält das von mir geschriebene PS-Plug-in-Installationstool, das die Benutzerinstallation vereinfachen kann)

[Dieses Plug-in in wurde kürzlich im Jahr 2013 veröffentlicht -4-1 Update, verbesserte UI-Interaktionsleistung]

// Die neueste Sammlung von PS-Plug-Ins, die ich entwickelt habe (einschließlich ICO, OILPAINT, DRAWTable usw.)

http://files.cnblogs. com/hoodlum1980/PsPlugIns_V2013.zip

Nach der Installation und dem Neustart von Photoshop:

Rufen Sie diesen Filter im Menü auf: Filter - Hoodlum1980 - OilPaint.

Sie können das Dialogfeld „Info“ im Menü sehen: Hilfe – Info zum Plug-in – OilPaint... (das Erscheinungsbild entspricht fast dem Dialogfeld „Info“ des von mir entwickelten Plug-ins für das ICO-Dateiformat).

Im Menü: Hilfe – Systeminformationen können Sie sehen, ob das Element „OilPaint“ geladen wurde und dessen Versionsinformationen.

(6) Einige weniger wichtige Zusatzanweisungen

6.1 Die von mir verwendete Ausgabe-Patchgröße beträgt 128 * 128 Pixel während des Spiegelverarbeitungsprozesses , zeigt jeder Schritt des Fortschrittsbalkens, den Sie in der Photoshop-Statusleiste sehen, den Abschluss eines Ausgabepatches an. Der Eingabe-Patch ist normalerweise größer oder gleich dem Ausgabe-Patch, und die Größe des Eingabe-Patches hängt vom Radius in den Filterparametern ab (dem Pixelabstand, um den der Vorlagenradius in vier Richtungen nach außen erweitert wird).

 6.2 Um die Empfindlichkeit gegenüber Abbruchereignissen zu verbessern, erkenne ich im Filterkernalgorithmus alle 16 verarbeiteten Pixel in der aktuellen Zeile (Pixelindex in Zeile & 0x0F == 0x0F) einen Abbruch. Der Abbruch wird auch einmal nach der Verarbeitung jeder Zeile (in der Spaltenschleife) erkannt. Diese Erkennungsfrequenz ist jedoch etwas zu häufig. Zu häufig kann die Kosten für Funktionsaufrufe erhöhen.

 6.3 Mit demselben Bild und denselben Parametern habe ich meine Filter, FilterExplorer und PhotoSprite separat verarbeitet und sie dann in Photoshop verglichen. Da mein Algorithmus auf dem Quellcode von FilterExplorer basiert und dessen Algorithmus verbessert wurde, entspricht mein Algorithmus dem FilterExplorer, ist jedoch effizienter, sodass die Ergebnisse genau gleich sind. Aber der Gesamteffekt meines Filters und FilterExplorers kommt dem von PhotoSprite sehr nahe, die Ergebnisse unterscheiden sich jedoch geringfügig. Ich habe den Code von PhotoSprite überprüft und festgestellt, dass dies durch den Unterschied im Graustufenalgorithmus des Bildes verursacht wurde (als ich den Graustufenalgorithmus von PhotoSprite an den gleichen wie in FilterExplorer angepasst habe, waren die Verarbeitungsergebnisse dieselben).

In PhotoSprite wird zum Graustufen von Pixeln folgende Methode verwendet:
grey = (byte) ( ( 19661 * R + 38666 * G + 7209 * B ) >> 16 ) ;

Im FilterExplorer / dem von mir entwickelten Filter lautet die Methode zum Graustufen von Pixeln:
grey = (byte) (0.3 * R + 0.59 * G + 0.11 * B ) ;

  Die Graustufenmethode in PhotoSprite wandelt die Gleitkomma-Multiplikation in eine Ganzzahl-Multiplikation um. Die Effizienz kann leicht verbessert werden, aber die Leistung ist hier nicht signifikant.

(7) Referenzmaterialien

7.1 FilterExplorer-Quellcode.

 7.2 Photoshop 6.0 SDK-Dokumente.

 7.3 FillRed- und IcoFormat-Plug-in-Quellcode, von Hoodlum1980 (ich selbst).

(8) Korrekturverlauf

8.01 [H] Filterübergabe beim Zuweisen von Graustufen-Bucket-Speicher im Continue-Aufruf behoben. Die Speichergröße ist falsch (fälschlicherweise auf die Raumgröße der Graustufen-Bitmap eingestellt). Dieser Fehler kann unter den folgenden Bedingungen leicht ausgelöst werden: Die Dokumentgröße ist zu klein oder der Radiusparameter ist klein und der Glätteparameter ist groß. Diese Faktoren können dazu führen, dass ein Patch zu klein ist. Da die Zuweisung des grauen Bucket-Speicherplatzes zu diesem Zeitpunkt kleiner ist als die tatsächlich erforderliche Größe, kann nachfolgender Code das Speicherlimit überschreiten, was dazu führt, dass der PS-Prozess unerwartet beendet wird. 19.01.2011 19:01.

 8.02 [M] Eine neue Schaltfläche und Funktion zum Zoomen von Miniaturansichten hinzugefügt und der Code für die Schaltflächen zum Vergrößern und Verkleinern optimiert, um das Flimmern beim Vergrößern und Verkleinern zu reduzieren. 19.01.2011 19:06.

 8.03 [M] Miniaturbild-Mausziehfunktion hinzugefügt und den Code weiter angepasst, um das Miniaturbildrechteck zu korrigieren und ein Flackern beim Zoomen vollständig zu vermeiden. 2011-1-19 23:50.

 8.04 [L] Neue Funktion: Wenn die Maus über das Miniaturbild bewegt und per Drag & Drop verschoben wird, ändert sich der Cursor in die Form einer ausgestreckten/gegriffenen Hand. Dies wird erreicht, indem die Funktion in der Suite PEA UI Hooks Suite in PS aufgerufen wird. Das heißt, was Sie auf dem Miniaturbild sehen, ist der Cursor in PS. 20.01.2011 1:23.

 8.05 [M] Der Fehler wurde behoben, der dazu führte, dass der Radiusparameter falsch konvertiert wurde, wenn im Parameterdialogfeld auf die Schaltfläche „Vergrößern“ oder „Verkleinern“ geklickt wurde. Dieser Fehler führt dazu, dass die Miniaturansichten nach dem Klicken auf die Schaltfläche „Vergrößern“ falsch angezeigt werden. 20.01.2011 1:55.

 8.06 [L] Passen Sie den URL-Link im Dialogfeld „Info“ an ein SysLink-Steuerelement an, wodurch der Fensterprozedurcode des Dialogfelds „Info“ erheblich vereinfacht werden kann. 20.01.2011 18:12.

 8.07 [L] Aktualisieren Sie das Plug-in-Installationsassistent-Tool, sodass mehrere Plug-ins gleichzeitig installiert werden können. 20.01.2011 21:15.

 8.08 [L] Aufgrund von Berechnungsfehlern beim Skalieren von Miniaturansichten (der Grund ist unbekannt, möglicherweise aufgrund von Fehlern bei der Gleitkommaberechnung) wird das Bild in der unteren rechten Ecke am Rand möglicherweise nicht in die Miniaturansicht übersetzt , also lautet die Übersetzung „Fehlerpuffer zum Bereich hinzugefügt“. 27.01.2011.

 8.09 [L] Verschönerung: Ändern Sie die Zoom-Schaltfläche im Filterparameter-Dialogfeld, um die DirectUI-Technologie zu verwenden (eine neue Klasse CImgButton hinzugefügt). Der Schnittstelleneffekt ist besser als bei Verwendung der ursprünglichen Schaltflächensteuerung. 2011-2-14.

 8.10 [M] Leistung: Die interaktive Leistung des Schiebereglers zum Anpassen der Radius- und Glätteparameter wurde angepasst, wenn sich das Plug-in im Parameterdialogfeld befindet. 2013-4-1.


Weitere Artikel zu Photoshop-Ölgemäldeeffektfiltern finden Sie auf der chinesischen PHP-Website!


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