14

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?

David
  • 1,672
  • 2
  • 13
  • 32

2 Answers2

13

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.

Enigo
  • 3,685
  • 5
  • 29
  • 54
  • Wow, someone has nice awt skills there! :D It is a lot of copying and redundantly allocating memory of the image data for one image. I still have to figure out how to make this more performant, but it works and seems like a really good solution to me. Especially because it doesn't depend on any third party libs. Thank you very much for your effort! – David Feb 13 '18 at 13:23
  • @David, yeah, I believe the solution can be optimized :) glad it helped! – Enigo Feb 13 '18 at 20:39
  • 1
    Can't you just use `new ImageView(SwingFXUtils.toFXImage(bi, null))` for your `getFinalImageView()` method? Or are you still working around the "broken" image at that point? – James_D Feb 14 '18 at 13:25
  • @James_D, yeah, you're right, this part might be simplified. Now, we have all three of'em - Swing, AWT and JavaFX) – Enigo Feb 14 '18 at 21:50
  • Just a minor note: Your question maybe interesting for other Stackoverflor users in the future while you may be changing or deleting the image in your Dropbox folder. So maybe you want to [upload your image](https://stackoverflow.com/questions/28496851/how-to-upload-pictures-to-stackoverflow-for-posting) to Stackoverflow instead. **But** it is possible that the upload to Stackoverlow will "repair" the image in question, so feel free to ignore my comment if that is the case. – Markus Köbele Feb 15 '18 at 23:24
2

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.

stdunbar
  • 16,263
  • 11
  • 31
  • 53
  • Thank you for the answer, but by using it, my program would be dependent to ImageMagick to be installed on the system it is running on. Even though I use JMagic, it's just an interface and not the ImageMagick software itself. - Yes New Zealand is amazing! Thank you :) – David Feb 11 '18 at 14:16