Merge branch 'odin-lang:master' into patch-2

This commit is contained in:
FourteenBrush
2025-09-09 14:41:28 +02:00
committed by GitHub
16 changed files with 1257 additions and 20 deletions

View File

@@ -64,8 +64,16 @@ Image_Metadata :: union #shared_nil {
^QOI_Info,
^TGA_Info,
^BMP_Info,
^JPEG_Info,
}
Exif :: struct {
byte_order: enum {
little_endian,
big_endian,
},
data: []u8 `fmt:"-"`,
}
/*
@@ -112,8 +120,7 @@ Image_Option:
`.alpha_drop_if_present`
If the image has an alpha channel, drop it.
You may want to use `.alpha_
tiply` in this case.
You may want to use `.alpha_premultiply` in this case.
NOTE: For PNG, this also skips handling of the tRNS chunk, if present,
unless you select `alpha_premultiply`.
@@ -163,6 +170,7 @@ Error :: union #shared_nil {
PNG_Error,
QOI_Error,
BMP_Error,
JPEG_Error,
compress.Error,
compress.General_Error,
@@ -575,6 +583,138 @@ TGA_Info :: struct {
extension: Maybe(TGA_Extension),
}
/*
JPEG-specific
*/
JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF"
JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX"
Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif"
JPEG_Error :: enum {
None = 0,
Duplicate_SOI_Marker,
Invalid_JFXX_Extension_Code,
Encountered_SOS_Before_SOF,
Invalid_Quantization_Table_Precision,
Invalid_Quantization_Table_Index,
Invalid_Huffman_Coefficient_Type,
Invalid_Huffman_Table_Index,
Unsupported_Frame_Type,
Invalid_Frame_Bit_Depth_Combo,
Invalid_Sampling_Factor,
Unsupported_12_Bit_Depth,
Multiple_SOS_Markers,
Encountered_RST_Marker_Outside_ECS,
Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS
Invalid_Thumbnail_Size,
Huffman_Symbols_Exceeds_Max,
}
JFIF_Unit :: enum byte {
None = 0,
Dots_Per_Inch = 1,
Dots_Per_Centimeter = 2,
}
JFIF_APP0 :: struct {
version: u16be,
x_density: u16be,
y_density: u16be,
units: JFIF_Unit,
x_thumbnail: u8,
y_thumbnail: u8,
greyscale_thumbnail: bool,
thumbnail: []RGB_Pixel `fmt:"-"`,
}
JFXX_APP0 :: struct {
extension_code: JFXX_Extension_Code,
x_thumbnail: u8,
y_thumbnail: u8,
thumbnail: []byte `fmt:"-"`,
}
JFXX_Extension_Code :: enum u8 {
Thumbnail_JPEG = 0x10,
Thumbnail_1_Byte_Palette = 0x11,
Thumbnail_3_Byte_RGB = 0x13,
}
JPEG_Marker :: enum u8 {
SOF0 = 0xC0,
SOF1 = 0xC1,
SOF2 = 0xC2,
SOF3 = 0xC3,
DHT = 0xC4,
SOF5 = 0xC5,
SOF6 = 0xC6,
SOF7 = 0xC7,
JPG = 0xC8,
SOF9 = 0xC9,
SOF10 = 0xCA,
SOF11 = 0xCB,
DAC = 0xCC,
SOF13 = 0xCD,
SOF14 = 0xCE,
SOF15 = 0xCF,
RST0 = 0xD0,
RST1 = 0xD1,
RST2 = 0xD2,
RST3 = 0xD3,
RST4 = 0xD4,
RST5 = 0xD5,
RST6 = 0xD6,
RST7 = 0xD7,
SOI = 0xD8,
EOI = 0xD9,
SOS = 0xDA,
DQT = 0xDB,
DNL = 0xDC,
DRI = 0xDD,
DHP = 0xDE,
EXP = 0xDF,
APP0 = 0xE0,
APP1 = 0xE1,
APP2 = 0xE2,
APP3 = 0xE3,
APP4 = 0xE4,
APP5 = 0xE5,
APP6 = 0xE6,
APP7 = 0xE7,
APP8 = 0xE8,
APP9 = 0xE9,
APP10 = 0xEA,
APP11 = 0xEB,
APP12 = 0xEC,
APP13 = 0xED,
APP14 = 0xEE,
APP15 = 0xEF,
JPG0 = 0xF0,
JPG1 = 0xF1,
JPG2 = 0xF2,
JPG3 = 0xF3,
JPG4 = 0xF4,
JPG5 = 0xF5,
JPG6 = 0xF6,
JPG7 = 0xF7,
JPG8 = 0xF8,
JPG9 = 0xF9,
JPG10 = 0xFA,
JPG11 = 0xFB,
JPG12 = 0xFC,
JPG13 = 0xFD,
COM = 0xFE,
TEM = 0x01,
}
JPEG_Info :: struct {
jfif_app0: Maybe(JFIF_APP0),
jfxx_app0: Maybe(JFXX_APP0),
comments: [dynamic]string,
exif: [dynamic]Exif,
}
// Function to help with image buffer calculations
compute_buffer_size :: proc(width, height, channels, depth: int, extra_row_bytes := int(0)) -> (size: int) {
size = ((((channels * width * depth) + 7) >> 3) + extra_row_bytes) * height

View File

@@ -147,7 +147,7 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
return .JPEG
case s[:3] == "\xff\xd8\xff":
switch s[3] {
case 0xdb, 0xee, 0xe1, 0xe0:
case 0xdb, 0xee, 0xe1, 0xe0, 0xfe, 0xed:
return .JPEG
}
switch {

1016
core/image/jpeg/jpeg.odin Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
package jpeg
load :: proc{load_from_bytes, load_from_context}

View File

@@ -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
}
}

View File

@@ -366,7 +366,7 @@ chrm :: proc(c: image.PNG_Chunk) -> (res: cHRM, ok: bool) {
return
}
exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
exif :: proc(c: image.PNG_Chunk) -> (res: image.Exif, ok: bool) {
ok = true
@@ -396,4 +396,4 @@ exif :: proc(c: image.PNG_Chunk) -> (res: Exif, ok: bool) {
General helper functions
*/
compute_buffer_size :: image.compute_buffer_size
compute_buffer_size :: image.compute_buffer_size

View File

@@ -138,14 +138,6 @@ Text :: struct {
text: string,
}
Exif :: struct {
byte_order: enum {
little_endian,
big_endian,
},
data: []u8,
}
iCCP :: struct {
name: string,
profile: []u8,
@@ -250,10 +242,14 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
header := (^image.PNG_IHDR)(raw_data(c.data))^
// Validate IHDR
using header
if width == 0 || height == 0 || u128(width) * u128(height) > image.MAX_DIMENSIONS {
if width == 0 || height == 0 {
return {}, .Invalid_Image_Dimensions
}
if u128(width) * u128(height) > image.MAX_DIMENSIONS {
return {}, .Image_Dimensions_Too_Large
}
if compression_method != 0 {
return {}, compress.General_Error.Unknown_Compression_Method
}

View File

@@ -1,3 +1,4 @@
#+build linux, darwin, openbsd, freebsd, netbsd, haiku
package posix
when ODIN_OS == .Darwin {

View File

@@ -24,7 +24,7 @@ _sleep :: proc "contextless" (d: Duration) {
_tick_now :: proc "contextless" () -> Tick {
foreign odin_env {
tick_now :: proc "contextless" () -> f32 ---
tick_now :: proc "contextless" () -> f64 ---
}
return Tick{i64(tick_now()*1e6)}
}

View File

@@ -82,6 +82,7 @@ package all
@(require) import "core:image/png"
@(require) import "core:image/qoi"
@(require) import "core:image/tga"
@(require) import "core:image/jpeg"
@(require) import "core:io"
@(require) import "core:log"

View File

@@ -6473,7 +6473,14 @@ gb_internal CallArgumentError check_call_arguments_internal(CheckerContext *c, A
}
if (e && e->kind == Entity_Constant && is_type_proc(e->type)) {
if (o->mode != Addressing_Constant) {
bool ok = false;
if (o->mode == Addressing_Constant) {
ok = true;
} else if (o->value.kind == ExactValue_Procedure) {
ok = true;
}
if (!ok) {
if (show_error) {
error(o->expr, "Expected a constant procedure value for the argument '%.*s'", LIT(e->token.string));
}
@@ -7947,7 +7954,7 @@ gb_internal CallArgumentError check_polymorphic_record_type(CheckerContext *c, O
s = gb_string_append_fmt(s, "$%.*s", LIT(name));
if (v->kind == Entity_TypeName) {
if (v->type->kind != Type_Generic) {
if (v->type != nullptr && v->type->kind != Type_Generic) {
s = gb_string_append_fmt(s, "=");
s = write_type_to_string(s, v->type, false);
}
@@ -11423,6 +11430,7 @@ gb_internal ExprKind check_expr_base_internal(CheckerContext *c, Operand *o, Ast
o->mode = Addressing_Value;
o->type = type;
o->value = exact_value_procedure(node);
case_end;
case_ast_node(te, TernaryIfExpr, node);

View File

@@ -1797,6 +1797,9 @@ gb_internal void add_type_and_value(CheckerContext *ctx, Ast *expr, AddressingMo
}
expr = unparen_expr(expr);
if (expr == nullptr) {
break;
};
}
mutex_unlock(mutex);
}

View File

@@ -1,4 +1,5 @@
*.bmp
*.zip
*.png
*.jpg
math_big_test_library.*

View File

@@ -7,7 +7,7 @@ import zipfile
import hashlib
import hmac
TEST_SUITES = ['PNG', 'XML', 'BMP']
TEST_SUITES = ['PNG', 'XML', 'BMP', 'JPG']
DOWNLOAD_BASE_PATH = sys.argv[1] + "/{}"
ASSETS_BASE_URL = "https://raw.githubusercontent.com/odin-lang/test-assets/master/{}/{}"
HMAC_KEY = "https://odin-lang.org"
@@ -280,7 +280,9 @@ HMAC_DIGESTS = {
'rletopdown.bmp': "37500893aad0b40656aa80fd5c7c5f9b35d033018b8070d8b1d7baeb34c90f90462288b13295204b90aa3e5c9be797d22a328e3714ab259334e879a09a3de175",
'shortfile.bmp': "be3ffade7999304f00f9b7d152b5b27811ad1166d0fd43004392467a28f44b6a4ec02a23c0296bacd4f02f8041cd824b9ca6c9fc31fed27e36e572113bb47d73",
'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae",
'emblem-1024.jpg': "d7b7e3ffaa5cda04c667e3742752091d78e02aa2d3c7a63406af679ce810a0a86666b10fcab12cc7ead2fadf2f6c2e1237bc94f892a62a4c218e18a20f96dbe4",
'unicode.xml': "e0cdc94f07fdbb15eea811ed2ae6dcf494a83d197dafe6580c740270feb0d8f5f7146d4a7d4c2d2ea25f8bd9678bc986123484b39399819a6b7262687959d1ae",
}
def try_download_file(url, out_file):

View File

@@ -19,6 +19,7 @@ import pbm "core:image/netpbm"
import "core:image/png"
import "core:image/qoi"
import "core:image/tga"
import "core:image/jpeg"
import "core:bytes"
import "core:hash"
@@ -28,6 +29,7 @@ import "core:time"
TEST_SUITE_PATH_PNG :: ODIN_ROOT + "tests/core/assets/PNG"
TEST_SUITE_PATH_BMP :: ODIN_ROOT + "tests/core/assets/BMP"
TEST_SUITE_PATH_JPG :: ODIN_ROOT + "tests/core/assets/JPG"
I_Error :: image.Error
@@ -2360,6 +2362,52 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) {
return
}
// JPG test image
Basic_JPG_Tests := []Test{
{
"emblem-1024", {
{Default, nil, {1024, 1024, 3, 8}, 0x_46a29e0f},
},
},
}
@test
jpeg_test_basic :: proc(t: ^testing.T) {
run_jpg_suite(t, Basic_JPG_Tests)
}
run_jpg_suite :: proc(t: ^testing.T, suite: []Test) {
for file in suite {
test_file := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".jpg"}, context.allocator)
defer delete(test_file)
for test in file.tests {
img, err := jpeg.load(test_file, test.options)
passed := (test.expected_error == nil && err == nil) || (test.expected_error == err)
testing.expectf(t, passed, "%q failed to load with error %v.", file.file, err)
if err == nil { // No point in running the other tests if it didn't load.
pixels := bytes.buffer_to_bytes(&img.pixels)
dims := Dims{img.width, img.height, img.channels, img.depth}
testing.expectf(t, test.dims == dims, "%v has %v, expected: %v.", file.file, dims, test.dims)
img_hash := hash.crc32(pixels)
testing.expectf(t, test.hash == img_hash, "%v test #1's hash is %08x, expected %08x with %v.", file.file, img_hash, test.hash, test.options)
// Save to BMP file to check load
test_bmp := strings.concatenate({TEST_SUITE_PATH_JPG, "/", file.file, ".bmp"}, context.temp_allocator)
save_err := bmp.save(test_bmp, img)
testing.expectf(t, save_err == nil, "expected saving to BMP in memory not to raise error, got %v", save_err)
}
bmp.destroy(img)
}
}
return
}
@test
will_it_blend :: proc(t: ^testing.T) {
Pixel :: image.RGB_Pixel

View File

@@ -1370,7 +1370,7 @@ foreign lib {
// Create a motor joint
// @see b2MotorJointDef for details
CreateMotorJoint :: proc(worldId: WorldId, def: MotorJointDef) -> JointId ---
CreateMotorJoint :: proc(worldId: WorldId, #by_ptr def: MotorJointDef) -> JointId ---
// Set the motor joint linear offset target
MotorJoint_SetLinearOffset :: proc(jointId: JointId, linearOffset: Vec2) ---