I am having trouble loading a JPEG image shot by a Samsung Galaxy S7 edge with javafx (image available at https://www.dropbox.com/s/w6lvdnqwcgw321s/20171122_140732.jpg?dl=0). I am using the Image class to load the image.
import java.io.FileInputStream;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class JPEGProblem extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage window) throws Exception {
Image img = new Image(new FileInputStream("/path/to/image.jpg"));
if (img.getException() != null)
throw img.getException();
ImageView imgView = new ImageView(img);
window.setScene(new Scene(new Pane(imgView)));
window.show();
}
}
The constructor call, which tries to load the image prints the following error message on the error stream:
Feb 04, 2018 11:48:23 PM com.sun.javafx.tk.quantum.PrismImageLoader2$PrismLoadListener imageLoadWarning WARNING: Invalid SOS parameters for sequential JPEG
The exception, that I get from the image object is an IOException with the message:
Unsupported marker type 0x65
I've done some research and it turns out, that it is a known issue with panorama images shot by a samsung phone. As pointed out in this thread: https://forums.adobe.com/thread/2131432, some of the 0xFF bytes, that indicate the following byte to be meta information rather than actual data are not escaped by adding a following 0x00 byte after the 0xFF.
However I tried to write code that manipulates the image data in order to add the missing 0x00 bytes, but that turned out to be far more complicated, than expected and I don't want to write my own JPEG parser/loader.
There are some programs, that can display those invalid JPEG images e.g. Microsoft Fotos or Paint. It seems like they tolerate these invalid images, treating those spurious markers as content data.
Is there any way to load these invalid images with java, without dealing with the single bytes myself?
Your question is awesome, really made me think and search for a couple of hours)
Here is a bit hacky solution (without 3rd party libs) that I ended up with:
import javafx.application.Application;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.image.ImageView;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferInt;
import java.awt.image.DirectColorModel;
import java.awt.image.PixelGrabber;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
public class JPEGProblem extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage window) throws Exception {
BufferedImage bi = getBufferedImage();
ImageView imgView = getFinalImageView(bi);
window.setScene(new Scene(new Pane(imgView)));
window.show();
}
private BufferedImage getBufferedImage() throws InterruptedException {
final java.awt.Image image = Toolkit.getDefaultToolkit().createImage("path\to\file");
final int[] RGB_MASKS = {0xFF0000, 0xFF00, 0xFF};
final ColorModel RGB_OPAQUE =
new DirectColorModel(32, RGB_MASKS[0], RGB_MASKS[1], RGB_MASKS[2]);
PixelGrabber pg = new PixelGrabber(image, 0, 0, -1, -1, true);
pg.grabPixels();
int width = pg.getWidth(), height = pg.getHeight();
DataBuffer buffer = new DataBufferInt((int[]) pg.getPixels(), pg.getWidth() * pg.getHeight());
WritableRaster raster = Raster.createPackedRaster(buffer, width, height, width, RGB_MASKS, null);
return new BufferedImage(RGB_OPAQUE, raster, false, null);
}
private ImageView getFinalImageView(BufferedImage bi) throws Exception {
ImageView imgView = new ImageView(SwingFXUtils.toFXImage(bi, null));
imgView.setFitWidth(1024);
imgView.setFitHeight(756);
imgView.setRotate(180);
return imgView;
}
}
The problem here is that standard Image
api cannot read "broken" images, so we need to read it somehow differently. For this Toolkit.getDefaultToolkit().createImage() method can be used. Actually, the getBufferedImage
part was taken from this answer, so, all credits for this go there)
In getFinalImageView
method we simply transforms this BufferedImage
into javafx Image
and then into ImageView
using ImageIO class.
Result:
Note! I can still observe some exceptions in logs, but they don't prevent this code from successful execution.
Depending on your environment, I was able to get ImageMagick to read and re-write the image. I used the following code to test:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ReadImage {
public static void main(String[] argv) {
try {
BufferedImage img = ImageIO.read(new File("/path/to/20171122_140732.jpg"));
System.out.println( "image is " + img.getHeight() + " pixels in height and " + img.getWidth() + " pixels wide");
} catch (IOException e) {
e.printStackTrace();
}
}
}
If I ran it on your original image I got:
javax.imageio.IIOException: Bogus DQT index 14
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readImage(Native Method)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.readInternal(JPEGImageReader.java:1247)
at com.sun.imageio.plugins.jpeg.JPEGImageReader.read(JPEGImageReader.java:1050)
at javax.imageio.ImageIO.read(ImageIO.java:1448)
at javax.imageio.ImageIO.read(ImageIO.java:1308)
at com.hotjoe.so.imagereader.ReadImage.main(ReadImage.java:15)
So I then ran (on Ubuntu but ImageMagick is cross platform)
convert-im6 -rotate 360 20171122_140732.jpg blah.jpg
convert-im6
is the executable name under Ubuntu - it may be different on different O/S's.
This gave me an error:
convert: Invalid SOS parameters for sequential JPEG `20171122_140732.jpg' @ warning/jpeg.c/JPEGWarningHandler/352.
convert: Corrupt JPEG data: 61 extraneous bytes before marker 0x65 `20171122_140732.jpg' @ warning/jpeg.c/JPEGWarningHandler/352.
convert: Unsupported marker type 0x65 `20171122_140732.jpg' @ warning/jpeg.c/JPEGErrorHandler/319.
but it still worked:
image is 3760 pixels in height and 11888 pixels wide
And remind me to get to New Zealand - that's a beautiful picture.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With