我已经阅读了PNG文件规范并学习到,在PNG签名的前8个字节之后,我们有IHDR块。该图像指出我们有13(0x0000000D)字节长度的IHDR。{{link1:}}
我用Swift编写了一段代码来读取同一个PNG文件,并打印出从PNG签名后第4个字节开始不给我一个长度为13字节的IHDR的字节块。代码在控制台的输出结果是
PNG Signature Bytes: 89 50 4E 47 0D 0A 1A 0A
File offset: 8
IHDR length bytes: 00 00 00 04
File offset: 12
IHDR Chunktype bytes: 43 67 42 49
File offset: 16
IHDR Data byte: 50 00 20 02
File offset: 20
pngImageWidth: 1342185474
pngImageWidth: 20480
pngImageHeight: 8194
我错过了什么,还是我读的规范已经过时了?
实际上,前8个字节是PNG签名字节。我不理解IHDR字节中期望的长度和块类型(IHDR的宽度、高度和其他字节因不同文件而异,但根据我的阅读,长度和块类型应该相同)。
读取PNG文件的代码很简单,如下所示:
enum PNGFileAnatomyConstants {
static let pngSignatureLength = 8
static let ihdrLength = 4
static let chunkTypeLength = 4
static let chunkCRCLength = 4
static let imageWidthLength = 4
static let imageHeigthLength = 4
}
func anatomyOfPNGFile() {
let bundle = Bundle.main
guard let pngFileUrl = bundle.url(forResource: "PNGFileSignature", withExtension: "png") else { fatalError() }
do {
// Signature start------------------------------------------------------------------------------------------
let readFileHandle = try FileHandle(forReadingFrom: pngFileUrl)
defer {
readFileHandle.closeFile()
}
let pngSignatureData = readFileHandle.readData(ofLength: PNGFileAnatomyConstants.pngSignatureLength)
let signatureString = pngSignatureData.hexEncodedString(options: [Data.HexEncodingOptions.upperCase])
if signatureString != "89 50 4E 47 0D 0A 1A 0A " {
fatalError(" Not a png")
}
print("PNG Signature Bytes: \(signatureString)")
print("File offset: \(readFileHandle.offsetInFile)")
// Signature ebd------------------------------------------------------------------------------------------
// IHDR Length start------------------------------------------------------------------------------------------
let ihdrLengthDataBigEndian = readFileHandle.readData(ofLength: PNGFileAnatomyConstants.ihdrLength)
let ihdrLength: UInt32
if PlatformEndianess.isLittleEndian {
ihdrLength = Data(ihdrLengthDataBigEndian.reversed()).withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt32>) -> UInt32 in
return unsafePointer.pointee
})
} else {
ihdrLength = ihdrLengthDataBigEndian.withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt32>) -> UInt32 in
return unsafePointer.pointee
})
}
let ihdrLengthDataBigEndianString = ihdrLengthDataBigEndian.hexEncodedString(options: [.upperCase])
print("IHDR length bytes: \(ihdrLengthDataBigEndianString)")
print("File offset: \(readFileHandle.offsetInFile)")
// IHDR Length end------------------------------------------------------------------------------------------
// IHDR chunk type start------------------------------------------------------------------------------------------
let ihdrChunkTypeData = readFileHandle.readData(ofLength: PNGFileAnatomyConstants.chunkTypeLength)
let ihdrChunkTypeDataString = ihdrChunkTypeData.hexEncodedString(options: [Data.HexEncodingOptions.upperCase])
print("IHDR Chunktype bytes: \(ihdrChunkTypeDataString)")
print("File offset: \(readFileHandle.offsetInFile)")
// IHDR chunk type end------------------------------------------------------------------------------------------
// IHDR data byte start------------------------------------------------------------------------------------------
let ihdrData = readFileHandle.readData(ofLength: Int(ihdrLength))
let ihdrDataString = ihdrData.hexEncodedString(options: [.upperCase])
print("IHDR Data byte: \(ihdrDataString)")
print("File offset: \(readFileHandle.offsetInFile)")
// IHDR data byte end------------------------------------------------------------------------------------------
do {
let pngImageWidth: UInt32
if PlatformEndianess.isLittleEndian {
pngImageWidth = Data(ihdrData.reversed()).withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt32>) -> UInt32 in
return unsafePointer.pointee
})
} else {
pngImageWidth = ihdrData.withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt32>) -> UInt32 in
return unsafePointer.pointee
})
}
print("pngImageWidth: \(pngImageWidth)")
}
do {
let pngImageWidth: UInt16
let widthData = Data(bytes: [ihdrData[0], ihdrData[1]])
if PlatformEndianess.isLittleEndian {
pngImageWidth = Data(widthData.reversed()).withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt16>) -> UInt16 in
return unsafePointer.pointee
})
} else {
pngImageWidth = widthData.withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt16>) -> UInt16 in
return unsafePointer.pointee
})
}
print("pngImageWidth: \(pngImageWidth)")//20480
let pngImageHeight: UInt16
let heightData = Data(bytes: [ihdrData[2], ihdrData[3]])
if PlatformEndianess.isLittleEndian {
pngImageHeight = Data(heightData.reversed()).withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt16>) -> UInt16 in
return unsafePointer.pointee
})
} else {
pngImageHeight = heightData.withUnsafeBytes({ (unsafePointer: UnsafePointer<UInt16>) -> UInt16 in
return unsafePointer.pointee
})
}
print("pngImageHeight: \(pngImageHeight)")//20480
}
} catch {
fatalError(error.localizedDescription)
}
}
extension Data {
struct HexEncodingOptions: OptionSet {
let rawValue: Int
static let upperCase = HexEncodingOptions(rawValue: 1 << 0)
}
func hexEncodedString(options: HexEncodingOptions = []) -> String {
let hexDigits = Array((options.contains(.upperCase) ? "0123456789ABCDEF " : "0123456789abcdef ").utf16)
var chars: [unichar] = []
chars.reserveCapacity(3 * count)
for byte in self {
chars.append(hexDigits[Int(byte / 16)])
chars.append(hexDigits[Int(byte % 16)])
chars.append(hexDigits.last!)
}
return String(utf16CodeUnits: chars, count: chars.count)
}
}
class PlatformEndianess {
static var isLittleEndian: Bool = {
var integer: UInt16 = 0x0001
return withUnsafeBytes(of: &integer, { (rawBufferPointer) -> Bool in
return rawBufferPointer.first == 0x01
})
}()
}