Entwickler-Ecke

Multimedia / Grafik - PNG verlustlos vergrößern/verkleinern


Gerhard_S - Mo 09.09.13 23:30
Titel: PNG verlustlos vergrößern/verkleinern
Hallo,
mit Delphi XE2 funktioniert folgender Code auch mit einer PNG-Datei:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
uses PNGImage;
...
procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
   begin
     Image1.Picture.LoadFromFile(OpenPictureDialog1.FileName);
     Image1.Proportional := true;  //verändert Größe automatisch und proportional richtig
     Label3.Caption := OpenPictureDialog1.FileName;
     Label1.Caption := 'Originale Breite ist '+ IntToStr(Image1.Picture.Width)+ ' px';
     Label2.Caption := 'Originale Höhe ist '+ IntToStr(Image1.Picture.Height)+ ' px';
     Label4.Caption := 'neue Breite ist '+ IntToStr(Image1.Width)+ ' px';
     Label5.Caption := 'neue Höhe ist '+ IntToStr(Image1.Height)+ ' px';
   end;
end;

Wenn aber das Image größer oder kleiner als die PNG-Datei ist, gibt es Verzerrungen. Kennt jemand eine Möglichkeit, das abzustellen und so schön wie beim JPG-Format zu machen?


MeierZwoo - Di 10.09.13 00:16

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
... so schön wie beim JPG-Format zu machen?

Was ist beim stretchen von JPG "schön"?

Und allgemein, wenn du Verzerrungen vermeiden willst, darfst Du grundsätzlich nicht beide Zielgrößen einfach vorgeben, sondern nur eine und dann die zweite Größe aus dem Stammverhältnis errechnen.


Gerhard_S - Di 10.09.13 03:04

Einfach mal testen, und du siehst den Unterschied zwischen Jpg und Png. Übrigens: In Delphi XE2 übernimmt Pascal die Rechnerei. Für den Notfall habe ich natürlich immer noch eine Prozedur.


Delete - Di 10.09.13 04:47

Es gibt zwei Arten von Verzerrungen:

1. Das Seitenverhältnis von Original- und Zielgrafik ist unterschiedlich. In dem Fall wird die Zielgrafik horizontal oder vertikal verzerrt dargestellt. Man vermeidet diesen unschönen Effekt, indem man darauf achtet, das Seitenverhältnis in der Zielgrafik beizubehalten.

2. Da es sich um eine Pixel-Grafik (im Gegensatz zur Vektor-Grafik) handelt, können bei Vergrößerung/Verkleinerung nicht alle Original-Pixel 1:1 dargestellt werden. Es wird interpoliert. Um den dadurch hervorgerufenen und wohlbekannten Treppchen-Effekt abzuschwächen, wäre ein Anti-Aliasing [http://www.swissdelphicenter.ch/de/showcode.php?id=1484] zu empfehlen.


Gerhard_S - Di 10.09.13 18:59


Delphi-Quelltext
1:
 SL2 := Image.Picture.Bitmap.ScanLine[m];                    

ergibt "Bereichsüberschreitung bei Zeilenindex".
Hier der Code zum Laden:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
procedure TForm1.Button2Click(Sender: TObject);
var
  oBitmap :TBitmap;
  oJPG :TJPEGImage;
  oPNG :TPNGImage;
begin
  oPNG := TPNGImage.Create;
  if OpenPictureDialog1.Execute then
  oPNG.LoadFromFile(OpenPictureDialog1.FileName);
  oBitmap := TBitmap.Create;
  oBitmap.Assign(oPNG);
  Image1.Picture.Bitmap := oBitmap;
  Antialiasing(Image1, 80);
end;


Delete - Di 10.09.13 20:05

1. Frage: Wozu die Variable oBitmap statt gleich Image1.Picture.Bitmap.Assign(oPNG)?

2. Frage: Wozu die Variable oJPG? Die wird doch gar nicht verwendet?

3. Frage: Wo gibst du oBitmap und oPNG wieder frei? Oder gehört es etwa zu deiner Programm-Architektur, Speicherlecks zu hinterlassen?

4. Frage: Wieso verwendest du keinen Try-Finally-Block und gibst die erzeugten Objekte im Finally-Abschnitt wieder frei?

5. Wenn du den Antialiasing-Code anschaust, kannst du erkennen, daß ein RGB-Bitmap mit 24 Bit Farbtiefe erwartet wird. Für andere Formate schreibst du dir die Methode einfach um oder wandelst dein Bitmap vor der Antialias-Behandlung entsprechend. Graustufen beispielsweise besitzen keine RGB-Eigenschaften ...


Gerhard_S - Mi 11.09.13 01:23

Die "Bereichsüberschreitung bei Zeilenindex" hat definitiv nichts mit der Farbtiefe zu tun, auch nicht mit einem Speicherleck.
Mein erster Code war nur dazu gedacht, einen ungefähren Eindruck von der Ladeprozedur zu geben.
Ich teste mit 2 PNG-Bildern.
Beide haben 24-bpp-Farbtiefe.
Bild 1 bringt die Bereichsüberschreitung reproduzierbar (nachdem das Bild fertig geladen ist und gut dargestellt wird). Angaben von Irfanview: Komprimierung: PNG - ZIP. Die Größe ist 215 x 215 px. Aktuelle Farben: 16,7 Millionen (24 BitsPerPixel). Gezählte Farben: 1680.
Bild 2 bringt keine Bereichsüberschreitung, aber dafür hat das Bild am linken Rand einen grünen vertikalen Balken und die Darstellung sieht so aus, als ob das Bild unter einer schlecht geputzten Glasscheibe läge. Angaben von Irfanview: Komprimierung: PNG - ZIP. Die Größe ist 430 x 430 px. Aktuelle Farben: 16,7 Millionen (24 BitsPerPixel). Gezählte Farben: 5821.
Hier mein aktueller Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TForm1.Button2Click(Sender: TObject);
var
  oPNG :TPNGImage;
begin
  try
  oPNG := TPNGImage.Create;
  if OpenPictureDialog1.Execute then
    begin
    oPNG.LoadFromFile(OpenPictureDialog1.FileName);
    Image1.Picture.Bitmap.Assign(oPNG);
    Antialiasing(Image1, 80);
    end;
  finally
    oPNG.Free;
  end;
end;


WasWeißDennIch - Mi 11.09.13 08:13

Ich habe es mit PNGs noch nie probiert, aber hast Du es mal etwa so versucht: http://www.delphipraxis.net/1095307-post20.html? Wahrscheinlich geht dabei aber ggf. die Transparenz flöten.


Delete - Mi 11.09.13 08:31

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
Die "Bereichsüberschreitung bei Zeilenindex" hat definitiv nichts mit der Farbtiefe zu tun, auch nicht mit einem Speicherleck.
Mein erster Code war nur dazu gedacht, einen ungefähren Eindruck von der Ladeprozedur zu geben.
Ich teste mit 2 PNG-Bildern.
Beide haben 24-bpp-Farbtiefe.
Bild 1 bringt die Bereichsüberschreitung reproduzierbar (nachdem das Bild fertig geladen ist und gut dargestellt wird). Angaben von Irfanview: Komprimierung: PNG - ZIP. Die Größe ist 215 x 215 px. Aktuelle Farben: 16,7 Millionen (24 BitsPerPixel). Gezählte Farben: 1680.
Bild 2 bringt keine Bereichsüberschreitung, aber dafür hat das Bild am linken Rand einen grünen vertikalen Balken und die Darstellung sieht so aus, als ob das Bild unter einer schlecht geputzten Glasscheibe läge. Angaben von Irfanview: Komprimierung: PNG - ZIP. Die Größe ist 430 x 430 px. Aktuelle Farben: 16,7 Millionen (24 BitsPerPixel). Gezählte Farben: 5821.
Hier mein aktueller Code:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
procedure TForm1.Button2Click(Sender: TObject);
var
  oPNG :TPNGImage;
begin
  try
  oPNG := TPNGImage.Create;
  if OpenPictureDialog1.Execute then
    begin
    oPNG.LoadFromFile(OpenPictureDialog1.FileName);
    Image1.Picture.Bitmap.Assign(oPNG);
    Antialiasing(Image1, 80);
    end;
  finally
    oPNG.Free;
  end;
end;

Der Code, auf den ich mich bezog und den du nun wunderbar korrigiert hast, zeigte eben so viele Fehler, daß ich dabei wohl das eigentliche Problem aus den Augen verloren hatte. Davon abgesehen hat dein Code ebenfalls nichts mit der Bereichsüberschreitung zu tun, sondern einzig die von den Schweizern kopierte Methode. Die einzige Möglichkeit einer Bereichsüberschreitung, die mir auffällt, liegt in der Deklaration des Arrays:


Delphi-Quelltext
1:
2:
3:
type
  TRGBTripleArray = array[0..32767of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;


Ich verwendete diese Methode bislang nur bei sehr kleinen Bildern, die mir als Vorschau-Grafik dienten. Dabei traten keinerlei Probleme auf. Bei einer Größe von 230 x 230 Pixeln ergeben sich bereits 52900 Pixel, da hülfe es möglicherweise, wenn du das Array entsprechend vergrößerst. Wenn du durch die Methode stepst, wirst du sicher leicht selber herausfinden, wo genau die Bereichsüberschreitung stattfindet.

Da diese Antialias-Methode bei größeren Bildern jedoch recht langsam wird, solltest du dich vielleicht nach einer besseren Methode umschauen oder selbst optimieren.


Blup - Mi 11.09.13 09:50

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
Die "Bereichsüberschreitung bei Zeilenindex" hat definitiv nichts mit der Farbtiefe zu tun, auch nicht mit einem Speicherleck.
Mein erster Code war nur dazu gedacht, einen ungefähren Eindruck von der Ladeprozedur zu geben.
Ich teste mit 2 PNG-Bildern.
Beide haben 24-bpp-Farbtiefe.

Die Farbtiefe der Ausgangsbilder spielt keine Rolle.
Wichtig ist wie die Bitmap die Bilddaten im Speicher ablegt.
Die Funktion "Antialiasing" geht davon aus, die Daten liegen im Format "pf24bit" vor.
Da aber keine Überprüfung erfolgt, kann es andernfalls eine Zugriffsverletzung oder seltsame Effekte geben.

Delphi-Quelltext
1:
2:
Image1.Picture.Bitmap.PixelFormat := pf24bit;
Antialiasing(Image1, 80);


Gerhard_S - Mi 11.09.13 12:26

Auch die neue hinzugefügte Zeile
Image1.Picture.Bitmap.PixelFormat := pf24bit;
hat keine Veränderung ergeben. Die oben geschilderten Fehler bei Bild 1 und Bild 2 treten weiterhin auf.
Zum Testen hänge ich die Bilder an.


Blup - Mi 11.09.13 13:21

Die Funktion Antialiasing() geht davon aus, die Größe der Bitmap stimmt mit der Größe des Image überein.
Ist das nicht der Fall, treten Fehler auf.
Überhaupt ist diese Funktion nicht besonders gut programmiert.


IhopeonlyReader - Mi 11.09.13 13:29

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Überhaupt ist diese Funktion nicht besonders gut programmiert.

Sie ist auf Schnelligkeit aus, abfragen zur Sicherheit, Richtigkeit etc. werden nicht gemacht, falls nicht nötig. deshalb werden sie aus der Funktion rausgelassen und dem Programmierer überlassen.
Allerdings sollte dieser es natürlich wissen.


Blup - Mi 11.09.13 13:34

Sie ist nicht mal schnell, nur mal schnell so hinprogrammiert...

Ich hab mir die beiden Grafiken im Image angeschaut und vergrößert/verkleinert.
Die Komponente macht das eigentlich schon sehr gut, ich sehe da keine Verzerrungen.
In welcher Größe sollen den diese Bilder dargestellt werden?


Gerhard_S - Mi 11.09.13 15:10

Die gewünschte Größe errechnet der Befehl
Antialiasing(Image1, 80);
doch selbst? Oder?
Image1 ist auf der Form 521 x 345 px groß.


Delete - Mi 11.09.13 15:49

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
Die gewünschte Größe errechnet der Befehl
Antialiasing(Image1, 80);
doch selbst? Oder?
Image1 ist auf der Form 521 x 345 px groß.


Hast du denn, wie empfohlen, das Array in der Antialias-Methode vergrößert?

Delphi-Quelltext
1:
2:
3:
type
  TRGBTripleArray = array[0..180000of TRGBTriple;
  PRGBTripleArray = ^TRGBTripleArray;


Bitmap- und Imagegröße müssen zudem nicht zwangsläufig identisch sein. Davon kannst du dich selbst überzeugen, wenn du in eine TImage-Komponente mit sagen wir mal 500 x 500 Pixeln eine Bitmap mit 1000 x 1000 Pixeln lädst und TImage.AutoSize und Stretch auf False stehen: Es wird nur ein Teil der Bitmap angezeigt. Stellst du AutoSize auf True, paßt sich die Größe der Komponente der Größe der Bitmap an, wogegen bei AutoSize := False und Stretch := True die Bitmap verzerrt (verkleinert) dargestellt wird, aber nicht wirklich kleiner wird. Das ist wie mit einem Objekt, das du durch ein umgedrehtes Fernrohr beobachtest: Das Objekt erscheint kleiner, wird selbst aber nicht kleiner oder, im ersten Fall: Das Fenster, durch das du das Objekt beobachtest, zeigt nur einen Teil des Objekts, weil das Fenster zu klein ist, um das ganze Objekt sehen zu können.


Blup - Mi 11.09.13 17:21

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
Die gewünschte Größe errechnet der Befehl
Antialiasing(Image1, 80);
doch selbst? Oder?
Image1 ist auf der Form 521 x 345 px groß.

"Oder" trifft zu. Schau dir doch den Code, den du da leichtfertig in dein Projekt kopiert hast, mal an.
Die Größe in der das TImage die Grafik darstellt und die tatsächliche Größe der Grafik müssen nicht übereinstimmen.
Durch die Eigenschaft "Stretch := True" weist du das TImage an, die Grafik so darzustellen, dass der ganze Bereich ausgefüllt wird.
Die Größe der Grafik ändert sich dadurch nicht, diese wird nur gezoomt auf den Bildschirm gezeichnet.
Die Funktion Antialiasing() berücksichtigt aber genau das nicht.
Dort wird stillschweigend davon ausgegangen, die Grafik und das Image sind 100% gleich gross.
Da das Image aber größer als deine Grafik ist, wird dabei wild auf Speicher zugegriffen, der eventuell nicht mal zur Grafik gehört.

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:

Image1 ist auf der Form 521 x 345 px groß.

Das Seitenverhältnis stimmt nicht mit dem deiner Bilder überein.
Dann kann das Bild nur verzerrt sein.
Antialiasing hilft dagegen überhaupt nicht.
Nur wenn auch die Eigenschaft "Proportional := True" gesetzt ist, bleiben die Seitenverhältnisse erhalten.


Delete - Mi 11.09.13 19:28

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Die Größe in der das TImage die Grafik darstellt und die tatsächliche Größe der Grafik müssen nicht übereinstimmen.

Das hatte ich oben doch bereits geschrieben!

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Durch die Eigenschaft "Stretch := True" weist du das TImage an, die Grafik so darzustellen, dass der ganze Bereich ausgefüllt wird. Die Größe der Grafik ändert sich dadurch nicht, diese wird nur gezoomt auf den Bildschirm gezeichnet.

Auch hier wiederholst du lediglich meine Aussagen.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Die Funktion Antialiasing() berücksichtigt aber genau das nicht. Dort wird stillschweigend davon ausgegangen, die Grafik und das Image sind 100% gleich gross.
Da das Image aber größer als deine Grafik ist, wird dabei wild auf Speicher zugegriffen, der eventuell nicht mal zur Grafik gehört.

Und das hattest du selbst weiter oben bereits erläutert.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Das Seitenverhältnis stimmt nicht mit dem deiner Bilder überein. Dann kann das Bild nur verzerrt sein. Antialiasing hilft dagegen überhaupt nicht.

Antialiasing hatte ich oben eingebracht, weil der TE nirgends kenntlich machte, um welche Art von Verzerrung es sich handelte:

"Es gibt zwei Arten von Verzerrungen:
1. Das Seitenverhältnis von Original- und Zielgrafik ist unterschiedlich. In dem Fall wird die Zielgrafik horizontal oder vertikal verzerrt dargestellt. Man vermeidet diesen unschönen Effekt, indem man darauf achtet, das Seitenverhältnis in der Zielgrafik beizubehalten.
2. Da es sich um eine Pixel-Grafik (im Gegensatz zur Vektor-Grafik) handelt, können bei Vergrößerung/Verkleinerung nicht alle Original-Pixel 1:1 dargestellt werden. Es wird interpoliert. Um den dadurch hervorgerufenen und wohlbekannten Treppchen-Effekt abzuschwächen, wäre ein Anti-Aliasing zu empfehlen."

Und was macht der Fragesteller daraufhin: Er kopiert sich sofort die verlinkte Antialias-Methode, ohne erstens darauf zu antworten, um welche Art von Verzerrung es sich handelt, ohne zweitens den kopierten Code auch nur ansatzweise zu verstehen und jammert dann drittens rum, daß dumpfes Copy&Paste zu fehlerhaftem Verhalten führt.

user profile iconBlup hat folgendes geschrieben Zum zitierten Posting springen:
Nur wenn auch die Eigenschaft "Proportional := True" gesetzt ist, bleiben die Seitenverhältnisse erhalten.

Und hier wiederholst du wieder meine obigen Ausführungen.

Ich meine, es ist ja nicht verboten, sich in Wiederholungen zu ergeben, kann aber dennoch keinen Sinn darin erkennen, insbesondere nicht, wenn es sich um einen quasi merkbefreiten Fragesteller zu handeln scheint. Was würdest du wohl empfinden, wenn jemand deine Hinweise und Lösungsvorschläge als seine eigenen im nächsten Posting einfach wiederholt?

Und nun wissen wir noch immer nicht, um welche Art von Verzerrung es sich ursprünglich gehandelt hat. Perlen vor die Säue – Perlsau eben ...


Blup - Do 12.09.13 09:29

@Perlsau
Mir ist schon klar das diese Informationen im Wesentlichen bereits genannt wurden.
Aber wie du selbst festgestellt hast, wurden diese nicht verstanden.
Scheinbar sind Begriffe wie "Seitenverhältnis" und "Antialiasing" nicht bekannt und das Nachschlagen im Internet zu schwierig...
Die einzige Möglichkeit überhaupt noch zu helfen die ich in solchen Fällen sehe, ist die Fakten auf andere Art zusammenzufassen, vieleicht durch die eine oder andere Information zu ergänzen und so die Zusammenhänge deutlich zu machen. Da ich dabei nicht wissendlich wörtlich zitiere, halte ich einen Verweis auf vorhergehende Beiräge zu jedem einzelnen Fakt für überflüssig.

Die Array-Definition muss nicht verändert werden, diese dient nur der Adressierung der einzelnen Pixel einer Zeile.
Bitmaps mit einer Breite von mehr als 32000 Pixeln können vermutlich durch den Grafiktreiber sowieso nicht dargestellt werden.


Gerhard_S - Do 12.09.13 14:44

Hallo,
anbei zwei Screenshots mit den Störungen. Jetzt würde ich gern eine qualifizierte Beschreibung der Störungen lesen.


Delete - Do 12.09.13 14:52

user profile iconGerhard_S hat folgendes geschrieben Zum zitierten Posting springen:
Hallo,
anbei zwei Screenshots mit den Störungen. Jetzt würde ich gern eine qualifizierte Beschreibung der Störungen lesen.

Plonk


WasWeißDennIch - Do 12.09.13 15:31

Würde diese Qualität genügen (das Original ist das links oben)?


Gerhard_S - Do 12.09.13 18:23

Ja, reicht völlig aus.


WasWeißDennIch - Do 12.09.13 18:36

Dann schau Dir doch nochmal den meinerseits verlinkten Beitrag auf der ersten Seite an, damit habe ich das gemacht.


Gerhard_S - Do 12.09.13 23:42

Danke für den Hinweis. Eine Frage habe ich noch: Bei deinem Code muss auch die Ausgangsgrafik einem Steuerelement zugeordnet (assigned) werden. Meine Benutzer sollen aber nur die modifizierte Grafik sehen. Das könnte man mit Image1.Visible := false regeln, scheint mir aber nicht sehr elegant zu sein. Kennst du einen anderen Weg?


WasWeißDennIch - Fr 13.09.13 08:10

Wie kommst Du darauf? Ich habe den Code nicht gespeichert, aber das sah etwa so aus:

Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
var
  PngOriginal, PngGross, PngKlein: TPngImage;
begin
  PngGross := nil;
  PngKlein := nil;
  PngOriginal := TPngImage.Create;
  try
    PngOriginal.LoadFromFile(Dateiname);
    PngKlein := TPngImage.Create;
    PngGross := TPngImage.Create;
    (* Statt der Dimensionen von TImages können natürlich auch absolute Werte übergeben werden *)
    StretchGraphic(PngOriginal, PngKlein, ImgKlein.Width, ImgKlein.Height);
    StretchGraphic(PngOriginal, PngGross, ImgGross.Width, ImgGross.Height);
    (* Der Rest dient ja nur der Anzeige, kann auch weggelassen werden *)
    ImgOriginal.Picture.Assign(ImgOriginal);
    ImgKlein.Picture.Assign(ImgKlein);
    ImgGross.Picture.Assign(ImgGross);
  finally
    PngOriginal.Free;
    PngGross.Free;
    PngOriginal.Free;
  end;
end;


Gerhard_S - Fr 13.09.13 22:35

Ich meinte das hier:

Delphi-Quelltext
1:
2:
procedure StretchGraphic(const src, dest: TGraphic;
   DestWidth, DestHeight: integer; Smooth: Boolean = true);

in der ersten Zeile hinter begin wird postuliert:
Assert(Assigned(src) and Assigned(dest));


WasWeißDennIch - Sa 14.09.13 12:39

Ja, das eine ist die Quellgrafik, das andere die Zielgrafik. Um das eine in das andere zu bekommen, müssen ja beide da sein, oder? Das hat aber mit Steuerelementen nichts zu tun.