Heim > Artikel > Web-Frontend > Serie zur Analyse des Prinzips des PhotoShop-Algorithmus – Stilisierung – Kanten finden.
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:
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:
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 Randdes 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:
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. 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. 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?
Gleicher Effekt, gleiches Bild, Berechnungszeit 330ms . Schauen wir uns den Assembler-Code desselben Codes an: 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: 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. 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();
}
<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. 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();
}
<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>