///|
fn image_u32(data : Bytes, pos : Int) -> UInt {
(data[pos].to_uint() << 24) |
(data[pos + 1].to_uint() << 16) |
(data[pos + 2].to_uint() << 8) |
data[pos + 3].to_uint()
}
///|
/// Construct PNG/JPEG artwork from its image header; pixels and CRC are not decoded.
pub fn picture_from_image(
data : Bytes,
kind? : Int = 3,
description? : String = "",
) -> Picture raise FlacError {
check_block_size(data.length())
let (media_type, width, height, depth) = if data.has_prefix(
b"\x89PNG\r\n\x1a\n",
) {
guard data.length() >= 33 && data[8:16] == b"\x00\x00\x00\x0dIHDR" else {
raise Invalid(8, "missing complete PNG IHDR")
}
let width = image_u32(data, 16)
let height = image_u32(data, 20)
guard width > 0U &&
height > 0U &&
width <= 0x7fffffffU &&
height <= 0x7fffffffU else {
raise Invalid(16, "invalid PNG dimensions")
}
let bits = data[24].to_int()
let channels = match data[25].to_int() {
0 => {
guard [1, 2, 4, 8, 16].contains(bits) else {
raise Invalid(24, "invalid PNG bit depth")
}
1
}
2 => {
guard bits == 8 || bits == 16 else {
raise Invalid(24, "invalid PNG bit depth")
}
3
}
3 => {
guard [1, 2, 4, 8].contains(bits) else {
raise Invalid(24, "invalid PNG bit depth")
}
1
}
4 => {
guard bits == 8 || bits == 16 else {
raise Invalid(24, "invalid PNG bit depth")
}
2
}
6 => {
guard bits == 8 || bits == 16 else {
raise Invalid(24, "invalid PNG bit depth")
}
4
}
_ => raise Invalid(25, "invalid PNG color type")
}
guard data[26] == b'\x00' && data[27] == b'\x00' && data[28].to_int() <= 1 else {
raise Invalid(26, "unsupported PNG IHDR methods")
}
("image/png", width, height, (bits * channels).reinterpret_as_uint())
} else if data.has_prefix(b"\xff\xd8") {
jpeg_dimensions(data)
} else {
raise Invalid(0, "cover must have a PNG or JPEG header")
}
let picture = {
kind,
media_type,
description,
width,
height,
depth,
colors: 0U,
data,
}
validate_picture(picture)
ignore(encode_picture(picture))
picture
}
///|
fn jpeg_dimensions(data : Bytes) -> (String, UInt, UInt, UInt) raise FlacError {
let mut pos = 2
for ;; {
guard pos < data.length() && data[pos] == b'\xff' else {
raise Invalid(pos, "missing JPEG marker")
}
for ;; {
pos += 1
guard pos < data.length() else {
raise Invalid(pos, "truncated JPEG marker")
}
if data[pos] != b'\xff' {
break
}
}
let marker = data[pos].to_int()
pos += 1
guard marker != 0xda && marker != 0xd9 else {
raise Invalid(pos, "JPEG lacks a supported SOF header")
}
guard marker != 0 && marker != 0xd8 && !(marker >= 0xd0 && marker <= 0xd7) else {
raise Invalid(pos, "unexpected JPEG standalone marker")
}
if marker == 1 {
continue
}
guard data.length() - pos >= 2 else {
raise Invalid(pos, "truncated JPEG segment length")
}
let length = (data[pos].to_int() << 8) | data[pos + 1].to_int()
guard length >= 2 && length <= data.length() - pos else {
raise Invalid(pos, "truncated JPEG segment")
}
let sof = marker >= 0xc0 &&
marker <= 0xcf &&
marker != 0xc4 &&
marker != 0xc8 &&
marker != 0xcc
if sof {
guard length >= 8 else { raise Invalid(pos, "short JPEG SOF") }
let precision = data[pos + 2].to_int()
let height = (data[pos + 3].to_int() << 8) | data[pos + 4].to_int()
let width = (data[pos + 5].to_int() << 8) | data[pos + 6].to_int()
let components = data[pos + 7].to_int()
guard precision > 0 &&
height > 0 &&
width > 0 &&
components > 0 &&
length == 8 + components * 3 else {
raise Invalid(pos, "invalid JPEG SOF dimensions or components")
}
return (
"image/jpeg",
width.reinterpret_as_uint(),
height.reinterpret_as_uint(),
(precision * components).reinterpret_as_uint(),
)
}
pos += length
}
}