Browse Source

Merge pull request #5670 from Kelimion/jpeg-updates

Small updates to JPEG loader
Jeroen van Rijn 2 days ago
parent
commit
edc58c020c
2 changed files with 748 additions and 736 deletions
  1. 15 13
      core/image/common.odin
  2. 733 723
      core/image/jpeg/jpeg.odin

+ 15 - 13
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

+ 733 - 723
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
 		}
 	}