Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Cannot load JPEG with java created by Samsung phone

Tags:

java

jpeg

javafx

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?

like image 257
David Avatar asked Feb 06 '18 23:02

David


2 Answers

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: enter image description here

Note! I can still observe some exceptions in logs, but they don't prevent this code from successful execution.

like image 177
Enigo Avatar answered Nov 14 '22 06:11

Enigo


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.

like image 2
stdunbar Avatar answered Nov 14 '22 06:11

stdunbar