Heim >Web-Frontend >PS-Tutorial >Serie zur Analyse des Prinzips des PhotoShop-Algorithmus – Stilisierung – Kanten finden.

Serie zur Analyse des Prinzips des PhotoShop-Algorithmus – Stilisierung – Kanten finden.

高洛峰
高洛峰Original
2017-02-21 09:14:272198Durchsuche

Der Grund, warum ich keine Titel wie Serie 1 und Serie 2 schreibe, ist, dass ich nicht weiß, wie lange ich durchhalten kann. Ich weiß, dass ich nicht sehr begabt bin, wenn es um die Artikulation von Dingen und den Reichtum der Sprache geht. Und ein Stück Code erfordert, dass ich den Prozess anhand der Grundprinzipien – „vorläufige Implementierung“, Optimierungsgeschwindigkeit und anderer Prozesse – sorgfältig in Worten beschreibe, was wahrscheinlich keine leichte Aufgabe ist.

Ich kann nicht sagen, dass einige der Algorithmen in Photoshop, die ich beherrsche, zu 100 % korrekt sind, aber vom Ausführungseffekt her gibt es in der allgemeinen Richtung definitiv kein Problem.

Derzeit habe ich wahrscheinlich fast 100 PS-Algorithmen aus den Artikeln anderer Leute, Open-Source-Codes und meinem eigenen Denken gelernt. Wenn es die Zeit und meine eigene Geduld erlauben, werde ich diese Dinge langsam klären, obwohl diese Algorithmen in den Augen vieler Menschen keinen Forschungswert haben. Es macht Sinn, lassen Sie mich es einfach als eine Möglichkeit der Selbstwertschätzung und Selbstzufriedenheit nutzen.

Heute sprechen wir über den Kantensuchalgorithmus. Vielleicht würden viele Leute es nicht lesen, nachdem ich das Prinzip erklärt habe, aber es gibt ein paar Leute, die es sorgfältig studiert haben.

Lass uns zuerst ein Rendering veröffentlichen:

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

Prinzip: Die Ergebnisse gängiger Sobel-Kantenoperatoren können invertiert werden.

Um Sie zum Weiterlesen zu animieren, gebe ich zunächst die Ausführungsgeschwindigkeit meines Codes an: Für digitale Bilder von 3000*4000*3 die Verarbeitung Zeit300ms.

Was ist Sobel? Ich habe ein paar Bilder von Baidu kopiert und die Adresse geändert:

PhotoShop算法原理解析系列 -  风格化---》查找边缘。 Das werde ich nicht Erklären Sie zu viel über die beiden oben genannten Formeln. Sie müssen nur wissen, dass A das Eingabebild und G das Ausgabebild von A ist. Der letzte Schritt besteht aus: G=255-G, dem Kantensuchalgorithmus.

Es gibt ein Problem mit dem Kantenfindungsalgorithmus, wie die Pixel am

physischen Rand

des Bildes verarbeitet werden Ignorieren Sie einfach die vier Kantenpixel, da eine professionelle Bildverarbeitungssoftware gegen die grundlegendsten Prinzipien verstößt. Eine separate Codeverarbeitung für Kanten führt zu redundanten und umständlichen Problemen bei der Codierung. Der einfachste und effizienteste Weg, das Problem zu lösen, ist die Verwendung von Sentinel-Grenzen. Wer viele Spezialeffektalgorithmen geschrieben hat, sollte wissen, dass außer dem Algorithmus, der ein einzelnes Pixel verarbeitet, keine Sicherung des Originalbilds (nicht unbedingt eine globale) erforderlich ist Algorithmen, die Domäneninformationen erfordern, ändern ein Pixel im vorherigen Schritt des Algorithmus, und der aktuelle Schritt des Algorithmus erfordert einen unveränderten Pixelwert. Daher klont dieser Algorithmus im Allgemeinen das Originalbild, bevor er beginnt Aus den geklonten Daten werden Domäneninformationen gelesen. Wenn der Klonvorgang kein vollständiger Klon ist, sondern die entsprechende Grenze erweitert und dann geklont wird, ist es möglich, das obige Problem der Grenzverarbeitung zu lösen.

Zum Beispiel wird für das Bild unten, das 19×14 Pixel groß ist, unser Backup-Bild oben, unten, links und rechts um ein Pixel erweitert und mit gefüllt Kantenwerte sollen 21*16 werden. Größe:

 

 

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

Auf diese Weise führt das Abtasten der entsprechenden Punkte des erweiterten Klonbilds bei der Berechnung der 3 * 3-Flächenpixel des Originalbilds nicht zu dem Problem, dass es nicht innerhalb des Bildbereichs liegt, und das wird auch der Fall sein viel weniger Urteilsvermögen bei der Codierung. Auch die Lesbarkeit wird verbessert.

Beachten Sie im Hinblick auf die Berechnungsgeschwindigkeit, dass die obige Berechnungsformel G eine Quadratwurzeloperation enthält. Dies ist aufgrund der Besonderheit der Bilddaten ein zeitaufwändiger Prozess. Es muss eine Ganzzahl sein. Sie können die Geschwindigkeit optimieren, indem Sie eine Nachschlagetabelle verwenden, was eine Überlegung bei der Erstellung der Tabelle erfordert.

Was die spezifischen Themen dieses Artikels betrifft, werden wir sie in zwei Schritten besprechen. Erstens: Erstellen Sie eine Nachschlagetabelle für alle möglichen Situationen unter dem Stammzeichen. Schauen Sie sich die Berechnungsformeln von GX und GY an und überlegen Sie, wie hoch der Maximalwert der Quadratsumme der beiden ist. Vielleicht möchten Sie eine Weile darüber nachdenken. Zweitens: Erstellen Sie einfach eine Nachschlagetabelle im Bereich von 0^2 bis 255^2 und stellen Sie dann sicher, dass die Zahl unter dem Wurzelzeichen nicht größer als 255^2 ist. Dies ist möglich, weil der maximale Wert der Bilddaten 255 beträgt. Wenn die Zahl unter dem Wurzelzeichen größer als 255 ^ 2 ist, muss sie nach dem Ermitteln des Quadratwurzelwerts immer noch auf 255 angepasst werden. Daher sollte in diesem Algorithmus Letzteres gewählt werden.

private void CmdFindEdgesArray_Click(object sender, EventArgs e)
{    int X, Y;    int Width, Height, Stride, StrideC, HeightC;    int Speed, SpeedOne, SpeedTwo, SpeedThree;    int BlueOne, BlueTwo, GreenOne, GreenTwo, RedOne, RedTwo;    int PowerRed, PowerGreen, PowerBlue;
    Bitmap Bmp = (Bitmap)Pic.Image;    if (Bmp.PixelFormat != PixelFormat.Format24bppRgb) throw new Exception("不支持的图像格式.");    byte[] SqrValue = new byte[65026];    for (Y = 0; Y < 65026; Y++) SqrValue[Y] = (byte)(255 - (int)Math.Sqrt(Y));      // 计算查找表,注意已经砸查找表里进行了反色
    Width = Bmp.Width; Height = Bmp.Height; Stride = (int)((Bmp.Width * 3 + 3) & 0XFFFFFFFC);
    StrideC = (Width + 2) * 3; HeightC = Height + 2;                                 // 宽度和高度都扩展2个像素

    byte[] ImageData = new byte[Stride * Height];                                    // 用于保存图像数据,(处理前后的都为他)
    byte[] ImageDataC = new byte[StrideC * HeightC];                                // 用于保存扩展后的图像数据

    fixed (byte* Scan0 = &ImageData[0])
    {
        BitmapData BmpData = new BitmapData();
        BmpData.Scan0 = (IntPtr)Scan0;                                              //  设置为字节数组的的第一个元素在内存中的地址
        BmpData.Stride = Stride;
        Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, PixelFormat.Format24bppRgb, BmpData);

        Stopwatch Sw = new Stopwatch();                                             //  只获取计算用时        Sw.Start();        for (Y = 0; Y < Height; Y++)
        {
            System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1), 3);        // 填充扩展图的左侧第一列像素(不包括第一个和最后一个点)
            System.Buffer.BlockCopy(ImageData, Stride * Y + (Width - 1) * 3, ImageDataC, StrideC * (Y + 1) + (Width + 1) * 3, 3);  // 填充最右侧那一列的数据
            System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1) + 3, Width * 3);
        }
        System.Buffer.BlockCopy(ImageDataC, StrideC, ImageDataC, 0, StrideC);              //  第一行
        System.Buffer.BlockCopy(ImageDataC, (HeightC - 2) * StrideC, ImageDataC, (HeightC - 1) * StrideC, StrideC);    //  最后一行               

        for (Y = 0; Y < Height; Y++)
        {
            Speed = Y * Stride;
            SpeedOne = StrideC * Y;            for (X = 0; X < Width; X++)
            {
                SpeedTwo = SpeedOne + StrideC;          //  尽量减少计算
                SpeedThree = SpeedTwo + StrideC;        //  下面的就是严格的按照Sobel算字进行计算,代码中的*2一般会优化为移位或者两个Add指令的,如果你不放心,当然可以直接改成移位
                BlueOne = ImageDataC[SpeedOne] + 2 * ImageDataC[SpeedTwo] + ImageDataC[SpeedThree] - ImageDataC[SpeedOne + 6] - 2 * ImageDataC[SpeedTwo + 6] - ImageDataC[SpeedThree + 6];
                GreenOne = ImageDataC[SpeedOne + 1] + 2 * ImageDataC[SpeedTwo + 1] + ImageDataC[SpeedThree + 1] - ImageDataC[SpeedOne + 7] - 2 * ImageDataC[SpeedTwo + 7] - ImageDataC[SpeedThree + 7];
                RedOne = ImageDataC[SpeedOne + 2] + 2 * ImageDataC[SpeedTwo + 2] + ImageDataC[SpeedThree + 2] - ImageDataC[SpeedOne + 8] - 2 * ImageDataC[SpeedTwo + 8] - ImageDataC[SpeedThree + 8];
                BlueTwo = ImageDataC[SpeedOne] + 2 * ImageDataC[SpeedOne + 3] + ImageDataC[SpeedOne + 6] - ImageDataC[SpeedThree] - 2 * ImageDataC[SpeedThree + 3] - ImageDataC[SpeedThree + 6];
                GreenTwo = ImageDataC[SpeedOne + 1] + 2 * ImageDataC[SpeedOne + 4] + ImageDataC[SpeedOne + 7] - ImageDataC[SpeedThree + 1] - 2 * ImageDataC[SpeedThree + 4] - ImageDataC[SpeedThree + 7];
                RedTwo = ImageDataC[SpeedOne + 2] + 2 * ImageDataC[SpeedOne + 5] + ImageDataC[SpeedOne + 8] - ImageDataC[SpeedThree + 2] - 2 * ImageDataC[SpeedThree + 5] - ImageDataC[SpeedThree + 8];

                PowerBlue = BlueOne * BlueOne + BlueTwo * BlueTwo;
                PowerGreen = GreenOne * GreenOne + GreenTwo * GreenTwo;
                PowerRed = RedOne * RedOne + RedTwo * RedTwo;                if (PowerBlue > 65025) PowerBlue = 65025;           //  处理掉溢出值
                if (PowerGreen > 65025) PowerGreen = 65025;                if (PowerRed > 65025) PowerRed = 65025;
                ImageData[Speed] = SqrValue[PowerBlue];             //  查表
                ImageData[Speed + 1] = SqrValue[PowerGreen];
                ImageData[Speed + 2] = SqrValue[PowerRed];

                Speed += 3;                                  // 跳往下一个像素
                SpeedOne += 3;
            }
        }
        Sw.Stop();        this.Text = "计算用时: " + Sw.ElapsedMilliseconds.ToString() + " ms";

        Bmp.UnlockBits(BmpData);                         //  必须先解锁,否则Invalidate失败     }
    Pic.Invalidate();
}

Der Einfachheit halber wird dies zunächst mithilfe eines eindimensionalen Arrays in C# und des Timing-Teils implementiert berücksichtigt nicht die Bilddatenerfassung und -aktualisierung, da die Bilddaten während des realen Bildverarbeitungsprozesses erfasst worden sein müssen.

Führen Sie für den obigen Code nach dem Kompilieren in den Release-Modus die kompilierte EXE-Datei aus. Für ein 3000*4000*3-Farbbild dauert es etwa 480 ms Denken Sie daran, die Spalte „JIT-Optimierung abbrechen (nur Hosting)“ zu deaktivieren, wenn das Modul unter „Optionen – „Debugging“ – „Allgemein“ geladen wird.

PhotoShop算法原理解析系列 -  风格化---》查找边缘。

Die Fülldaten im obigen Code erstellen kein neues Bild und füllen dann die Bilddaten darin aus. aber füllt direkt ein Array. Ist das Bild nicht eigentlich nur ein Teil des kontinuierlichen Speichers plus ein paar Header-Informationen, also reicht nur ein Teil des Speichers aus?

Das Füllen von Klondaten verwendet die Systemfunktion Buffer.BlockCopy, die dem zuvor verwendeten CopyMemory ähnelt und sehr schnell ist.

Um die Ausführungsgeschwindigkeit weiter zu erhöhen, werfen wir zunächst einen Blick auf den Code des wichtigsten zeitaufwändigen Teils des Algorithmus, also den Code darin für (X = 0; > Ich habe den obigen Assembler-Code nur ein wenig kommentiert, darunter das letzte 0000073c-Label, wir haben die Rückgabe verfolgt und eine andere Funktion aufgerufen:

                                                                                                                                                               beim Abrufen aller Vor der Eingabe eines Array-Elements Aus der Analyse geht hervor, dass dies meiner Meinung nach dazu dient, etwas Ähnliches zu tun, um festzustellen, ob der Index des Arrays außerhalb der Grenzen liegt. Wenn wir sicherstellen können, dass unser Algorithmus die Grenze nicht überschreitet, wird dieser Teil des Codes sehr nützlich sein. Wird es mich dann nicht davon abhalten, meine Geschäfte zu machen?

<span style="font-size: 13px;"> BlueOne = ImageDataC[SpeedOne] + <span style="color: #800080;">2</span> * ImageDataC[SpeedTwo] + ImageDataC[SpeedThree] - ImageDataC[SpeedOne + <span style="color: #800080;">6</span>] - <span style="color: #800080;">2</span> * ImageDataC[SpeedTwo + <span style="color: #800080;">6</span>] - ImageDataC[SpeedThree + <span style="color: #800080;">6</span><span style="color: #000000;">];<br><br></span><span style="color: #800080;">00000302</span><span style="color: #000000;">  cmp         ebx,edi 
</span><span style="color: #800080;">00000304</span><span style="color: #000000;">  jae         0000073C             //   数组是否越界?
0000030a  movzx       eax,</span><span style="color: #0000ff;">byte</span> ptr [esi+ebx+<span style="color: #800080;">8</span><span style="color: #000000;">]    //  将ImageDataC[SpeedOne]中的数据传送的eax寄存器
0000030f  mov         dword ptr [ebp</span>-<span style="color: #000000;">80h],eax 
</span><span style="color: #800080;">00000312</span>  mov         edx,dword ptr [ebp-<span style="color: #000000;">2Ch] 
</span><span style="color: #800080;">00000315</span><span style="color: #000000;">  cmp         edx,edi 
</span><span style="color: #800080;">00000317</span><span style="color: #000000;">  jae         0000073C            //     数组是否越界?           
0000031d  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">]   //   将ImageDataC[SpeedTwo]中的数据传送到edx寄存器</span><span style="color: #800080;">00000322</span><span style="color: #000000;">  add         edx,edx             //    计算2*ImageDataC[SpeedTwo]    </span><span style="color: #800080;">00000324</span><span style="color: #000000;">  add         eax,edx             //    计算ImageDataC[SpeedOne]+2*ImageDataC[SpeedTwo],并保存在eax寄存器中           </span><span style="color: #800080;">00000326</span><span style="color: #000000;">  cmp         ecx,edi 
</span><span style="color: #800080;">00000328</span><span style="color: #000000;">  jae         0000073C 
0000032e  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+ecx+<span style="color: #800080;">8</span><span style="color: #000000;">]   //    将ImageDataC[SpeedThree]中的数据传送到edx寄存器</span><span style="color: #800080;">00000333</span>  mov         dword ptr [ebp+<span style="color: #000000;">FFFFFF78h],edx 
</span><span style="color: #800080;">00000339</span><span style="color: #000000;">  add         eax,edx 
0000033b  lea         edx,[ebx</span>+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000033e  cmp         edx,edi 
</span><span style="color: #800080;">00000340</span><span style="color: #000000;">  jae         0000073C 
</span><span style="color: #800080;">00000346</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
0000034b  mov         dword ptr [ebp</span>+<span style="color: #000000;">FFFFFF7Ch],edx 
</span><span style="color: #800080;">00000351</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000353</span>  mov         edx,dword ptr [ebp-<span style="color: #000000;">2Ch] 
</span><span style="color: #800080;">00000356</span>  add         edx,<span style="color: #800080;">6</span> <span style="color: #800080;">00000359</span><span style="color: #000000;">  cmp         edx,edi 
0000035b  jae         0000073C 
</span><span style="color: #800080;">00000361</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000366</span><span style="color: #000000;">  add         edx,edx 
</span><span style="color: #800080;">00000368</span><span style="color: #000000;">  sub         eax,edx 
0000036a  lea         edx,[ecx</span>+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000036d  cmp         edx,edi 
0000036f  jae         0000073C 
</span><span style="color: #800080;">00000375</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edx+<span style="color: #800080;">8</span><span style="color: #000000;">] 
0000037a  mov         dword ptr [ebp</span>+<span style="color: #000000;">FFFFFF74h],edx 
</span><span style="color: #800080;">00000380</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000382</span>  mov         dword ptr [ebp-30h],eax </span>

Zu diesem Zweck halte ich es für notwendig, Zeiger direkt zu verwenden, um Algorithmen in C# zu implementieren. C# verfügt über einen unsicheren Modus und Zeiger, daher ist es sehr praktisch und Zeiger können mit ausgedrückt werden *, Sie können auch [] verwenden, zum Beispiel haben *(P+4) und P[4] die gleiche Bedeutung. Dann kann der obige Code mit sehr wenigen Änderungen in eine Zeigerversion geändert werden.

Gleicher Effekt, gleiches Bild, Berechnungszeit 330ms .

Schauen wir uns den Assembler-Code desselben Codes an:

private void CmdFindEdgesPointer_Click(object sender, EventArgs e)
    {        int X, Y;        int Width, Height, Stride, StrideC, HeightC;        int Speed, SpeedOne, SpeedTwo, SpeedThree;        int BlueOne, BlueTwo, GreenOne, GreenTwo, RedOne, RedTwo;        int PowerRed, PowerGreen, PowerBlue;
        Bitmap Bmp = (Bitmap)Pic.Image;        if (Bmp.PixelFormat != PixelFormat.Format24bppRgb) throw new Exception("不支持的图像格式.");        byte[] SqrValue = new byte[65026];        for (Y = 0; Y < 65026; Y++) SqrValue[Y] = (byte)(255 - (int)Math.Sqrt(Y));      // 计算查找表,注意已经砸查找表里进行了反色
        Width = Bmp.Width; Height = Bmp.Height; Stride = (int)((Bmp.Width * 3 + 3) & 0XFFFFFFFC);
        StrideC = (Width + 2) * 3; HeightC = Height + 2;                                 // 宽度和高度都扩展2个像素

        byte[] ImageData = new byte[Stride * Height];                                    // 用于保存图像数据,(处理前后的都为他)
        byte[] ImageDataC = new byte[StrideC * HeightC];                                 // 用于保存扩展后的图像数据

        fixed (byte* P = &ImageData[0], CP = &ImageDataC[0], LP = &SqrValue[0])
        {            byte* DataP = P, DataCP = CP, LutP = LP;
            BitmapData BmpData = new BitmapData();
            BmpData.Scan0 = (IntPtr)DataP;                                              //  设置为字节数组的的第一个元素在内存中的地址
            BmpData.Stride = Stride;
            Bmp.LockBits(new Rectangle(0, 0, Bmp.Width, Bmp.Height), ImageLockMode.ReadWrite | ImageLockMode.UserInputBuffer, PixelFormat.Format24bppRgb, BmpData);

            Stopwatch Sw = new Stopwatch();                                             //  只获取计算用时            Sw.Start();            for (Y = 0; Y < Height; Y++)
            {
                System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1), 3);        // 填充扩展图的左侧第一列像素(不包括第一个和最后一个点)
                System.Buffer.BlockCopy(ImageData, Stride * Y + (Width - 1) * 3, ImageDataC, StrideC * (Y + 1) + (Width + 1) * 3, 3);  // 填充最右侧那一列的数据
                System.Buffer.BlockCopy(ImageData, Stride * Y, ImageDataC, StrideC * (Y + 1) + 3, Width * 3);
            }
            System.Buffer.BlockCopy(ImageDataC, StrideC, ImageDataC, 0, StrideC);              //  第一行
            System.Buffer.BlockCopy(ImageDataC, (HeightC - 2) * StrideC, ImageDataC, (HeightC - 1) * StrideC, StrideC);    //  最后一行               

            for (Y = 0; Y < Height; Y++)
            {
                Speed = Y * Stride;
                SpeedOne = StrideC * Y;                for (X = 0; X < Width; X++)
                {
                    SpeedTwo = SpeedOne + StrideC;          //  尽量减少计算
                    SpeedThree = SpeedTwo + StrideC;        //  下面的就是严格的按照Sobel算字进行计算,代码中的*2一般会优化为移位或者两个Add指令的,如果你不放心,当然可以直接改成移位
                    BlueOne = DataCP[SpeedOne] + 2 * DataCP[SpeedTwo] + DataCP[SpeedThree] - DataCP[SpeedOne + 6] - 2 * DataCP[SpeedTwo + 6] - DataCP[SpeedThree + 6];
                    GreenOne = DataCP[SpeedOne + 1] + 2 * DataCP[SpeedTwo + 1] + DataCP[SpeedThree + 1] - DataCP[SpeedOne + 7] - 2 * DataCP[SpeedTwo + 7] - DataCP[SpeedThree + 7];
                    RedOne = DataCP[SpeedOne + 2] + 2 * DataCP[SpeedTwo + 2] + DataCP[SpeedThree + 2] - DataCP[SpeedOne + 8] - 2 * DataCP[SpeedTwo + 8] - DataCP[SpeedThree + 8];
                    BlueTwo = DataCP[SpeedOne] + 2 * DataCP[SpeedOne + 3] + DataCP[SpeedOne + 6] - DataCP[SpeedThree] - 2 * DataCP[SpeedThree + 3] - DataCP[SpeedThree + 6];
                    GreenTwo = DataCP[SpeedOne + 1] + 2 * DataCP[SpeedOne + 4] + DataCP[SpeedOne + 7] - DataCP[SpeedThree + 1] - 2 * DataCP[SpeedThree + 4] - DataCP[SpeedThree + 7];
                    RedTwo = DataCP[SpeedOne + 2] + 2 * DataCP[SpeedOne + 5] + DataCP[SpeedOne + 8] - DataCP[SpeedThree + 2] - 2 * DataCP[SpeedThree + 5] - DataCP[SpeedThree + 8];

                    PowerBlue = BlueOne * BlueOne + BlueTwo * BlueTwo;
                    PowerGreen = GreenOne * GreenOne + GreenTwo * GreenTwo;
                    PowerRed = RedOne * RedOne + RedTwo * RedTwo;                    if (PowerBlue > 65025) PowerBlue = 65025;           //  处理掉溢出值
                    if (PowerGreen > 65025) PowerGreen = 65025;                    if (PowerRed > 65025) PowerRed = 65025;

                    DataP[Speed] = LutP[PowerBlue];                     //  查表
                    DataP[Speed + 1] = LutP[PowerGreen];
                    DataP[Speed + 2] = LutP[PowerRed];

                    Speed += 3;                                         //  跳往下一个像素
                    SpeedOne += 3;
                }
            }
            Sw.Stop();            this.Text = "计算用时: " + Sw.ElapsedMilliseconds.ToString() + " ms";

            Bmp.UnlockBits(BmpData);                         //  必须先解锁,否则Invalidate失败         }
        Pic.Invalidate();
    }

Der erstellte Assembler-Code ist prägnant, hat eine klare Bedeutung und es fehlen im Vergleich dazu viele Anweisungen. Natürlich wird es viel schneller gehen.

Achten Sie auf diesen Code:

<span style="font-size: 13px;">BlueOne = DataCP[SpeedOne] + <span style="color: #800080;">2</span> * DataCP[SpeedTwo] + DataCP[SpeedThree] - DataCP[SpeedOne + <span style="color: #800080;">6</span>] - <span style="color: #800080;">2</span> * DataCP[SpeedTwo + <span style="color: #800080;">6</span>] - DataCP[SpeedThree + <span style="color: #800080;">6</span><span style="color: #000000;">];<br><br></span><span style="color: #800080;">00000318</span>  movzx       eax,<span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">edi] 
0000031c  mov         dword ptr [ebp</span>-<span style="color: #000000;">74h],eax 
0000031f  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">ebx] 
</span><span style="color: #800080;">00000323</span><span style="color: #000000;">  add         edx,edx 
</span><span style="color: #800080;">00000325</span><span style="color: #000000;">  add         eax,edx 
</span><span style="color: #800080;">00000327</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+<span style="color: #000000;">ecx] 
0000032b  mov         dword ptr [ebp</span>-<span style="color: #000000;">7Ch],edx 
0000032e  add         eax,edx 
</span><span style="color: #800080;">00000330</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+edi+<span style="color: #800080;">6</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000335</span>  mov         dword ptr [ebp-<span style="color: #000000;">78h],edx 
</span><span style="color: #800080;">00000338</span><span style="color: #000000;">  sub         eax,edx 
0000033a  movzx       edx,</span><span style="color: #0000ff;">byte</span> ptr [esi+ebx+<span style="color: #800080;">6</span><span style="color: #000000;">] 
0000033f  add         edx,edx 
</span><span style="color: #800080;">00000341</span><span style="color: #000000;">  sub         eax,edx 
</span><span style="color: #800080;">00000343</span>  movzx       edx,<span style="color: #0000ff;">byte</span> ptr [esi+ecx+<span style="color: #800080;">6</span><span style="color: #000000;">] 
</span><span style="color: #800080;">00000348</span>  mov         dword ptr [ebp-<span style="color: #000000;">80h],edx 
0000034b  sub         eax,edx 
0000034d  mov         dword ptr [ebp</span>-30h],eax </span>

Wenn Sie ihn durch ersetzen :

Die Geschwindigkeit des Codes ist tatsächlich langsamer als bei der reinen Array-Version. Was den Grund angeht, ist Übung das A und O. Ich kenne dieses Ergebnis jedenfalls nicht. Sie können sich auf einen Artikel von Tie Brother beziehen:

Talking .NET Type Public's Public, Fixed's Fixed

Natürlich können Sie auch kleine Aktionen weiter optimieren, Zum Beispiel movzx eax,byte ptr [esi+edi] In diesem Satz ist esi tatsächlich die Basisadresse des Arrays. Wenn Sie DataCP[SpeedOne] so schreiben, haben Sie jedes Mal diese Basisadresse + Offset. Wenn eine Zeigervariable direkt und dynamisch in Echtzeit gesteuert werden kann, sodass sie direkt auf die angeforderte Position zeigt, ist eine Hinzufügung weniger erforderlich. Obwohl die Optimierung nicht offensichtlich ist, kann sie grundsätzlich die zuvor in der Frage erwähnte Zeit von 300 ms erreichen. Den konkreten Code finden Sie im Anhang.

Viele Leute interessieren sich möglicherweise nicht für meine Dinge und sagen, dass es besser ist, diese Dinge auf die GPU zu werfen, als das, was Sie jetzt haben ... Ich hoffe, diese Freunde werden sie nicht angreifen Zu viel. Jeder hat seine eigenen Hobbys, ich mag nur CPU.

Weitere Serie zur Analyse des Prinzips des PhotoShop-Algorithmus – Stilisierung – Kanten finden. Für verwandte Artikel achten Sie bitte auf die chinesische 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