mirror of
https://github.com/Ed94/Odin.git
synced 2026-08-06 15:48:51 +00:00
image/jpeg: implement jpeg decoding for baseline and extended sequential jpegs
This commit is contained in:
committed by
Jeroen van Rijn
parent
d704c45c24
commit
a6f18c3367
+133
-2
@@ -64,6 +64,7 @@ Image_Metadata :: union #shared_nil {
|
|||||||
^QOI_Info,
|
^QOI_Info,
|
||||||
^TGA_Info,
|
^TGA_Info,
|
||||||
^BMP_Info,
|
^BMP_Info,
|
||||||
|
^JPEG_Info,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -112,8 +113,7 @@ Image_Option:
|
|||||||
|
|
||||||
`.alpha_drop_if_present`
|
`.alpha_drop_if_present`
|
||||||
If the image has an alpha channel, drop it.
|
If the image has an alpha channel, drop it.
|
||||||
You may want to use `.alpha_
|
You may want to use `.alpha_premultiply` in this case.
|
||||||
tiply` in this case.
|
|
||||||
|
|
||||||
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
|
||||||
unless you select `alpha_premultiply`.
|
unless you select `alpha_premultiply`.
|
||||||
@@ -163,6 +163,7 @@ Error :: union #shared_nil {
|
|||||||
PNG_Error,
|
PNG_Error,
|
||||||
QOI_Error,
|
QOI_Error,
|
||||||
BMP_Error,
|
BMP_Error,
|
||||||
|
JPEG_Error,
|
||||||
|
|
||||||
compress.Error,
|
compress.Error,
|
||||||
compress.General_Error,
|
compress.General_Error,
|
||||||
@@ -575,6 +576,136 @@ TGA_Info :: struct {
|
|||||||
extension: Maybe(TGA_Extension),
|
extension: Maybe(TGA_Extension),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
JPEG-specific
|
||||||
|
*/
|
||||||
|
JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF"
|
||||||
|
JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX"
|
||||||
|
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
JFIF_Unit :: enum byte {
|
||||||
|
None = 0,
|
||||||
|
Dots_Per_Inch = 1,
|
||||||
|
Dots_Per_Centimeter = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
JFIF_APP0 :: struct {
|
||||||
|
version: u16be,
|
||||||
|
units: JFIF_Unit,
|
||||||
|
x_density: u16be,
|
||||||
|
y_density: u16be,
|
||||||
|
x_thumbnail: byte,
|
||||||
|
y_thumbnail: byte,
|
||||||
|
thumbnail: []RGB_Pixel `fmt:"-"`,
|
||||||
|
greyscale_thumbnail: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
JFXX_APP0 :: struct {
|
||||||
|
extension_code: JFXX_Extension_Code,
|
||||||
|
x_thumbnail: int,
|
||||||
|
y_thumbnail: int,
|
||||||
|
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: Maybe(Exif),
|
||||||
|
}
|
||||||
|
|
||||||
// Function to help with image buffer calculations
|
// Function to help with image buffer calculations
|
||||||
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
|
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
|
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height
|
||||||
|
|||||||
@@ -0,0 +1,928 @@
|
|||||||
|
package jpeg
|
||||||
|
|
||||||
|
import "core:bytes"
|
||||||
|
import "core:compress"
|
||||||
|
import "core:fmt"
|
||||||
|
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 :: 162
|
||||||
|
HUFFMAN_MAX_BITS :: 16
|
||||||
|
// 768 bytes of 24-bit RGB values.
|
||||||
|
THUMBNAIL_PALETTE_SIZE :: 768
|
||||||
|
BLOCK_SIZE :: 8
|
||||||
|
COEFFICIENT_COUNT :: BLOCK_SIZE * BLOCK_SIZE
|
||||||
|
|
||||||
|
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 {
|
||||||
|
next := u64(z.input_data[1])
|
||||||
|
|
||||||
|
if b == 0xFF {
|
||||||
|
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
|
||||||
|
b = u64(z.input_data[2])
|
||||||
|
z.input_data = z.input_data[3:]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
z.input_data = z.input_data[1:]
|
||||||
|
}
|
||||||
|
} 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..<HUFFMAN_MAX_BITS {
|
||||||
|
bit := read_bits_msb(ctx, 1)
|
||||||
|
possible_code = (possible_code << 1) | bit
|
||||||
|
|
||||||
|
for j := huffman_table.offsets[i]; j < huffman_table.offsets[i + 1]; j += 1 {
|
||||||
|
if possible_code == huffman_table.codes[j] {
|
||||||
|
return huffman_table.symbols[j]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.allocator) -> (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
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if slice.equal(ident[:], image.JFIF_Magic[:]) {
|
||||||
|
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,
|
||||||
|
units,
|
||||||
|
x_density,
|
||||||
|
y_density,
|
||||||
|
cast(u8)x_thumbnail,
|
||||||
|
cast(u8)y_thumbnail,
|
||||||
|
thumbnail,
|
||||||
|
greyscale_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,
|
||||||
|
x_thumbnail,
|
||||||
|
y_thumbnail,
|
||||||
|
thumbnail,
|
||||||
|
}
|
||||||
|
img.metadata = info
|
||||||
|
}
|
||||||
|
case .Thumbnail_1_Byte_Palette: // NOTE: 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,
|
||||||
|
x_thumbnail,
|
||||||
|
y_thumbnail,
|
||||||
|
pixels,
|
||||||
|
}
|
||||||
|
img.metadata = info
|
||||||
|
}
|
||||||
|
case:
|
||||||
|
return img, .Invalid_JFXX_Extension_Code
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// - 1 for the NUL byte
|
||||||
|
compress.read_slice(ctx, length - len(ident) - 1) or_return
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// case .APP1: // Exif metadata
|
||||||
|
// unimplemented("APP1")
|
||||||
|
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 {
|
||||||
|
if info.comments == nil {
|
||||||
|
info.comments = make([dynamic]string, 0, 8, allocator) or_return
|
||||||
|
}
|
||||||
|
append(&info.comments, strings.clone(comment))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 := 0
|
||||||
|
for length, i in lengths {
|
||||||
|
num_symbols += cast(int)length
|
||||||
|
huffman[type][index].offsets[i + 1] = cast(u8)num_symbols
|
||||||
|
}
|
||||||
|
|
||||||
|
symbols := compress.read_slice(ctx, 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..<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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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: 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 width == 0 || height == 0 {
|
||||||
|
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..<components {
|
||||||
|
id := cast(Component)compress.read_u8(ctx) or_return
|
||||||
|
|
||||||
|
// TODO: some images write zero-based IDs for the components, which violates the spec, but most (if not all)
|
||||||
|
// decoders handle them just fine. Should we support that too?
|
||||||
|
// 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 {
|
||||||
|
fmt.println("Found unknown component ID:", id)
|
||||||
|
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
|
||||||
|
fmt.println(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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 component_id < .Y || component_id > .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..<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)
|
||||||
|
|
||||||
|
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..<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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
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.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
|
||||||
|
|
||||||
|
if img.channels == 3 {
|
||||||
|
out[y * img.width + x] = {
|
||||||
|
cast(byte)blocks[mcu_idx][.Y][pixel_idx],
|
||||||
|
cast(byte)blocks[mcu_idx][.Cb][pixel_idx],
|
||||||
|
cast(byte)blocks[mcu_idx][.Cr][pixel_idx],
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
img.pixels.buf[y * img.width + x] = cast(byte)blocks[mcu_idx][.Y][pixel_idx]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy :: proc(img: ^Image) {
|
||||||
|
if img == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
bytes.buffer_destroy(&img.pixels)
|
||||||
|
|
||||||
|
if v, ok := img.metadata.(^image.JPEG_Info); ok {
|
||||||
|
if jfxx, jfxx_ok := v.jfxx_app0.?; jfxx_ok {
|
||||||
|
delete(jfxx.thumbnail)
|
||||||
|
}
|
||||||
|
if jfif, jfif_ok := v.jfif_app0.?; jfif_ok {
|
||||||
|
delete(jfif.thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
for comment in v.comments {
|
||||||
|
delete(comment)
|
||||||
|
}
|
||||||
|
delete(v.comments)
|
||||||
|
|
||||||
|
free(v)
|
||||||
|
}
|
||||||
|
free(img)
|
||||||
|
}
|
||||||
|
|
||||||
|
@(init, private)
|
||||||
|
_register :: proc() {
|
||||||
|
image.register(.JPEG, load_from_bytes, destroy)
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
package jpeg
|
||||||
|
|
||||||
|
load :: proc{load_from_bytes, load_from_context}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package jpeg
|
||||||
|
|
||||||
|
import "core:os"
|
||||||
|
|
||||||
|
load :: proc{load_from_file, load_from_bytes, load_from_context}
|
||||||
|
|
||||||
|
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -76,12 +76,13 @@ package all
|
|||||||
@(require) import "core:hash"
|
@(require) import "core:hash"
|
||||||
@(require) import "core:hash/xxhash"
|
@(require) import "core:hash/xxhash"
|
||||||
|
|
||||||
@(require) import "core:image"
|
import image "core:image"
|
||||||
@(require) import "core:image/bmp"
|
import bmp "core:image/bmp"
|
||||||
@(require) import "core:image/netpbm"
|
import netpbm "core:image/netpbm"
|
||||||
@(require) import "core:image/png"
|
import png "core:image/png"
|
||||||
@(require) import "core:image/qoi"
|
import qoi "core:image/qoi"
|
||||||
@(require) import "core:image/tga"
|
import tga "core:image/tga"
|
||||||
|
import jpeg "core:image/jpeg"
|
||||||
|
|
||||||
@(require) import "core:io"
|
@(require) import "core:io"
|
||||||
@(require) import "core:log"
|
@(require) import "core:log"
|
||||||
|
|||||||
Reference in New Issue
Block a user