|
@@ -219,841 +219,851 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
|
|
defer delete(blocks)
|
|
defer delete(blocks)
|
|
|
|
|
|
loop: for {
|
|
loop: for {
|
|
|
|
+ // Loop until we find 0xFF.
|
|
first = compress.read_u8(ctx) or_return
|
|
first = compress.read_u8(ctx) or_return
|
|
- if first == 0xFF {
|
|
|
|
- marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return
|
|
|
|
- if expect_EOI && marker != .EOI {
|
|
|
|
- return img, .Extra_Data_After_SOS
|
|
|
|
|
|
+ (first == 0xFF) or_continue
|
|
|
|
+
|
|
|
|
+ marker := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return
|
|
|
|
+ if expect_EOI && marker != .EOI {
|
|
|
|
+ return img, .Extra_Data_After_SOS
|
|
|
|
+ }
|
|
|
|
+ #partial switch marker {
|
|
|
|
+ case cast(image.JPEG_Marker)0xFF:
|
|
|
|
+ // If we encounter multiple FF bytes then just skip them
|
|
|
|
+ continue
|
|
|
|
+ case .SOI:
|
|
|
|
+ return img, .Duplicate_SOI_Marker
|
|
|
|
+ case .APP0:
|
|
|
|
+ ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return
|
|
|
|
+ length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2)
|
|
|
|
+ for {
|
|
|
|
+ b := compress.read_u8(ctx) or_return
|
|
|
|
+ if b == 0x00 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ append(&ident, b) or_return
|
|
}
|
|
}
|
|
- #partial switch marker {
|
|
|
|
- case cast(image.JPEG_Marker)0xFF:
|
|
|
|
- // If we encounter multiple FF bytes then just skip them
|
|
|
|
- continue
|
|
|
|
- case .SOI:
|
|
|
|
- return img, .Duplicate_SOI_Marker
|
|
|
|
- case .APP0:
|
|
|
|
- ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return
|
|
|
|
- length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2)
|
|
|
|
- for {
|
|
|
|
- b := compress.read_u8(ctx) or_return
|
|
|
|
- if b == 0x00 {
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
- append(&ident, b) or_return
|
|
|
|
|
|
+ if slice.equal(ident[:], image.JFIF_Magic[:]) {
|
|
|
|
+ if length != 14 {
|
|
|
|
+ // Malformed APP0. Skip it
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
- if slice.equal(ident[:], image.JFIF_Magic[:]) {
|
|
|
|
- if length != 14 {
|
|
|
|
- // Malformed APP0. Skip it
|
|
|
|
- compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
- continue
|
|
|
|
|
|
+
|
|
|
|
+ version := compress.read_data(ctx, u16be) or_return
|
|
|
|
+ units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return)
|
|
|
|
+ x_density := compress.read_data(ctx, u16be) or_return
|
|
|
|
+ y_density := compress.read_data(ctx, u16be) or_return
|
|
|
|
+ x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ thumbnail: []image.RGB_Pixel
|
|
|
|
+
|
|
|
|
+ if x_thumbnail * y_thumbnail != 0 {
|
|
|
|
+ greyscale_thumbnail := false
|
|
|
|
+ thumbnail_size := x_thumbnail * y_thumbnail * 3
|
|
|
|
+ // According to the JFIF spec, the thumbnail should always be made of RGB pixels.
|
|
|
|
+ // But some jpegs encode single-channel thumbnails.
|
|
|
|
+ if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 {
|
|
|
|
+ thumbnail_size = x_thumbnail * y_thumbnail
|
|
|
|
+ greyscale_thumbnail = true
|
|
|
|
+ } else {
|
|
|
|
+ return img, .Invalid_Thumbnail_Size
|
|
}
|
|
}
|
|
|
|
+ thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return)
|
|
|
|
|
|
- version := compress.read_data(ctx, u16be) or_return
|
|
|
|
- units := cast(image.JFIF_Unit)(compress.read_u8(ctx) or_return)
|
|
|
|
- x_density := compress.read_data(ctx, u16be) or_return
|
|
|
|
- y_density := compress.read_data(ctx, u16be) or_return
|
|
|
|
- x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- thumbnail: []image.RGB_Pixel
|
|
|
|
-
|
|
|
|
- if x_thumbnail * y_thumbnail != 0 {
|
|
|
|
- greyscale_thumbnail := false
|
|
|
|
- thumbnail_size := x_thumbnail * y_thumbnail * 3
|
|
|
|
- // According to the JFIF spec, the thumbnail should always be made of RGB pixels.
|
|
|
|
- // But some jpegs encode single-channel thumbnails.
|
|
|
|
- if thumbnail_size != length - 14 && thumbnail_size / 3 == length - 14 {
|
|
|
|
- thumbnail_size = x_thumbnail * y_thumbnail
|
|
|
|
- greyscale_thumbnail = true
|
|
|
|
|
|
+ if .return_metadata in options {
|
|
|
|
+ thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return
|
|
|
|
+ copy(thumbnail, thumb_pixels)
|
|
|
|
+
|
|
|
|
+ info: ^image.JPEG_Info
|
|
|
|
+ if img.metadata == nil {
|
|
|
|
+ info = new(image.JPEG_Info) or_return
|
|
} else {
|
|
} else {
|
|
- return img, .Invalid_Thumbnail_Size
|
|
|
|
|
|
+ info = img.metadata.(^image.JPEG_Info)
|
|
}
|
|
}
|
|
- thumb_pixels := slice.reinterpret([]image.RGB_Pixel, compress.read_slice_from_memory(ctx, x_thumbnail * y_thumbnail) or_return)
|
|
|
|
-
|
|
|
|
- if .return_metadata in options {
|
|
|
|
- thumbnail = make([]image.RGB_Pixel, x_thumbnail * y_thumbnail) or_return
|
|
|
|
- copy(thumbnail, thumb_pixels)
|
|
|
|
-
|
|
|
|
- info: ^image.JPEG_Info
|
|
|
|
- if img.metadata == nil {
|
|
|
|
- info = new(image.JPEG_Info) or_return
|
|
|
|
- } else {
|
|
|
|
- info = img.metadata.(^image.JPEG_Info)
|
|
|
|
- }
|
|
|
|
- info.jfif_app0 = image.JFIF_APP0{
|
|
|
|
- version,
|
|
|
|
- x_density,
|
|
|
|
- y_density,
|
|
|
|
- units,
|
|
|
|
- cast(u8)x_thumbnail,
|
|
|
|
- cast(u8)y_thumbnail,
|
|
|
|
- greyscale_thumbnail,
|
|
|
|
- thumbnail,
|
|
|
|
- }
|
|
|
|
- img.metadata = info
|
|
|
|
|
|
+ info.jfif_app0 = image.JFIF_APP0{
|
|
|
|
+ version,
|
|
|
|
+ x_density,
|
|
|
|
+ y_density,
|
|
|
|
+ units,
|
|
|
|
+ cast(u8)x_thumbnail,
|
|
|
|
+ cast(u8)y_thumbnail,
|
|
|
|
+ greyscale_thumbnail,
|
|
|
|
+ thumbnail,
|
|
}
|
|
}
|
|
|
|
+ img.metadata = info
|
|
}
|
|
}
|
|
- } else if slice.equal(ident[:], image.JFXX_Magic[:]) {
|
|
|
|
- extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return
|
|
|
|
- thumbnail: []byte
|
|
|
|
-
|
|
|
|
- switch extension_code {
|
|
|
|
- // We return the JPEG-compressed bytes for this type of thumbnail.
|
|
|
|
- // It's up to the user if they want to decode it by checking the extension code
|
|
|
|
- // and calling image.load() on the thumbnail.
|
|
|
|
- // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data.
|
|
|
|
- case .Thumbnail_JPEG:
|
|
|
|
- // +1 for the NUL byte
|
|
|
|
- thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code))
|
|
|
|
- thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return
|
|
|
|
-
|
|
|
|
- if .return_metadata in options {
|
|
|
|
- thumbnail = make([]byte, thumbnail_len) or_return
|
|
|
|
- copy(thumbnail, thumbnail_jpeg)
|
|
|
|
-
|
|
|
|
- info: ^image.JPEG_Info
|
|
|
|
- if img.metadata == nil {
|
|
|
|
- info = new(image.JPEG_Info) or_return
|
|
|
|
- } else {
|
|
|
|
- info = img.metadata.(^image.JPEG_Info)
|
|
|
|
- }
|
|
|
|
- info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
- extension_code,
|
|
|
|
- 0,
|
|
|
|
- 0,
|
|
|
|
- thumbnail,
|
|
|
|
- }
|
|
|
|
- img.metadata = info
|
|
|
|
- }
|
|
|
|
- case .Thumbnail_3_Byte_RGB:
|
|
|
|
- x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
-
|
|
|
|
- if .return_metadata in options {
|
|
|
|
- thumbnail = make([]byte, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
- copy(thumbnail, pixels)
|
|
|
|
-
|
|
|
|
- info: ^image.JPEG_Info
|
|
|
|
- if img.metadata == nil {
|
|
|
|
- info = new(image.JPEG_Info) or_return
|
|
|
|
- } else {
|
|
|
|
- info = img.metadata.(^image.JPEG_Info)
|
|
|
|
- }
|
|
|
|
- info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
- extension_code,
|
|
|
|
- cast(u8)x_thumbnail,
|
|
|
|
- cast(u8)y_thumbnail,
|
|
|
|
- thumbnail,
|
|
|
|
- }
|
|
|
|
- img.metadata = info
|
|
|
|
|
|
+ }
|
|
|
|
+ } else if slice.equal(ident[:], image.JFXX_Magic[:]) {
|
|
|
|
+ extension_code := cast(image.JFXX_Extension_Code)compress.read_u8(ctx) or_return
|
|
|
|
+ thumbnail: []byte
|
|
|
|
+
|
|
|
|
+ switch extension_code {
|
|
|
|
+ // We return the JPEG-compressed bytes for this type of thumbnail.
|
|
|
|
+ // It's up to the user if they want to decode it by checking the extension code
|
|
|
|
+ // and calling image.load() on the thumbnail.
|
|
|
|
+ // Not sure where to document that though, maybe it's better if the thumbnail is always raw pixel data.
|
|
|
|
+ case .Thumbnail_JPEG:
|
|
|
|
+ // +1 for the NUL byte
|
|
|
|
+ thumbnail_len := length - (size_of(image.JFXX_Magic) + 1 + size_of(image.JFXX_Extension_Code))
|
|
|
|
+ thumbnail_jpeg := compress.read_slice(ctx, thumbnail_len) or_return
|
|
|
|
+
|
|
|
|
+ if .return_metadata in options {
|
|
|
|
+ thumbnail = make([]byte, thumbnail_len) or_return
|
|
|
|
+ copy(thumbnail, thumbnail_jpeg)
|
|
|
|
+
|
|
|
|
+ info: ^image.JPEG_Info
|
|
|
|
+ if img.metadata == nil {
|
|
|
|
+ info = new(image.JPEG_Info) or_return
|
|
|
|
+ } else {
|
|
|
|
+ info = img.metadata.(^image.JPEG_Info)
|
|
}
|
|
}
|
|
- case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with.
|
|
|
|
- x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
- palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return)
|
|
|
|
- old_pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail) or_return
|
|
|
|
-
|
|
|
|
- if .return_metadata in options {
|
|
|
|
- pixels := make([]byte, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
- for i in 0..<x_thumbnail*y_thumbnail {
|
|
|
|
- pixel := palette[old_pixels[i]]
|
|
|
|
- pixels[i] = pixel.r
|
|
|
|
- pixels[i + 1] = pixel.g
|
|
|
|
- pixels[i + 2] = pixel.b
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- info: ^image.JPEG_Info
|
|
|
|
- if img.metadata == nil {
|
|
|
|
- info = new(image.JPEG_Info) or_return
|
|
|
|
- } else {
|
|
|
|
- info = img.metadata.(^image.JPEG_Info)
|
|
|
|
- }
|
|
|
|
- info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
- extension_code,
|
|
|
|
- cast(u8)x_thumbnail,
|
|
|
|
- cast(u8)y_thumbnail,
|
|
|
|
- pixels,
|
|
|
|
- }
|
|
|
|
- img.metadata = info
|
|
|
|
|
|
+ info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
+ extension_code,
|
|
|
|
+ 0,
|
|
|
|
+ 0,
|
|
|
|
+ thumbnail,
|
|
}
|
|
}
|
|
- case:
|
|
|
|
- return img, .Invalid_JFXX_Extension_Code
|
|
|
|
|
|
+ img.metadata = info
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- // - 1 for the NUL byte
|
|
|
|
- compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- case .APP1: // Metadata
|
|
|
|
- length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2)
|
|
|
|
- if .return_metadata not_in options {
|
|
|
|
- compress.read_slice(ctx, length) or_return
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- info: ^image.JPEG_Info
|
|
|
|
- if img.metadata == nil {
|
|
|
|
- info = new(image.JPEG_Info) or_return
|
|
|
|
- } else {
|
|
|
|
- info = img.metadata.(^image.JPEG_Info)
|
|
|
|
- }
|
|
|
|
|
|
+ case .Thumbnail_3_Byte_RGB:
|
|
|
|
+ x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
|
|
- ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return
|
|
|
|
- for {
|
|
|
|
- b := compress.read_u8(ctx) or_return
|
|
|
|
- if b == 0x00 {
|
|
|
|
- break
|
|
|
|
- }
|
|
|
|
- append(&ident, b) or_return
|
|
|
|
- }
|
|
|
|
|
|
+ if .return_metadata in options {
|
|
|
|
+ thumbnail = make([]byte, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
+ copy(thumbnail, pixels)
|
|
|
|
|
|
- if slice.equal(ident[:], image.Exif_Magic[:]) {
|
|
|
|
- // Padding byte according to section 4.7.2.2 in Exif spec 3.0
|
|
|
|
- compress.read_u8(ctx) or_return
|
|
|
|
-
|
|
|
|
- exif: image.Exif
|
|
|
|
- peek := compress.peek_data(ctx, [4]byte) or_return
|
|
|
|
- if peek[0] == 'M' && peek[1] == 'M' {
|
|
|
|
- exif.byte_order = .big_endian
|
|
|
|
- if peek[2] != 0 || peek[3] != 42 {
|
|
|
|
- // - 2 for the NUL byte and padding byte
|
|
|
|
- compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
- continue
|
|
|
|
|
|
+ info: ^image.JPEG_Info
|
|
|
|
+ if img.metadata == nil {
|
|
|
|
+ info = new(image.JPEG_Info) or_return
|
|
|
|
+ } else {
|
|
|
|
+ info = img.metadata.(^image.JPEG_Info)
|
|
}
|
|
}
|
|
- } else if peek[0] == 'I' && peek[1] == 'I' {
|
|
|
|
- exif.byte_order = .little_endian
|
|
|
|
- if peek[2] != 42 || peek[3] != 0 {
|
|
|
|
- compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
- continue
|
|
|
|
|
|
+ info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
+ extension_code,
|
|
|
|
+ cast(u8)x_thumbnail,
|
|
|
|
+ cast(u8)y_thumbnail,
|
|
|
|
+ thumbnail,
|
|
|
|
+ }
|
|
|
|
+ img.metadata = info
|
|
|
|
+ }
|
|
|
|
+ case .Thumbnail_1_Byte_Palette: // NOTE(illusionman1212): NOT TESTED. Couldn't find a jpeg to test this with.
|
|
|
|
+ x_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ y_thumbnail := cast(int)compress.read_u8(ctx) or_return
|
|
|
|
+ palette := slice.reinterpret([]image.RGB_Pixel, compress.read_slice(ctx, THUMBNAIL_PALETTE_SIZE / 3) or_return)
|
|
|
|
+ old_pixels := compress.read_slice(ctx, x_thumbnail * y_thumbnail) or_return
|
|
|
|
+
|
|
|
|
+ if .return_metadata in options {
|
|
|
|
+ pixels := make([]byte, x_thumbnail * y_thumbnail * 3) or_return
|
|
|
|
+ for i in 0..<x_thumbnail*y_thumbnail {
|
|
|
|
+ pixel := palette[old_pixels[i]]
|
|
|
|
+ pixels[i] = pixel.r
|
|
|
|
+ pixels[i + 1] = pixel.g
|
|
|
|
+ pixels[i + 2] = pixel.b
|
|
}
|
|
}
|
|
- } else {
|
|
|
|
- // If we can't determine the endianness then this Exif data is likely a continuation of the previous
|
|
|
|
- // APP1 Exif data
|
|
|
|
|
|
|
|
- // We only treat it as such if a previous Exif entry exists and its data length is the max
|
|
|
|
- if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 {
|
|
|
|
- exif.byte_order = info.exif[len(info.exif) - 1].byte_order
|
|
|
|
|
|
+ info: ^image.JPEG_Info
|
|
|
|
+ if img.metadata == nil {
|
|
|
|
+ info = new(image.JPEG_Info) or_return
|
|
} else {
|
|
} else {
|
|
- compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
- continue
|
|
|
|
|
|
+ info = img.metadata.(^image.JPEG_Info)
|
|
}
|
|
}
|
|
|
|
+ info.jfxx_app0 = image.JFXX_APP0{
|
|
|
|
+ extension_code,
|
|
|
|
+ cast(u8)x_thumbnail,
|
|
|
|
+ cast(u8)y_thumbnail,
|
|
|
|
+ pixels,
|
|
|
|
+ }
|
|
|
|
+ img.metadata = info
|
|
}
|
|
}
|
|
-
|
|
|
|
- // - 2 for the NUL byte and padding byte
|
|
|
|
- data := compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
- exif.data = make([]byte, len(data)) or_return
|
|
|
|
- copy(exif.data, data)
|
|
|
|
-
|
|
|
|
- append(&info.exif, exif) or_return
|
|
|
|
- img.metadata = info
|
|
|
|
- } else {
|
|
|
|
- // - 1 for the NUL byte
|
|
|
|
- compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
- continue
|
|
|
|
- }
|
|
|
|
- case .COM:
|
|
|
|
- length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
- comment := string(compress.read_slice(ctx, cast(int)length) or_return)
|
|
|
|
- if .return_metadata in options {
|
|
|
|
- if info, ok := img.metadata.(^image.JPEG_Info); ok {
|
|
|
|
- append(&info.comments, strings.clone(comment)) or_return
|
|
|
|
- }
|
|
|
|
|
|
+ case:
|
|
|
|
+ return img, .Invalid_JFXX_Extension_Code
|
|
}
|
|
}
|
|
- case .DQT:
|
|
|
|
- length := cast(int)(compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
|
|
+ } else {
|
|
|
|
+ // - 1 for the NUL byte
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ case .APP1: // Metadata
|
|
|
|
+ length := cast(int)((compress.read_data(ctx, u16be) or_return) - 2)
|
|
|
|
+ if .return_metadata not_in options {
|
|
|
|
+ compress.read_slice(ctx, length) or_return
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ info: ^image.JPEG_Info
|
|
|
|
+ if img.metadata == nil {
|
|
|
|
+ info = new(image.JPEG_Info) or_return
|
|
|
|
+ } else {
|
|
|
|
+ info = img.metadata.(^image.JPEG_Info)
|
|
|
|
+ }
|
|
|
|
|
|
- for length > 0 {
|
|
|
|
- precision_and_index := compress.read_u8(ctx) or_return
|
|
|
|
- precision := precision_and_index >> 4
|
|
|
|
- index := precision_and_index & 0xF
|
|
|
|
|
|
+ ident := make([dynamic]byte, 0, 16, context.temp_allocator) or_return
|
|
|
|
+ for {
|
|
|
|
+ b := compress.read_u8(ctx) or_return
|
|
|
|
+ if b == 0x00 {
|
|
|
|
+ break
|
|
|
|
+ }
|
|
|
|
+ append(&ident, b) or_return
|
|
|
|
+ }
|
|
|
|
|
|
- if precision != 0 && precision != 1 {
|
|
|
|
- return img, .Invalid_Quantization_Table_Precision
|
|
|
|
|
|
+ if slice.equal(ident[:], image.Exif_Magic[:]) {
|
|
|
|
+ // Padding byte according to section 4.7.2.2 in Exif spec 3.0
|
|
|
|
+ compress.read_u8(ctx) or_return
|
|
|
|
+
|
|
|
|
+ exif: image.Exif
|
|
|
|
+ peek := compress.peek_data(ctx, [4]byte) or_return
|
|
|
|
+ if peek[0] == 'M' && peek[1] == 'M' {
|
|
|
|
+ exif.byte_order = .big_endian
|
|
|
|
+ if peek[2] != 0 || peek[3] != 42 {
|
|
|
|
+ // - 2 for the NUL byte and padding byte
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
-
|
|
|
|
- if index < 0 || index > 3 {
|
|
|
|
- return img, .Invalid_Quantization_Table_Index
|
|
|
|
|
|
+ } else if peek[0] == 'I' && peek[1] == 'I' {
|
|
|
|
+ exif.byte_order = .little_endian
|
|
|
|
+ if peek[2] != 42 || peek[3] != 0 {
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
+ } else {
|
|
|
|
+ // If we can't determine the endianness then this Exif data is likely a continuation of the previous
|
|
|
|
+ // APP1 Exif data
|
|
|
|
|
|
- // When precision is 0, we read 64 u8s.
|
|
|
|
- // when it's 1, we read 64 u16s.
|
|
|
|
- table_bytes := 64
|
|
|
|
- if precision == 1 {
|
|
|
|
- table_bytes = 128
|
|
|
|
- table := compress.read_slice(ctx, table_bytes) or_return
|
|
|
|
- for v, i in slice.reinterpret([]u16be, table) {
|
|
|
|
- quantization[index][i] = v
|
|
|
|
- }
|
|
|
|
|
|
+ // We only treat it as such if a previous Exif entry exists and its data length is the max
|
|
|
|
+ if len(info.exif) > 0 && len(info.exif[len(info.exif) - 1].data) == SEGMENT_MAX_SIZE - len(ident) - 2 {
|
|
|
|
+ exif.byte_order = info.exif[len(info.exif) - 1].byte_order
|
|
} else {
|
|
} else {
|
|
- table := compress.read_slice(ctx, table_bytes) or_return
|
|
|
|
- for v, i in table {
|
|
|
|
- quantization[index][i] = cast(u16be)v
|
|
|
|
- }
|
|
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
+ continue
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ // - 2 for the NUL byte and padding byte
|
|
|
|
+ data := compress.read_slice(ctx, length - len(ident) - 2) or_return
|
|
|
|
+ exif.data = make([]byte, len(data)) or_return
|
|
|
|
+ copy(exif.data, data)
|
|
|
|
|
|
- length -= table_bytes + 1
|
|
|
|
|
|
+ append(&info.exif, exif) or_return
|
|
|
|
+ img.metadata = info
|
|
|
|
+ } else {
|
|
|
|
+ // - 1 for the NUL byte
|
|
|
|
+ compress.read_slice(ctx, length - len(ident) - 1) or_return
|
|
|
|
+ continue
|
|
|
|
+ }
|
|
|
|
+ case .COM:
|
|
|
|
+ length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
+ comment := string(compress.read_slice(ctx, cast(int)length) or_return)
|
|
|
|
+ if .return_metadata in options {
|
|
|
|
+ if info, ok := img.metadata.(^image.JPEG_Info); ok {
|
|
|
|
+ append(&info.comments, strings.clone(comment)) or_return
|
|
}
|
|
}
|
|
- case .DHT:
|
|
|
|
- length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
|
|
+ }
|
|
|
|
+ case .DQT:
|
|
|
|
+ length := cast(int)(compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
|
|
- for length > 0 {
|
|
|
|
- type_index := compress.read_u8(ctx) or_return
|
|
|
|
- type := cast(Coefficient)((type_index >> 4) & 0xF)
|
|
|
|
- index := type_index & 0xF
|
|
|
|
|
|
+ for length > 0 {
|
|
|
|
+ precision_and_index := compress.read_u8(ctx) or_return
|
|
|
|
+ precision := precision_and_index >> 4
|
|
|
|
+ index := precision_and_index & 0xF
|
|
|
|
|
|
- if type != .DC && type != .AC {
|
|
|
|
- return img, .Invalid_Huffman_Coefficient_Type
|
|
|
|
- }
|
|
|
|
|
|
+ if precision != 0 && precision != 1 {
|
|
|
|
+ return img, .Invalid_Quantization_Table_Precision
|
|
|
|
+ }
|
|
|
|
|
|
- if index < 0 || index > 3 {
|
|
|
|
- return img, .Invalid_Huffman_Table_Index
|
|
|
|
- }
|
|
|
|
|
|
+ if index < 0 || index > 3 {
|
|
|
|
+ return img, .Invalid_Quantization_Table_Index
|
|
|
|
+ }
|
|
|
|
|
|
- lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return
|
|
|
|
- num_symbols: u8 = 0
|
|
|
|
- for length, i in lengths {
|
|
|
|
- num_symbols += length
|
|
|
|
- huffman[type][index].offsets[i + 1] = num_symbols
|
|
|
|
|
|
+ // When precision is 0, we read 64 u8s.
|
|
|
|
+ // when it's 1, we read 64 u16s.
|
|
|
|
+ table_bytes := 64
|
|
|
|
+ if precision == 1 {
|
|
|
|
+ table_bytes = 128
|
|
|
|
+ table := compress.read_slice(ctx, table_bytes) or_return
|
|
|
|
+ for v, i in slice.reinterpret([]u16be, table) {
|
|
|
|
+ quantization[index][i] = v
|
|
}
|
|
}
|
|
-
|
|
|
|
- if num_symbols > HUFFMAN_MAX_SYMBOLS {
|
|
|
|
- return img, .Huffman_Symbols_Exceeds_Max
|
|
|
|
|
|
+ } else {
|
|
|
|
+ table := compress.read_slice(ctx, table_bytes) or_return
|
|
|
|
+ for v, i in table {
|
|
|
|
+ quantization[index][i] = cast(u16be)v
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return
|
|
|
|
- copy(huffman[type][index].symbols[:], symbols)
|
|
|
|
|
|
+ length -= table_bytes + 1
|
|
|
|
+ }
|
|
|
|
+ case .DHT:
|
|
|
|
+ length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
|
|
- length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols)
|
|
|
|
|
|
+ for length > 0 {
|
|
|
|
+ type_index := compress.read_u8(ctx) or_return
|
|
|
|
+ type := cast(Coefficient)((type_index >> 4) & 0xF)
|
|
|
|
+ index := type_index & 0xF
|
|
|
|
|
|
- code: u32 = 0
|
|
|
|
- for i in 0..<HUFFMAN_MAX_BITS {
|
|
|
|
- for j := huffman[type][index].offsets[i]; j < huffman[type][index].offsets[i + 1]; j += 1 {
|
|
|
|
- huffman[type][index].codes[j] = code
|
|
|
|
- code += 1
|
|
|
|
- }
|
|
|
|
- code <<= 1
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- case .EOI:
|
|
|
|
- break loop
|
|
|
|
- case .DRI:
|
|
|
|
- // Length
|
|
|
|
- compress.read_data(ctx, u16be) or_return
|
|
|
|
- restart_interval = cast(int)compress.read_data(ctx, u16be) or_return
|
|
|
|
- case .RST0..=.RST7: // Handled by the bit reader. These shouldn't appear outside the entropy coded stream.
|
|
|
|
- return img, .Encountered_RST_Marker_Outside_ECS
|
|
|
|
- case .SOF0, .SOF1: // Baseline sequential DCT, and extended sequential DCT
|
|
|
|
- if img.channels != 0 {
|
|
|
|
- return img, .Multiple_SOS_Markers
|
|
|
|
|
|
+ if type != .DC && type != .AC {
|
|
|
|
+ return img, .Invalid_Huffman_Coefficient_Type
|
|
}
|
|
}
|
|
|
|
|
|
- // Length
|
|
|
|
- compress.read_data(ctx, u16be) or_return
|
|
|
|
- precision := compress.read_u8(ctx) or_return
|
|
|
|
- height := compress.read_data(ctx, u16be) or_return
|
|
|
|
- width := compress.read_data(ctx, u16be) or_return
|
|
|
|
- components := compress.read_u8(ctx) or_return
|
|
|
|
- img.width = cast(int)width
|
|
|
|
- img.height = cast(int)height
|
|
|
|
- img.depth = cast(int)precision
|
|
|
|
- img.channels = cast(int)components
|
|
|
|
-
|
|
|
|
- // TODO: 12-bit precision is valid too but we don't support it.
|
|
|
|
- if precision == 12 {
|
|
|
|
- return img, .Unsupported_12_Bit_Depth
|
|
|
|
- }
|
|
|
|
- if precision != 8 {
|
|
|
|
- return img, .Invalid_Frame_Bit_Depth_Combo
|
|
|
|
|
|
+ if index < 0 || index > 3 {
|
|
|
|
+ return img, .Invalid_Huffman_Table_Index
|
|
}
|
|
}
|
|
|
|
|
|
- // TODO: spec allows for the height to be 0 on the condition that a DNL marker MUST exist to define
|
|
|
|
- // how many lines in the frame we have.
|
|
|
|
- // ISO/IEC 10918-1: 1993.
|
|
|
|
- // Section B.2.5
|
|
|
|
- if img.width == 0 || img.height == 0 {
|
|
|
|
- return img, .Invalid_Image_Dimensions
|
|
|
|
|
|
+ lengths := compress.read_slice(ctx, HUFFMAN_MAX_BITS) or_return
|
|
|
|
+ num_symbols: u8 = 0
|
|
|
|
+ for length, i in lengths {
|
|
|
|
+ num_symbols += length
|
|
|
|
+ huffman[type][index].offsets[i + 1] = num_symbols
|
|
}
|
|
}
|
|
|
|
|
|
- if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS {
|
|
|
|
- return img, .Image_Dimensions_Too_Large
|
|
|
|
|
|
+ if num_symbols > HUFFMAN_MAX_SYMBOLS {
|
|
|
|
+ return img, .Huffman_Symbols_Exceeds_Max
|
|
}
|
|
}
|
|
|
|
|
|
- // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components
|
|
|
|
- if components != 1 && components != 3 {
|
|
|
|
- return img, .Invalid_Number_Of_Channels
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- mcu_width = (img.width + 7) / BLOCK_SIZE
|
|
|
|
- mcu_height = (img.height + 7) / BLOCK_SIZE
|
|
|
|
- block_width = mcu_width
|
|
|
|
- block_height = mcu_height
|
|
|
|
|
|
+ symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return
|
|
|
|
+ copy(huffman[type][index].symbols[:], symbols)
|
|
|
|
|
|
- for _ in 0..<components {
|
|
|
|
- id := cast(Component)compress.read_u8(ctx) or_return
|
|
|
|
|
|
+ length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols)
|
|
|
|
|
|
- if id == Component(0) {
|
|
|
|
- zero_based_components = true
|
|
|
|
|
|
+ code: u32 = 0
|
|
|
|
+ for i in 0..<HUFFMAN_MAX_BITS {
|
|
|
|
+ for j := huffman[type][index].offsets[i]; j < huffman[type][index].offsets[i + 1]; j += 1 {
|
|
|
|
+ huffman[type][index].codes[j] = code
|
|
|
|
+ code += 1
|
|
}
|
|
}
|
|
|
|
+ code <<= 1
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ case .EOI:
|
|
|
|
+ break loop
|
|
|
|
+ case .DRI:
|
|
|
|
+ // Length
|
|
|
|
+ compress.read_data(ctx, u16be) or_return
|
|
|
|
+ restart_interval = cast(int)compress.read_data(ctx, u16be) or_return
|
|
|
|
+ case .RST0..=.RST7: // Handled by the bit reader. These shouldn't appear outside the entropy coded stream.
|
|
|
|
+ return img, .Encountered_RST_Marker_Outside_ECS
|
|
|
|
+ case .SOF0, .SOF1: // Baseline sequential DCT, and extended sequential DCT
|
|
|
|
+ if img.channels != 0 {
|
|
|
|
+ return img, .Multiple_SOS_Markers
|
|
|
|
+ }
|
|
|
|
|
|
- if zero_based_components {
|
|
|
|
- id += Component(1)
|
|
|
|
- }
|
|
|
|
|
|
+ // Length
|
|
|
|
+ compress.read_data(ctx, u16be) or_return
|
|
|
|
+ precision := compress.read_u8(ctx) or_return
|
|
|
|
+ height := compress.read_data(ctx, u16be) or_return
|
|
|
|
+ width := compress.read_data(ctx, u16be) or_return
|
|
|
|
+ components := compress.read_u8(ctx) or_return
|
|
|
|
+ img.width = cast(int)width
|
|
|
|
+ img.height = cast(int)height
|
|
|
|
+ img.depth = cast(int)precision
|
|
|
|
+ img.channels = cast(int)components
|
|
|
|
+
|
|
|
|
+ // TODO: 12-bit precision is valid too but we don't support it.
|
|
|
|
+ if precision == 12 {
|
|
|
|
+ return img, .Unsupported_12_Bit_Depth
|
|
|
|
+ }
|
|
|
|
+ if precision != 8 {
|
|
|
|
+ return img, .Invalid_Frame_Bit_Depth_Combo
|
|
|
|
+ }
|
|
|
|
|
|
- // TODO: while others that use CMYK have these IDs 67, 77, 89, 75 which are CMYK in ASCII
|
|
|
|
- // TODO: even more weird ids. 82, 71, 66 which is RGB in ASCII
|
|
|
|
- if id < .Y || id > .Cr {
|
|
|
|
- return img, .Image_Does_Not_Adhere_to_Spec
|
|
|
|
- }
|
|
|
|
|
|
+ // TODO: spec allows for the height to be 0 on the condition that a DNL marker MUST exist to define
|
|
|
|
+ // how many lines in the frame we have.
|
|
|
|
+ // ISO/IEC 10918-1: 1993.
|
|
|
|
+ // Section B.2.5
|
|
|
|
+ if img.width == 0 || img.height == 0 {
|
|
|
|
+ return img, .Invalid_Image_Dimensions
|
|
|
|
+ }
|
|
|
|
|
|
- h_v_factors := compress.read_u8(ctx) or_return
|
|
|
|
- horizontal_sampling := h_v_factors >> 4
|
|
|
|
- vertical_sampling := h_v_factors & 0xF
|
|
|
|
|
|
+ if u128(img.width) * u128(img.height) > image.MAX_DIMENSIONS {
|
|
|
|
+ return img, .Image_Dimensions_Too_Large
|
|
|
|
+ }
|
|
|
|
|
|
- // TODO: spec says the range for the sampling factors is 1-4
|
|
|
|
- // We only support 1,2 for now.
|
|
|
|
- if horizontal_sampling < 1 || horizontal_sampling > 2 {
|
|
|
|
- return img, .Invalid_Sampling_Factor
|
|
|
|
- }
|
|
|
|
- if vertical_sampling < 1 || vertical_sampling > 2 {
|
|
|
|
- return img, .Invalid_Sampling_Factor
|
|
|
|
- }
|
|
|
|
|
|
+ // TODO: Some JPEGs use CMYK as the color model which means there will be 4 components
|
|
|
|
+ if components != 1 && components != 3 {
|
|
|
|
+ return img, .Invalid_Number_Of_Channels
|
|
|
|
+ }
|
|
|
|
|
|
- if id == .Y {
|
|
|
|
- if horizontal_sampling == 2 && mcu_width % 2 == 1 {
|
|
|
|
- block_width += 1
|
|
|
|
- }
|
|
|
|
- if vertical_sampling == 2 && mcu_height % 2 == 1 {
|
|
|
|
- block_height += 1
|
|
|
|
- }
|
|
|
|
- } else {
|
|
|
|
- if horizontal_sampling != 1 && vertical_sampling != 1 {
|
|
|
|
- return img, .Invalid_Sampling_Factor
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
|
|
+ if img.metadata != nil {
|
|
|
|
+ info := img.metadata.(^image.JPEG_Info)
|
|
|
|
+ info.frame_type = marker
|
|
|
|
+ }
|
|
|
|
|
|
- quantization_table_idx := compress.read_u8(ctx) or_return
|
|
|
|
|
|
+ mcu_width = (img.width + 7) / BLOCK_SIZE
|
|
|
|
+ mcu_height = (img.height + 7) / BLOCK_SIZE
|
|
|
|
+ block_width = mcu_width
|
|
|
|
+ block_height = mcu_height
|
|
|
|
|
|
- if quantization_table_idx < 0 || quantization_table_idx > 3 {
|
|
|
|
- return img, .Invalid_Quantization_Table_Index
|
|
|
|
- }
|
|
|
|
|
|
+ for _ in 0..<components {
|
|
|
|
+ id := cast(Component)compress.read_u8(ctx) or_return
|
|
|
|
|
|
- color_components[id].quantization_table_idx = quantization_table_idx
|
|
|
|
- color_components[id].v_sampling_factor = cast(int)vertical_sampling
|
|
|
|
- color_components[id].h_sampling_factor = cast(int)horizontal_sampling
|
|
|
|
|
|
+ if id == Component(0) {
|
|
|
|
+ zero_based_components = true
|
|
}
|
|
}
|
|
- case .SOF2: // Progressive DCT
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF3: // Lossless (sequential)
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF5: // Differential sequential DCT
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF6: // Differential progressive DCT
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF7: // Differential lossless (sequential)
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF9: // Extended sequential DCT, Arithmetic coding
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF10: // Progressive DCT, Arithmetic coding
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF11: // Lossless (sequential), Arithmetic coding
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF13: // Differential sequential DCT, Arithmetic coding
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF14: // Differential progressive DCT, Arithmetic coding
|
|
|
|
- fallthrough
|
|
|
|
- case .SOF15: // Differential lossless (sequential), Arithmetic coding
|
|
|
|
- return img, .Unsupported_Frame_Type
|
|
|
|
- case .SOS:
|
|
|
|
- if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 {
|
|
|
|
- return img, .Encountered_SOS_Before_SOF
|
|
|
|
|
|
+
|
|
|
|
+ if zero_based_components {
|
|
|
|
+ id += Component(1)
|
|
}
|
|
}
|
|
|
|
|
|
- if .do_not_decompress_image in options {
|
|
|
|
- return img, nil
|
|
|
|
|
|
+ // TODO: while others that use CMYK have these IDs 67, 77, 89, 75 which are CMYK in ASCII
|
|
|
|
+ // TODO: even more weird ids. 82, 71, 66 which is RGB in ASCII
|
|
|
|
+ if id < .Y || id > .Cr {
|
|
|
|
+ return img, .Image_Does_Not_Adhere_to_Spec
|
|
}
|
|
}
|
|
|
|
|
|
- // Length
|
|
|
|
- compress.read_data(ctx, u16be) or_return
|
|
|
|
- num_components := compress.read_u8(ctx) or_return
|
|
|
|
- if num_components != 1 && num_components != 3 {
|
|
|
|
- return img, .Invalid_Number_Of_Channels
|
|
|
|
|
|
+ h_v_factors := compress.read_u8(ctx) or_return
|
|
|
|
+ horizontal_sampling := h_v_factors >> 4
|
|
|
|
+ vertical_sampling := h_v_factors & 0xF
|
|
|
|
+
|
|
|
|
+ // TODO: spec says the range for the sampling factors is 1-4
|
|
|
|
+ // We only support 1,2 for now.
|
|
|
|
+ if horizontal_sampling < 1 || horizontal_sampling > 2 {
|
|
|
|
+ return img, .Invalid_Sampling_Factor
|
|
|
|
+ }
|
|
|
|
+ if vertical_sampling < 1 || vertical_sampling > 2 {
|
|
|
|
+ return img, .Invalid_Sampling_Factor
|
|
}
|
|
}
|
|
|
|
|
|
- for _ in 0..<num_components {
|
|
|
|
- component_id := cast(Component)compress.read_u8(ctx) or_return
|
|
|
|
- if zero_based_components {
|
|
|
|
- component_id += Component(1)
|
|
|
|
|
|
+ if id == .Y {
|
|
|
|
+ if horizontal_sampling == 2 && mcu_width % 2 == 1 {
|
|
|
|
+ block_width += 1
|
|
}
|
|
}
|
|
- if component_id < .Y || component_id > .Cr {
|
|
|
|
- return img, .Image_Does_Not_Adhere_to_Spec
|
|
|
|
|
|
+ if vertical_sampling == 2 && mcu_height % 2 == 1 {
|
|
|
|
+ block_height += 1
|
|
}
|
|
}
|
|
|
|
+ } else {
|
|
|
|
+ if horizontal_sampling != 1 && vertical_sampling != 1 {
|
|
|
|
+ return img, .Invalid_Sampling_Factor
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
|
|
- // high 4 is DC, low 4 is AC
|
|
|
|
- coefficient_indices := compress.read_u8(ctx) or_return
|
|
|
|
- dc_table_idx := coefficient_indices >> 4
|
|
|
|
- ac_table_idx := coefficient_indices & 0xF
|
|
|
|
|
|
+ quantization_table_idx := compress.read_u8(ctx) or_return
|
|
|
|
|
|
- if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) {
|
|
|
|
- return img, .Invalid_Huffman_Table_Index
|
|
|
|
- }
|
|
|
|
|
|
+ if quantization_table_idx < 0 || quantization_table_idx > 3 {
|
|
|
|
+ return img, .Invalid_Quantization_Table_Index
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ color_components[id].quantization_table_idx = quantization_table_idx
|
|
|
|
+ color_components[id].v_sampling_factor = cast(int)vertical_sampling
|
|
|
|
+ color_components[id].h_sampling_factor = cast(int)horizontal_sampling
|
|
|
|
+ }
|
|
|
|
+ case .SOF2: // Progressive DCT
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF3: // Lossless (sequential)
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF5: // Differential sequential DCT
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF6: // Differential progressive DCT
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF7: // Differential lossless (sequential)
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF9: // Extended sequential DCT, Arithmetic coding
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF10: // Progressive DCT, Arithmetic coding
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF11: // Lossless (sequential), Arithmetic coding
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF13: // Differential sequential DCT, Arithmetic coding
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF14: // Differential progressive DCT, Arithmetic coding
|
|
|
|
+ fallthrough
|
|
|
|
+ case .SOF15: // Differential lossless (sequential), Arithmetic coding
|
|
|
|
+ if img.metadata != nil {
|
|
|
|
+ info := img.metadata.(^image.JPEG_Info)
|
|
|
|
+ info.frame_type = marker
|
|
|
|
+ }
|
|
|
|
+ return img, .Unsupported_Frame_Type
|
|
|
|
+ case .SOS:
|
|
|
|
+ if img.channels == 0 && img.depth == 0 && img.width == 0 && img.height == 0 {
|
|
|
|
+ return img, .Encountered_SOS_Before_SOF
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ if .do_not_decompress_image in options {
|
|
|
|
+ return img, nil
|
|
|
|
+ }
|
|
|
|
|
|
- color_components[component_id].dc_table_idx = dc_table_idx
|
|
|
|
- color_components[component_id].ac_table_idx = ac_table_idx
|
|
|
|
|
|
+ // Length
|
|
|
|
+ compress.read_data(ctx, u16be) or_return
|
|
|
|
+ num_components := compress.read_u8(ctx) or_return
|
|
|
|
+ if num_components != 1 && num_components != 3 {
|
|
|
|
+ return img, .Invalid_Number_Of_Channels
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ for _ in 0..<num_components {
|
|
|
|
+ component_id := cast(Component)compress.read_u8(ctx) or_return
|
|
|
|
+ if zero_based_components {
|
|
|
|
+ component_id += Component(1)
|
|
|
|
+ }
|
|
|
|
+ if component_id < .Y || component_id > .Cr {
|
|
|
|
+ return img, .Image_Does_Not_Adhere_to_Spec
|
|
}
|
|
}
|
|
- // TODO: These aren't used for sequential DCT, only progressive and lossless.
|
|
|
|
- Ss := compress.read_u8(ctx) or_return
|
|
|
|
- _ = Ss
|
|
|
|
- Se := compress.read_u8(ctx) or_return
|
|
|
|
- _ = Se
|
|
|
|
- Ah_Al := compress.read_u8(ctx) or_return
|
|
|
|
- _ = Ah_Al
|
|
|
|
-
|
|
|
|
- blocks = make([]Block, block_height * block_width) or_return
|
|
|
|
-
|
|
|
|
- previous_dc: [Component]i16
|
|
|
|
-
|
|
|
|
- luma_v_sampling_factor := color_components[.Y].v_sampling_factor
|
|
|
|
- luma_h_sampling_factor := color_components[.Y].h_sampling_factor
|
|
|
|
-
|
|
|
|
- restart_interval *= luma_v_sampling_factor * luma_h_sampling_factor
|
|
|
|
- #no_bounds_check for y := 0; y < mcu_height; y += luma_v_sampling_factor {
|
|
|
|
- for x := 0; x < mcu_width; x += luma_h_sampling_factor {
|
|
|
|
- blk := y * block_width + x
|
|
|
|
-
|
|
|
|
- if restart_interval != 0 && blk % restart_interval == 0 {
|
|
|
|
- previous_dc[.Y] = 0
|
|
|
|
- previous_dc[.Cb] = 0
|
|
|
|
- previous_dc[.Cr] = 0
|
|
|
|
- byte_align(ctx)
|
|
|
|
- }
|
|
|
|
- for c in 1..=img.channels {
|
|
|
|
- c := cast(Component)c
|
|
|
|
- for v in 0..<color_components[c].v_sampling_factor {
|
|
|
|
- h_loop:
|
|
|
|
- for h in 0..<color_components[c].h_sampling_factor {
|
|
|
|
- mcu := &blocks[(y + v) * block_width + (h + x)][c]
|
|
|
|
- dc_table := huffman[.DC][color_components[c].dc_table_idx]
|
|
|
|
- ac_table := huffman[.AC][color_components[c].ac_table_idx]
|
|
|
|
- quantization_table := quantization[color_components[c].quantization_table_idx]
|
|
|
|
-
|
|
|
|
- length := get_symbol(ctx, dc_table)
|
|
|
|
-
|
|
|
|
- if length > 11 {
|
|
|
|
- return img, .Corrupt
|
|
|
|
- }
|
|
|
|
|
|
|
|
- dc_coeff := cast(i16)read_bits_msb(ctx, length)
|
|
|
|
|
|
+ // high 4 is DC, low 4 is AC
|
|
|
|
+ coefficient_indices := compress.read_u8(ctx) or_return
|
|
|
|
+ dc_table_idx := coefficient_indices >> 4
|
|
|
|
+ ac_table_idx := coefficient_indices & 0xF
|
|
|
|
|
|
- if length != 0 && dc_coeff < (1 << (length - 1)) {
|
|
|
|
- dc_coeff -= (1 << length) - 1
|
|
|
|
- }
|
|
|
|
- mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0]
|
|
|
|
- previous_dc[c] = dc_coeff + previous_dc[c]
|
|
|
|
|
|
+ if (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) {
|
|
|
|
+ return img, .Invalid_Huffman_Table_Index
|
|
|
|
+ }
|
|
|
|
|
|
- for i := 1; i < COEFFICIENT_COUNT; i += 1 {
|
|
|
|
- // High nibble is amount of 0s to skip.
|
|
|
|
- // Low nibble is length of coeff.
|
|
|
|
- symbol := get_symbol(ctx, ac_table)
|
|
|
|
|
|
+ color_components[component_id].dc_table_idx = dc_table_idx
|
|
|
|
+ color_components[component_id].ac_table_idx = ac_table_idx
|
|
|
|
+ }
|
|
|
|
+ // TODO: These aren't used for sequential DCT, only progressive and lossless.
|
|
|
|
+ Ss := compress.read_u8(ctx) or_return
|
|
|
|
+ _ = Ss
|
|
|
|
+ Se := compress.read_u8(ctx) or_return
|
|
|
|
+ _ = Se
|
|
|
|
+ Ah_Al := compress.read_u8(ctx) or_return
|
|
|
|
+ _ = Ah_Al
|
|
|
|
+
|
|
|
|
+ blocks = make([]Block, block_height * block_width) or_return
|
|
|
|
+
|
|
|
|
+ previous_dc: [Component]i16
|
|
|
|
+
|
|
|
|
+ luma_v_sampling_factor := color_components[.Y].v_sampling_factor
|
|
|
|
+ luma_h_sampling_factor := color_components[.Y].h_sampling_factor
|
|
|
|
+
|
|
|
|
+ restart_interval *= luma_v_sampling_factor * luma_h_sampling_factor
|
|
|
|
+ #no_bounds_check for y := 0; y < mcu_height; y += luma_v_sampling_factor {
|
|
|
|
+ for x := 0; x < mcu_width; x += luma_h_sampling_factor {
|
|
|
|
+ blk := y * block_width + x
|
|
|
|
+
|
|
|
|
+ if restart_interval != 0 && blk % restart_interval == 0 {
|
|
|
|
+ previous_dc[.Y] = 0
|
|
|
|
+ previous_dc[.Cb] = 0
|
|
|
|
+ previous_dc[.Cr] = 0
|
|
|
|
+ byte_align(ctx)
|
|
|
|
+ }
|
|
|
|
+ for c in 1..=img.channels {
|
|
|
|
+ c := cast(Component)c
|
|
|
|
+ for v in 0..<color_components[c].v_sampling_factor {
|
|
|
|
+ h_loop:
|
|
|
|
+ for h in 0..<color_components[c].h_sampling_factor {
|
|
|
|
+ mcu := &blocks[(y + v) * block_width + (h + x)][c]
|
|
|
|
+ dc_table := huffman[.DC][color_components[c].dc_table_idx]
|
|
|
|
+ ac_table := huffman[.AC][color_components[c].ac_table_idx]
|
|
|
|
+ quantization_table := quantization[color_components[c].quantization_table_idx]
|
|
|
|
+
|
|
|
|
+ length := get_symbol(ctx, dc_table)
|
|
|
|
+
|
|
|
|
+ if length > 11 {
|
|
|
|
+ return img, .Corrupt
|
|
|
|
+ }
|
|
|
|
|
|
- // Special symbol used to indicate
|
|
|
|
- // that the rest of the MCU is filled with 0s
|
|
|
|
- if symbol == 0x00 {
|
|
|
|
- continue h_loop
|
|
|
|
- }
|
|
|
|
|
|
+ dc_coeff := cast(i16)read_bits_msb(ctx, length)
|
|
|
|
|
|
- amnt_zeros := int(symbol >> 4)
|
|
|
|
- ac_coeff_len := symbol & 0xF
|
|
|
|
- ac_coeff: i16 = 0
|
|
|
|
|
|
+ if length != 0 && dc_coeff < (1 << (length - 1)) {
|
|
|
|
+ dc_coeff -= (1 << length) - 1
|
|
|
|
+ }
|
|
|
|
+ mcu[0] = (dc_coeff + previous_dc[c]) * cast(i16)quantization_table[0]
|
|
|
|
+ previous_dc[c] = dc_coeff + previous_dc[c]
|
|
|
|
+
|
|
|
|
+ for i := 1; i < COEFFICIENT_COUNT; i += 1 {
|
|
|
|
+ // High nibble is amount of 0s to skip.
|
|
|
|
+ // Low nibble is length of coeff.
|
|
|
|
+ symbol := get_symbol(ctx, ac_table)
|
|
|
|
+
|
|
|
|
+ // Special symbol used to indicate
|
|
|
|
+ // that the rest of the MCU is filled with 0s
|
|
|
|
+ if symbol == 0x00 {
|
|
|
|
+ continue h_loop
|
|
|
|
+ }
|
|
|
|
|
|
- if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 {
|
|
|
|
- return img, .Corrupt
|
|
|
|
- }
|
|
|
|
|
|
+ amnt_zeros := int(symbol >> 4)
|
|
|
|
+ ac_coeff_len := symbol & 0xF
|
|
|
|
+ ac_coeff: i16 = 0
|
|
|
|
|
|
- i += amnt_zeros
|
|
|
|
|
|
+ if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 {
|
|
|
|
+ return img, .Corrupt
|
|
|
|
+ }
|
|
|
|
|
|
- ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len)
|
|
|
|
- if ac_coeff < (1 << (ac_coeff_len - 1)) {
|
|
|
|
- ac_coeff -= (1 << ac_coeff_len) - 1
|
|
|
|
- }
|
|
|
|
|
|
+ i += amnt_zeros
|
|
|
|
|
|
- mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i]
|
|
|
|
|
|
+ ac_coeff = cast(i16)read_bits_msb(ctx, ac_coeff_len)
|
|
|
|
+ if ac_coeff < (1 << (ac_coeff_len - 1)) {
|
|
|
|
+ ac_coeff -= (1 << ac_coeff_len) - 1
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+ mcu[zigzag[i]] = ac_coeff * cast(i16)quantization_table[i]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- for c in 1..=img.channels {
|
|
|
|
- c := cast(Component)c
|
|
|
|
-
|
|
|
|
- for v in 0..<color_components[c].v_sampling_factor {
|
|
|
|
- for h in 0..< color_components[c].h_sampling_factor {
|
|
|
|
- mcu := &blocks[(y + v) * block_width + (x + h)][c]
|
|
|
|
- for i in 0..<BLOCK_SIZE {
|
|
|
|
- g0 := cast(f32)mcu[0 * BLOCK_SIZE + i] * s0
|
|
|
|
- g1 := cast(f32)mcu[4 * BLOCK_SIZE + i] * s4
|
|
|
|
- g2 := cast(f32)mcu[2 * BLOCK_SIZE + i] * s2
|
|
|
|
- g3 := cast(f32)mcu[6 * BLOCK_SIZE + i] * s6
|
|
|
|
- g4 := cast(f32)mcu[5 * BLOCK_SIZE + i] * s5
|
|
|
|
- g5 := cast(f32)mcu[1 * BLOCK_SIZE + i] * s1
|
|
|
|
- g6 := cast(f32)mcu[7 * BLOCK_SIZE + i] * s7
|
|
|
|
- g7 := cast(f32)mcu[3 * BLOCK_SIZE + i] * s3
|
|
|
|
-
|
|
|
|
- f4 := g4 - g7
|
|
|
|
- f5 := g5 + g6
|
|
|
|
- f6 := g5 - g6
|
|
|
|
- f7 := g4 + g7
|
|
|
|
-
|
|
|
|
- e0 := g0
|
|
|
|
- e1 := g1
|
|
|
|
- e2 := g2 - g3
|
|
|
|
- e3 := g2 + g3
|
|
|
|
- e4 := f4
|
|
|
|
- e5 := f5 - f7
|
|
|
|
- e6 := f6
|
|
|
|
- e7 := f5 + f7
|
|
|
|
- e8 := f4 + f6
|
|
|
|
-
|
|
|
|
- d0 := e0
|
|
|
|
- d1 := e1
|
|
|
|
- d2 := e2 * m1
|
|
|
|
- d3 := e3
|
|
|
|
- d4 := e4 * m2
|
|
|
|
- d5 := e5 * m3
|
|
|
|
- d6 := e6 * m4
|
|
|
|
- d7 := e7
|
|
|
|
- d8 := e8 * m5
|
|
|
|
-
|
|
|
|
- c0 := d0 + d1
|
|
|
|
- c1 := d0 - d1
|
|
|
|
- c2 := d2 - d3
|
|
|
|
- c3 := d3
|
|
|
|
- c4 := d4 + d8
|
|
|
|
- c5 := d5 + d7
|
|
|
|
- c6 := d6 - d8
|
|
|
|
- c7 := d7
|
|
|
|
- c8 := c5 - c6
|
|
|
|
-
|
|
|
|
- b0 := c0 + c3
|
|
|
|
- b1 := c1 + c2
|
|
|
|
- b2 := c1 - c2
|
|
|
|
- b3 := c0 - c3
|
|
|
|
- b4 := c4 - c8
|
|
|
|
- b5 := c8
|
|
|
|
- b6 := c6 - c7
|
|
|
|
- b7 := c7
|
|
|
|
-
|
|
|
|
- mcu[0 * BLOCK_SIZE + i] = cast(i16)(b0 + b7)
|
|
|
|
- mcu[1 * BLOCK_SIZE + i] = cast(i16)(b1 + b6)
|
|
|
|
- mcu[2 * BLOCK_SIZE + i] = cast(i16)(b2 + b5)
|
|
|
|
- mcu[3 * BLOCK_SIZE + i] = cast(i16)(b3 + b4)
|
|
|
|
- mcu[4 * BLOCK_SIZE + i] = cast(i16)(b3 - b4)
|
|
|
|
- mcu[5 * BLOCK_SIZE + i] = cast(i16)(b2 - b5)
|
|
|
|
- mcu[6 * BLOCK_SIZE + i] = cast(i16)(b1 - b6)
|
|
|
|
- mcu[7 * BLOCK_SIZE + i] = cast(i16)(b0 - b7)
|
|
|
|
- }
|
|
|
|
|
|
+ for c in 1..=img.channels {
|
|
|
|
+ c := cast(Component)c
|
|
|
|
+
|
|
|
|
+ for v in 0..<color_components[c].v_sampling_factor {
|
|
|
|
+ for h in 0..< color_components[c].h_sampling_factor {
|
|
|
|
+ mcu := &blocks[(y + v) * block_width + (x + h)][c]
|
|
|
|
+ for i in 0..<BLOCK_SIZE {
|
|
|
|
+ g0 := cast(f32)mcu[0 * BLOCK_SIZE + i] * s0
|
|
|
|
+ g1 := cast(f32)mcu[4 * BLOCK_SIZE + i] * s4
|
|
|
|
+ g2 := cast(f32)mcu[2 * BLOCK_SIZE + i] * s2
|
|
|
|
+ g3 := cast(f32)mcu[6 * BLOCK_SIZE + i] * s6
|
|
|
|
+ g4 := cast(f32)mcu[5 * BLOCK_SIZE + i] * s5
|
|
|
|
+ g5 := cast(f32)mcu[1 * BLOCK_SIZE + i] * s1
|
|
|
|
+ g6 := cast(f32)mcu[7 * BLOCK_SIZE + i] * s7
|
|
|
|
+ g7 := cast(f32)mcu[3 * BLOCK_SIZE + i] * s3
|
|
|
|
+
|
|
|
|
+ f4 := g4 - g7
|
|
|
|
+ f5 := g5 + g6
|
|
|
|
+ f6 := g5 - g6
|
|
|
|
+ f7 := g4 + g7
|
|
|
|
+
|
|
|
|
+ e0 := g0
|
|
|
|
+ e1 := g1
|
|
|
|
+ e2 := g2 - g3
|
|
|
|
+ e3 := g2 + g3
|
|
|
|
+ e4 := f4
|
|
|
|
+ e5 := f5 - f7
|
|
|
|
+ e6 := f6
|
|
|
|
+ e7 := f5 + f7
|
|
|
|
+ e8 := f4 + f6
|
|
|
|
+
|
|
|
|
+ d0 := e0
|
|
|
|
+ d1 := e1
|
|
|
|
+ d2 := e2 * m1
|
|
|
|
+ d3 := e3
|
|
|
|
+ d4 := e4 * m2
|
|
|
|
+ d5 := e5 * m3
|
|
|
|
+ d6 := e6 * m4
|
|
|
|
+ d7 := e7
|
|
|
|
+ d8 := e8 * m5
|
|
|
|
+
|
|
|
|
+ c0 := d0 + d1
|
|
|
|
+ c1 := d0 - d1
|
|
|
|
+ c2 := d2 - d3
|
|
|
|
+ c3 := d3
|
|
|
|
+ c4 := d4 + d8
|
|
|
|
+ c5 := d5 + d7
|
|
|
|
+ c6 := d6 - d8
|
|
|
|
+ c7 := d7
|
|
|
|
+ c8 := c5 - c6
|
|
|
|
+
|
|
|
|
+ b0 := c0 + c3
|
|
|
|
+ b1 := c1 + c2
|
|
|
|
+ b2 := c1 - c2
|
|
|
|
+ b3 := c0 - c3
|
|
|
|
+ b4 := c4 - c8
|
|
|
|
+ b5 := c8
|
|
|
|
+ b6 := c6 - c7
|
|
|
|
+ b7 := c7
|
|
|
|
+
|
|
|
|
+ mcu[0 * BLOCK_SIZE + i] = cast(i16)(b0 + b7)
|
|
|
|
+ mcu[1 * BLOCK_SIZE + i] = cast(i16)(b1 + b6)
|
|
|
|
+ mcu[2 * BLOCK_SIZE + i] = cast(i16)(b2 + b5)
|
|
|
|
+ mcu[3 * BLOCK_SIZE + i] = cast(i16)(b3 + b4)
|
|
|
|
+ mcu[4 * BLOCK_SIZE + i] = cast(i16)(b3 - b4)
|
|
|
|
+ mcu[5 * BLOCK_SIZE + i] = cast(i16)(b2 - b5)
|
|
|
|
+ mcu[6 * BLOCK_SIZE + i] = cast(i16)(b1 - b6)
|
|
|
|
+ mcu[7 * BLOCK_SIZE + i] = cast(i16)(b0 - b7)
|
|
|
|
+ }
|
|
|
|
|
|
- for i in 0..<BLOCK_SIZE {
|
|
|
|
- g0 := cast(f32)mcu[i * BLOCK_SIZE + 0] * s0
|
|
|
|
- g1 := cast(f32)mcu[i * BLOCK_SIZE + 4] * s4
|
|
|
|
- g2 := cast(f32)mcu[i * BLOCK_SIZE + 2] * s2
|
|
|
|
- g3 := cast(f32)mcu[i * BLOCK_SIZE + 6] * s6
|
|
|
|
- g4 := cast(f32)mcu[i * BLOCK_SIZE + 5] * s5
|
|
|
|
- g5 := cast(f32)mcu[i * BLOCK_SIZE + 1] * s1
|
|
|
|
- g6 := cast(f32)mcu[i * BLOCK_SIZE + 7] * s7
|
|
|
|
- g7 := cast(f32)mcu[i * BLOCK_SIZE + 3] * s3
|
|
|
|
-
|
|
|
|
- f4 := g4 - g7
|
|
|
|
- f5 := g5 + g6
|
|
|
|
- f6 := g5 - g6
|
|
|
|
- f7 := g4 + g7
|
|
|
|
-
|
|
|
|
- e0 := g0
|
|
|
|
- e1 := g1
|
|
|
|
- e2 := g2 - g3
|
|
|
|
- e3 := g2 + g3
|
|
|
|
- e4 := f4
|
|
|
|
- e5 := f5 - f7
|
|
|
|
- e6 := f6
|
|
|
|
- e7 := f5 + f7
|
|
|
|
- e8 := f4 + f6
|
|
|
|
-
|
|
|
|
- d0 := e0
|
|
|
|
- d1 := e1
|
|
|
|
- d2 := e2 * m1
|
|
|
|
- d3 := e3
|
|
|
|
- d4 := e4 * m2
|
|
|
|
- d5 := e5 * m3
|
|
|
|
- d6 := e6 * m4
|
|
|
|
- d7 := e7
|
|
|
|
- d8 := e8 * m5
|
|
|
|
-
|
|
|
|
- c0 := d0 + d1
|
|
|
|
- c1 := d0 - d1
|
|
|
|
- c2 := d2 - d3
|
|
|
|
- c3 := d3
|
|
|
|
- c4 := d4 + d8
|
|
|
|
- c5 := d5 + d7
|
|
|
|
- c6 := d6 - d8
|
|
|
|
- c7 := d7
|
|
|
|
- c8 := c5 - c6
|
|
|
|
-
|
|
|
|
- b0 := c0 + c3
|
|
|
|
- b1 := c1 + c2
|
|
|
|
- b2 := c1 - c2
|
|
|
|
- b3 := c0 - c3
|
|
|
|
- b4 := c4 - c8
|
|
|
|
- b5 := c8
|
|
|
|
- b6 := c6 - c7
|
|
|
|
- b7 := c7
|
|
|
|
-
|
|
|
|
- mcu[i * BLOCK_SIZE + 0] = cast(i16)(b0 + b7)
|
|
|
|
- mcu[i * BLOCK_SIZE + 1] = cast(i16)(b1 + b6)
|
|
|
|
- mcu[i * BLOCK_SIZE + 2] = cast(i16)(b2 + b5)
|
|
|
|
- mcu[i * BLOCK_SIZE + 3] = cast(i16)(b3 + b4)
|
|
|
|
- mcu[i * BLOCK_SIZE + 4] = cast(i16)(b3 - b4)
|
|
|
|
- mcu[i * BLOCK_SIZE + 5] = cast(i16)(b2 - b5)
|
|
|
|
- mcu[i * BLOCK_SIZE + 6] = cast(i16)(b1 - b6)
|
|
|
|
- mcu[i * BLOCK_SIZE + 7] = cast(i16)(b0 - b7)
|
|
|
|
- }
|
|
|
|
|
|
+ for i in 0..<BLOCK_SIZE {
|
|
|
|
+ g0 := cast(f32)mcu[i * BLOCK_SIZE + 0] * s0
|
|
|
|
+ g1 := cast(f32)mcu[i * BLOCK_SIZE + 4] * s4
|
|
|
|
+ g2 := cast(f32)mcu[i * BLOCK_SIZE + 2] * s2
|
|
|
|
+ g3 := cast(f32)mcu[i * BLOCK_SIZE + 6] * s6
|
|
|
|
+ g4 := cast(f32)mcu[i * BLOCK_SIZE + 5] * s5
|
|
|
|
+ g5 := cast(f32)mcu[i * BLOCK_SIZE + 1] * s1
|
|
|
|
+ g6 := cast(f32)mcu[i * BLOCK_SIZE + 7] * s7
|
|
|
|
+ g7 := cast(f32)mcu[i * BLOCK_SIZE + 3] * s3
|
|
|
|
+
|
|
|
|
+ f4 := g4 - g7
|
|
|
|
+ f5 := g5 + g6
|
|
|
|
+ f6 := g5 - g6
|
|
|
|
+ f7 := g4 + g7
|
|
|
|
+
|
|
|
|
+ e0 := g0
|
|
|
|
+ e1 := g1
|
|
|
|
+ e2 := g2 - g3
|
|
|
|
+ e3 := g2 + g3
|
|
|
|
+ e4 := f4
|
|
|
|
+ e5 := f5 - f7
|
|
|
|
+ e6 := f6
|
|
|
|
+ e7 := f5 + f7
|
|
|
|
+ e8 := f4 + f6
|
|
|
|
+
|
|
|
|
+ d0 := e0
|
|
|
|
+ d1 := e1
|
|
|
|
+ d2 := e2 * m1
|
|
|
|
+ d3 := e3
|
|
|
|
+ d4 := e4 * m2
|
|
|
|
+ d5 := e5 * m3
|
|
|
|
+ d6 := e6 * m4
|
|
|
|
+ d7 := e7
|
|
|
|
+ d8 := e8 * m5
|
|
|
|
+
|
|
|
|
+ c0 := d0 + d1
|
|
|
|
+ c1 := d0 - d1
|
|
|
|
+ c2 := d2 - d3
|
|
|
|
+ c3 := d3
|
|
|
|
+ c4 := d4 + d8
|
|
|
|
+ c5 := d5 + d7
|
|
|
|
+ c6 := d6 - d8
|
|
|
|
+ c7 := d7
|
|
|
|
+ c8 := c5 - c6
|
|
|
|
+
|
|
|
|
+ b0 := c0 + c3
|
|
|
|
+ b1 := c1 + c2
|
|
|
|
+ b2 := c1 - c2
|
|
|
|
+ b3 := c0 - c3
|
|
|
|
+ b4 := c4 - c8
|
|
|
|
+ b5 := c8
|
|
|
|
+ b6 := c6 - c7
|
|
|
|
+ b7 := c7
|
|
|
|
+
|
|
|
|
+ mcu[i * BLOCK_SIZE + 0] = cast(i16)(b0 + b7)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 1] = cast(i16)(b1 + b6)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 2] = cast(i16)(b2 + b5)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 3] = cast(i16)(b3 + b4)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 4] = cast(i16)(b3 - b4)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 5] = cast(i16)(b2 - b5)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 6] = cast(i16)(b1 - b6)
|
|
|
|
+ mcu[i * BLOCK_SIZE + 7] = cast(i16)(b0 - b7)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- // Convert the YCbCr pixel data to RGB
|
|
|
|
- cbcr_blk := &blocks[y * block_width + x]
|
|
|
|
- for v := luma_v_sampling_factor - 1; v >= 0; v -= 1 {
|
|
|
|
- for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 {
|
|
|
|
- y_blk := &blocks[(y + v) * block_width + (x + h)]
|
|
|
|
-
|
|
|
|
- for j := BLOCK_SIZE - 1; j >= 0; j -= 1 {
|
|
|
|
- for k := BLOCK_SIZE - 1; k >= 0; k -= 1 {
|
|
|
|
- i := j * BLOCK_SIZE + k
|
|
|
|
- cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v
|
|
|
|
- cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h
|
|
|
|
- cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column
|
|
|
|
-
|
|
|
|
- r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255)
|
|
|
|
- g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255)
|
|
|
|
- b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255)
|
|
|
|
-
|
|
|
|
- y_blk[.Y][i] = r
|
|
|
|
- y_blk[.Cb][i] = g
|
|
|
|
- y_blk[.Cr][i] = b
|
|
|
|
- }
|
|
|
|
|
|
+ // Convert the YCbCr pixel data to RGB
|
|
|
|
+ cbcr_blk := &blocks[y * block_width + x]
|
|
|
|
+ for v := luma_v_sampling_factor - 1; v >= 0; v -= 1 {
|
|
|
|
+ for h := luma_h_sampling_factor - 1; h >= 0; h -= 1 {
|
|
|
|
+ y_blk := &blocks[(y + v) * block_width + (x + h)]
|
|
|
|
+
|
|
|
|
+ for j := BLOCK_SIZE - 1; j >= 0; j -= 1 {
|
|
|
|
+ for k := BLOCK_SIZE - 1; k >= 0; k -= 1 {
|
|
|
|
+ i := j * BLOCK_SIZE + k
|
|
|
|
+ cbcr_pixel_row := j / luma_v_sampling_factor + 4 * v
|
|
|
|
+ cbcr_pixel_column := k / luma_h_sampling_factor + 4 * h
|
|
|
|
+ cbcr_pixel := cbcr_pixel_row * BLOCK_SIZE + cbcr_pixel_column
|
|
|
|
+
|
|
|
|
+ r := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255)
|
|
|
|
+ g := cast(i16)clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcr_pixel] + 128, 0, 255)
|
|
|
|
+ b := cast(i16)clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcr_pixel] + 128, 0, 255)
|
|
|
|
+
|
|
|
|
+ y_blk[.Y][i] = r
|
|
|
|
+ y_blk[.Cb][i] = g
|
|
|
|
+ y_blk[.Cr][i] = b
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- orig_channels := img.channels
|
|
|
|
|
|
+ orig_channels := img.channels
|
|
|
|
|
|
- // We automatically expand grayscale images to RGB
|
|
|
|
- if img.channels == 1 {
|
|
|
|
- img.channels += 2
|
|
|
|
- }
|
|
|
|
|
|
+ // We automatically expand grayscale images to RGB
|
|
|
|
+ if img.channels == 1 {
|
|
|
|
+ img.channels += 2
|
|
|
|
+ }
|
|
|
|
|
|
- if .alpha_add_if_missing in options {
|
|
|
|
- img.channels += 1
|
|
|
|
- orig_channels += 1
|
|
|
|
- }
|
|
|
|
|
|
+ if .alpha_add_if_missing in options {
|
|
|
|
+ img.channels += 1
|
|
|
|
+ orig_channels += 1
|
|
|
|
+ }
|
|
|
|
|
|
- if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil {
|
|
|
|
- return img, .Unable_To_Allocate_Or_Resize
|
|
|
|
- }
|
|
|
|
|
|
+ if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil {
|
|
|
|
+ return img, .Unable_To_Allocate_Or_Resize
|
|
|
|
+ }
|
|
|
|
|
|
- switch orig_channels {
|
|
|
|
- case 1: // Grayscale JPEG expanded to RGB
|
|
|
|
- out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:])
|
|
|
|
- out_idx := 0
|
|
|
|
- for y in 0..<img.height {
|
|
|
|
- mcu_row := y / BLOCK_SIZE
|
|
|
|
- pixel_row := y % BLOCK_SIZE
|
|
|
|
- for x in 0..<img.width {
|
|
|
|
- mcu_col := x / BLOCK_SIZE
|
|
|
|
- pixel_col := x % BLOCK_SIZE
|
|
|
|
- mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
- pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
-
|
|
|
|
- luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx]
|
|
|
|
- out[out_idx] = {luma, luma, luma}
|
|
|
|
-
|
|
|
|
- out_idx += 1
|
|
|
|
- }
|
|
|
|
|
|
+ switch orig_channels {
|
|
|
|
+ case 1: // Grayscale JPEG expanded to RGB
|
|
|
|
+ out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:])
|
|
|
|
+ out_idx := 0
|
|
|
|
+ for y in 0..<img.height {
|
|
|
|
+ mcu_row := y / BLOCK_SIZE
|
|
|
|
+ pixel_row := y % BLOCK_SIZE
|
|
|
|
+ for x in 0..<img.width {
|
|
|
|
+ mcu_col := x / BLOCK_SIZE
|
|
|
|
+ pixel_col := x % BLOCK_SIZE
|
|
|
|
+ mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
+ pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
+
|
|
|
|
+ luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx]
|
|
|
|
+ out[out_idx] = {luma, luma, luma}
|
|
|
|
+
|
|
|
|
+ out_idx += 1
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- case 2: // Grayscale JPEG expanded to RGBA
|
|
|
|
- out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:])
|
|
|
|
- out_idx := 0
|
|
|
|
- for y in 0..<img.height {
|
|
|
|
- mcu_row := y / BLOCK_SIZE
|
|
|
|
- pixel_row := y % BLOCK_SIZE
|
|
|
|
-
|
|
|
|
- for x in 0..<img.width {
|
|
|
|
- mcu_col := x / BLOCK_SIZE
|
|
|
|
- pixel_col := x % BLOCK_SIZE
|
|
|
|
- mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
- pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
-
|
|
|
|
- luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx]
|
|
|
|
- out[out_idx] = {luma, luma, luma, 255}
|
|
|
|
- out_idx += 1
|
|
|
|
- }
|
|
|
|
|
|
+ case 2: // Grayscale JPEG expanded to RGBA
|
|
|
|
+ out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:])
|
|
|
|
+ out_idx := 0
|
|
|
|
+ for y in 0..<img.height {
|
|
|
|
+ mcu_row := y / BLOCK_SIZE
|
|
|
|
+ pixel_row := y % BLOCK_SIZE
|
|
|
|
+
|
|
|
|
+ for x in 0..<img.width {
|
|
|
|
+ mcu_col := x / BLOCK_SIZE
|
|
|
|
+ pixel_col := x % BLOCK_SIZE
|
|
|
|
+ mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
+ pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
+
|
|
|
|
+ luma := cast(byte)blocks[mcu_idx][.Y][pixel_idx]
|
|
|
|
+ out[out_idx] = {luma, luma, luma, 255}
|
|
|
|
+ out_idx += 1
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- case 3:
|
|
|
|
- out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:])
|
|
|
|
- out_idx := 0
|
|
|
|
- for y in 0..<img.height {
|
|
|
|
- mcu_row := y / BLOCK_SIZE
|
|
|
|
- pixel_row := y % BLOCK_SIZE
|
|
|
|
-
|
|
|
|
- for x in 0..<img.width {
|
|
|
|
- mcu_col := x / BLOCK_SIZE
|
|
|
|
- pixel_col := x % BLOCK_SIZE
|
|
|
|
- mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
- pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
-
|
|
|
|
- out[out_idx] = {
|
|
|
|
- cast(byte)blocks[mcu_idx][.Y][pixel_idx],
|
|
|
|
- cast(byte)blocks[mcu_idx][.Cb][pixel_idx],
|
|
|
|
- cast(byte)blocks[mcu_idx][.Cr][pixel_idx],
|
|
|
|
- }
|
|
|
|
- out_idx += 1
|
|
|
|
|
|
+ case 3:
|
|
|
|
+ out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:])
|
|
|
|
+ out_idx := 0
|
|
|
|
+ for y in 0..<img.height {
|
|
|
|
+ mcu_row := y / BLOCK_SIZE
|
|
|
|
+ pixel_row := y % BLOCK_SIZE
|
|
|
|
+
|
|
|
|
+ for x in 0..<img.width {
|
|
|
|
+ mcu_col := x / BLOCK_SIZE
|
|
|
|
+ pixel_col := x % BLOCK_SIZE
|
|
|
|
+ mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
+ pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
+
|
|
|
|
+ out[out_idx] = {
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Y][pixel_idx],
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Cb][pixel_idx],
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Cr][pixel_idx],
|
|
}
|
|
}
|
|
|
|
+ out_idx += 1
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- case 4:
|
|
|
|
- out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:])
|
|
|
|
- out_idx := 0
|
|
|
|
- for y in 0..<img.height {
|
|
|
|
- mcu_row := y / BLOCK_SIZE
|
|
|
|
- pixel_row := y % BLOCK_SIZE
|
|
|
|
-
|
|
|
|
- for x in 0..<img.width {
|
|
|
|
- mcu_col := x / BLOCK_SIZE
|
|
|
|
- pixel_col := x % BLOCK_SIZE
|
|
|
|
- mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
- pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
-
|
|
|
|
- out[out_idx] = {
|
|
|
|
- cast(byte)blocks[mcu_idx][.Y][pixel_idx],
|
|
|
|
- cast(byte)blocks[mcu_idx][.Cb][pixel_idx],
|
|
|
|
- cast(byte)blocks[mcu_idx][.Cr][pixel_idx],
|
|
|
|
- 255, // Alpha
|
|
|
|
- }
|
|
|
|
- out_idx += 1
|
|
|
|
|
|
+ case 4:
|
|
|
|
+ out := mem.slice_data_cast([]image.RGBA_Pixel, img.pixels.buf[:])
|
|
|
|
+ out_idx := 0
|
|
|
|
+ for y in 0..<img.height {
|
|
|
|
+ mcu_row := y / BLOCK_SIZE
|
|
|
|
+ pixel_row := y % BLOCK_SIZE
|
|
|
|
+
|
|
|
|
+ for x in 0..<img.width {
|
|
|
|
+ mcu_col := x / BLOCK_SIZE
|
|
|
|
+ pixel_col := x % BLOCK_SIZE
|
|
|
|
+ mcu_idx := mcu_row * block_width + mcu_col
|
|
|
|
+ pixel_idx := pixel_row * BLOCK_SIZE + pixel_col
|
|
|
|
+
|
|
|
|
+ out[out_idx] = {
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Y][pixel_idx],
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Cb][pixel_idx],
|
|
|
|
+ cast(byte)blocks[mcu_idx][.Cr][pixel_idx],
|
|
|
|
+ 255, // Alpha
|
|
}
|
|
}
|
|
|
|
+ out_idx += 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
+ }
|
|
|
|
|
|
- expect_EOI = true
|
|
|
|
|
|
+ expect_EOI = true
|
|
|
|
|
|
- case .TEM:
|
|
|
|
- // TEM doesn't have a length, continue to next marker
|
|
|
|
- case:
|
|
|
|
- length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
- compress.read_slice_from_memory(ctx, cast(int)length) or_return
|
|
|
|
- }
|
|
|
|
|
|
+ case .TEM:
|
|
|
|
+ // TEM doesn't have a length, continue to next marker
|
|
|
|
+ case:
|
|
|
|
+ length := (compress.read_data(ctx, u16be) or_return) - 2
|
|
|
|
+ compress.read_slice_from_memory(ctx, cast(int)length) or_return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|