从 Eclipse 和命令行运行时,BufferedImage 字节具有不同的字节顺序

2024-03-20

我试图转换一个BufferedImage's byte[]从 32 位 RGBA 到 24 位 RGB。根据这个答案 https://stackoverflow.com/a/9470843/2581401最快的方式获得byte[]从图像中可以看出:

byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();

因此,我迭代所有字节,假设它们的顺序为 R G B A,并且对于每 4 个字节,我将前 3 个写入输出 byte[](即忽略 alpha 值)。

当从 Eclipse 运行并且字节转换正确时,此方法工作正常。但是,当我从命令行运行相同的程序时,会以相反的字节顺序返回相同的字节!

The test image I use for my test is a 5x5 black image where only its top-left corner is different having the RGBA color [aa cc ee ff]: Test image 5x5

为了方便起见,还提供了放大版本:

我的文件夹结构是:

- src/  
  - test.png
  - test/
      - TestBufferedImage.java  

SSCCE 的内容如下:

package test;

import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.io.IOException;
import java.io.InputStream;

import javax.imageio.ImageIO;

public class TestBufferedImage {

  private static void log(String s) {
    System.out.println(s);
  }

  private static String toByteString(byte b) {
    // Perform a bitwise AND for convenience while printing. 
    // Otherwise Integer.toHexString() interprets values as integers and a negative byte 0xFF will be printed as "ffffffff"
    return Integer.toHexString(b & 0xFF);
  }

  /**
   * @param args
   * @throws IOException 
   */
  public static void main(String[] args) throws IOException {

    InputStream stream = TestBufferedImage.class.getClassLoader().getResourceAsStream("test.png");
    BufferedImage image = ImageIO.read(stream);
    stream.close();
    log("Image loaded succesfully, width=" + image.getWidth() + " height=" + image.getHeight());

    log("Converting from 32-bit to 24-bit...");
    DataBufferByte buffer = (DataBufferByte)image.getRaster().getDataBuffer(); 
    byte[] input = buffer.getData();
    byte[] output = convertTo24Bit(input);
    log("Converted total of " + input.length + " bytes to " + output.length + " bytes");
  }

  private static byte[] convertTo24Bit(byte[] input) {
    int dataLength = input.length;
    byte[] convertedData = new byte[ dataLength * 3 / 4 ];

    for (int i = 0, j = 0; i < dataLength; i+=4, j+=3) {
      convertIntByteToByte(input, i, convertedData, j);
    }
    return convertedData;
  }

  private static void convertIntByteToByte(byte[] src, int srcIndex, byte[] out, int outIndex) {
    byte r = src[srcIndex];
    byte g = src[srcIndex+1];
    byte b = src[srcIndex+2];
    byte a = src[srcIndex+3];

    out[outIndex] = r;
    out[outIndex+1] = g; 
    out[outIndex+2] = b; 

    log("i=" + srcIndex 
        + " Converting [" + toByteString(r) + ", " + toByteString(g) 
        + ", " + toByteString(b) + ", " + toByteString(a) + "] --> ["
        + toByteString(out[outIndex]) + ", " + toByteString(out[outIndex+1])
        + ", " + toByteString(out[outIndex+2]) + "]"
        );
  }

}

从 Eclipse 运行时的输出(版本:Juno Service Release 2 Build id:20130225-0426):

Image loaded succesfully, width=5 height=5
Converting from 32-bit to 24-bit...
i=0 Converting [aa, cc, ee, ff] --> [aa, cc, ee]   // <-- Bytes have the correct order
i=4 Converting [0, 0, 0, ff] --> [0, 0, 0]
i=8 Converting [0, 0, 0, ff] --> [0, 0, 0]
.....
i=96 Converting [0, 0, 0, ff] --> [0, 0, 0]
Converted total of 100 bytes to 75 bytes

从命令行 (Windows Vista) 运行时的输出java test.TestBufferedImage:

Image loaded succesfully, width=5 height=5
Converting from 32-bit to 24-bit...
i=0 Converting [ff, ee, cc, aa] --> [ff, ee, cc]    // <-- Bytes are returned with a different byte order!
i=4 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
i=8 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
.....
i=96 Converting [ff, 0, 0, 0] --> [ff, 0, 0]
Converted total of 100 bytes to 75 bytes

那么有没有人遇到过类似的问题和/或可以解释到底发生了什么?为什么从 Eclipse 内部运行时字节顺序不同?


在回答我自己的问题之前,我真的、真的、真的感谢@Joni 和@haraldK,他们为我指明了正确的方向。我对内部结构的了解BufferedImage, ColorModel, SampleModel类似的事情不太好,所以他们帮助了我。

所以这就是发生的事情:

首先,不同的行为是由不同的 JRE 引起的。打印java版本的日志语句显示Eclipse打印1.6.0_16-b01当命令行打印时1.6.0_31-b05。显然是图像加载的实现(这将是PNGImageReader类)在版本之间发生了变化,我怀疑它在这个变化 https://bugs.java.com/bugdatabase/view_bug?bug_id=6549882.

尽管两个版本使用相同的ColorModel and SampleModel所以我无法理解这个变化(对我来说这似乎是一个真正的密码破译者)所以我进一步调查了。

两个版本之间的重要变化PNGImageReader曾在Iterator<ImageTypeSpecifier> getImageTypes()决定可用于创建新图像的可用兼容格式的方法:

版本1.6.0_16-b01:

...
case PNG_COLOR_RGB_ALPHA:
    // Component R, G, B, A (non-premultiplied)
    rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    bandOffsets = new int[4];
    bandOffsets[0] = 0;
    bandOffsets[1] = 1;
    bandOffsets[2] = 2;
    bandOffsets[3] = 3;

    l.add(ImageTypeSpecifier.createInterleaved(rgb,
                                               bandOffsets,
                                               dataType,
                                               true,
                                               false));
    break;

版本1.6.0_31-b05:

...
case PNG_COLOR_RGB_ALPHA:
    if (bitDepth == 8) {
        // some standard types of buffered images
        // wich can be used as destination
        l.add(ImageTypeSpecifier.createFromBufferedImageType(
                                           BufferedImage.TYPE_4BYTE_ABGR));

        l.add(ImageTypeSpecifier.createFromBufferedImageType(
                                           BufferedImage.TYPE_INT_ARGB));
    }

    // Component R, G, B, A (non-premultiplied)
    rgb = ColorSpace.getInstance(ColorSpace.CS_sRGB);
    bandOffsets = new int[4];
    bandOffsets[0] = 0;
    bandOffsets[1] = 1;
    bandOffsets[2] = 2;
    bandOffsets[3] = 3;

    l.add(ImageTypeSpecifier.createInterleaved(rgb,
                                               bandOffsets,
                                               dataType,
                                               true,
                                               false));
    break;

所以在新版本中,新ImageTypeSpecifier告诉 PNG 加载器,内部表示的兼容图像是BufferedImage.TYPE_4BYTE_ABGR由于它是列表中的第一种类型,因此加载程序会继续使用它。这就是颜色通道的带(以及字节)被反转的原因。

意识到上述情况后,我突然意识到这并不是我想象的错误或代码破坏者。原因是因为我正在使用byte[] pixels = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();为了获取字节,我实际上是在侵入内部数据结构(即忽略SampleModel)的图像。合同中没有任何内容ImageReader保证字节的顺序。如果需要,它可以自由地更改其内部数据结构(这就是封装的要点,对吧?)。正确配置的方法ImageReader如果您想要除默认行为之外的任何其他内容,请获取其默认行为ImageReadParam并配置它。然后使用将其传回给读者reader.read(imageIndex, param);

因为我实际上希望读者返回图像字节的特定格式,所以我应该这样做:

log("Java version: " + System.getProperty("java.runtime.version"));

// get the reader
ImageReader ir = ImageIO.getImageReadersByFormatName("png").next();

// get the default param    
ImageReadParam p = ir.getDefaultReadParam();
p.setDestinationType(
    // define the image type to return if supported
    ImageTypeSpecifier.createInterleaved(
        ColorSpace.getInstance(ColorSpace.CS_sRGB), 
        new int[] {0, 1, 2, 3},    // <-- the order of the color bands to return so the bytes are in the desired order
        DataBuffer.TYPE_BYTE,
        true, false)
        );

InputStream stream = TestBufferedImage.class.getClassLoader().getResourceAsStream("test.png");

ImageInputStream imageStream = ImageIO.createImageInputStream(stream);
ir.setInput(imageStream);
BufferedImage image = ir.read(0, p); 

现在,两个版本都将以相同的 RGBA 形式返回字节顺序,即在两种情况下都会打印不同颜色的输出:

...
i=0 Converting [aa, cc, ee, ff] --> [aa, cc, ee] 
...
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从 Eclipse 和命令行运行时,BufferedImage 字节具有不同的字节顺序 的相关文章

  • ListView:防止视图回收

    我有一个使用回收视图的 ListView 我试图阻止视图被回收 所以我使用 setHasTransientState android support v4 view ViewCompatJB setHasTransientState Vie
  • 最终字段可能尚未/已经初始化[重复]

    这个问题在这里已经有答案了 可能的重复 如何处理抛出检查异常的静态最终字段初始值设定项 https stackoverflow com questions 1866770 how to handle a static final field
  • Spring @Validated 在服务层

    Hej 我想使用 Validated group Foo class 在执行方法之前验证参数的注释 如下所示 public void doFoo Foo Validated groups Foo class foo 当我将此方法放入 Spr
  • 如果主引用指向 null,WeakReference 或 SoftReference 有何不同

    我正在读关于WeakReference https docs oracle com javase 10 docs api java lang ref WeakReference html and SoftReference https do
  • 如何停止使用扫描仪从标准输入读取多行?

    我正在做一个 JAVA 作业 应该处理多行输入 指令显示 输入是从标准输入读取的 给出了示例输入的示例 one 1 two 2 three 3 我不明白上面的示例输入 从标准输入读取 是什么意思 这是我编写的一个测试程序 它可以消除我的困惑
  • 为什么Java中的文件名与公共类名相同? [关闭]

    Closed 这个问题是基于意见的 help closed questions 目前不接受答案 在Java中 文件名应该与文件名相同public class包含在该文件中 为什么这是一个限制 它有什么目的 Java 有一个有趣的方法 如果给
  • 为什么我的 @OneToMany 属性出现主键违规?

    我有一个实体 Entity public class Student GeneratedValue strategy GenerationType AUTO Id private long id OneToMany private Set
  • 如何修复maven错误JAVA_HOME环境变量未正确定义

    当我在虚拟环境中检查maven的版本时 出现以下错误 The JAVA HOME environment variable is not defined correctly This environment variable is need
  • Hazelcast:连接到远程集群

    我们有一组 Hazelcast 节点 全部运行在一个远程系统 具有许多节点的单个物理系统 上 我们希望从外部客户端连接到该集群 一个 Java 应用程序 它使用如下代码连接到 Hazelcast ClientConfig clientCon
  • JPA 的 Hibernate 查询提示

    我一直在尝试为所有可以通过设置的提示找到一个明确的资源Query setHint String Object JPA 中的方法调用 但我一无所获 有人知道一个好的参考吗 See 3 4 1 7 查询提示 http docs jboss or
  • EJB 中 @Stateless 相对于 @Singleton 的真正用例是什么

    如果我正确理解EJB Singleton实际上与普通Java中的Singleton相同 也是spring中的单例 gt 一个实例 每个调用同时通过同一个实例 Stateless 声明一个 bean 它可以 但不得 具有多个实例 但限制是一个
  • Java8 项目上的 SonarQube 给出 jacoco-Exception

    我刚刚下载了最新版本 SonarQube 4 3 然后尝试使用以下命令构建 java 8 项目 mvn clean install mvn sonar sonar 这给了我下面的例外 谷歌搜索 我的印象是这是一个早期的问题 应该已经解决 h
  • SDK尚未初始化,请务必先调用FacebookSdk.sdkInitialize()

    我在实现 Facebook SDK 时遇到此错误 并且我tried https stackoverflow com questions 15490399 error inflating class com facebook widget l
  • 应用程序中空指针异常[重复]

    这个问题在这里已经有答案了 我正在尝试在我的应用程序中实施应用程序内计费 我写了这段代码 public class Settings extends PreferenceFragment ServiceConnection mService
  • 从特定 JAR 文件读取资源(文件的重复路径)

    假设您有 jar1 和artifactId 动物园 jar2 和artifactId 动物 两个 jar 都有一个具有相同路径的资源文件 例如 animals animal txt 有什么方法可以从特定的 jar 中读取该文件吗 使用 ge
  • 动态创建 JSON 对象

    我正在尝试使用以下格式创建 JSON 对象 tableID 1 price 53 payment cash quantity 3 products ID 1 quantity 1 ID 3 quantity 2 我知道如何使用 JSONOb
  • 内部类的访问修饰符[重复]

    这个问题在这里已经有答案了 可能的重复 受保护 公共内部类 https stackoverflow com questions 595179 protected public inner classes 我确信这个问题已经被问过 但我找不到
  • 浮点字节序?

    我正在为实时海上模拟器编写客户端和服务器 并且由于我必须通过套接字发送大量数据 因此我使用二进制数据来最大化可以发送的数据量 我已经了解整数字节顺序以及如何使用htonl and ntohl为了规避字节顺序问题 但我的应用程序与几乎所有模拟
  • 如何让JComboBox中的内容居中显示?

    目前我有这个JComboBox 我怎样才能将其中的内容居中 String strs new String 15158133110 15158133124 15158133458 JComboBox com new JComboBox str
  • 在 for 循环比较中使用集合大小

    Java 中 Collections 的 size 方法是否有编译器优化 考虑以下代码 for int i 0 i

随机推荐

  • 如何获取多字节字符串的字节大小

    如何在 Visual C 中获取多字节字符串的字节大小 有没有函数或者我必须自己计算字符 或者 更一般地说 如何获得 TCHAR 字符串的正确字节大小 解决方案 tcslen T TCHAR string sizeof TCHAR EDIT
  • 这个吐司是从哪里来的?

    由于某种原因 我正在开发的应用程序正在显示一个 toast 显示我的设备上剩余的内部存储空间 即使我没有对此进行编码 这是一个屏幕截图 https i stack imgur com z2ERU png https i stack imgu
  • Python 2.7 的蓝牙? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 对 Python 2 7 进行蓝牙编程的最佳方法是什么 我尝试使用pybluez https code
  • 使用预定脚本部署 Shiny App

    我有一个简单的脚本 可以在 Rstudio 中用于部署应用程序 rsconnect setAccountInfo name xx token xx secret xx xx library rsconnect deployApp xxx l
  • 链接后台 NSURLSession 上传

    有人成功链接 NSURLSession 后台上传吗 我正在尝试使用 NSURLSession 的后台上传来上传 5 MB 的巨大视频文件 上传必须按顺序进行 整个事情在前台工作得很好 我为此使用 AFNetwoking 它是多部分上传 但是
  • 尝试从一个页面推送到另一页面时出现“找不到组件工厂”错误

    尝试从一个页面推送到另一页面时出现错误 当我尝试推送到同一页面时 它不会给出该错误 只有我在从一个页面推送到另一页面时遇到错误 setRoot 也没有给出错误 this navCtrl push Page7 我已将 Page7 添加到 ap
  • Pandas fillna() 基于特定列属性

    假设我有这张桌子 Type Killed Survived Dog 5 2 Dog 3 4 Cat 1 7 Dog nan 3 cow nan 2 其中的价值之一Killed缺少 Type Dog 我想将平均值归咎于 Killed for
  • 最好的 SQL Server 性能优化技术是什么? [关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 我始终采取的方法是首先使用最少的索引集部署数据库 然后根据性能要求添加 更改索引 这种方法效果相当好 但是 它仍然没有告诉我可以在哪里
  • Rails 4.0 的自定义错误处理

    我正在使用 Ruby 2 0 和 Rails 4 0 构建 Ruby on Rails api 我的应用程序几乎只是一个 JSON API 因此如果发生错误 500 404 我想捕获该错误并返回格式良好的 JSON 错误消息 我试过了thi
  • 子集化 data.frame 时的 NA 会发生一些意想不到的事情

    考虑以下代码 当你没有明确测试NA在您的情况下 该代码将在稍后您的数据发生更改时失败 gt A toy example gt a lt as data frame cbind col1 c 1 2 3 4 col2 c 2 NA 2 3 c
  • 如何使 MapView 对象透明(alpha)?

    All 我需要在 MapView 对象上显示信息 那里没有问题 问题是 有时 MapView 对象显示的地图详细信息在视觉上与我的叠加数据相竞争 因此 我想做的是提供一种通过使用 alpha 通道在视觉上 缩小 MapView 对象的方法
  • 如何自动检测用户的时区?

    您好 我正在创建一个网络应用程序 如果用户注册 我们将显示创建日期 为此 我们在我的sql表中使用当前时间戳 它显示服务器时间 但我们不知道如何根据用户时区转换时间 因为我们不是获取用户所在国家 地区 任何人都可以帮我解决它吗 提前致谢 使
  • “return 0”和“exit (0)”之间的区别[重复]

    这个问题在这里已经有答案了 有什么区别吗return 0 and exit 0 在函数中使用时 如果是 我应该什么时候使用return 0 or exit 0 在一个函数中 return退出该函数 同时exit退出程序 In main函数执
  • backbone.js 收集事件

    我开发了一个 jquery 和backbone js 网络应用程序 一个组件有一个 html 表 该表后面是一个backbone js 集合 该集合中的任何更改都会导致 html 表的更新 所以我写 this collection bind
  • UITableView重新加载数据

    我正在为 iPhone 制作一个基于导航的应用程序 我的视图控制器之一如下所示 interface NewComputerViewController UIViewController
  • 什么时候会使用 BRICK 权限?

    在Android中 曾经有一个名为BRICK http developer android com reference android Manifest permission html BRICK可用于潜在地禁用该设备 除了将其视为都市神话
  • 除 None 之外的任何类型的 Mypy 注释[重复]

    这个问题在这里已经有答案了 我怎样才能注释一个类型 除了None 换句话说 这个类型是Any但不是None 你可以做Union int str 但排除None来自那个工会
  • Scala Futures 如何与 flatMap 链接在一起?

    我正在 Scala 中首次使用 Futures 并且正在研究使用 flatMap 组合器的示例 我一直在关注这个讨论 http docs scala lang org overviews core futures html http doc
  • 当构建系统已引用 System.Core 时,添加对 System.Core 的引用

    即使项目已生成 Visual Studio Intellisense 也无法识别动态关键字 我尝试添加对System Core来解决问题 我收到此错误 无法添加对 System Core 的引用 该组件是 已经被构建系统自动引用 我注意到我
  • 从 Eclipse 和命令行运行时,BufferedImage 字节具有不同的字节顺序

    我试图转换一个BufferedImage s byte 从 32 位 RGBA 到 24 位 RGB 根据这个答案 https stackoverflow com a 9470843 2581401最快的方式获得byte 从图像中可以看出