为什么相同的图像(编辑:PNG)在 Java 中产生两个略有不同的字节数组?

tccw

我正在编写一个简单的应用程序,其中一个功能是能够将对象序列化为图像,以便用户可以共享其项目的照片,其中嵌入了构建指令。目前,其他用户可以将图像拖放到 JavaFX ListView 中,侦听器将执行一些代码来解码和解析嵌入的 JSON。

如果图像存储在本地,这可以正常工作,但我也希望允许用户从浏览器拖放图像,而不必先将它们保存在本地。为了测试这一点,我在一个基本的 HTML 文件中链接了一个工作图像,以便我可以在 Chrome 中呈现它。

最初我使用的是从 Dragboard 获取的文件的路径,但是当图像来自浏览器时,我需要(我认为)能够直接接受图像,因此下面的重载 decode() 方法。

我遇到的问题是,根据图像是来自浏览器还是来自我的主要本地存储中的某个位置,我最终得到了两个略有不同的字节数组。我对 Java 中的图像(或一般情况下)了解得不够多,无法理解为什么会这样并且无法在其他地方找到答案。浏览器来源的拖放会产生一个足够不同的字节数组,我无法 100% 正确解码消息,因此无法将其反序列化为对象。但是,如果我右键单击并保存基于浏览器的图像,它会正确加载。

我在下面包含了一个可重现的片段和一个测试图像。我的 JDK 是 Amazon Corretto 8 的最新版本。

可重现的片段:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ListView;
import javafx.scene.image.Image;
import javafx.scene.image.PixelFormat;
import javafx.scene.image.PixelReader;
import javafx.scene.image.WritablePixelFormat;
import javafx.scene.input.TransferMode;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;


import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.util.List;

public class MainApp extends Application {
    private static final int OFFSET = 40;
    private ListView<String> listView;
    private GridPane gridPane;

    @Override
    public void start(Stage primaryStage) throws Exception {
        listView = new ListView<>();
        gridPane = new GridPane();
        gridPane.addRow(0, listView);
        setDragDropAction();
        Scene scene = new Scene(gridPane, 250, 250);
        primaryStage.setScene(scene);
        primaryStage.sizeToScene();
        primaryStage.show();
    }

// the same drag-drop logic I am using in my project
    private void setDragDropAction() {
        gridPane.setOnDragOver(event -> {
            if (event.getDragboard().hasFiles() || event.getDragboard().hasImage()) {
                event.acceptTransferModes(TransferMode.ANY);
            }
            event.consume();
        });

        gridPane.setOnDragDropped(event -> {
            List<File> files = event.getDragboard().getFiles();
            try {
                if (!files.isEmpty()) {
                    String decoded = decode(files.get(0));
                    System.out.println(decoded);
                } else if (event.getDragboard().hasImage()) {
                    Image image = event.getDragboard().getImage();
                    String decoded = decode(image);
                    System.out.println(decoded);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        });
    }

    // decode using the file path
    public String decode(File file) throws IOException {
        byte[] byteImage = imageToByteArray(file);
        return getStringFromBytes(byteImage);
    }

    // this results in a different byte array and a failed decode
    public String decode(Image image) {
        int width = (int) image.getWidth();
        int height = (int) image.getHeight();
        PixelReader reader = image.getPixelReader();
        byte[] byteImage = new byte[width * height * 4];
        WritablePixelFormat<ByteBuffer> format = PixelFormat.getByteBgraInstance();
        reader.getPixels(0, 0, width, height, format, byteImage, 0, width * 4);

        return getStringFromBytes(byteImage);
    }

    private String getStringFromBytes(byte[] byteImage) {
        int offset = OFFSET;
        byte[] byteLength = new byte[4];
        System.arraycopy(byteImage, 1, byteLength, 0, (offset / 8) - 1);
        int length = byteArrayToInt(byteLength);
        byte[] result = new byte[length];
        for (int b = 0; b < length; ++b) {
            for (int i = 0; i < 8; ++i, ++offset) {
                result[b] = (byte) ((result[b] << 1) | (byteImage[offset] & 1));
            }
        }
        return new String(result);
    }

    private int byteArrayToInt(byte[] b) {
        return b[3] & 0xFF
                | (b[2] & 0xFF) << 8
                | (b[1] & 0xFF) << 16
                | (b[0] & 0xFF) << 24;
    }

    private byte[] imageToByteArray(File file) throws IOException {
        BufferedImage image;
        URL path = file.toURI().toURL();
        image = ImageIO.read(path);
        return ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
    }

}

另一种使用 SwingFXUtils.fromFXImage() 解码方法的尝试也不起作用:


// Create a buffered image with the same imageType (6) as the type returned from ImageIO.read() with a local drag-drop
public String decode(Image image) throws IOException {
        int width = (int) image.getWidth();
        int height = (int) image.getHeight();
        BufferedImage buffImageFinal = new BufferedImage(width, height, 6); // imageType = 6
        BufferedImage buffImageTemp =  SwingFXUtils.fromFXImage(image, null);
        Graphics2D g = buffImageFinal.createGraphics();
        g.drawImage(buffImageTemp, 0, 0, width, height, null);
        g.dispose();
        return getStringFromBytes(((DataBufferByte) buffImageFinal.getRaster().getDataBuffer()).getData());
    }

显示数据变化的图像:

图像的角落。不同的像素以红色突出显示。

显示差异的前 12 个字节

用于测试的示例图像:

用于测试的示例图像

解码的测试消息应该打印出来(“这是我的最小可重现示例的测试消息”。从浏览器中拖动图像不起作用,将图像保存在本地并拖放它。

Reti43

根据来自 OP 的评论,event.getDragboard.getImage()加载具有预乘 alpha 的图像,而ImageIO.read()没有。规避这种情况的一种简单方法是从事件中获取 url(无论图像是如何拖放的)并使用ImageIO.read().

gridPane.setOnDragDropped(event -> {
    if (event.getDragboard().hasUrl()) {
        try {
            URL path = new URL(event.getDragboard().getUrl());
            String decoded = decode(path);
            System.out.println(decoded);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
});

通过以下修改

public String decode(URL url) throws IOException {
    byte[] byteImage = imageToByteArray(url);
    return getStringFromBytes(byteImage);
}

private byte[] imageToByteArray(URL url) throws IOException {
    BufferedImage image = ImageIO.read(url);
    return ((DataBufferByte) image.getRaster().getDataBuffer()).getData();
}

请注意,装载仅一个URL支持有限的格式,看到这里

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

即使一个图像的裁剪/比率略有不同,我如何检测到两个图像“相同”?

从字节数组到图像Java

比较两个字节数组?(Java)

相同的字节数组=> Java和C#中的BigInteger值不同

Java从字节数组获取图像扩展

Java字节数组大于jpg图像

为什么两个相同的字符串变量不相等?是因为字节数组大小吗?

将两个略有不同的JSON字符串(相同的结构,不同的名称)反序列化为同一类

连接两个表并在两个不同的行中产生具有相同值的表

为什么不能在Java中将字节数组存储在整数数组中

为什么两个仅在注释方面不同的java文件会产生不同的类文件?

从包含3个通道像素信息的3d字节数组创建Java位图图像

Java为什么DatagramPacket的构造函数需要字节数组的长度?

为什么在 Java 中将字节数组转换为 BitSet 会得到错误的位表示

为什么我的字节数组没有不同,即使 print() 说它们是不同的?

MySQL和Java:检索到的字节数组与存储时的字节数组不同

具有相同验证但略有不同的两个字段的自定义验证

Java中两个字节数组的绝对值减法

在Java中,如何迭代两个字节数组的位?

Java字节数组替换所有出现的字节数组/字符串

为什么在情况2中,对于相同的值,两个不同的答案来了:Java 7?

为什么java ExecutorService newSingleThreadExecutor产生两个线程?

为什么用图像绘制的画布的颜色与图像本身的颜色略有不同?

为什么 numpy 数组上的 set 函数返回的值略有不同?

JSP(Java):编码字节数组,并在JavaScript中与C#相同

Java堆内存持有大量的字节数组

无法从Java中的已修改字节数组生成图像

如何在Java中将图像转换为字节数组?

将字节数组从Java保存到php中的图像文件