无法使用三星手机创建的Java加载JPEG

2022-09-04 02:58:08

我在加载使用javafx的三星Galaxy S7边缘拍摄的JPEG图像时遇到问题(图像可在 https://www.dropbox.com/s/w6lvdnqwcgw321s/20171122_140732.jpg?dl=0 上找到)。我正在使用 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();
   }

}

尝试加载图像的构造函数调用在错误流上打印以下错误消息:

Feb 04, 2018 11:48:23 PM com.sun.javafx.tk.quantum.PrismImageLoader2$PrismLoadListener imageLoadWarning 警告: 顺序 JPEG 的 SOS 参数无效

我从图像对象获得的异常是带有消息的 IOException:

不支持的标记类型0x65

我做了一些研究,事实证明,这是三星手机拍摄的全景图像的已知问题。正如此线程中所指出的:https://forums.adobe.com/thread/2131432,一些0xFF字节,指示以下字节是元信息而不是实际数据,不会通过在0xFF后添加以下0x00字节来转义。
然而,我试图编写操作图像数据的代码,以添加缺失的0x00字节,但结果比预期的要复杂得多,我不想编写自己的JPEG解析器/加载器。
有一些程序可以显示那些无效的JPEG图像,例如Microsoft Fotos或Paint。他们似乎容忍这些无效图像,将这些虚假标记视为内容数据。
有没有办法用java加载这些无效的图像,而无需自己处理单个字节?


答案 1

你的问题真棒,真的让我思考和搜索了几个小时)

这是一个有点黑客的解决方案(没有第三方库),我最终得到了:

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;
    }
}

这里的问题是标准api无法读取“损坏”的图像,因此我们需要以某种方式以不同的方式读取它。对于此 Toolkit.getDefaultToolkit().createImage() 方法,可以使用。实际上,该部分是从这个答案中获取的,因此,所有学分都在那里)ImagegetBufferedImage

在方法中,我们简单地将其转换为,然后转换为使用ImageIO类。getFinalImageViewBufferedImagejavafx ImageImageView

结果:enter image description here

注意!我仍然可以在日志中观察到一些异常,但它们不会阻止此代码成功执行。


答案 2

根据您的环境,我能够让ImageMagick读取和重写图像。我使用以下代码进行测试:

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();
        }
    }
}

如果我在你的原始图像上运行它,我得到:

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)

所以我然后运行(在Ubuntu上,但ImageMagick是跨平台的)

convert-im6 -rotate 360 20171122_140732.jpg blah.jpg

convert-im6是 Ubuntu 下的可执行文件名称 - 它在不同的 O/S 上可能不同。

这给了我一个错误:

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.

但它仍然有效:

image is 3760 pixels in height and 11888 pixels wide

并提醒我去新西兰 - 这是一幅美丽的照片。


推荐