diff --git a/core/image/common.odin b/core/image/common.odin index 690ebc045be..0e5668e501a 100644 --- a/core/image/common.odin +++ b/core/image/common.odin @@ -64,8 +64,16 @@ Image_Metadata :: union #shared_nil { ^QOI_Info, ^TGA_Info, ^BMP_Info, + ^JPEG_Info, } +Exif :: struct { + byte_order: enum { + little_endian, + big_endian, + }, + data: []u8 `fmt:"-"`, +} /* @@ -112,8 +120,7 @@ Image_Option: `.alpha_drop_if_present` If the image has an alpha channel, drop it. - You may want to use `.alpha_ - tiply` in this case. + You may want to use `.alpha_premultiply` in this case. NOTE: For PNG, this also skips handling of the tRNS chunk, if present, unless you select `alpha_premultiply`. @@ -163,6 +170,7 @@ Error :: union #shared_nil { PNG_Error, QOI_Error, BMP_Error, + JPEG_Error, compress.Error, compress.General_Error, @@ -575,6 +583,138 @@ TGA_Info :: struct { extension: Maybe(TGA_Extension), } + +/* + JPEG-specific +*/ +JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF" +JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX" +Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif" + +JPEG_Error :: enum { + None = 0, + Duplicate_SOI_Marker, + Invalid_JFXX_Extension_Code, + Encountered_SOS_Before_SOF, + Invalid_Quantization_Table_Precision, + Invalid_Quantization_Table_Index, + Invalid_Huffman_Coefficient_Type, + Invalid_Huffman_Table_Index, + Unsupported_Frame_Type, + Invalid_Frame_Bit_Depth_Combo, + Invalid_Sampling_Factor, + Unsupported_12_Bit_Depth, + Multiple_SOS_Markers, + Encountered_RST_Marker_Outside_ECS, + Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS + Invalid_Thumbnail_Size, + Huffman_Symbols_Exceeds_Max, +} + +JFIF_Unit :: enum byte { + None = 0, + Dots_Per_Inch = 1, + Dots_Per_Centimeter = 2, +} + +JFIF_APP0 :: struct { + version: u16be, + x_density: u16be, + y_density: u16be, + units: JFIF_Unit, + x_thumbnail: u8, + y_thumbnail: u8, + greyscale_thumbnail: bool, + thumbnail: []RGB_Pixel `fmt:"-"`, +} + +JFXX_APP0 :: struct { + extension_code: JFXX_Extension_Code, + x_thumbnail: u8, + y_thumbnail: u8, + thumbnail: []byte `fmt:"-"`, +} + +JFXX_Extension_Code :: enum u8 { + Thumbnail_JPEG = 0x10, + Thumbnail_1_Byte_Palette = 0x11, + Thumbnail_3_Byte_RGB = 0x13, +} + +JPEG_Marker :: enum u8 { + SOF0 = 0xC0, + SOF1 = 0xC1, + SOF2 = 0xC2, + SOF3 = 0xC3, + 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, + RST3 = 0xD3, + RST4 = 0xD4, + RST5 = 0xD5, + RST6 = 0xD6, + RST7 = 0xD7, + SOI = 0xD8, + EOI = 0xD9, + SOS = 0xDA, + DQT = 0xDB, + DNL = 0xDC, + DRI = 0xDD, + DHP = 0xDE, + EXP = 0xDF, + APP0 = 0xE0, + APP1 = 0xE1, + APP2 = 0xE2, + APP3 = 0xE3, + APP4 = 0xE4, + APP5 = 0xE5, + APP6 = 0xE6, + APP7 = 0xE7, + APP8 = 0xE8, + APP9 = 0xE9, + APP10 = 0xEA, + APP11 = 0xEB, + APP12 = 0xEC, + APP13 = 0xED, + APP14 = 0xEE, + APP15 = 0xEF, + JPG0 = 0xF0, + JPG1 = 0xF1, + JPG2 = 0xF2, + JPG3 = 0xF3, + JPG4 = 0xF4, + JPG5 = 0xF5, + JPG6 = 0xF6, + JPG7 = 0xF7, + JPG8 = 0xF8, + JPG9 = 0xF9, + JPG10 = 0xFA, + JPG11 = 0xFB, + JPG12 = 0xFC, + JPG13 = 0xFD, + COM = 0xFE, + TEM = 0x01, +} + +JPEG_Info :: struct { + jfif_app0: Maybe(JFIF_APP0), + jfxx_app0: Maybe(JFXX_APP0), + comments: [dynamic]string, + exif: [dynamic]Exif, +} + // Function to help with image buffer calculations compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) { size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height diff --git a/core/image/general.odin b/core/image/general.odin index e92b54f185a..644015d5489 100644 --- a/core/image/general.odin +++ b/core/image/general.odin @@ -147,7 +147,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type { return .JPEG case s[:3] == "\xff\xd8\xff": switch s[3] { - case 0xdb, 0xee, 0xe1, 0xe0: + case 0xdb, 0xee, 0xe1, 0xe0, 0xfe, 0xed: return .JPEG } switch { diff --git a/core/image/jpeg/jpeg.odin b/core/image/jpeg/jpeg.odin new file mode 100644 index 00000000000..31bb11a13d6 --- /dev/null +++ b/core/image/jpeg/jpeg.odin @@ -0,0 +1,1012 @@ +package jpeg + +import "core:bytes" +import "core:compress" +import "core:math" +import "core:mem" +import "core:image" +import "core:slice" +import "core:strings" + +Image :: image.Image +Error :: image.Error +Options :: image.Options + +HUFFMAN_MAX_SYMBOLS :: 176 +HUFFMAN_MAX_BITS :: 16 +// 768 bytes of 24-bit RGB values. +THUMBNAIL_PALETTE_SIZE :: 768 +BLOCK_SIZE :: 8 +COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE +SEGMENT_MAX_SIZE :: 65533 + +Coefficient :: enum u8 { + DC, + AC, +} + +Component :: enum u8 { + Y = 1, + Cb = 2, + Cr = 3, +} + +HuffmanTable :: struct { + symbols: [HUFFMAN_MAX_SYMBOLS]byte, + codes: [HUFFMAN_MAX_SYMBOLS]u32, + offsets: [HUFFMAN_MAX_BITS + 1]byte, +} + +QuantizationTable :: [COEFFICIENT_COUNT]u16be + +ColorComponent :: struct { + dc_table_idx: u8, + ac_table_idx: u8, + quantization_table_idx: u8, + v_sampling_factor: int, + h_sampling_factor: int, +} + +// 8x8 block of pixels +Block :: [Component][COEFFICIENT_COUNT]i16 + +@(private="file") +zigzag := [?]byte{ + 0, 1, 8, 16, 9, 2, 3, 10, + 17, 24, 32, 25, 18, 11, 4, 5, + 12, 19, 26, 33, 40, 48, 41, 34, + 27, 20, 13, 6, 7, 14, 21, 28, + 35, 42, 49, 56, 57, 50, 43, 36, + 29, 22, 15, 23, 30, 37, 44, 51, + 58, 59, 52, 45, 38, 31, 39, 46, + 53, 60, 61, 54, 47, 55, 62, 63, +} + +@(optimization_mode="favor_size", private="file") +refill_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width := i8(48)) { + refill := u64(width) + b := u64(0) + + if z.num_bits > refill { + return + } + + for { + if len(z.input_data) != 0 { + b = u64(z.input_data[0]) + + if len(z.input_data) > 1 && b == 0xFF { + next := u64(z.input_data[1]) + + if next == 0x00 { + // 0x00 is used as a stuffing to indicate that the 0xFF is part of the data and not + // the beginning of a marker + z.input_data = z.input_data[2:] + } else if next >= cast(u64)image.JPEG_Marker.RST0 && next <= cast(u64)image.JPEG_Marker.RST7 { + // Skip any RSTn markers if we encounter them + if len(z.input_data) > 2 { + b = u64(z.input_data[2]) + z.input_data = z.input_data[3:] + } else { + b = 0 + } + } + } else { + z.input_data = z.input_data[1:] + } + } else { + b = 0 + } + + z.code_buffer |= ((b << 56) >> u8(z.num_bits)) + z.num_bits += 8 + if z.num_bits > refill { + break + } + } +} + +@(optimization_mode="favor_size", private="file") +consume_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) { + z.code_buffer <<= width + z.num_bits -= u64(width) +} + +@(private="file") +byte_align :: #force_inline proc(z: ^compress.Context_Memory_Input) { + skip := z.num_bits % 8 + consume_bits_msb(z, cast(u8)skip) +} + +@(optimization_mode="favor_size", private="file") +peek_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + if z.num_bits < u64(width) { + refill_msb(z) + } + return u32((z.code_buffer &~ (max(u64) >> width)) >> (64 - width)) +} + +@(optimization_mode="favor_size", private="file") +read_bits_msb :: #force_inline proc(z: ^compress.Context_Memory_Input, width: u8) -> u32 { + k := #force_inline peek_bits_msb(z, width) + #force_inline consume_bits_msb(z, width) + return k +} + +load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { + ctx := &compress.Context_Memory_Input{ + input_data = data, + } + + img, err = load_from_context(ctx, options, allocator) + return img, err +} + +@(private="file") +get_symbol :: proc(ctx: ^$C, huffman_table: HuffmanTable) -> byte { + possible_code: u32 = 0 + + for i in 0.. (img: ^Image, err: Error) { + context.allocator = allocator + options := options + + // Precalculate IDCT scaling factors + m0 := 2.0 * math.cos_f32(1.0 / 16.0 * 2.0 * math.PI) + m1 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m3 := 2.0 * math.cos_f32(2.0 / 16.0 * 2.0 * math.PI) + m5 := 2.0 * math.cos_f32(3.0 / 16.0 * 2.0 * math.PI) + m2 := m0 - m5 + m4 := m0 + m5 + + s0 := math.cos_f32(0.0 / 16.0 * math.PI) / math.sqrt_f32(8.0) + s1 := math.cos_f32(1.0 / 16.0 * math.PI) / 2.0 + s2 := math.cos_f32(2.0 / 16.0 * math.PI) / 2.0 + s3 := math.cos_f32(3.0 / 16.0 * math.PI) / 2.0 + s4 := math.cos_f32(4.0 / 16.0 * math.PI) / 2.0 + s5 := math.cos_f32(5.0 / 16.0 * math.PI) / 2.0 + s6 := math.cos_f32(6.0 / 16.0 * math.PI) / 2.0 + s7 := math.cos_f32(7.0 / 16.0 * math.PI) / 2.0 + + if .info in options { + options += {.return_metadata, .do_not_decompress_image} + options -= {.info} + } + + if .return_header in options && .return_metadata in options { + options -= {.return_header} + } + + first := compress.read_u8(ctx) or_return + soi := cast(image.JPEG_Marker)compress.read_u8(ctx) or_return + if first != 0xFF && soi != .SOI { + return img, .Invalid_Signature + } + + img = new(Image) or_return + img.which = .JPEG + + expect_EOI := false + zero_based_components := false + huffman: [Coefficient][4]HuffmanTable + quantization: [4]QuantizationTable + color_components: [Component]ColorComponent + restart_interval: int + // Image width and height in MCUs + mcu_width: int + mcu_height: int + // Image width and height in blocks + block_width: int + block_height: int + blocks: []Block + defer delete(blocks) + + loop: for { + 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 + } + #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 + } + + 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) + + 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 + } + } + } 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 + } + 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.. 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 { + 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) + + 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 .DQT: + length := cast(int)(compress.read_data(ctx, u16be) or_return) - 2 + + for length > 0 { + precision_and_index := compress.read_u8(ctx) or_return + precision := precision_and_index >> 4 + index := precision_and_index & 0xF + + if precision != 0 && precision != 1 { + return img, .Invalid_Quantization_Table_Precision + } + + if index < 0 || index > 3 { + return img, .Invalid_Quantization_Table_Index + } + + // 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 + } + } else { + table := compress.read_slice(ctx, table_bytes) or_return + for v, i in table { + quantization[index][i] = cast(u16be)v + } + } + + length -= table_bytes + 1 + } + case .DHT: + length := (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 + + if type != .DC && type != .AC { + return img, .Invalid_Huffman_Coefficient_Type + } + + if index < 0 || index > 3 { + return img, .Invalid_Huffman_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 + } + + if num_symbols > HUFFMAN_MAX_SYMBOLS { + return img, .Huffman_Symbols_Exceeds_Max + } + + symbols := compress.read_slice(ctx, cast(int)num_symbols) or_return + copy(huffman[type][index].symbols[:], symbols) + + length -= cast(u16be)(1 + HUFFMAN_MAX_BITS + num_symbols) + + code: u32 = 0 + for i in 0.. image.MAX_DIMENSIONS { + return img, .Invalid_Image_Dimensions + } + + // 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 + + for _ in 0.. .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + 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 + } + + 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 + } + } + + quantization_table_idx := compress.read_u8(ctx) or_return + + 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 + unimplemented("SOF2") + 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 .do_not_decompress_image in options { + return img, nil + } + + // 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.. .Cr { + return img, .Image_Does_Not_Adhere_to_Spec + } + + // 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 (dc_table_idx < 0 || dc_table_idx > 3) || (ac_table_idx < 0 || ac_table_idx > 3) { + return img, .Invalid_Huffman_Table_Index + } + + 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.. 11 { + return img, .Corrupt + } + + dc_coeff := cast(i16)read_bits_msb(ctx, length) + + 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 + } + + amnt_zeros := int(symbol >> 4) + ac_coeff_len := symbol & 0xF + ac_coeff: i16 = 0 + + if i + amnt_zeros >= COEFFICIENT_COUNT || ac_coeff_len > 10 { + return img, .Corrupt + } + + i += amnt_zeros + + 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..= 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 + cbcrPixelRow := j / luma_v_sampling_factor + 4 * v + cbcrPixelColumn := k / luma_h_sampling_factor + 4 * h + cbcrPixel := cbcrPixelRow * BLOCK_SIZE + cbcrPixelColumn + + r := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.402 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) + g := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] - 0.344 * cast(f32)cbcr_blk[.Cb][cbcrPixel] - 0.714 * cast(f32)cbcr_blk[.Cr][cbcrPixel] + 128, 0, 255) + b := cast(i16)math.clamp(cast(f32)y_blk[.Y][i] + 1.772 * cast(f32)cbcr_blk[.Cb][cbcrPixel] + 128, 0, 255) + + y_blk[.Y][i] = r + y_blk[.Cb][i] = g + y_blk[.Cr][i] = b + } + } + } + } + } + } + + if resize(&img.pixels.buf, img.width * img.height * img.channels) != nil { + return img, .Unable_To_Allocate_Or_Resize + } + + out := mem.slice_data_cast([]image.RGB_Pixel, img.pixels.buf[:]) + for y in 0.. (img: ^Image, err: Error) { + context.allocator = allocator + + data, ok := os.read_entire_file(filename) + defer delete(data) + + if ok { + return load_from_bytes(data, options) + } else { + return nil, .Unable_To_Read_File + } +} diff --git a/core/image/png/helpers.odin b/core/image/png/helpers.odin index a9495ed4d4e..97e70226c0c 100644 --- a/core/image/png/helpers.odin +++ b/core/image/png/helpers.odin @@ -366,7 +366,7 @@ chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) { return } -exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { +exif :: proc(c: image.PNG_Chunk) -> (res: image.Exif, ok: bool) { ok = true @@ -396,4 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) { General helper functions */ -compute_buffer_size :: image.compute_buffer_size \ No newline at end of file +compute_buffer_size :: image.compute_buffer_size diff --git a/core/image/png/png.odin b/core/image/png/png.odin index 2d3665e9482..add3d4f9946 100644 --- a/core/image/png/png.odin +++ b/core/image/png/png.odin @@ -138,14 +138,6 @@ Text :: struct { text: string, } -Exif :: struct { - byte_order: enum { - little_endian, - big_endian, - }, - data: []u8, -} - iCCP :: struct { name: string, profile: []u8, diff --git a/examples/all/all_main.odin b/examples/all/all_main.odin index 4a8a198d3bb..d584cfe0a34 100644 --- a/examples/all/all_main.odin +++ b/examples/all/all_main.odin @@ -79,6 +79,7 @@ import netpbm "core:image/netpbm" import png "core:image/png" import qoi "core:image/qoi" import tga "core:image/tga" +import jpeg "core:image/jpeg" import io "core:io" import log "core:log" @@ -214,6 +215,7 @@ _ :: netpbm _ :: png _ :: qoi _ :: tga +_ :: jpeg _ :: io _ :: log _ :: math