ich generiere gerade von einem Stream ein File.
Das File wird auch erstellt, aber es ist leer, also wohl keine Bytes. (Größe ist 0 MB)
Mein InputStream ist allerdings nicht leer.
Wie kann ich das näher analysieren wo nun das eigentliche Problem ist?
try(InputStream is = uploadFileHelper.getInputStream()){byte[] buf =newbyte[8192];int n;long total =0;while((n = is.read(buf))!=-1){
total += n;}System.out.printf("%d bytes read\n", total);// oder was auch immer Du zum Loggen verwendest.}catch(IOException ex){
ex.printStackTrace();}
Also ich bekomme nun die Anzahl an Bytes, die von der anderen Methode kommt.
Allerdings bekomme ich dann nun hier eine Exception: int bytes = uploadFileHelper.getInputStream().read();
-> java.io.IOException: Stream Closed
Java:
try(InputStream is = uploadFileHelper.getInputStream()){byte[] buf =newbyte[8192];int n;long total =0;while((n = is.read(buf))!=-1){
total += n;}System.out.printf("%d bytes read\n", total);// oder was auch immer Du zum Loggen verwendest.}catch(IOException ex){
ex.printStackTrace();}File file =newFile(storageAttachment.getFullPath());int bytes = uploadFileHelper.getInputStream().read();int test = uploadFileHelper.getInputStream().available();FileUtils.copyInputStreamToFile(uploadFileHelper.getInputStream(), file);
Setz den Spaß mal dort ein, wo Du aktuell FileUtils.copyInputStreamToFile verwendest. Wenn das funktioniert, lass Dir noch das file ausgeben (System.out.println). Wenn auch das passt, dann versuch mal
Java:
try(InputStream is = uploadFileHelper.getInputStream();FileOutputStream os =newFileOutputStream(file)){byte[] buf =newbyte[8192];int n;long total =0;while((n = is.read(buf))!=-1){
os.write(buf,0, n);
total += n;}System.out.printf("%d bytes read\n", total);// oder was auch immer Du zum Loggen verwendest.}catch(IOException ex){
ex.printStackTrace();}
Wie könnte nun noch das realisieren:
1. Stream umwandeln in eine Bilddatei
2. Bilddatei hat immer eine bestimmte Größe (z.B. 500x500px)
3. Bilddatei wiederum als Stream ausgeben...
Generell auch die Frage wie man sich das mit einem Stream vorstellen kann.
Wird hier temporär (Temp-Folder) auf dem Client anhand der Bytes eine Datei erstellt? Oder auf dem Server?
Was passiert wenn ich einen Stream nicht schließe (weil vergessen), die Aktion aber sowieso in einem "Stateless" EJB Container / @ViewScoped JSF Bean stattfindet?
Also was hier etwas unklar ist, ist die Frage, wie das Scaling gemacht werden soll. Sollen die Verhältnisse beibehalten werden oder soll das Bild einfach gedehnt / verzerrt werden?
Eine Utility Klasse, die ich einmal geschrieben habe, behält die Verhältnisse bei:
Java:
packagede.kneitzel.images;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;/**
* Helper class to scale images
*/publicclassImageScaler{publicstaticbyte[]createScaledImage(byte[] originalImageBytes,int width,int height){// Validationif(originalImageBytes ==null)returnnull;if(originalImageBytes.length==0)return originalImageBytes;try{// Create the image from a byte array.BufferedImage originalImage =ImageIO.read(newByteArrayInputStream(originalImageBytes));// Get values required to scale the Image:double scaleWidth =(double) width /(double) originalImage.getWidth();double scaleHeight =(double) height /(double) originalImage.getHeight();double scaleFactor;if(scaleWidth > scaleHeight){
scaleFactor = scaleHeight;}else{
scaleFactor = scaleWidth;}int newHeight =(int)(scaleFactor * originalImage.getHeight());int newWidth =(int)(scaleFactor * originalImage.getWidth());int x =(width - newWidth)/2;int y =(height - newHeight)/2;// Scale the imageBufferedImage scaledImage =newBufferedImage(width, height,BufferedImage.TYPE_INT_ARGB);Graphics2D graphics = scaledImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawImage(originalImage, x, y, newWidth, newHeight,null);// Get the bytes of the imagetry(ByteArrayOutputStream stream =newByteArrayOutputStream()){ImageIO.write(scaledImage,"png", stream);return stream.toByteArray();}}catch(Exception ex){returnnull;}}}
Da Du generell mit Streams arbeiten möchtest, ändert sich lediglich am Anfang und am Ende etwas: BufferedImage originalImage = ImageIO.read(yourInputStream);
Sprich: statt ein ByteArray zu übergeben wird hier einfach dein InputStream an ImageIO.read gegeben.
Und am Ende kann der ganze Block mit dem ByteArrayOutputStream weg und Du schreibst einfach das neue Bild direkt in den Outputstream: ImageIO.write(scaledImage, "png", yourOutputStream);
(Ich hatte halt die Bilder in byte Arrays und brauchte diese auch genau so.)
Also der Workflow sieht so aus:
1) Upload Beleg (Rechnung) über Web GUI... Kann eine Bilddatei oder PDF sein
2) Beleg wird in Bilddatei umgewandelt um es in der WebGUI als Vorschau anzuzeigen
a) Wenn PDF, dann werden daraus mehrere PNG / JPG -Dateien generiert
b) Wenn Bilddatei, dann skaliere das Bild herunter (macht kein Sinn ein 5MB Bild in der WebGUI als Vorschau anzuzeigen)
1) Funktioniert bereits
2) Umwandlung funktioniert
3) Skalierung fehlt eben noch...
-> Also sprich ich möchte die Vorschau eig. immer in einer fixen Größe haben.
Indem Du es in eine "Box" mit einer fixen Größe einpasst. Wenn ich mich gerade nicht vertue (bin nicht ganz bei der Sache), dann sollte das die neue Bildgröße liefern:
Java:
publicDimensioncalculateScaledDimension(Dimension imgDimension,Dimension boxDimension){int h = boxDimension.height;int w = imgDimension.width * boxDimension.height / imgDimension.height;if(w > boxDimension.width){
w = boxDimension.width;
h = imgDimension.height * boxDimension.width / imgDimension.width;}returnnewDimension(w, h);}
Also der Workflow sieht so aus:
1) Upload Beleg (Rechnung) über Web GUI... Kann eine Bilddatei oder PDF sein
2) Beleg wird in Bilddatei umgewandelt um es in der WebGUI als Vorschau anzuzeigen
a) Wenn PDF, dann werden daraus mehrere PNG / JPG -Dateien generiert
b) Wenn Bilddatei, dann skaliere das Bild herunter (macht kein Sinn ein 5MB Bild in der WebGUI als Vorschau anzuzeigen)
1) Funktioniert bereits
2) Umwandlung funktioniert
3) Skalierung fehlt eben noch...
-> Also sprich ich möchte die Vorschau eig. immer in einer fixen Größe haben.
Das mit dem runter skalieren sollte mit dem Code von mir doch gehen. Also wenn die Größe 500x500 sein soll, dann gibst Du das so an. Wenn die Bilder in der Regel A4 Format oder so hatten, dann passt Du es etwas an. Und bei der scalierung bleiben die Verhältnisse gleich.
Alternative könnte für Dich sein, dass Du die Ränder weg lassen willst, d.h. Du bekommst als Ergebnis ein Bild der Größe 500xAAA oder AAAx500 mit AAA<500.
Alles was sich dann im Code ändern würde:
newWidth und newHeight wurde ja berechnet. Statt das Bild nun in die vorgegebene Größe zu bauen, erzeugst Du ein Bild in genau dieser berechneten Größe.
Ich überlege gerade, woran es gerade scheitert....
Also ein ganz wichtiger Aspekt ist das Seitenverhältnis. Wenn Du ein Bild vergrößerst oder verkleinerst, dann kannst du das Seitenverhältnis beibehalten oder eben nicht beibehalten.
Wenn das Seitenverhältnis gleich bleibt, bedeutet dies:
a) Du kannst nicht auf beliebige Größen gehen. Beispiel: Bild ist 100x50 gross. Dann kannst Du es auf 500x250 vergrößern, aber eben nicht auf 500x500.
b) Das Bild selbst bleibt aber (mehr oder weniger) gleich, d.h. ein Quadrat bleibt ein Quadrat.
Wenn die Seitenverhältnisse nicht gleich bleiben, bedeutet dies:
a) Du kannst auf beliebige Größen gehen. Also ein Bild von 100x50 kannst Du auf 500x500 vergrößern.
b) Der Inhalt wird aber dann verzerrt. Ein Quadrat im 100x50 Bild wird im 500x500 Bild kein Quadrat mehr sein sondern ein Rechteck (Wo eine Kante doppelt so lang ist wie die andere).
Du willst es als Vorschau oder so speichern, d.h. die Seitenverhältnisse müssen gleich bleiben, damit die Vorschau nicht verzerrt ist.
Dadurch kannst Du aber nicht mehr die Zielgröße 500x500 erreichen, sondern - je nach Größe des Bildes - entstehen Ränder.
Daraus folgt dann:
a) es muss die neue Größe berechnet werden. Dies macht mein Code in den Zeilen, in denen new* berechnet wird.
b) es muss berechnet werden, wo das Bild dargestellt werden muss (Die Ränder recht/links oder oben/unten sollen gleich groß sein). Dazu berechne ich die Werte x und y.
Wenn man da hat, kann man ein neues Bild erzeugen und dann das vorhandene Bild in dieses neue Bild mit der richtigen Größe an die berechnete Position malen.
Das ist also die Beschreibung von meinem Code.
Wenn Du generell mit Streams arbeiten willst, dann wäre die Funktion - wie schon beschrieben - anzupassen:
Java:
packagede.kneitzel.images;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;/**
* Helper class to scale images
*/publicclassImageScaler{publicstaticvoidcreateScaledImage(InputStream inStream,OutputStream outStream,int width,int height){try{// Create the image from a byte array.BufferedImage originalImage =ImageIO.read(inStream);// Get values required to scale the Image:double scaleWidth =(double) width /(double) originalImage.getWidth();double scaleHeight =(double) height /(double) originalImage.getHeight();double scaleFactor;if(scaleWidth > scaleHeight){
scaleFactor = scaleHeight;}else{
scaleFactor = scaleWidth;}int newHeight =(int)(scaleFactor * originalImage.getHeight());int newWidth =(int)(scaleFactor * originalImage.getWidth());int x =(width - newWidth)/2;int y =(height - newHeight)/2;// Scale the imageBufferedImage scaledImage =newBufferedImage(width, height,BufferedImage.TYPE_INT_ARGB);Graphics2D graphics = scaledImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawImage(originalImage, x, y, newWidth, newHeight,null);// Write the imageImageIO.write(scaledImage,"png", outStream);}catch(Exception ex){System.out.println("Exception occured: "+ ex.getMessage());
e.printStackTrace();}}}
(Direkt hier im Editor geschrieben, also kann ggf. Fehler beinhalten!)
Diese Methode kannst Du nun direkt verwenden: createScaledImage(yourInStream, yourOutStream, 500, 500);
createScaledImage passt evtl. nicht mehr als Name ... scaleImage würde mir jetzt besser gefallen für diese Version, aber da kannst Du ja frei walten.
Wurde das jetzt etwas deutlicher?
Und evtl. noch die Variante, wo ein Bild ohne Ränder erzeugt wird, also ggf. kleiner als die Vorgabe:
Java:
packagede.kneitzel.images;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;/**
* Helper class to scale images
*/publicclassImageScaler{publicstaticvoidcreateScaledImage(InputStream inStream,OutputStream outStream,int maxWidth,int maxHeight){try{// Create the image from a byte array.BufferedImage originalImage =ImageIO.read(inStream);// Get values required to scale the Image:double scaleWidth =(double) maxWidth /(double) originalImage.getWidth();double scaleHeight =(double) maxHeight /(double) originalImage.getHeight();double scaleFactor;if(scaleWidth > scaleHeight){
scaleFactor = scaleHeight;}else{
scaleFactor = scaleWidth;}int height =(int)(scaleFactor * originalImage.getHeight());int width =(int)(scaleFactor * originalImage.getWidth());// Scale the imageBufferedImage scaledImage =newBufferedImage(width, height,BufferedImage.TYPE_INT_ARGB);Graphics2D graphics = scaledImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawImage(originalImage,0,0, width, height,null);// Write the imageImageIO.write(scaledImage,"png", outStream);}catch(Exception ex){System.out.println("Exception occured: "+ ex.getMessage());
e.printStackTrace();}}}
Sprich: Wenn Du ein Bild hast: 1000x500 und du rufst es mit 500x500 auf, damm hast Du im outStream ein Bild der Größe 500x250 erhalten. (Wieder ungetestet - nur zur Verdeutlichung der Möglichkeiten!)
Ok, danke...
Aber die Methode "createScaledImage" soll doch wieder ein InputSteam zurückgeben....
OutputStream bei den Parametern verstehe ich ebenfalls nicht.
Ich gehe doch mit einem InputStream in the Methode rein und am Ende soll wieder ein InputStream (verkleinertes Bild) herauskommen....
Würde mir eher sowas vorstellen? public static InputStream createScaledImage(InputStream inStream, int maxWidth, int maxHeight){..}
Wenn Du einen InputStream zurück geben willst, dann kannst Du z.B. den Block von der ersten Version nehmen, die das Bild in ein byte[] schreibt.
Und auf dieses byte[] kannst Du dann ein ByteArrayInputStream erzeugen.
Also dann sowas wie z.B.:
Java:
// Get the bytes of the imagetry(ByteArrayOutputStream stream =newByteArrayOutputStream()){ImageIO.write(scaledImage,"png", stream);returnnewByteArrayInputStream(stream.toByteArray());}
Aber so langsam haben wir dann jetzt alle möglichen Alternativen durch, oder?
Ich habe meine Klasse einmal etwas umgeschrieben und JavaDoc Kommentare hinzu gefügt. Evtl. wird die so direkt nutzbar. (Dann ggf. das package umändern und einfach 1:1 nutzen.) Ist ein 08/15 Code daher ohne irgendwelche Lizenz direkt verwendbar / änderbar.
Aber ist alles noch ungetestet - habe jetzt keine Zeit gehabt, den Unit-Test anzupassen. Sollte etwas nicht funktionieren, dann bitte einmal Bescheid geben.
Java:
packagede.kneitzel.images;importjavax.imageio.ImageIO;importjava.awt.*;importjava.awt.image.BufferedImage;importjava.io.ByteArrayInputStream;importjava.io.ByteArrayOutputStream;importjava.io.InputStream;/**
* Helper class to scale images
*/publicclassImageScaler{/**
* Target format to use for all generated images.
*/privatestaticString TARGET_FORMAT="png";/**
* Creates a new scaled image.
* <p>
* The image is scaled with keeping the original ratio from width to height.
* <p>
* If enforceSize is false, the given size is used as a possible maximum size. The target picture might be
* smaller if the ratio height to width given is different to the ratio of the original image.
* If enforceSize is true, the new image can have a transparent arew on top/bottom or
* right/left side.
* @param originalImageBytes Bytes of the original image.
* @param width (Maximum) Width of the target image.
* @param height (Maximum) Height of the target image.
* @param enforceSize Should the size be enforced. If false, the target image will not have empty borders on top/bottom or right/left.
* @return Byte Array with the new image or null in case an error occured.
*/publicstaticbyte[]createScaledImage(finalbyte[] originalImageBytes,finalint width,finalint height,finalboolean enforceSize){// Validationif(originalImageBytes ==null)returnnull;if(originalImageBytes.length==0)returnnull;try{// Create the image from a byte array.BufferedImage originalImage =ImageIO.read(newByteArrayInputStream(originalImageBytes));returncreateScaledImage(originalImage, width, height, enforceSize);}catch(Exception ex){returnnull;}}/**
* Scales an image to a new size.
* <p>
* If enforceSize is false, the given size is used as a possible maximum size. The target picture might be
* smaller if the ratio height to width given is different to the ratio of the original image.
* If enforceSize is true, the new image can have a transparent arew on top/bottom or
* right/left side.
* @param originalImageBytes Bytes of the original image.
* @param width (Maximum) Width of the target image.
* @param height (Maximum) Height of the target image.
* @param enforceSize Should the size be enforced. If false, the target image will not have empty borders on top/bottom or right/left.
* @return InputStream for the new image or null if a new image couldn't be created.
*/publicstaticInputStreamscaledImage(finalbyte[] originalImageBytes,finalint width,finalint height,finalboolean enforceSize){// Get the scaled image.byte[] bytes =createScaledImage(originalImageBytes, width, height, enforceSize);// Validation of result.if(bytes ==null|| bytes.length ==0)returnnull;// Return new InputStream.returnnewByteArrayInputStream(bytes);}/**
* Creates the scaled image of a BufferedImage.
* <p>
* If enforceSize is false, the given size is used as a possible maximum size. The target picture might be
* smaller if the ratio height to width given is different to the ratio of the original image.
* If enforceSize is true, the new image can have a transparent arew on top/bottom or
* right/left side.
* @param originalImage Original image to scale.
* @param width (Maximum) Width of the target image.
* @param height (Maximum) Height of the target image.
* @param enforceSize Should the size be enforced. If false, the target image will not have empty borders on top/bottom or right/left.
* @return Byte Array with the new image or null in case an error occured.
*/protectedstaticbyte[]createScaledImage(finalBufferedImage originalImage,finalint width,finalint height,finalboolean enforceSize){// Validationif(originalImage ==null)returnnull;try{// Get the scale factor.double scaleWidth =(double) width /(double) originalImage.getWidth();double scaleHeight =(double) height /(double) originalImage.getHeight();double scaleFactor;if(scaleWidth > scaleHeight){
scaleFactor = scaleHeight;}else{
scaleFactor = scaleWidth;}// Calculate target size of scaled image.int newHeight =(int)(scaleFactor * originalImage.getHeight());int newWidth =(int)(scaleFactor * originalImage.getWidth());// Cooordinates of new picture and size of new picture.int x, y, usedHeight, usedWidth;if(enforceSize){
usedHeight = height;
usedWidth = width;
x =(width - newWidth)/2;
y =(height - newHeight)/2;}else{
x =0;
y =0;
usedHeight = newHeight;
usedWidth = newWidth;}// Scale the imageBufferedImage scaledImage =newBufferedImage(usedWidth, usedHeight,BufferedImage.TYPE_INT_ARGB);Graphics2D graphics = scaledImage.createGraphics();
graphics.setRenderingHint(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
graphics.drawImage(originalImage, x, y, newWidth, newHeight,null);// Get the bytes of the imagetry(ByteArrayOutputStream stream =newByteArrayOutputStream()){ImageIO.write(scaledImage, TARGET_FORMAT, stream);return stream.toByteArray();}}catch(Exception ex){returnnull;}}}