Delphi-PRAXiS
Seite 2 von 6     12 34     Letzte »    

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Multimedia (https://www.delphipraxis.net/16-multimedia/)
-   -   Delphi Durchschnittsfarbe eines Bitmap "schnell" ermitteln (https://www.delphipraxis.net/207856-durchschnittsfarbe-eines-bitmap-schnell-ermitteln.html)

himitsu 11. Mai 2021 12:09

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.

TiGü 11. Mai 2021 12:21

AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
 
Bitte alle Kirchen im Dorf lassen, er verwendetet das für Desktophintergründe.

Michael II 11. Mai 2021 13:03

AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
 
Zitat:

Zitat von Rollo62 (Beitrag 1489128)
Zitat:

Zitat von Michael II (Beitrag 1488991)
In einigen der hier geposteten Beispiele befürchte ich bei grossen Bitmaps Integerüberlauf in r,g,b.

Ja das sprang mir auch gerade ins Auge.
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.

Zitat:

Zitat von TiGü (Beitrag 1489137)
Bitte alle Kirchen im Dorf lassen, er verwendetet das für Desktophintergründe.

Und genau deshalb ist es wichtig, dass der Fragende eine wie verlangt schnelle und funktionierende Lösung hat.
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.

TiGü 11. Mai 2021 14:31

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!

KodeZwerg 11. Mai 2021 14:47

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:
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;
Macht man das so in etwa? (Nur hier im Edit getippst...)

grizzly 11. Mai 2021 14:57

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:

Michael II 11. Mai 2021 15:01

AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
 
Zitat:

Zitat von TiGü (Beitrag 1489151)
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!

OK ich hatte überlesen, dass KodeZwerg den Durchschnittswert aus einer einfarbigen Bitmap errechnen wollte. Wenn dem echt so ist (ich verstand sein Anliegen so, dass er den RGB-Durchschnittswert eines Bild berechnen wollte; das macht ja deine Funktion auch), dann muss KodeZwerg den Durchschnitt nicht berechnen. Bei einem einfarbigen Bitmap reicht es irgend ein Pixel zu nehmen, da ja dann alle gleich sind. Dann gibt es auch beim neuen "TrillionK Monitor" in der Nähe von Alpha Centauri exakt 0 Probleme. Ich glaube wir schreiben aneinander vorbei, ich habe das Problem offensichtlich komplett überschätzt, ich weiss nicht wieso, ich bin hier raus, die meisten sind ja total glücklich ;-).

Amateurprofi 11. Mai 2021 15:16

AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
 
Zitat:

Zitat von KodeZwerg (Beitrag 1488977)
Guten morgen Gemeinde!

Ein Neuer Tag, ein neues problem :-)
...
Ich versuche aus einem Bitmap eine Durchschnittsfarbe zu ermitteln.
...
Hier ist das was ich aus seinem guten Beispiel #2 gemacht habe, es funktioniert, aber doch recht langsam:
...

Hallo KodeZwerg:
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;

Jens01 11. Mai 2021 15:24

AW: Durchschnittsfarbe eines Bitmap "schnell" ermitteln
 
Zitat:

Ja das sprang mir auch gerade ins Auge.
Trotzdem, theoretisch gibt es Überläufe erst ab Bildgrößen von 24000x24000.
Ich denke es ist noch ein bischen Zeit bis dahin
Man kann auch einfach den Durchschnittswert aufaddieren.

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);

himitsu 11. Mai 2021 15:52

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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 09:57 Uhr.
Seite 2 von 6     12 34     Letzte »    

Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz