Image Datei verkleinern

Für Probleme bezüglich Grafik, Audio, GL, ACS, ...
Antworten
oleg
Beiträge: 25
Registriert: So 6. Okt 2019, 17:04
OS, Lazarus, FPC: Win10Pro-(Laz 2.2.0 - FPC 3.2.0)-Rasp4(8GB)+MySQL+GITEA 1.18.0 <-> GIT 2.3.2
Wohnort: Leipzig

Image Datei verkleinern

Beitrag von oleg »

Szenario: Für eine Dokumentation sollen Bilder bereitgestellt werden, diese später in eine PDF integriert werden und das gesamte Dokument per E-Mail versendet werden.
Es ist wahrscheinlich, das dabei bis zu 60 Bilder bereitgestellt werden, deshalb müssen die Bilder in der Größe reduziert werden.
Meine Rechnung dabei 100 bis 150 kb pro Bild * 60 Bilder = ca. 6000 kb bis 9000 kb für die PDF + ein paar Kilobyte für die E-Mail selbst. Bis max. 12 MB kann die E-Mail direkt versendet werden, darüber wird nur noch ein Link für den Download erzeugt.
Wichtig dabei ist: Die Dateigrößen werden in KB bzw MB festgelegt.

Die nachfolgende Prozedur erfüllt zwar den Zweck und macht genau dieses, ist aber auf Grund der vielen Speicherprüfung vom Prozess her sehr langsam, zumal davon auszugehen ist, dass Benutzer Handyfotos mit 6 - 12 MB einreichen.
Zeitkritisch ist das ganze nicht, da wird dem Benutzer auch mal eine Sanduhr präsentiert, aber es müsste zumindest noch eine Benutzerinfo oder eine Progressbar rein.
Gibt es für diese Zielstellung evt. noch einen besseren Ansatz ?

Code: Alles auswählen

procedure Form1.ScaleImage(SourceFile, DestFile: String; AInterpolationClass: TInterpolationClass); 
 var
  DestImg, SourceImg: TFPCustomImage;
  xWidht, xHeight : Integer;
  xpWidht, xpHeight : Double;
  DestCanvas: TFPCustomCanvas;
  R: TFPCustomImageReader;
  W: TFPCustomImageWriter;
  ext: String;
  i : Integer;
begin
  // Read image
  SourceImg := TFPMemoryImage.Create(0, 0);
  try
    ext := Lowercase(ExtractFileExt(sourceFile));
    if (ext = '.jpg') or (ext = '.jpeg') then
      R := TFPReaderJPEG.Create
    else
    if (ext = '.png') then
      R := TFPReaderPNG.Create
    else begin
      ShowMessage(SourceFile + ': Image format not supported');
      Halt;
    end;
    SourceImg.LoadFromFile(SourceFile, R);
  finally
    R.Free;
  end;
  // Höhe und Breite des Orginal bestimmen
  xWidht  := SourceImg.Width;
  xHeight := SourceImg.Height;

  // Proportional verkleinern: % : (x/100)*ImgSize
  // Ausgehend von 100% in 5% Schritten verkleinern bis Dateigröße erreicht ist
Screen.Cursor := crHourGlass;

 for i := 95 downto 1 do begin
   if i mod 5 = 0 then
     begin
      xpWidht  := Round((i/100)* xWidht);
      xpHeight := Round((i/100) * xHeight);

  DestImg := TFPMemoryImage.Create(Round(xpWidht), Round(xpHeight));  // (DestWidth, DestHeight);
  try
    DestCanvas := TFPImageCanvas.Create(DestImg);
    try
      // Interpolate
      DestCanvas.Interpolation := AInterpolationClass.Create;
      try
        DestCanvas.StretchDraw(0, 0, Round(xpWidht), Round(xpHeight), SourceImg); 
      finally
        DestCanvas.Interpolation.Free;
      end;
    finally
      DestCanvas.Free;
    end;

    // Save interpolated image
    ext := Lowercase(ExtractFileExt(sourceFile));
    if (ext = '.jpg') or (ext = '.jpeg') then
      W := TFPWriterJPEG.Create
    else
    if (ext = '.png') then
      W := TFPWriterPNG.Create
    else begin
      ShowMessage(DestFile + ': Destination image format not supported');
      Halt;
    end;
    DestImg.SaveToFile(DestFile, W);
  finally
    DestImg.Free;
  end;
   //  Dateigröße <= Zielvorgabe
   if GetFileSize(PChar(DestFile)) <= AppConfig.MaxImageSize then
     Halt;
  end;
 end;
 Screen.Cursor := crDefault;
end;
Testlauf:

Code: Alles auswählen

procedure Form1.Button2Click(Sender: TObject);
 var
  SRC_File, DEST_File : String;
  // zu Testzwecken den Wert ändern zu
  // MaxImageSize : Integer; 
begin
// MaxImageSize := 100 * 1024; // 100 kb
 SRC_File  := 'IMG_20220808_162415.jpg'; // 7,8 MB
 DEST_File := '____Test14.jpg';

 // wenn Bild zu groß so lange verändern - bis max Dateigröße erreicht ist
// Rückmeldung wann erfolgreich - fehlt noch
 if GetFileSize(PChar(SRC_File)) >  AppConfig.MaxImageSize then
  begin
   ScaleImage(SRC_File, DEST_File, TCubicInterpolation); 
  end;
end;            

Code: Alles auswählen

// Programmkopf
uses ...
  // Image Compress
  fpimage, fpcanvas, fpimgcanv,
  fpreadjpeg, fpwritejpeg,
  fpreadpng, fpwritepng,
  extinterpolation
  ;

type
    TInterpolationClass = class of TFPCustomInterpolation;   

Benutzeravatar
theo
Beiträge: 10468
Registriert: Mo 11. Sep 2006, 19:01

Re: Image Datei verkleinern

Beitrag von theo »

Das würde ich pragmatisch lösen.
Für JPEGs einer bestimmten Grösse/Fläche und einer bestimmten Kompression dürfte eine maximale Dateigrösse ermittelbar sein.
Ich würde dann direkt auf diese "Fläche" reduzieren. Wenn es ausnahmsweise immer noch zu gross sein sollte, kannst du immer noch mit dem Loop kommen.
Für PNG das Gleiche, mit den für PNG passenden Werten.

oleg
Beiträge: 25
Registriert: So 6. Okt 2019, 17:04
OS, Lazarus, FPC: Win10Pro-(Laz 2.2.0 - FPC 3.2.0)-Rasp4(8GB)+MySQL+GITEA 1.18.0 <-> GIT 2.3.2
Wohnort: Leipzig

Re: Image Datei verkleinern

Beitrag von oleg »

Das hatte ich auch schon überlegt, das ich bei Dateigrößen jenseits von 3MB oder Auflösungen > 1200 x 12000 direkt bei 30% anfange zu schauen, ob die Dateigröße passt.

Hatte auch zusätzlich gedacht, dass man vielleicht schon den SaveStream abfangen kann, bevor die Datei geschrieben wird.

Sozusagen mit MemoryStrem.Size <= VorgabeGrösse.
Da hab ich aber noch keine genaue Idee wie das geht, ebensowenig wie die Rückmeldung, wenn die Datei fertig ist (Ausser vielleicht über ein Timer, der prüft ob die fertige Datei existiert).

Benutzeravatar
theo
Beiträge: 10468
Registriert: Mo 11. Sep 2006, 19:01

Re: Image Datei verkleinern

Beitrag von theo »

oleg hat geschrieben:
Di 6. Jun 2023, 13:01
Hatte auch zusätzlich gedacht, dass man vielleicht schon den SaveStream abfangen kann, bevor die Datei geschrieben wird.

Sozusagen mit MemoryStrem.Size <= VorgabeGrösse.
Da hab ich aber noch keine genaue Idee wie das geht, ebensowenig wie die Rückmeldung, wenn die Datei fertig ist (Ausser vielleicht über ein Timer, der prüft ob die fertige Datei existiert).
Speichere es halt zuerst in einen MemoryStream, dort kannst du Size abfragen.

Code: Alles auswählen

AStrm:=TMemoryStream.Create;
DestImg.SaveToStream(AStrm,W);
if AStrm.Size < Irgendwas
begin
 AStrm.Position:=0; 
 AStrm.SaveToFile('..');
end;
AStrm.Free;

wp_xyz
Beiträge: 4869
Registriert: Fr 8. Apr 2011, 09:01

Re: Image Datei verkleinern

Beitrag von wp_xyz »

Ich würde mir zuerst überlegen, wie groß die Bilder im PDF sein sollen: Ganzseitig? Halbseitig? Viertelseitig. Oder nur winzig, so wie Kleinbild-Kontaktabzüge aus der guten alten Analog-Zeit?

Angenommen, du planst "Postkarten-Größe", 10x15 cm. Das sind 4x6 Zoll. Wenn die Bilder mit "Druckerauflösung" (300dpi) im PDF enthalten sind, werden das 1200x1800 px. Ein Handy-Foto hat bei mir 4600x2600 px mit 6.9 MB. Verkleinert auf Postkarte würde der Speicherbedarf größenordnungsmäßig im Verhältnis der Pixelzahl (1200x1800 / (4600x2600) = 18% zurückgehen, und wir hätten etwas mehr als 1 MB.

Wenn du stattdessen mit Bildschirmauflösung zufrieden bist, wären die dpi um den Faktor 3 kleiner, die Pixelzahl um den Faktor 3^2 = 9; die Bilder wären nur noch 400 x 600 px groß, und damit wären wir in dem von dir im ersten Beitrag genannten 100-150 KB.

Damit wäre gezeigt, das dein Vorhaben realistisch ist, und Bilder in vernünftiger Größe im PDF aufgenommen werden können.

Nun zur Größen-Skalierung: Ich würde da gar nicht viel herumprobieren, sondern die eingelesenen Bilder gleich auf die Zielgröße herunterskalieren. Evtl. kannst du auch noch etwas mit der JPG-Qualität herumspielen.

oleg
Beiträge: 25
Registriert: So 6. Okt 2019, 17:04
OS, Lazarus, FPC: Win10Pro-(Laz 2.2.0 - FPC 3.2.0)-Rasp4(8GB)+MySQL+GITEA 1.18.0 <-> GIT 2.3.2
Wohnort: Leipzig

Re: Image Datei verkleinern

Beitrag von oleg »

Hallo und erst mal ganz herzlichen dank für Euer Feedback und dass ihr euch die Zeit dafür nehmt.
Getestet habe ich natürlich vorher dass die Bilder von der Bildgröße dann auch dem eigentlich Zweck noch nachkommen können. Für die Dokumentation sind wohl so üblich 3 - 4 Bilder pro Seite, was dem Postkartenformat recht ähnlich kommt. Allerdings reicht die Standard Auflösung. Vielen Dank mal an die Pixelrechner. Da hab ich wieder was gelernt bzw. das gelernte mal wieder rausgeholt. dpi.

Mit der jpeg Kompression zu spielen, habe ich bereits versucht und verworfen. Erstens auch sehr zeitaufwendig und man nimmt wirklich nur Qualität raus. Alles unter 85 ist nicht mehr zu gebrauchen.

Dank euch, weiss ich zumindest schon mal, dass ich nicht ganz auf dem Holzweg bin. Ich hatte doch gehofft, das es da schon besserer Ansätze gibt. Ich werde jetzt wahrscheinlich direkt auf die benötigte Größe scalieren, und den Benutzer höflich um etwas Geduld bitten. Was schmeißt der auch mit so großen Bildern rum. :D

Benutzeravatar
Lincoln Six Echo
Beiträge: 138
Registriert: Di 26. Aug 2014, 16:42
OS, Lazarus, FPC: Win10, Debian
CPU-Target: I7/I9/Q9650/u.a.
Wohnort: Hamburg

Re: Image Datei verkleinern

Beitrag von Lincoln Six Echo »

Das alles geht 10 bis 100 x schneller. Ich würde es mit Low Level Pixelzugriffen machen, d.h. direkt auf die jeweiligen Bilddaten zugreifen und per Lanczos runterrechnen, ins verkleinerte Bild schreiben, speichern, fertig.

Antworten