Ressourcen wieder freigeben -> Bildfunktionen

Heute mal nur ein ganz kleiner Beitrag, aber man kennt es ja „In der Kürze liegt die Würze“ und „Klein aber oho“. Besonders diese kleinen Tipps können manchmal gute Ergebnisse bringen. Thema ist die Bearbeitung von Bildern mittels der Image-Funktionen von PHP.

Und zwar habe ich ein Script erstellt, das zu große hochgeladene Bilder (man kennt das ja von den Sonstwieviel-MegaPixel-Kameras) so verkleinert, dass sie eine bestimmte Dateigröße nicht überschreiten.
Da es aber leider nicht möglich ist, im Vorhinein die Bildgröße des Bildes (Breite x Höhe) zu bestimmen, bei der das Bild die Dateigröße x hat, musste ich das sequentiell machen. Also erst 90% der Originalgröße, dann 80% usw. bis die Dateigröße kleiner oder gleich der geforderten Grenze war.

Als ich dieses Script testete kam der äußerst unbeliebte Fehler

failed to open stream: Cannot allocate memory

Unbeliebt deshalb, weil man ihn oft nur schwer beheben kann.
Das Problem ist nämlich, dass das Bild als Image-Ressource geladen und anschließend per imagejpeg auf der Platte gespeichert wird. Die Image-Ressource belegt aber auch nach dem Speichern auf der Platte noch Ressourcen (= Arbeitsspeicher). Und wenn man die 90% Größe durch hat, gehts weiter mit 80% (das 90%-Bild ist ja dann eigentlich überflüssig) usw.
Die Ressourcen werden standardmäßig von PHP erst wieder freigegeben, wenn das Script ganz abgearbeitet wurde (rekursive Geschichten sind da also ganz schlecht, wenn man sich nicht um den Speicher kümmert).

Hier also kurz der Ablauf meines Scripts:

  1. Laden der Original-Bilddatei durch ein Formular
  2. Prüfen ob Dateigröße > x (durch das assoziative Array $_FILES[‚xyz‘][’size‘])
  3. Falls 2. true, Funktion zum Verkleinern aufrufen mit 90% Bildgröße
  4. Bild als Image-Ressource laden, Größe per imagesx und imagesy ermitteln und das Zielbild per imagecopyresampled() auf 0,9 der Originalgröße resizen.
  5. per imagejpeg() auf der Festplatte abspeichern
  6. Dateigröße ermitteln, falls immernoch > x,dann zu Schritt 3 mit 10% kleinerer Bildgröße, ansonsten Funktion verlassen

Als Beispiel möchte ich also mal von einem Bild ausgehen, das 1 MB als Image-Ressource belegt (also nicht als jpg, png oder sonstwas sondern PHP-intern als Ressource). Im Worst-Case werden 9 Rekursionsdurchläufe ausgeführt, wenn man immer 10% nach unten geht (90, 80, 70, … , 10).
Das bedeutet, dass sich der belegte Speicher so hochschaukelt (wenn man von einer Proportionalität zwischen belegtem Speicher und Bildgröße ausgeht):
0,9*1000 + 0,8*1000 + 0,7*1000 + … + 0,1*1000 = 900 + 800 + 700 + … + 100 = 4500 kB
Und das obwohl sämtliche bereits abgearbeiteten Bilder gar nicht mehr benötigt werden, weil sie ja als von der Dateigröße zu groß ausgewertet wurden. Es wäre also die 4,5-fache Speichermenge nötig (plus der temporären Datei).

Bei mir geholfen hat letztlich – und damit kommen wir hier mal zum Punkt – die Funktion imagedestroy(). Diese gibt den Speicher für die Bild-Ressource wieder frei. Außerdem kann man per unlink dann noch die gespeicherte Bilddatei entfernen, die ja benötigt wurde, um die Dateigröße zu bestimmen – also man entfernt sie natürlich nur, wenn die Dateigröße dann immernoch über der Dateigrößen-Grenze x ist, ansonsten endet die Rekursion und man nimmt das Bid einfach.
Oben beschriebener Fehler kommt dadurch nicht mehr. Somit ist die Speichernutzung des Apache sehr viel effektiver, also ruhig mal ordentlich und sauber arbeiten 😉

Falls jemand Tipps hat, wie man obige Aufgabe schneller erledigen kann, bitte in den Kommentaren posten. Schwachpunkte waren vor allem, dass man nicht ermitteln kann, bei welcher Bildgröße eine Datei eine bestimmte Dateigröße hat sowie das man die resultierende Dateigröße der Image-Ressource erst nach Zwischenspeichern auf der Platte feststellen kann. Vielleicht gibts dafür ja Lösungen, die mir nicht eingefallen sind.

Dieser Beitrag wurde in   PHP veröffentlicht.
Fügen Sie ein Lesezeichen für den   permanenten Link hinzu.

Jan hat 152 Beiträge geschrieben

26 Kommentare zu “Ressourcen wieder freigeben -> Bildfunktionen

  1. Daniel sagt:

    Hi – hab den Beitrag mal kurz überflogen. Bin schon recht müde 😉

    Du schreibst, dass du die hochgeladene Datei auf Platte speicherst um die Größe zu ermitteln?!?

    Die Dateigröße steht direkt im Array für die Upload-Verwaltung: Wenn du deine Datei im Upload-Formular z.B. „image001“ getauft hast, enthält

    $_FILES[‚image001′][’size‘]

    die Größe der Datei in Bytes.

    So, jetzt gehts in die Heia. Bis denne

    Daniel

  2. Alexander sagt:

    Könntest Du mal den Source posten. Mich würde das interessieren. Gehts Du dabei auch auf die Bildqualität ein? Also zum Beispiel 90% Bildgröße und dann die Bildqualität (JPEG) bis auf max. 80% runterschrauben?

  3. Markus sagt:

    Typo3 nutzt für solche Dinge gern ImageMagick, da man hiermit gleich auch alle unnötigen Bildinformationen loswerden kann.

    z.B. per „convert original.jpg -thumbnail thumbnail.jpg“

    ImageMagick hat IMHO die beste Qualität und die geringste Dateigröße. Vielleicht lässt sich damit auch einfacher die Dateigröße bestimmen, meine Thumbnails waren in ihrer Dateigröße oft ähnlich.

  4. admin sagt:

    @Daniel: Korrekt, das mache ich auch (hab ich vielleicht bissl falsch beschrieben), allerdings funktioniert das nur mit der Datei, die vom User hochgeladen wurde (also die allererste Überprüfung) und nicht mehr mit denen, die ich verkleinert habe.

    @Alexander: Eher ungern, weil das dann immer gleich kopiert wird. Anhand obiger Beschreibung sollte jeder, der sich mit PHP auskennt, das Script nachbauen können.

    @Markus: Gut, ich habe jetzt GD2 benutzt, weil das bei php.net so schön beschrieben ist. Eigentlich bietet mir das auch alles, was ich haben möchte (es funktioniert ja, nur muss man eben immer zwischenspeichern). Und über die Qualität kann ich mit GD2 nicht meckern. Optional könnte ichs nochmal schärfen im Nachhinein, aber bin auch so sehr zufrieden mit den Bildern.

    Zu Imagemagick: Allein über einen Parameter thumbnail würde ich es allerdings nicht machen wollen, denn ich will das Bild ja so groß wie möglich abspeichern. Sonst könnte ich alles auf 100 x 100 px machen und dann sind die garantiert unter Dateigröße x. Aber das hilft ja nix, die Leute wollen ja noch was erkennen auf den Bildern.
    Weiß da jemand mehr, wie Typo3 das macht (möchte ich jetzt ungern runterladen und durchforsten)?

  5. Jo sagt:

    Fällt mir auch nichts anderes ein wenn du das File als JPEG speichern willst, da die Grösse eines JPEGs ja je nach Farben im Bild variieren kann.
    Das Limit kann übrigens über memory_limit in der php.ini festgelegt werden, bei manchen Hostern hilft bei zu knapp verfügbarem Speicher z.B. ein
    ini_set('memory_limit', '64M');
    (ist aber wahrscheinlich selten erlaubt…)

    Ob das Skalieren eines Bildes funktionieren wird, kann übrigens schon im Vornhinein geprüft werden: Ein Pixel benötigt PHP intern ja 3 Bytes (je 1 Byte für R, G und B) und nochmals ein Byte für den Alpha Channel.
    Ein Bild von 2000x2000px benötigt also schon mal 16MB im Speicher…

    // gibt z.B. 12M zurück
    $limit = ini_get(\'memory_limit\');
    // in Bytes umrechnen
    $limit = (int)$limit * 1024 * 1024;
    $size = getimagesize($file);
    // so viel Bytes werden benötigt
    $pixels = $size[0] * $size[1] * 4;
    // prüfen, ob mehr Bytes benötigt 
    // werden als verfügbar sind
    if ($pixels > $limit) {
      // wird nicht gehen
    } else {
      // Bild verarbeiten
    }

    Praktisch, um dem User mitzuteilen dass sein Bild zu gross für die Verarbeitung ist.
    Obiger Code ist quick&dirty und ungetestet, aber soll ja auch nur das Prinzip darstellen 😉 z.B. könnte man noch überprüfen, ob die Angabe von memory_limit wirklich in MB erfolgt. Zudem benötigt auch das Script selber noch Ressourcen, was evtl. berücksichtigt werden sollte…

    Aber nochmals zurück zum Thema, deine Problematik ist mir im Produktiveinsatz noch nie begegnet. Bilder skaliere ich immer auf eine bestimmte Auflösung, nicht auf die Dateigrösse. Die paar Bytes drüber oder drunter sind mir bei den heutigen HDD’s dann auch egal 🙂

  6. Jo sagt:

    @admin:
    Du solltest mal das CSS für das Code-Tag überarbeiten 😀 zudem werden Quotes bei der Ausgabe escaped oder so.
    Wird eigentlich in den Kommentaren ein PHP Tag mit Syntax-Hightlighting unterstützt? Wäre manchmal noch sinnvoll.
    Kannst diesen Kommentar auch wieder löschen 😉

  7. admin sagt:

    Syntax-Highlighting im Kommentar geht leider nicht. Da sprint das Plugin nicht an (und ich hab auch leider keine Zeit da irgendwo rumzufummeln, damit das geschieht. Das gleiche ist mit dem Escapen von Hochkommata und Gänsefüßchen.

    Dein Ansatz des Prüfens, ob es überhaupt funktionieren kann, ist schon mal sehr gut. Meine Besucher (also nicht hier im Blog sondern in nem Projekt, wo obiges Script zum Einsatz kommt) sind nur leider oft überfordert, wenn ich denen sage, dass ein Bild zu groß ist, weil sie nicht wissen, wie sie es kleiner kriegen.

    Das nur über die Bildgröße zu regeln ginge natürlich auch, ich denke mal drüber nach … eigentlich gehts mir ja nur um Fotos von diesen 10 MPx-Kameras, die die Leute ohne Bedenken einfach auf meine Seite schießen 😉

  8. Björn sagt:

    Also ich bezweifel das dieser Ansatz der beste Weg ist. Mit GD2 verkleinert man keine Bilder von 10Megapixel-Kameras.

    Ebenso würde ich nur begrenzt ImageMagick einsetzen da dies Ressourcen fressend ist. Besonders für die Verkleinerung von Bildern würde ich GraphicMagick empfehlen, da dies Serverschonender gegenüber ImageMagick ist, sowie Qualität und Dateigröße enorme Unterschiede aufweisen.

    Ich habe mit nen Kollegen einmal ein Test gemacht und beide Grafik-Tools ausgewertet.

    Habe gerade nochmal nach dem Ergebnis gesucht: http://www.bildercache.de/anzeige/20060915-161300-73.jpg

    Dabei in der 6er Version Angaben vom Speicher machen kann. Um grössere Bilder entgegen zu kommen.

    Zu dem ganzen muss man einfach sagen das dabei die Voraussetzung eins der beiden Tools ist, was aber in der heutigen Zeit zu 99% der Fall ist.

  9. admin sagt:

    GD2 ist in eurem Test ja gar nicht vertreten. Wieso verkleinert „man“ also damit keine Bilder?
    Und wie gesagt: Mit der Qualität habe ich bei GD2 keine Probleme. Sieht gut aus (mit reichts auf jeden Fall).

  10. Björn sagt:

    Weil GD mehr Ressourcen verbraucht und Serverlastiger ist.

    Bei Imagemagick/Graphicmagick rufst du ein externes Programm auf, GD wiederum nicht da dies im PHP drin ist.

    Wenn du Bilder die 3000×3000 Pixel oder so haben, braucht GD länger als Imagemagick.

    Dazu ist Imagemagick leicht und komfortabel zu bedienen und hast mehr Möglichkeiten.

    Als Bilder-Hoster muss ich schauen wohin mit der Performance. Dazu Qualität und Dateigröße ebenfalls um den Traffic aber auch die Ladezeit für den User so niedrig wie möglich zu halten.

  11. Björn sagt:

    Ich kann mich gerne für einen ausführlicheren Test überreden dort die neuen Versionen von IM, GM und auch GD mit im Spiel ist. 🙂

  12. admin sagt:

    Sehr gern, freue mich über alles, was Performance-Dinge untersucht. Falls Du keine Plattform für die Veröffentlichung hast, biete ich Dir den Blog hier dafür an (selbstverständlich mit Nennung Deines Namens und Link, wenn Du magst).

  13. Ralf sagt:

    Mal so als Idee wie du im Voraus den Speicherbedarf berechnen könntest.
    Nachdem das Bild hochgeladen ist, kennst du Breite und Höhe. Daraus lässt sich der Speicherbedarf für ein unkomprimiertes Bild berechnen (Breite x Höhe).
    Ebenfalls bekannt ist die Größe der hochgeladenen Datei (Speicherbedarf des Jpeg-Bildes) bekannt.
    Wenn du nun die tatsächliche Dateigröße von der theoretische Größe (Breite x Höhe) abziehst, erhälst du so etwas wie den Komprimierungsfaktor.
    Bsp: Größe der hochgeladenen Datei: 4MB; Speicherbedarf aus Breite x Höhe 4MB; Komprimierungsfaktor wären dann ca. 30%
    Der Rest ist dann Dreisatz. (Neue Breite x Neue Höhe) – Komprimierungsfaktor = gewünschte Dateigröße.

    das ist jetzt aber alles reine Theorie, ich habe es nicht ausprobiert ob es funktioniert. Das Problem ist nämlich die Komprimierung. Denn du kannst nicht voraussagen wie gut das fertige Jpeg-bild komprimiert wird, da dies vom Inhalt abhängig ist.
    Ich habe es gerade eben mal schnell mit einer einfarbigen Fläche (1024×1024 Pixel) ausprobiert. Das Originalbild braucht ~7KB, bei 90% sind es 6KB, bei 80% 5KB und bei 50% nur noch 2KB. Es ist also recht schwer vorauszusehen wie groß die endgültige Datei sein wird.

    Um trotzdem möglichst nah an den gewünschten Wert zu kommen, würde ich aber eine Iteration verwenden anstatt die rekursive Methode. Iterationen sind i.d.R. effizienter.

  14. admin sagt:

    @Ralf: Die Gedanken habe ich mir auch gemacht, aber es besteht eben beim JPEG-Kompressionsverfahren keine direkte Proportionalität zwischen Bildgröße und Dateigröße. Probieren werde ichs trotzdem mal 😉

    Dann ist da glaube ich ein Schreibfehler bei deinem Beispiel. Da stehen beide Male 4MB. Oder habe ichs nicht verstanden 😉 ?

    Was genau meinst Du mit iterativ? Im Prinzip mache ich es ja iterativ. Der Aufruf erfolgt zwar rekursiv, jedoch ist die maximale Anzahl an Durchläufen begrenzt. Natürlich kann ich das auch durch 10 Funktionsaufrufe mit dazwischenliegenden Prüfungen der Dateigröße machen, aber gewonnen wäre dadurch nix.

  15. Ralf sagt:

    Yupp, ist ein Schreibfehler.

    Stimmt, die Komprimierung ist abhängig vom Inhalt. Wenn du ein Bild um 10% verkleinerst, ändert sich der Inhalt nicht wesentlich. Der Komprimierungsfaktor bleibt also annähernd gleich. Vorausgesetzt natürlich das Bild besteht nicht aus 100×100 Pixeln.
    Verkleinerst du hingegen ein Bild von 1024×1024 Pixel auf 100×100 Pixel, so gehen erhebliche Teile der Bildinformationen verloren. Deswegen ist dann der Komprimierungsfaktor ein ganz anderer.
    Bei kleinen Änderungen und hohen Auflösungen müsste es also doch recht gut klappen.

    Ich bin kein Mathematiker, musste also ein wenig suchen um die korrekte Bezeichnung zu finden. Ich kannte das unter Iteration, naja…
    Gemeint habe ich aber Intervallhalbierungsverfahren.

    Nehmen wir mal an das gewünschte Ergebnis würde bei rund 60% liegen. Mit deiner Methode müsstest du 4 Bilder erzeugen.
    Mit der Methode oben wärst du schon mit 3 Bildern (50%, 75% und 62,5%) am Ziel. Auf den ersten Blick kein großer Gewinn, aber in dem Blog hier geht es ja um Performance 😉
    Der Performancegewinn wird erst richtig deutlich, wenn das gewünschte Ergebnis bei rund 30% liegen würde. Mit deiner Methode müsstest du 7 Bilder erzeugen (90,80,70,60,50,40,30%). Mit der Methode oben wäre man schon nach 4 Schritten am Ziel. Also fast doppelt so schnell.

  16. admin sagt:

    Jo, der Gedanke zum Komprimierungsfaktor ist mir dann nach dem Schreiben des Kommentars auch noch gekommen (deshalb hab ich noch schnell das „probiere ich trotzdem mal aus“ reingeworfen 😉
    Stimme ich Dir zu – mal sehen, ob das funktioniert.

    Mit dem Intervallhalbierungsverfahren ist auch keine schlechte Idee. So könnte man es genau an eine bestimmte Dateigröße annähern. Ob dieses Verfahren sinnvoll ist, ist allerdings abhängig von der Abbruchgrenze und der durchschnittlichen Größe der behandelten Bilder.
    Liegt die durchschnittliche Zielgröße bspw. bei 80%, verbraucht das Verfahren eher mehr Ressourcen (50 – 75 – 87,5 – 81,25).

    Aber trotzdem sehr interessante Ansätze, die ich mir mal hinsichtlich ihrer Praxistauglichkeit (für meine Seite) ansehe.

  17. Ralf sagt:

    Du kannst ja ein Logfile anlegen in dem du alle erzeugten Größen abspeicherst. Aus diesem Logfile wiederum erzeugst du einen Mittelwert für die durchschnittlich erzeugte Größe. Diesen Mittelwert nimmst du dann als untere Grenze.
    Pendelt sich der Mittelwert mit der Zeit z.B. bei 80% ein, dürften dann zwei, drei Versuche genügen um eine optimale Größe zu finden. So würde sich das Script quasi selber Optimieren anstatt immer mittels Try&Error zu arbeiten.

  18. Jan sagt:

    @Ralf: Das ist ne gute Idee. Ein Intervallhalbierungsverfahren mit Vorwissen sozusagen. Schade nur um den vielen Speicher, der dafür verbraucht wird, denn ich muss ja wirklich die Skalierungsfaktoren aller Bilder speichern. Lieber wär mir nur eine Zahl zu speichern, aber da bekommt man keinen ordentlichen Durchschnitt.

    Bsp: Faktoren 10%, 80%, 50%, 20%
    Durchschnitt, wenn alle gespeichert:
    (10+80+50+20)/4 = 40

    Durchschnitt mit nur einem gespeicherten Wert:
    (10+80)/2 = 45 -> (45 + 50)/2 = 47,5 -> (47,5 + 20)/2 = 33,75

    Denn der Wert macht ja wirklich nur für das Skalieren in der Zukunft Sinn, nicht mal für das Bild selbst, das ist so verschwenderisch. Gibts dafür noch nen Ausweg?
    Und ich möchte jetzt nicht hören, dass Speicher ja heutzutage genug vorhanden ist.

  19. Ralf sagt:

    Also in einem Kommentar beschwerst du dich das das Verfahren mehr Ressourcen verbrauchen würde (Zitat: Liegt die durchschnittliche Zielgröße bspw. bei 80%, verbraucht das Verfahren eher mehr Ressourcen). Im nächsten beschwerst du dich über zu viele verschiedene Werte.

    Ein brauchbarer Durchschnittswert lässt sich bestimmt nicht aus 4 Werten berechnen. Bei 100 und mehr fallen Ausreißer wie z.B. 10% kaum noch ins Gewicht.
    Selbst wenn die Bandbreite sehr groß wäre, wäre das Halbierungsverfahren noch besser. Denn bei extremen Werten wie z.B. 10% sparst du letzten Endes mehr Ressourcen als wie bei den anderen Werten (um die 80%) verschwendet werden.

  20. admin sagt:

    Nee, hast Du falsch verstanden. Ich find das Intervallhalbierungsverfahren schon ne tolle Idee. Und wenn man das ganze mit dem Durchschnittswert der bisherigen Bilder beginnt, noch besser!

    Meine Anmerkung zum Durchschnittswert bezog sich eher darauf, dass der Skalierungsfaktor für jedes Bild gespeichert werden muss und er als Einzelwert nie benötigt wird. Dass ich beim Beispiel 4 Werte genommen habe, spielt da keine Rolle.
    Aber ich speichere ihn einfach mit und gut ist…

  21. Ralf sagt:

    (bin nicht der Ralf von oben 😉 )
    DEr Beitrag hier ist zwar schon etwas Älter, aber naja 😉
    speicher doch einfach immer nur 2 werte ab:
    summe der Prozentzahlen und anzahl der darin enthaltenen Daten. Das spart dann Platz und du hast deinen Durchschnitt.
    Ich mach mal ein Beispiel:

    1. Datei: 50%
    2. Datei: 75%
    3. Datei: 88%
    4. Datei: 95%
    Summe = 308 Anzahl 4

    beim nächsten Afruf rechnest du 308/4 -> hast den durchschnitt. Nach dem Iterieren speicherst du dann „Summe+neuer Wert“ und „Anzahl+1“ ab. Hoffe das ist so verständlich 😉

  22. Markus.sagt[…, meine Thumbnails waren in ihrer Dateigröße oft ähnlich.]

    Man sagt je kleiner die Bilder werden je ähnlicher werden sie sich.
    z.B. Zwin bitmap'[bmp] ohne header , metadat. und bei gleichem Namen waren 1px hoch wie auch breit. und nahm die gleiche Menge ein. Dennoch war das eine Schwarz und das andere Weiß.

    Oje : (

Eine Antwort schreiben

Ihre E-Mail-Adresse wird nicht veröffentlicht. Benötigte Felder sind markiert mit *

You may use these HTML tags and attributes: <a href=""> <blockquote cite=""> <pre lang=""> <b> <strong> <i> <em>