AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Zurück Delphi-PRAXiS Programmierung allgemein Multimedia Delphi TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...
Thema durchsuchen
Ansicht
Themen-Optionen

TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

Ein Thema von Gausi · begonnen am 26. Sep 2019 · letzter Beitrag vom 11. Okt 2019
Antwort Antwort
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#1

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 27. Sep 2019, 10:11
Kurze Antwort: Alles drei!

1. Threadsicherheit: TWICImage liegt in der Vcl.Graphics Unit und damit im Vcl Namespace -> per se nicht threadsicher.
Benutzt intern TBitmap und TCanvas. Sie Implementierung: Für AssignTo wenn Dest is TBitmap , zur Übergabe an die Zwischenablage und zum Zeichnen.
Des Weiteren ist die class var FImagingFactory: IWICImagingFactory; nicht darauf ausgelegt bzw. der Getter dazu und ich würde mich nicht darauf verlassen.

2.Overhead. Das was du brauchst ist eine Threadeigene-Instanz der Factory (im Execute erzeugen!) und dann in einer Schleife deine Dateiliste abarbeiten und verkleinern.
Das SetSize führt bspw. bei TWICImage zu einer Exception, dass das nicht implementiert wurde (First chance exception at $758B3522. Exception class EInvalidGraphicOperation with message 'Cannot change the size of a WIC Image'. Process Project1.exe (12164))
Also sowas geht halt nicht:
Delphi-Quellcode:
procedure TForm1.FormCreate(Sender: TObject);
var
    MyProc: TProc;
begin
    MyProc := procedure
        var
            WicImage: TWICImage;
        begin
          CoInitializeEx(nil, COINIT_MULTITHREADED);
          WicImage := TWICImage.Create;
          WicImage.LoadFromFile('C:\Users\Du\Desktop\DeinBild.png');
          WicImage.SetSize(WicImage.Width div 2, WicImage.Height div 2);
          WicImage.SaveToFile('C:\Users\Du\Desktop\DeinBild2.png');
          WicImage.Free;
          CoUninitialize;
        end;
    TThread.CreateAnonymousThread(MyProc).Start;
end;
Dann kannst du dir den Code dafür selber schnell zusammenschreiben:
https://docs.microsoft.com/en-us/win...es-howto-scale

3. Eben, wenn du irgendwelche Sonderlocken hast, dann musst du die eh per Zugriff auf TWicImage.Handle und WicImage.ImagingFactory herzaubern. Dann kann man das auch gleich direkt machen.
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
913 Beiträge
 
Delphi 12 Athens
 
#2

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 27. Sep 2019, 14:26
Ok, danke nochmal. Dann werde ich mich da mal reinfuxen. WinAPI-nahe Programmierung liegt mir nicht so, und C in Delphi übersetzen ist auch nicht so meine Stärke. Aber mit Querlesen der MS-Doku und dem Code von TWICImage werde ich da schon klar kommen, hoffe ich. So umfangreich ist mein Vorhaben ja nicht an der Stelle.

Und dann kann ich mir auch die externe Bib (graphics32) im Code sparen ...

Edit: Sehr schön. Ich habe jetzt Quick&Dirty Code, der "funktioniert". Eingabe ist ein Stream mit Bilddaten (egal ob PNG, JPEG oder Bitmap), und Ausgabe ist eine auf die Zielgröße reduzierte JPEG-Datei. Stream als Eingabe passt mir da sehr gut, weil ich nicht immer nur Bilddateien skalieren möchte, sondern auch mal Bilder, die ich aus einem Container-Dateiformat raushole.

Muss ich morgen nochmal in Ruhe drübergucken, den Code ggf. etwas aufräumen und Fehlerbehandlung einbauen. Poste ich dann auch mal, damit es zu dem Problem ein weiteres Code-Beispiel gibt.
Being smart will count for nothing if you don't make the world better. You have to use your smarts to count for something, to serve life, not death.

Geändert von Gausi (27. Sep 2019 um 21:06 Uhr)
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
913 Beiträge
 
Delphi 12 Athens
 
#3

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 28. Sep 2019, 20:16
Hier jetzt mein aktueller Code dazu.

Sieht furchtbar lang aus, ist aber so ziemlich genau das, was auch beim TWICImage beim Laden und Speichern alles so gemacht wird, und was die WIC halt so verlangt. Besonders die Variablenliste kann einen erstmal schocken.

Zur Erklärung:
Parameter
  • aStream: Ein Stream mit Bilddaten (FileStream von einer Bilddatei, MemoryStream mit Bilddaten aus einem ID3-Tag, ...)
  • aFilename: Dateiname der Zieldatei (wird ggf. erstellt)
  • destWidth/destHeight: Zielgröße des Bildes. Das Originalbild wird so skaliert, dass es in das Rechteck destWidth*destHeight hineinpasst
  • aWICImagingFactory: eine WICImagingFactory, damit diese bei vielen Skalierungen nicht immer neu erstellt werden muss. Bei NIL wird eine lokale neu erzeugt.
  • Overwrite: Flag, das in meiner Anwendung gelegentlich gebraucht wird. Damit werden bereits vorhandene Dateien überschrieben, ansonsten wird abgebrochen - aber trotzdem "Erfolg" zurückgeliefert. Das ist in meinem Anwendungsfall so sinnvoll
Ausgabe:
  • True, falls das skalierte Bild erfolgreich erstellt wurde (oder ggf. bereits existiert)
Das Bildformat wird automatisch aus dem Stream ermittelt. Das Ausgabeformat ist immer JPEG.
Eine ggf. vorhandene WICImagingFactory muss im Kontext des Threads erzeugt werden, in dem die Funktion laufen soll.

Fehlerbehandlung könnte intensiver sein, und ein Rückgabewert mit mehr Info als "hat geklappt" wäre ggf. auch sinnvoll. Das ist dann aber dem geneigten Leser zur Übung überlassen.

Aber das sollte dann so ziemlich Threadsafe sein, komplett ohne VCL und TGraphic.

Delphi-Quellcode:
function ScalePicStreamToFile(aStream: TStream; aFilename: UnicodeString; destWidth, destHeight: Integer; aWICImagingFactory: IWICImagingFactory; OverWrite: Boolean = False): boolean;
var
    hr: HRESULT;
    isLocalFactory: Boolean;
    // for proper scaling
    xfactor, yfactor:double;
    origWidth, origHeight: Cardinal;
    newWidth, newHeight: Cardinal;
    // reading the source image
    SourceAdapter: IStream;
    BitmapDecoder: IWICBitmapDecoder;
    DecodeFrame: IWICBitmapFrameDecode;
    SourceBitmap: IWICBitmap;
    SourceScaler: IWICBitmapScaler;
    // writing the resized image
    DestStream: TMemoryStream;
    DestAdapter: IStream;
    DestWICStream: IWICStream;
    BitmapEncoder: IWICBitmapEncoder;
    EncodeFrame: IWICBitmapFrameEncode;
    Props: IPropertyBag2;
begin
    result := False;
    if Not Overwrite and FileExists(aFilename) then
    begin
        result := True;
        exit;
    end;

    isLocalFactory := (aWICImagingFactory = nil);
    if isLocalFactory then
        CoCreateInstance(CLSID_WICImagingFactory, nil, CLSCTX_INPROC_SERVER or
          CLSCTX_LOCAL_SERVER, IUnknown, aWICImagingFactory);

    // read the image data from stream
    SourceAdapter := TStreamAdapter.Create(aStream);
    hr := aWICImagingFactory.CreateDecoderFromStream(SourceAdapter, guid_null, WICDecodeMetadataCacheOnDemand, BitmapDecoder);
    if Succeeded(hr) then hr := BitmapDecoder.GetFrame(0, DecodeFrame);
    if Succeeded(hr) then hr := aWICImagingFactory.CreateBitmapFromSource(DecodeFrame, WICBitmapCacheOnLoad, SourceBitmap);
    if Succeeded(hr) then hr := SourceBitmap.GetSize(origWidth, origHeight);

    // calculate proper scaling
    xfactor:= (destWidth) / origWidth;
    yfactor:= (destHeight) / origHeight;
    if xfactor > yfactor then
    begin
        newWidth := round(origWidth * yfactor);
        newHeight := round(origHeight * yfactor);
    end else
    begin
        newWidth := round(origWidth * xfactor);
        newHeight := round(origHeight * xfactor);
    end;

    // scale the original image
    if Succeeded(hr) then hr := aWICImagingFactory.CreateBitmapScaler(SourceScaler);
    if Succeeded(hr) then hr := SourceScaler.Initialize(SourceBitmap, NewWidth, NewHeight, WICBitmapInterpolationModeFant);

    if Succeeded(hr) then
    begin
        // Reading and scaling the original image was successful.
        // Now try to save the scaled image
        DestStream := TMemoryStream.create;
        try
            // create new WICStream
            DestAdapter := TStreamAdapter.Create(DestStream);
            if Succeeded(hr) then hr := aWICImagingFactory.CreateStream(DestWICStream);
            if Succeeded(hr) then hr := DestWICStream.InitializeFromIStream(DestAdapter);
            // create and prepare JPEG-Encoder
            if Succeeded(hr) then hr := aWICImagingFactory.CreateEncoder(GUID_ContainerFormatJpeg, guid_null, BitmapEncoder);
            if Succeeded(hr) then hr := BitmapEncoder.Initialize(DestWICStream, WICBitmapEncoderNoCache);
            if Succeeded(hr) then hr := BitmapEncoder.CreateNewFrame(EncodeFrame, Props);
            if Succeeded(hr) then hr := EncodeFrame.Initialize(Props);
            if Succeeded(hr) then hr := EncodeFrame.SetSize(newWidth, newHeight);
            // write image data
            if Succeeded(hr) then hr := EncodeFrame.WriteSource(SourceScaler, nil);
            if Succeeded(hr) then hr := EncodeFrame.Commit;
            if Succeeded(hr) then hr := BitmapEncoder.Commit;
            // finally save the stream to the destination file
            if Succeeded(hr) then
                try
                    DestStream.SaveToFile(aFilename);
                    result := True;
                except
                    // silent exception here, but (try to) delete the destination file, if it exists
                    result := False;
                    if FileExists(aFilename) then DeleteFile(aFilename);
                end;
        finally
            DestStream.Free;
        end;
    end;

    if isLocalFactory then
        aWICImagingFactory._Release;
end;
Being smart will count for nothing if you don't make the world better. You have to use your smarts to count for something, to serve life, not death.
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
913 Beiträge
 
Delphi 12 Athens
 
#4

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 11. Okt 2019, 09:47
Ich muss hier nochmal nachfragen, weil ich die Interface-Geschichte noch nicht ganz verstanden habe, glaube ich.

Die Funktion aus dem letzten Posting (eigentlich eine private Methode einer großen Klasse, von der im Programm genau eine Instanz existiert) rufe ich mal aus einem Nebenthread auf, und mal aus dem VCL-Thread. Dabei nutze ich nicht TThread, sondern BeginThread, woraus dann die threaded Methoden aus der großen Klasse aufgerufen werden.

Um nicht jedesmal die Factory neu zu erstellen, habe ich dafür zwei private Member-Variablen in der Klasse
Delphi-Quellcode:
WICImagingFactory_VCL: IWICImagingFactory;
WICImagingFactory_ScanThread: IWICImagingFactory;
Mit IWICImagingFactory = interface(IUnknown) aus der Unit Winapi.Wincodec.


Aufgerufen wird die Methode dann über

ScalePicStreamToFile(aStream, aFilenname, 240, 240, GetProperImagingFactory(ScanMode)) Scanmode ist ein Aufzählungstyp und steuert "VCL oder Thread". Die Factory bekomme ich dann mit dieser privaten Methode, die bei Bedarf die Factory erstellt, und ansonsten die bestehende zurückliefert.

Delphi-Quellcode:
function TMyClass.GetProperImagingFactory(ScanMode: CoverScanThreadMode): IWICImagingFactory;
begin
    case ScanMode of
        tm_VCL: begin
            if WICImagingFactory_VCL = Nil then
                CoCreateInstance(CLSID_WICImagingFactory, nil, CLSCTX_INPROC_SERVER or
                    CLSCTX_LOCAL_SERVER, IUnknown, WICImagingFactory_VCL);
            result := WICImagingFactory_VCL;
        end;
        tm_Thread: begin
            if WICImagingFactory_ScanThread = Nil then
                CoCreateInstance(CLSID_WICImagingFactory, nil, CLSCTX_INPROC_SERVER or
                    CLSCTX_LOCAL_SERVER, IUnknown, WICImagingFactory_ScanThread);
            result := WICImagingFactory_ScanThread;

        end;
    end;
end;
Jetzt habe ich beim Thread das Problem, dass nach Ende des Threads die Factory nutzlos wird (sie muss wohl immer im Kontext des Threads erstellt werden, in dem sie genutzt wird). Daher muss ich die freigeben, und die Variable auf Nil setzen, damit beim nächsten Thread (es läuft aber immer nur einer nebenbei) wieder eine neue erstellt wird.

Das habe ich so gemacht
Delphi-Quellcode:
WICImagingFactory_ScanThread._Release
WICImagingFactory_ScanThread := Nil
Bei mir läuft das, bei vielen anderen knallt die Zuweisung auf Nil. So grob habe ich auch schon verstanden, warum: Wenn durch das Release der Referenzzähler Null wird, wird das Objekt dahinter freigegeben. Die Zuweisung auf Nil hingegen ruft intern wieder Release auf, aber das Objekt ist schon weg.

In der VCL-Komponente TWICImage ist diese Factory eine Class Var. Wenn ich den Code aus TWICImage.Destroy übernehme, komme ich auf
Delphi-Quellcode:
if WICImagingFactory_ScanThread._Release = 0 then
  Pointer(WICImagingFactory_ScanThread) := Nil;
Das funktioniert dann. Sehe ich das richtig, dass durch den Cast auf Pointer einfach nur die Variable auf NIL gesetzt wird, und die "Interface-Magic" dahinter nicht aktiviert wird, und somit das erreicht wird, was ich haben will? Nämlich dass das Objekt weg ist, und die Variable Nil ist?

Oder ist der ganze Ansatz kompletter Murks?
Being smart will count for nothing if you don't make the world better. You have to use your smarts to count for something, to serve life, not death.
  Mit Zitat antworten Zitat
TiGü

Registriert seit: 6. Apr 2011
Ort: Berlin
3.079 Beiträge
 
Delphi 10.4 Sydney
 
#5

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 11. Okt 2019, 11:45
Das funktioniert dann. Sehe ich das richtig, dass durch den Cast auf Pointer einfach nur die Variable auf NIL gesetzt wird, und die "Interface-Magic" dahinter nicht aktiviert wird, und somit das erreicht wird, was ich haben will? Nämlich dass das Objekt weg ist, und die Variable Nil ist?
Ja!

Oder ist der ganze Ansatz kompletter Murks?
Möglich, aber man muss sich immer Raum für Verbesserung lassen. Version 2.0 wirds richten.

Warum das direkte Arbeiten mit BeginThread und kein normaler Delphi-TThread? Historische Gründe?
  Mit Zitat antworten Zitat
Benutzerbild von Gausi
Gausi

Registriert seit: 17. Jul 2005
913 Beiträge
 
Delphi 12 Athens
 
#6

AW: TPicture, TJPegImage, TBitmap, TBitmap32 und Threads ...

  Alt 11. Okt 2019, 12:50
Warum das direkte Arbeiten mit BeginThread und kein normaler Delphi-TThread? Historische Gründe?
Jep. Das Projekt dahinter ist mittlerweile im 15. Jahr und enthält einige Altlasten (begonnen mit Delphi 7, dann irgendwann der Unicode-Port zu Delphi 2009, jetzt 10.3 CE). Andere (auch alte) Teile finde ich aber immer noch "schön".

Das mit den Threads hat sich halt so entwickelt. An anderer Stelle nutze ich auch mal TThread. Besonders massive Vor- und Nachteile zwischen den beiden Varianten sehe ich nicht. Aufpassen, wenn der Thread auf den Daten arbeitet, die ggf. auch gerade angezeigt werden, muss man ohnehin.

Aber danke für die Bestätigung. Dann kann ich das so "ausliefern".
Being smart will count for nothing if you don't make the world better. You have to use your smarts to count for something, to serve life, not death.
  Mit Zitat antworten Zitat
Antwort Antwort


Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 06:57 Uhr.
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