为什么我未压缩的 PNG 像素数据不准确?
Why is my uncompressed PNG pixel data inaccurate?
我正在尝试使用 jzlib 扩充 PNG 文件的 IDAT 数据,并以十六进制打印出 R、G、B 和 A 值。我有一个可以运行的程序(没有错误),但打印出来的值似乎不正确。
我的图片是一个 2x2 全白的 PNG 文件,没有压缩(来自 Photoshop)。
这是我的代码:
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import java.io.*;
public class main {
private static final int imageWidth = 2;
private static final int imageHeight = 2;
public static void main(String[] args) {
String pngPath = args[0];
File pngFile = new File(pngPath);
try {
DataInputStream stream = new DataInputStream(new FileInputStream(pngFile));
byte[] data = new byte[8];
stream.readFully(data); //Read PNG Header
while (true) {
data = new byte[4];
stream.readFully(data); //Read Length
int length = ((data[0] & 0xFF) << 24)
| ((data[1] & 0xFF) << 16)
| ((data[2] & 0xFF) << 8)
| (data[3] & 0xFF); //Byte array to int
stream.readFully(data); //Read Name
String name = new String(data); //Byte array to String
if (name.equals("IDAT")) {
data = new byte[length];
stream.readFully(data); //Read Data
int maxInflateBuffer = 4 * (imageWidth + 1) * imageHeight;
byte[] outputBuffer = new byte[maxInflateBuffer];
long inflatedSize = inflate(data, outputBuffer, maxInflateBuffer);
int index = 0;
byte r, g, b, a;
for (int y = 0; y < imageHeight; y++) {
index++;
for (int x = 0; x < imageWidth; x++) {
r = outputBuffer[index];
g = outputBuffer[index + 1];
b = outputBuffer[index + 2];
a = outputBuffer[index + 3];
System.out.println(byteToHex(r) + " : " + byteToHex(g) + " : " + byteToHex(b) + " : " + byteToHex(a));
System.out.println("------------------------");
index += 4;
}
}
return;
} else { //Don't care about other chunks
data = new byte[length + 4]; //Data length + 4 byte CRC
stream.readFully(data); //Skip Data and CRC.
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static long inflate(byte[] data, byte[] outputBuffer, int maxInflateBuffer) throws GZIPException {
Inflater inflater = new Inflater(15);
inflater.setInput(data, true);
inflater.setOutput(outputBuffer);
try {
inflater.inflate(JZlib.Z_NO_COMPRESSION);
} finally {
inflater.inflateEnd();
}
return inflater.getTotalOut();
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String byteToHex(byte b) {
char[] hexChars = new char[2];
int v = b & 0xFF;
hexChars[0] = hexArray[v >>> 4];
hexChars[1] = hexArray[v & 0x0F];
return new String(hexChars);
}
}
这是输出,顺序为 R : G : B : A...
FF : FF : FF : 00
------------------------
00 : 00 : 02 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
我的假设是它看起来更像:
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
为什么不呢?怎么了?我在这上面花了一段时间,想不通。
这是一张 link 图片:
http://i.imgur.com/57lhTXP.png(右击->另存为)
编辑:
我尝试使用内置的 Java Inflater 库,正如 CoderNeji 在评论中建议的那样:
private static long inflate(byte[] data, byte[] outputBuffer, int maxInflateBuffer) {
Inflater inflater = new Inflater();
inflater.setInput(data, 0, maxInflateBuffer);
try {
inflater.inflate(outputBuffer);
} catch (DataFormatException e) {
e.printStackTrace();
}
return inflater.getTotalOut();
}
得到了同样的结果。此外,使用任何一种方法,2x2 黑色图像都会给出以下输出:
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
感谢您的帮助!
您忘记了 row filters。您在打印时跳过了第一个字节,因此您没有看到第一个字节(以及随后每行的所有其他 'first bytes',这不是一件坏事 - 除了您做得太早了)。
此外,您正在打印 alpha 通道输出,而图像没有。与您所说的相反,图像不是 RGBA,它是 RGB,没有 alpha。行
index += 4;
因此应该是
index += 3;
通过将打印的输出尺寸与您获得的 inflatedSize
进行比较,您可能已经注意到了这些错误。实际上,inflatedSize
应该是 14,并且您打印了 16 个字节的数据(还跳过了 2 个)。
实际解压后的数据,一行一行,为
01 : FF FF FF : 00 00 00
02 : 00 00 00 : 00 00 00
每行的第一个数字是行过滤器,后面是 2(宽度)* 3(通道)个像素数据条目。
这 2 行过滤器是 Sub 和 Up,从左到右,从上到下应用它们,你得到
FF FF FF : FF FF FF
FF FF FF : FF FF FF
翻译成“2x2 全白像素”,正如人们所期望的那样。
我正在尝试使用 jzlib 扩充 PNG 文件的 IDAT 数据,并以十六进制打印出 R、G、B 和 A 值。我有一个可以运行的程序(没有错误),但打印出来的值似乎不正确。
我的图片是一个 2x2 全白的 PNG 文件,没有压缩(来自 Photoshop)。
这是我的代码:
import com.jcraft.jzlib.GZIPException;
import com.jcraft.jzlib.Inflater;
import com.jcraft.jzlib.JZlib;
import java.io.*;
public class main {
private static final int imageWidth = 2;
private static final int imageHeight = 2;
public static void main(String[] args) {
String pngPath = args[0];
File pngFile = new File(pngPath);
try {
DataInputStream stream = new DataInputStream(new FileInputStream(pngFile));
byte[] data = new byte[8];
stream.readFully(data); //Read PNG Header
while (true) {
data = new byte[4];
stream.readFully(data); //Read Length
int length = ((data[0] & 0xFF) << 24)
| ((data[1] & 0xFF) << 16)
| ((data[2] & 0xFF) << 8)
| (data[3] & 0xFF); //Byte array to int
stream.readFully(data); //Read Name
String name = new String(data); //Byte array to String
if (name.equals("IDAT")) {
data = new byte[length];
stream.readFully(data); //Read Data
int maxInflateBuffer = 4 * (imageWidth + 1) * imageHeight;
byte[] outputBuffer = new byte[maxInflateBuffer];
long inflatedSize = inflate(data, outputBuffer, maxInflateBuffer);
int index = 0;
byte r, g, b, a;
for (int y = 0; y < imageHeight; y++) {
index++;
for (int x = 0; x < imageWidth; x++) {
r = outputBuffer[index];
g = outputBuffer[index + 1];
b = outputBuffer[index + 2];
a = outputBuffer[index + 3];
System.out.println(byteToHex(r) + " : " + byteToHex(g) + " : " + byteToHex(b) + " : " + byteToHex(a));
System.out.println("------------------------");
index += 4;
}
}
return;
} else { //Don't care about other chunks
data = new byte[length + 4]; //Data length + 4 byte CRC
stream.readFully(data); //Skip Data and CRC.
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
private static long inflate(byte[] data, byte[] outputBuffer, int maxInflateBuffer) throws GZIPException {
Inflater inflater = new Inflater(15);
inflater.setInput(data, true);
inflater.setOutput(outputBuffer);
try {
inflater.inflate(JZlib.Z_NO_COMPRESSION);
} finally {
inflater.inflateEnd();
}
return inflater.getTotalOut();
}
final protected static char[] hexArray = "0123456789ABCDEF".toCharArray();
public static String byteToHex(byte b) {
char[] hexChars = new char[2];
int v = b & 0xFF;
hexChars[0] = hexArray[v >>> 4];
hexChars[1] = hexArray[v & 0x0F];
return new String(hexChars);
}
}
这是输出,顺序为 R : G : B : A...
FF : FF : FF : 00
------------------------
00 : 00 : 02 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
我的假设是它看起来更像:
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
FF : FF : FF : FF
------------------------
为什么不呢?怎么了?我在这上面花了一段时间,想不通。
这是一张 link 图片: http://i.imgur.com/57lhTXP.png(右击->另存为)
编辑:
我尝试使用内置的 Java Inflater 库,正如 CoderNeji 在评论中建议的那样:
private static long inflate(byte[] data, byte[] outputBuffer, int maxInflateBuffer) {
Inflater inflater = new Inflater();
inflater.setInput(data, 0, maxInflateBuffer);
try {
inflater.inflate(outputBuffer);
} catch (DataFormatException e) {
e.printStackTrace();
}
return inflater.getTotalOut();
}
得到了同样的结果。此外,使用任何一种方法,2x2 黑色图像都会给出以下输出:
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
00 : 00 : 00 : 00
------------------------
感谢您的帮助!
您忘记了 row filters。您在打印时跳过了第一个字节,因此您没有看到第一个字节(以及随后每行的所有其他 'first bytes',这不是一件坏事 - 除了您做得太早了)。
此外,您正在打印 alpha 通道输出,而图像没有。与您所说的相反,图像不是 RGBA,它是 RGB,没有 alpha。行
index += 4;
因此应该是
index += 3;
通过将打印的输出尺寸与您获得的 inflatedSize
进行比较,您可能已经注意到了这些错误。实际上,inflatedSize
应该是 14,并且您打印了 16 个字节的数据(还跳过了 2 个)。
实际解压后的数据,一行一行,为
01 : FF FF FF : 00 00 00
02 : 00 00 00 : 00 00 00
每行的第一个数字是行过滤器,后面是 2(宽度)* 3(通道)个像素数据条目。
这 2 行过滤器是 Sub 和 Up,从左到右,从上到下应用它们,你得到
FF FF FF : FF FF FF
FF FF FF : FF FF FF
翻译成“2x2 全白像素”,正如人们所期望的那样。