Einzelnen Beitrag anzeigen

Benutzerbild von dummzeuch
dummzeuch

Registriert seit: 11. Aug 2012
Ort: Essen
1.468 Beiträge
 
Delphi 10.2 Tokyo Professional
 
#15

AW: weniger Scanline aufrufe ... Graustufenbild

  Alt 11. Feb 2024, 17:08
Zitat:
aber es "nervt" halt doch ein wenig, wenn behauptet statt gemessen wird .
Hey, ich hatte gemessen, nur eben vor 5 Jahren und nicht heute.

In #13 ist immernoch ein Aufruf von Scanline zuviel, denn Scanline[0] hattest Du ja bereits in StartPixel abgespeichert.

Theoretisch kann man die Differenz zwischen zwei Scanlines auch direkt berechnen, das spart dann nochmal einen Aufruf pro Bitmap:

  BitmapBytesPerLine := ((w * 8 * BytesPerPixel + 31) and not 31) div 8;
Das ist auch schneller als ein Auruf von

  Graphics.BytesPerScanline(w, BytesPerPixel * 8, 32)
(Ja, das hatte ich auch gemessen, wobei ein Blick in den BytesPerScanline-Code ausreicht, um zu verstehen wieso.)

BytesPerPixel = 1 für pf8Bit, und = 3 für pf24Bit.

Weshalb allerdings die Lösung mit TBitmap.Assign schneller sein soll als die ohne, ist mir gerade unklar, denn wie gesagt, ich hatte das damals gemessen. Bei neueren Delphis gibt es TBitmap.SetSize, was schneller ist als Höhe und Breite getrennt zu setzen wie in #1. Es kann auch einen Unterschied machen, ob man PixelFormat zuerst setzt und dann die Größe ändert oder umgekehrt.

Edit: Es ist vermutlich die unnötige Berechnung von InPixel und OutPixel für jedes Pixel:

Delphi-Quellcode:
  InPixel := AddToPtr(InScanLine0, InBytesPerLine * y + x * BytesPerPixel);
// ...
  OutPixel := AddToPtr(OutScanLine0, OutBytesPerLine * y + x * BytesPerPixel);
Da kann man in beiden for-Schleife mit Inc bzw. Dec arbeiten, wie Du das in #13 gemacht hast. Das hatte ich in meinem Code später noch geändert, nachdem ich den Blogpost geschrieben hatte.

Als "Beweis", hier der Code aus u_dzGraphicUtils, wie ich ihn schließlich verwendet habe, wobei das der generische Code mit einem Callback ist. Den Filter direkt in der Schleife zu implementieren ist natürlich schneller, da man jeweils den Callback-Aufruf spart.

Delphi-Quellcode:
procedure TBitmap24_FilterPixels(_SrcBmp, _DstBmp: TBitmap; _Callback: TPixel24FilterCallback);
const
  BytesPerPixel = 3;
var
  x: Integer;
  y: Integer;
  w: Integer;
  h: Integer;
  SrcLine: PByte;
  DstLine: PByte;
  SrcPixel: PByte;
  DstPixel: PByte;
  BytesPerLine: Integer;
begin
  Assert(Assigned(_SrcBmp));

  _SrcBmp.PixelFormat := pf24bit;
  _DstBmp.PixelFormat := pf24bit;
  w := _SrcBmp.Width;
  h := _SrcBmp.Height;
  TBitmap_SetSize(_DstBmp, w, h);

  if h = 0 then
    Exit; //==>

  BytesPerLine := ((w * 8 * BytesPerPixel + 31) and not 31) div 8;
  Assert(BytesPerLine = Graphics.BytesPerScanline(w, BytesPerPixel * 8, 32));

  SrcLine := _SrcBmp.ScanLine[0];
  DstLine := _DstBmp.ScanLine[0];
  for y := 0 to h - 1 do begin
    Assert(SrcLine = _SrcBmp.ScanLine[y]);
    Assert(DstLine = _DstBmp.ScanLine[y]);
    SrcPixel := SrcLine;
    DstPixel := DstLine;
    for x := 0 to w - 1 do begin
      PdzRgbTriple(DstPixel)^ := PdzRgbTriple(SrcPixel)^;
      _Callback(x, y, PdzRgbTriple(DstPixel)^);
      Inc(SrcPixel, BytesPerPixel);
      Inc(DstPixel, BytesPerPixel);
    end;
    Dec(SrcLine, BytesPerLine);
    Dec(DstLine, BytesPerLine);
  end;
end;
Und hier derselbe Code fuer eine 8 Bit Graustufen-Bitmap, nach dem der OP ja gefragt hatte:

Delphi-Quellcode:
procedure TBitmap8_FilterPixels(_SrcBmp, _DstBmp: TBitmap; _Callback: TPixel8FilterCallback);
const
  BytesPerPixel = 1;
var
  x: Integer;
  y: Integer;
  w: Integer;
  h: Integer;
  SrcLine: PByte;
  DstLine: PByte;
  SrcPixel: PByte;
  DstPixel: PByte;
  BytesPerLine: Integer;
begin
  Assert(Assigned(_SrcBmp));

  _SrcBmp.PixelFormat := pf8bit;
  _DstBmp.Assign(nil);
  _DstBmp.PixelFormat := pf8bit;
  w := _SrcBmp.Width;
  h := _SrcBmp.Height;
  _DstBmp.Palette := MakeGrayPalette;
  TBitmap_SetSize(_DstBmp, w, h);

  if h = 0 then
    Exit; //==>

  BytesPerLine := ((w * 8 * BytesPerPixel + 31) and not 31) div 8;
  Assert(BytesPerLine = Graphics.BytesPerScanline(w, BytesPerPixel * 8, 32));

  SrcLine := _SrcBmp.ScanLine[0];
  DstLine := _DstBmp.ScanLine[0];
  for y := 0 to h - 1 do begin
    Assert(SrcLine = _SrcBmp.ScanLine[y]);
    Assert(DstLine = _DstBmp.ScanLine[y]);
    SrcPixel := SrcLine;
    DstPixel := DstLine;
    for x := 0 to w - 1 do begin
      DstPixel^ := SrcPixel^;
      _Callback(x, y, DstPixel^);
      Inc(SrcPixel, BytesPerPixel);
      Inc(DstPixel, BytesPerPixel);
    end;
    Dec(SrcLine, BytesPerLine);
    Dec(DstLine, BytesPerLine);
  end;
end;
Die Assertions sollte man natürlich ausschalten oder rauslöschen, denn sonst wird unnötig Graphics.BytesPerScanline aufgerufen. Ebenso sollte Overflow Checking und Range Checking ausgeschaltet sein. Vgl. die ganzen IFDEFs am Anfang der Unit.

Wobei ich mich gerade selbst frage, weshalb ich da in der inneren Schleife nicht mit Inc ohne zweiten Parameter und dem passenden Pointer-Typ für SrcPixel und DstPixel gearbeitet habe. Vielleicht aus Kompatiblitätsgründen mit uralt-Delphi-Versionen? Deshalb gibt es auch die (inline)-Prozedur TBitmap_SetSize, welches für neuere Delphis TBitmap.SetSize aufruft, und für ältere notgedrungen TBitmap.Width und .Height getrennt setzt.
Thomas Mueller

Geändert von dummzeuch (11. Feb 2024 um 17:13 Uhr)
  Mit Zitat antworten Zitat