Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Guten morgen Gemeinde!
Ein Neuer Tag, ein neues problem :-) Ich versuche aus einem Bitmap eine Durchschnittsfarbe zu ermitteln. Bei dummzeuch wurde ich zwar fündig was mir bereits sehr half es zu beschleunigen, aber seine letzte Optimierung bekomme ich einfach nicht hin. Hier ist das was ich aus seinem guten Beispiel #2 gemacht habe, es funktioniert, aber doch recht langsam:
Delphi-Quellcode:
Sorry für meine schludrige code gestaltung, ist erst alpha phase.
function TfrmMain.GetAvgBmpColor: TColor;
type TRgbTriple = packed record // do not change the order of the fields, do not add any fields Blue: Byte; Green: Byte; Red: Byte; end; TRgbTripleArray = packed array[0..MaxInt div SizeOf(TRgbTriple) - 1] of TRgbTriple; PRgbTripleArray = ^TRgbTripleArray; var x, y: Integer; r, g, b: Integer; Pixel: TRgbTriple; Bmp: TBitmap; Filename: string; fs: TFileStream; wic: TWICImage; img: TImage; begin Filename := 'X:\Pfad\Bildname.ext'; if not FileExists(Filename) then Exit; bmp := TBitmap.Create; wic := TWICImage.Create; img := TImage.Create(Self); fs := TFileStream.Create(Filename, fmOpenRead); try bmp.PixelFormat := pf24bit; fs.Position := 0; wic.LoadFromStream(fs); Img.Picture.Assign(wic); bmp.Width := Img.Picture.Width; bmp.Height := Img.Picture.Height; bmp.Canvas.Draw(0, 0, Img.Picture.Graphic); r := 0; g := 0; b := 0; Assert(bmp.PixelFormat = pf24bit); for y := 0 to Pred(bmp.Height) do begin for x := 0 to Pred(bmp.Width) do begin Pixel := PRgbTripleArray(bmp.Scanline[y])^[x]; r := r + Pixel.Red; g := g + Pixel.Green; b := b + Pixel.Blue; end; end; r := r div (bmp.Width * bmp.Height); g := g div (bmp.Width * bmp.Height); b := b div (bmp.Width * bmp.Height); finally Result := RGB(r, g, b); bmp.Free; fs.Free; wic.Free; img.Free; end; end; Weiß jemand wie man das eventuell mit diesem Code #3 hinbekommt?
Delphi-Quellcode:
Ich bekomme immer den Fehler das "Pixel" nicht mit "InPixel^" kompatibel ist und weiß gerade nicht weiter.
// if you are using Delphi 2007 or older you need to correct the NativeInt declaration from 8 bytes to 4 bytes:
{$IF SizeOf(Pointer) = 4} type NativeInt = Integer; {$IFEND} function AddToPtr(const _Ptr: Pointer; _Offset: NativeInt): Pointer; inline; begin Result := Pointer(NativeInt(_Ptr) + _Offset); end; function PtrDiff(const _Ptr1, _Ptr2: Pointer): NativeInt; inline; begin Result := NativeInt(_Ptr1) - NativeInt(_Ptr2); end; var BytesPerPixel: NativeInt; InScanLine0: Pointer; InBytesPerLine: NativeInt; OutScanLine0: Pointer; InBytesPerLine: NativeInt; InPixel: PRgbTriple; OutPixel: PRgbTriple; // ... BytesPerPixel := SizeOf(Pixel) InScanLine0 := InBmp.ScanLine[0]; InBytesPerLine := NativeInt(_InBmp.ScanLine[1]) - NativeInt(InScanLine0); OutScanLine0 := _OutputBmp.ScanLine[0]; OutBytesPerLine := NativeInt(_OutBmp.ScanLine[1]) - NativeInt(OutScanLine0); OutPixel := OutScanLine0; for y := 0 to Height - 1 do begin for x := 0 to Width - 1 do begin InPixel := AddToPtr(InScanLine0, InBytesPerLine * y + x * BytesPerPixel); Pixel := InPixel^; doSomething(Pixel); OutPixel := AddToPtr(OutScanLine0, OutBytesPerLine * y + x * BytesPerPixel); OutPixel^ := Pixel; end; end; |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Delphi-Quellcode:
function TForm3.GetAvgBmpColor: TColor;
type TRgbTriple = packed record // do not change the order of the fields, do not add any fields Blue: Byte; Green: Byte; Red: Byte; end; TRgbTripleArray = packed array[0..MaxInt div SizeOf(TRgbTriple) - 1] of TRgbTriple; PRgbTripleArray = ^TRgbTripleArray; var x, y: Integer; r, g, b: Integer; Pixel: TRgbTriple; Bmp: TBitmap; Filename: string; wic: TWICImage; Resolution: Integer; ScanLinePtr: Pointer; begin Result := 0; Filename := 'Der magische Pfad'; if not FileExists(Filename) then Exit; bmp := TBitmap.Create; wic := TWICImage.Create; try wic.LoadFromFile(Filename); bmp.Assign(wic); bmp.PixelFormat := pf24bit; r := 0; g := 0; b := 0; Assert(bmp.PixelFormat = pf24bit); for y := 0 to Pred(Bmp.Height) do begin ScanLinePtr := bmp.Scanline[y]; // der springende Punkt! for x := 0 to Pred(Bmp.Width) do begin Pixel := PRgbTripleArray(ScanLinePtr)^[x]; r := r + Pixel.Red; g := g + Pixel.Green; b := b + Pixel.Blue; end; end; Resolution := (bmp.Width * bmp.Height); r := r div Resolution; g := g div Resolution; b := b div Resolution; Result := RGB(r, g, b); finally bmp.Free; wic.Free; end; end; |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Zitat:
Der erste code ist aus seinem blog nur halt für mich angepasst. Der zweite code ist 1:1 aus seinem blog, es sind 3 teile die aufeinander aufbauen. //edit ich bin ja auch noch nicht ganz wach, hallo dummzeuch, DANKE für deine blog!! @TiGü: WAHNSINN!! Danke für code-optimierung plus das einarbeiten des dritten codes!! |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
In einigen der hier geposteten Beispiele befürchte ich bei grossen Bitmaps Integerüberlauf in r,g,b.
Wenn zum Beispiel jedes Pixel einer Bitmap einen Rotwert=255 aufweist, dann läuft r nach >maxint/255 Pixeln über. Oder etwas anders geschrieben: Eine quadratische Bitmap mit >2901 Pixel Seitenlänge und Rotwert=255 wäre nicht gut... |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Du kannst dir noch ein paar Millisekunden sparen, wenn du nicht den "Umweg" über TBitmap machst:
Delphi-Quellcode:
Kam bei mir zumindest das gleiche Ergebnis bei rum, bitte nachprüfen!
uses
Winapi.Wincodec; type TWICImageHelper = class helper for TWICImage function GetAverageColor: TColor; end; function TForm3.GetAvgBmpColor: TColor; var Filename: string; wic: TWICImage; begin Result := 0; Filename := 'Dein Pfad zur Bilddatei'; if not FileExists(Filename) then Exit; wic := TWICImage.Create; try wic.LoadFromFile(Filename); Result := wic.GetAverageColor; finally wic.Free; end; end; { TWICImageHelper } function TWICImageHelper.GetAverageColor: TColor; type // copy from Vcl.Graphics PRGBQuadArray = ^TRGBQuadArray; TRGBQuadArray = array [Byte] of Winapi.Windows.TRGBQuad; var LWicBitmap: IWICBitmapSource; Stride: Cardinal; Buffer: array of Byte; x, y: Integer; BGRAPixel: TRGBQuad; r, g, b, Resolution, LBytesPerScanline: Integer; ScanLinePtr: Pointer; begin Result := 0; with Self do begin if FWicBitmap = nil then Exit; FWicBitmap.GetSize(FWidth, FHeight); Stride := FWidth * 4; SetLength(Buffer, Stride * FHeight); WICConvertBitmapSource(GUID_WICPixelFormat32bppBGRA, FWicBitmap, LWicBitmap); LWicBitmap.CopyPixels(nil, Stride, Length(Buffer), @Buffer[0]); r := 0; g := 0; b := 0; LBytesPerScanline := BytesPerScanline(FWidth, 32, 32); for y := 0 to FHeight - 1 do begin ScanLinePtr := PByte(@Buffer[0]) + y * LBytesPerScanline; for x := 0 to FWidth - 1 do begin BGRAPixel := PRGBQuadArray(ScanLinePtr)^[x]; r := r + BGRAPixel.rgbRed; g := g + BGRAPixel.rgbGreen; b := b + BGRAPixel.rgbBlue; end; end; Resolution := FWidth * FHeight; end; r := r div Resolution; g := g div Resolution; b := b div Resolution; Result := RGB(r, g, b); end; |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Könnte eine Änderung auf
Delphi-Quellcode:
da helfen?
var r, g, b: Int64;
Zitat:
Vom Geschwindigkeitsvergleich, meine ur-ur Version war ein Trabbi, danach fand ich dummzeuchs blog und ich hatte einen Sportwagen. Deine erste version war ein Tiefergelegter aufgemotzter Sportwagen, nun ist es ein Formel-1 Wagen! Ich nahm in Post #1 nur das WIC weil ich vorher nicht wissen kann was für ein Bildformat reinkommt, so als universal Empfänger halt, Konvertierung zu bmp nahm ich weil ich da wusste das es ein ScanLine gibt für die Punkte. Damit hast Du mir echt eine riesen Freude gemacht! Ganz herzlich vielen Dank für die Mühe, das ist Top! :thumb: |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Jo, gerne!
Was dem einen sein Problem, ist mein tägliches Code Kata. :wink: Man kann das bestimmt noch weiter optimieren. Bei der Addition der drei 32-Bit-Zahlen (oder 64-Bit, wenn man in keinen Überlauf will) gibt es bestimmt irgendeine kluge x64-Assemblerfunktion. Oder man schreibt die Schleifen um, so das nur jedes zweite oder vierte Pixel angeguckt wird. Je nachdem, wie genau man den Durchschnitt braucht. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Trotzdem, theoretisch gibt es Überläufe erst ab Bildgrößen von 24000x24000. Ich denke es ist noch ein bischen Zeit bis dahin :stupid: Aber ich würde auch einen Test o.ä. anlegen, der aufpoppt falls solche Größen mal erreicht werden. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
64 Bit compiliert, kannst Int64 benutzen und hast genug Platz.
Hier wird es ja von der CPU behandelt. In Win32 machen das Funktionen in der System.pas. OK, die Addition geht dennoch recht schnell und am Ende das eine DVI, da stört der etwas langsamere Code kaum. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Bitte alle Kirchen im Dorf lassen, er verwendetet das für Desktophintergründe.
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Zitat:
Schneller als via Scanline geht's fast nicht. Bei Verwendung von int64 muss man - wie himitsu schreibt - schon kurz nachdenken, wie das Verhalten in Bezug auf Speed unter 32 bzw. 64 bit ist. Bei Verwendung von integer für die rgb Werte stösst die Funktion auch bei diesem "Kirche im Dorf Verwendungszweck" zu rasch an ihre Grenzen. r kann maximal den Wert maxint=2^31-1 speichern. Wenn in jedem Pixel der Bitmap der Rotwert 255 beträgt, kannst du also maximal trunc((2^31-1)/255) Pixelwerte zusammenzählen. Bei einer quadratischen Bitmap bist du bei einer Seitenlänge s > sqrt((2^31-1)/255)) am Ende der Fahnenstange angelangt. Eine 2901*2901 Bitmap ist also gerade noch berechenbar. Grösser darf sie nicht sein. Oder direkt auf den Anwendungszweck bezogen: 4K Monitor: r erreicht maximal 3840*2160*255 = 2’115’072’000 < 2^31-1, geht gerade noch. Bereits bei meinem 8K Monitor, den ich nicht habe, fliegt's einem um die Ohren. Es ist mir bewusst, dass wir hier einen "Nebenschauplatz" diskutieren. Aber es ist wichtig, dass Code, welcher die eigenen vier Wände verlässt so geschrieben wird, dass er auch in einer Flugzeugsoftware eingebaut zuverlässig funktioniert. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Bei einem einfarbigen Hintergrund gibt es aber kein Bitmap-Wallpaper, da wird ein RGB-Wert aus der Registry gelesen.
Wer macht sich denn einen 8K Hintergrund als einfarbiges (!) Bitmap? Wer wirklich sicher gehen will, der ändert sich den Code halt. Kirchen -> Dörfer! |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Ja @TiGü, ich nutze das ausschließlich für das Theming Projekt aber gebe natürlich Recht das es diese "8k * 1 Farbe" Möglichkeit irgendwie geben könnte. Sinnfrei aber ja.
Auf Int64 ist es bereits umgestellt, aber auch das hat seine Grenzen. Mir fehlt da die Erfahrung wie man es "abfangen" könnte damit die Berechnung einfach ab einer gewissen Zahl aufhört weiterzuzählen, würde es aber gerne zur Sicherheit mit einbauen.
Delphi-Quellcode:
Macht man das so in etwa? (Nur hier im Edit getippst...)
var
r: Int64; i: Cardinal; begin r := 0; for i := 0 to High(Cardinal) do begin if r < High(Int64) then r := r + 1; end; end; |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Interessante Diskussion. Da kommt mir aber noch eine abstruse Idee: Man macht ein "Resize" des Bildes auf 1x1 Pixel großes Bild und schaut sich dann nur noch dieses Pixel an. Läuft das Resize auf der GPU wäre das auch ganz schon flott.
Ich habe natürlich keine Ahnung, wieviel Mühe sich so ein Bildverkleinerungsalgo macht, wenn das Ziel nur noch 1 Pixel groß ist... Viele Grüße Michael :duck: |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Ich hab dir mal etwas zusammengestoppelt. Aufruf mit GetAvgColor(Dateiname) oder GetAvgColor(Bitmap) Mit TestGetAvgColor; hab ich das Ergebnis und die Performance getestet und mit der Funktion aus #3 verglichen. Die zurückgegebenen Durchschnittsfarben sind identisch, die Ausführungszeiten sind dagegen höchst unterschiedlich.
Delphi-Quellcode:
FUNCTION AvgColor(P,LO,W,H:NativeInt):TColor;
// P : Zeiger auf das erste Pixel der ersten Zeile einer Bitmap // LO : Offset (in Bytes) auf die jeweils nächste Zeit // W : Breite der Bitmap // H : Höhe der Bitmap {$IFDEF CPUX86} const OfsBlueLo=0; OfsBlueHi=OfsBlueLo+4; OfsGreenLo=OfsBlueHi+4; OfsGreenHi=OfsGreenLo+4; OfsRedLo=OfsGreenHi+4; OfsRedHi=OfsRedLo+4; OfsCount=OfsRedHi+4; OfsH=OfsCount+4; OfsLO=OfsH+4; OfsStack=OfsLO+4; {$ENDIF} asm {$IFDEF CPUX86}// EAX=P, EDX=LO, ECX=W, Stack=H // Register retten push ebx push edi push esi // LO, H und Anzahl Pixel auf Stack legen push edx // LO mov ebx,H push ebx // H imul ebx,ecx push ebx // Anzahl Pixel // Summen auf Stack push 0 push 0 push 0 push 0 push 0 push 0 // ESI hinter erste Zeile lea ebp,[ecx+ecx*2] lea esi,[eax+ebp] neg ebp // Summen ermitteln @Loop1: mov edi,ebp xor ebx,ebx xor ecx,ecx xor edx,edx @Loop2: movzx eax,byte[esi+edi] // Blue add ebx,eax movzx eax,byte[esi+edi+1] // Green add ecx,eax movzx eax,byte[esi+edi+2] // Red add edx,eax add edi,3 jl @Loop2 // Nächstes Pixel add [esp+OfsBlueLo],ebx // Summe Blue adc [esp+OfsBlueHi],0 add [esp+OfsGreenLo],ecx // Summe Green adc [esp+OfsGreenHi],0 add [esp+OfsRedLo],edx // Summe Red adc [esp+OfsRedHi],0 // Zeiger auf nächste Zeile add esi,[esp+OfsLO]; dec [esp+OfsH] jnz @Loop1 // AvgWerte erbitteln mov eax,[esp+OfsBlueLo] mov edx,[esp+OfsBlueHi] div [esp+OfsCount] movzx ecx,al shl ecx,16 mov eax,[esp+OfsGreenLo] mov edx,[esp+OfsGreenHi] div [esp+OfsCount] mov ch,al mov eax,[esp+OfsRedLo] mov edx,[esp+OfsRedHi] div [esp+OfsCount] mov cl,al mov eax,ecx // Result=AvgColor // Stack bereinigen add esp,OfsStack // Register wieder herstellen pop esi pop edi pop ebx {$ELSE} // RCX=P, RDX=LO, R8=W, R9=H push r12 push r13 push r14 // Anzahl Pixel in R13 mov r13,R8 imul R13,R9 // R11 hinter erste Zeile, R12=-W*3 lea r12,[r8+r8*2] lea r11,[rcx+r12] neg r12 // Summen ermitteln xor rcx,rcx // Summe Blue xor r8,r8 // Summe Green xor r10,r10 // Summe Red @Loop1: mov r14,r12 @Loop2: movzx rax,byte[r11+r14] // Blue add rcx,rax movzx rax,byte[r11+r14+1] // Green add r8,rax movzx rax,byte[r11+r14+2] // Red add r10,rax add r14,3 jl @Loop2 // Nächstes Pixel // Zeiger auf nächste Zeile add r11,rdx; dec r9 jnz @Loop1 // AvgWerte erbitteln mov rax,rcx // Blue xor rdx,rdx div r13 movzx rcx,al shl rcx,16 mov rax,r8 // Green xor rdx,rdx div r13 mov ch,al mov rax,r10 xor rdx,rdx div r13 mov cl,al mov rax,rcx // Result=AvgColor // Register wieder herstellen pop r14 pop r13 pop r12 {$ENDIF} end;
Delphi-Quellcode:
FUNCTION GetAvgColor(Bmp:TBitmap):TColor; overload;
var LO,P:NativeInt; begin Assert(Bmp.PixelFormat=pf24bit); Assert(Bmp.Width>0); Assert(Bmp.Height>0); P:=NativeInt(Bmp.ScanLine[0]); LO:=NativeInt(Bmp.ScanLine[1])-P; Result:=AvgColor(P,LO,Bmp.Width,Bmp.Height); end;
Delphi-Quellcode:
FUNCTION GetAvgColor(Dsn:String):TColor; overload;
var Bmp:TBitmap; begin Result:=0; if not FileExists(Dsn) then raise Exception.Create('Datei "'+Dsn+'" nicht gefunden'); Bmp:=TBitmap.Create; Bmp.LoadFromFile(Dsn); Result:=GetAvgColor(Bmp); Bmp.Free; end;
Delphi-Quellcode:
// Aus #3 TiGü (Filename hier als Parameter statt lokale Variable)
function GetAvgBmpColor(Filename:String): TColor; type TRgbTriple = packed record // do not change the order of the fields, do not add any fields Blue: Byte; Green: Byte; Red: Byte; end; TRgbTripleArray = packed array[0..MaxInt div SizeOf(TRgbTriple) - 1] of TRgbTriple; PRgbTripleArray = ^TRgbTripleArray; var x, y: Integer; r, g, b: Integer; Pixel: TRgbTriple; Bmp: TBitmap; // Filename: string; wic: TWICImage; Resolution: Integer; ScanLinePtr: Pointer; begin Result := 0; //Filename := 'Der magische Pfad'; if not FileExists(Filename) then Exit; bmp := TBitmap.Create; wic := TWICImage.Create; try wic.LoadFromFile(Filename); bmp.Assign(wic); bmp.PixelFormat := pf24bit; r := 0; g := 0; b := 0; Assert(bmp.PixelFormat = pf24bit); for y := 0 to Pred(Bmp.Height) do begin ScanLinePtr := bmp.Scanline[y]; // der springende Punkt! for x := 0 to Pred(Bmp.Width) do begin Pixel := PRgbTripleArray(ScanLinePtr)^[x]; r := r + Pixel.Red; g := g + Pixel.Green; b := b + Pixel.Blue; end; end; Resolution := (bmp.Width * bmp.Height); r := r div Resolution; g := g div Resolution; b := b div Resolution; Result := RGB(r, g, b); finally bmp.Free; wic.Free; end; end;
Delphi-Quellcode:
PROCEDURE TestGetAvgColor;
const Width=2900; Height=2900; Color=$010203; var T0,T1,T2:Cardinal; R:TRect; Bmp:TBitmap; CL1,CL2:TColor; Dsn:String; begin Bmp:=TBitmap.Create; Bmp.PixelFormat:=pf24Bit; Bmp.SetSize(Width,Height); SetRect(R,0,0,Bmp.Width,Bmp.Height); Bmp.Canvas.Brush.Color:=Color; Bmp.Canvas.FillRect(R); Dsn:=ExtractFilePath(ParamStr(0))+'Test.bmp'; Bmp.SaveToFile(Dsn); Bmp.Free; T0:=GetTickCount; CL1:=GetAvgColor(Dsn); T1:=GetTickCount; CL2:=GetAvgBmpColor(Dsn); T2:=GetTickCount; ShowMessage('$'+IntToHex(CL1,8)+' '+IntToStr(T1-T0)+#13+ '$'+IntToHex(CL2,8)+' '+IntToStr(T2-T1)); end; |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
vom Prinzip her so:
Delphi-Quellcode:
r := 0;
Resolution := (bmp.Width * bmp.Height); for i := to do r := r + Pixelwert[i].red / Resolution; r := round(r); |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Mathematisch, von den Grenzen der Floats, macht es leider keinen Unterschied, ob man jedes Pixel dividiert und addiert, oder ob man erst alles addiert und erst am Ende dividiert.
Lezteres ist aber schneller, da "ganz" oft dividieren natürlich langsamter ist, als nur Einmal. Bei ganz ganz ganz vielen Pixeln ist der Wert irgendwann so groß, dass die nächste Addition des kleinen Wertes, abgeschnitten wird. Aber Double hat mit 52 Bit (52+11) mehr als der 32 Bit-Integer, somit dauert es da länger, bis zum Rechenfehler/überlauf, und gegenüber 64 Bit, und dessen Integeroperation (64 Bit-compilat), ist Float in der FPU dann wieder langsamer. (ja, eine einzelne FPU gibt es physisch nicht mehr) Single mit 23 Bits (23+8) raucht schon zu etwas früher ab. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Liste der Anhänge anzeigen (Anzahl: 1)
Zitat:
Ich habe ihn gegen den Turbo von TiGü antreten lassen, siehe Anhang. Muss ich noch eine besondere Einstellung vornehmen damit mir Dein Code einen Farbwert über 0 liefert? Getestet mit Delphi Rio, 32-bit build, Release, unter Windows 10 64bit aktuellste patches. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Coole Idee... ;-). Funktioniert auch gut... und sicher schnell, u.v.a. auch auf dem TrillionK Monitor. Ich habe keine Zeit fürs Messen. Die Werte sind (was ich auch erwartet habe, GrafikerInnen berechnen den Durchschnittswert wahrscheinlich eher über ein anderes Modell und etwas anders) nicht ganz gleich (wie beim RGB DS Rechnen). Bei einfarbigen Bitmaps aber natürlich identisch. Man könnte natürlich statt auf ein 1x1 Pixel zu skalieren eine etwas grössere Zielbitmap wählen. Der Code wird ultrakurz: GDI+
Delphi-Quellcode:
uses GDIPOBJ, GDIPAPI;
gr := TGPGraphics.Create( bmap ); gr.ScaleTransform( 1/bmap.GetWidth, 1/bmap.GetHeight ); gr.DrawImage( bmap, 0,0 ); bmap.GetPixel(0,0,col); |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
So wie ich es verstanden habe fügt sich ein RBG wert aus 3 DWORDs zusammen.
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Es ist ein DWORD, dass aus 3+1 Bytes besteht :wink:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
@Michael II
Nun passiert da was... aber mein Ergebniss ist falsch.
Delphi-Quellcode:
Falls Du Dich nochmal reinklinken könntest um mich zu korrigieren, das wäre nett!
function GetAvgGDIColor(const Filename: string): TColor;
var gr: TGPGraphics; Bmap: TGPBitmap; col: TGPColor; begin Result := 0; bmap := TGPBitmap.Create(Filename); try gr := TGPGraphics.Create( bmap ); try gr.ScaleTransform( 1/bmap.GetWidth, 1/bmap.GetHeight ); gr.DrawImage( bmap, 0,0 ); bmap.GetPixel(0,0, col); Result := RGB(GetRValue(col), GetGValue(col), GetBValue(col)); finally gr.Free; end; finally bmap.Free; end; end; Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Mathematisch gilt ja:
Code:
Und bei hohen Zahlen sind evtl. Rundungsfehler eher unwichtig.
a1 + a2 + a3 + a4 a1 a2 a3 a4 a1 + a2 a3 + a4
------------------- = --- + --- + --- + --- = --------- + --------- 4 4 4 4 4 4 4 |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Habe auf die Schnelle mit Cardinal gerechnet, und mangels Taschenrechner nur geschätzt. Stimmt Integer ist signifikant kleiner, trotzdem würde ich mit dem vollen Umfang und 8-Bit rechnen, wieso -1 ? s > sqrt( (2^31)/256 ) = sqrt((2^31)/2^8 ) = sqrt( (2^31-8) ) = sqrt( 2^23 ) = 2^( 23 / 2 ) = 2^11.5 = 2896 Also ich komme auf 2896x2896, ist wirklich zu wenig. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Zu deiner Frage wegen wieso -1. Du hast das Vorzeichenbit vergessen. Die grösste positive integer Zahl (maxint) sieht so aus m = 011111111 11111111 11111111 11111111. Wenn du zu m 1 addierst, hättest du 10000000 00000000 00000000 00000000 = 2^32. Die Zahl vor 2^32 hat damit den Wert maxint=2^32-1. 2^32 entspricht bei integer dem negativen Wert -2^32. Oder wenn du's lieber via geometrische Reihe rechnen willst: 011111111 11111111 11111111 11111111 hat den Wert s = 2^0+2^1+2^2+....+2^30 und 2s = 2^1+...+2^31 Subtrahierst du von Zeile 2 Zeile 1 ergibt sich s=2^31-2^0 = 2^31-1. Zu deinem Einwand, man sollte durch 2^8=256 teilen. Kurze Antwort: Nein. Lange Antwort: r liegt im Bereich [0..255] und nicht im Bereich [0..256]. Du musst dir also überlegen wie oft 255 in 2^32-1 Platz hat => (2^32-1)/255 Mal. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Nein, man muß gucken, wie oft 256 Platz hat,
denn die 0 ist auch ein gültiger Wert. 1..255 bzw. 0..254 wäre Platz für 255. Aber keine Sorge, auch andere vergessen die 0 gern. Daher wurde die Zahl 0 in vielen Kulturen auch erst sehr spät erfunden. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
In der Funktion
Delphi-Quellcode:
steht in der vorletzten Zeile
GetAvgColor(Dsn:String):TColor;
Code:
Ändere das bitte in
GetAvgColor(Bmp);
Code:
Hintergrund ist, dass ich ursprünglich die Funktion
Result:=GetAvgColor(Bmp);
Delphi-Quellcode:
hatte, also die, mit einer Bitmap als Parameter.
GetAvgColor(Bmp:TBitmap):TColor;
Die Funktion mit dem Dateinamen als Parameter habe ich später hinzugefügt, aber nicht mehr getestet. Dummerweise hatte ich da vergessen, Result zu setzen. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
|
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Dein Code ist fast voll ok so. Einzig bei der Auswertung von col.... Du musst darauf achten, dass TGPColor (anders als TColor!) wie folgt die Farbe speichert: ARGB d.h. ALPHA ROT GRÜN BLAU Und die von dir verwendeten Winapi.Windows TColor Funktionen GetRValue... rechnen mit (A)BGR=(ALPHA) BLAU GRÜN ROT. Du willst ein Resultat in TColor: Wenn du GetRValue auf col : TGPColor anwendest wirst du den Blauanteil der Farbe erhalten. Entsprechend: Wenn du GetBValue auf eine TGPColor anwendest wirst du den Rotanteil erhalten. Testen könntest du die Sache zum Beispiel so. Du erstellst eine hbit: TBitMap von der Grösse 500x500. Wir setzen alle Pixel auf RGB(255,128,64). Das Pixel (0,0) auf RGB(0,0,0).
Delphi-Quellcode:
Nun lässt du deine Funktionen auf hbit (mit anderen Farben auch prüfen!) los.
hbit := TBitMap.Create;
hbit.SetSize( 500, 500 ); for x := 0 to hbit.Height-1 do for y := 0 to hbit.Width-1 do hbit.Canvas.Pixels[x,y] := rgb(255,128,64); hbit.Canvas.Pixels[0,0] := rgb(0,0,0); fn := 'C:\Users\micha\Desktop\test.bmp'; hbit.SaveToFile( fn ); hbit.Free; TiGü via scanline liefert - da dort nach dem Summieren div anzahlpixels (statt trunc(summe/anzahlpixels+0.5) verwendet wird - als Durchschnittsfarbe (254,127,63). Nun lässt du die Funktion auf grizzlys GDI+ Idee, bzw. deine Lösung los: Du erhältst wie erwartet (255,128,64). |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
@KodeZwerg:
In #16 wurde in den Raum geworfen, ob man nicht einfach ein Resize auf 1x1 Pixel machen kann. Ich hab das mal geprüft und in meiner Funktion TestGetAvgColor; vor dem Bmp.Free folgendes eingefügt:
Delphi-Quellcode:
Das ShowMessage am Ende hab ich abgeändert in:
T3:=GetTickCount;
Bmp2:=TBitmap.Create; Bmp2.PixelFormat:=pf24Bit; Bmp2.SetSize(1,1); SetRect(R,0,0,1,1); Bmp2.Canvas.StretchDraw(R,Bmp); CL3:=Bmp2.Canvas.Pixels[0,0]; Bmp2.Free; T3:=GetTickCount-T3; ShowMessage('$'+IntToHex(CL1,8)+' '+IntToStr(T1-T0)+#13+ '$'+IntToHex(CL2,8)+' '+IntToStr(T2-T1)+#13+ '$'+IntToHex(CL3,8)+' '+IntToStr(T3)); Scheint zu funktionieren, was aber auch daran liegen könnte, dass in der Testprozedur alle Pixel die gleiche Farbe haben. Korrektur: Hab es gerade mit einem echten Bild geprüft. Die Methode, das Bild auf 1x1 Pixel zu reduzieren, liefert eine andere Durchschnittsfarbe. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Nein das stimmt nicht. Kulturen hin oder her. Schau noch einmal in den Code, um welchen es geht. Dort wird eine Variable r vom Typ integer auf 0 gesetzt. Und danach wird Pixel für Pixel geschaut, welcher Rotanteil vorliegt. Dieser Rotanteil wird jedes Mal zu r addiert. Nun wollen wir herausfinden, wann r frühestens überläuft. r läuft in Kulturen wo der Rotanteil aller Pixel immer 0 ist gar nie über, da r konstant 0 bleibt. D.h. in solchen Kulturen kannst du unendlich viele Rotanteile zu r addieren. Es gibt aber auch Kulturen (zum Beispiel bei gewissen Kulturen im Pferdekopfnebel), in welchen der Rotanteil aller Pixel immer maximal maxbyte = 11111111(bin) = 255(dec) ist. Wenn du dir jetzt überlegen willst, wann es bei der Summenbildung frühestens knallt, dann musst du einen Taschenrechner zur Hand nehmen und eine 0 eintippen. Nun addierst du 255 für Pixel 1, 255 für Pixel 2, 255 für Pixel 3... Nach p Pixeln bist du bei p*255 angelangt. Irgendwann fragst dich, wann p*255 > maxint erreicht wird. Diese Ungleichung kannst du lösen: p>maxint/255. Die Lösung lautet: Nach p>maxint/255 Pixeln läuft r über. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Ich nehme auch an, dass GDI+ beim Skalieren auf 1x1 nicht oft die gleiche Durchschnittsfarbe berechnet. Du könntest ja auch auf ein 10x10 oder ähnlich skalieren und dort rechnen. Die 1x1 Bitmap Farbe hängt natürlich u.a. vom verwendeten Skalier-Algorithmus ab. Bliebe die Frage: Welche Farbe ein Mensch als die "bessere" Durchschnittsfarbe bewerten würde. Hast du irgendwo einen Link auf wissenschaftliche Literatur (wäre interessant), wo sowas wie eine "Durchschnittsfarbe" besprochen wird. Ich nehme an, dass in der Grafikbranche nicht einfach über RGB addiert und der Mittelwert genommen wird. Da werden doch sicher andere Modelle "bemüht"? |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Liste der Anhänge anzeigen (Anzahl: 1)
@Amateurprofi: Ich war da auch blind, es klappt nun bestens!
@Michael II: Ich habe es nun hinbekommen das alles funktioniert! Danke mit den Alpha-Kanal Tipp, das hat mich irgendwie auf die richtige Spur geführt auch wenn ich teilweise nur Bahnhof verstanden habe ;) Im Anhang, für alle Interessierten, meine Testergebnisse bei einem Durchlauf incl Abbildung des GDI+ codes. (GetRed() GetGreen() GetBlue() war die Lösung, so hoffe ich jedenfalls) Ich muss fairer Weise sagen ich die Topic falsch betitelt habe, es müsste nicht Bitmap sondern Bild heißen. Ich werde es nochmal testen wenn ich eine "TBitmap bzw HBITMAP" Konvertierung eingepflanzt habe damit es mehr als nur reine Bitmaps annehmen kann. Ausgangspunkt sollte in allen Fällen ein WIC-Image sein, damit ich alles was Microsoft verarbeitet auch unterstützen kann. Diese Konvertierung von WIC nach ein passendes Bitmap-Format wird sich dann natürlich negativ auswirken und wahrscheinlich den jetzigen Vorsprung zumindest etwas egalisieren. Danke Euch beiden für diese sehr Interessanten Beiträge!! |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Reine Bitmaps habe ich gar keine zum Testen damit ich auch Eure (Assembler und GDI+) besser testen kann. |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
Anstelle auf 1x1, wäre meine Überlegung ein sinnvolles Resize erst dann durchzuführen wenn Int64 für die Berechnung nicht mehr ausreicht. Es wurden zwar viele Zahlen in den Raum geworfen, aber wie sollte man da Sinnvoll vorgehen?... Ein Bild besteht ja aus zwei Dimensionen, ein Int64 ist nur eine. Was ich meine, gibt es eine logik die so etwas berechnen kann, ein bild kann ja 100 million pixel Hoch aber nur 1 pixel breit sein. Andersrum genauso. Oder eben in beide Dimensionen sehr sehr viele Pixel besitzen. Also es gäbe halt mehr als nur eine Möglichkeit diese berechnung hier zum platzen zu bringen. Ein Resize auf eine Dimension die es nicht zum platzen bringt, das wäre das Sahnetörtchen ;-) |
AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
Zitat:
High(Uint64)=2^64-1=18446744073709551615 Da du momentan RGB Werte im Bereich 0..255 verwendest, kannst du nach dem grössten p (Anzahl Pixel deiner Bitmap) suchen, welches p*255 <= 1844674407370955165 erfüllt. p(max)=72’340’172’838’076’673 D.h. du kannst enorm grosse Bitmaps (mit maximal p(max) Pixeln) verarbeiten. |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:13 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz