diff options
| -rw-r--r-- | core/image/common.odin | 28 | ||||
| -rw-r--r-- | core/image/jpeg/jpeg.odin | 1456 |
2 files changed, 748 insertions, 736 deletions
diff --git a/core/image/common.odin b/core/image/common.odin index 0e5668e50..4014e2ae6 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -642,22 +642,23 @@ JFXX_Extension_Code :: enum u8 { } JPEG_Marker :: enum u8 { - SOF0 = 0xC0, - SOF1 = 0xC1, - SOF2 = 0xC2, - SOF3 = 0xC3, + SOF0 = 0xC0, // Baseline sequential DCT + SOF1 = 0xC1, // Extended sequential DCT + SOF2 = 0xC2, // Progressive DCT + SOF3 = 0xC3, // Lossless (sequential) + SOF5 = 0xC5, // Differential sequential DCT + SOF6 = 0xC6, // Differential progressive DCT + SOF7 = 0xC7, // Differential lossless (sequential) + SOF9 = 0xC9, // Extended sequential DCT, Arithmetic coding + SOF10 = 0xCA, // Progressive DCT, Arithmetic coding + SOF11 = 0xCB, // Lossless (sequential), Arithmetic coding + SOF13 = 0xCD, // Differential sequential DCT, Arithmetic coding + SOF14 = 0xCE, // Differential progressive DCT, Arithmetic coding + SOF15 = 0xCF, // Differential lossless (sequential), Arithmetic coding + DHT = 0xC4, - SOF5 = 0xC5, - SOF6 = 0xC6, - SOF7 = 0xC7, JPG = 0xC8, - SOF9 = 0xC9, - SOF10 = 0xCA, - SOF11 = 0xCB, DAC = 0xCC, - SOF13 = 0xCD, - SOF14 = 0xCE, - SOF15 = 0xCF, RST0 = 0xD0, RST1 = 0xD1, RST2 = 0xD2, @@ -713,6 +714,7 @@ JPEG_Info :: struct { jfxx_app0: Maybe(JFXX_APP0), comments: [dynamic]string, exif: [dynamic]Exif, + frame_type: JPEG_Marker, } // Function to help with image buffer calculations diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin index a2ad7e61e..818bd13d5 100644 --- a/core/image/jpeg/jpeg.odin +++ b/core/image/jpeg/jpeg.odin @@ -219,841 +219,851 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a defer delete(blocks) loop: for { + // Loop until we find 0xFF. 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 { - 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 { - 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 { - 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 } } |