Merge pull request #1788 from odin-lang/image-general-loader

Generalized `core:image` loader
This commit is contained in:
gingerBill
2022-05-15 13:04:26 +01:00
committed by GitHub
13 changed files with 143 additions and 85 deletions
+1 -1
View File
@@ -45,7 +45,7 @@ main :: proc() {
if len(args) < 2 { if len(args) < 2 {
stderr("No input file specified.\n") stderr("No input file specified.\n")
err := load(slice=TEST, buf=&buf, known_gzip_size=len(TEST)) err := load(data=TEST, buf=&buf, known_gzip_size=len(TEST))
if err == nil { if err == nil {
stdout("Displaying test vector: ") stdout("Displaying test vector: ")
stdout(bytes.buffer_to_string(&buf)) stdout(bytes.buffer_to_string(&buf))
+4 -4
View File
@@ -102,7 +102,7 @@ E_Deflate :: compress.Deflate_Error
GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le)) GZIP_MAX_PAYLOAD_SIZE :: i64(max(u32le))
load :: proc{load_from_slice, load_from_file, load_from_context} load :: proc{load_from_bytes, load_from_file, load_from_context}
load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
context.allocator = allocator context.allocator = allocator
@@ -112,16 +112,16 @@ load_from_file :: proc(filename: string, buf: ^bytes.Buffer, expected_output_siz
err = E_General.File_Not_Found err = E_General.File_Not_Found
if ok { if ok {
err = load_from_slice(data, buf, len(data), expected_output_size) err = load_from_bytes(data, buf, len(data), expected_output_size)
} }
return return
} }
load_from_slice :: proc(slice: []u8, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) { load_from_bytes :: proc(data: []byte, buf: ^bytes.Buffer, known_gzip_size := -1, expected_output_size := -1, allocator := context.allocator) -> (err: Error) {
buf := buf buf := buf
z := &compress.Context_Memory_Input{ z := &compress.Context_Memory_Input{
input_data = slice, input_data = data,
output = buf, output = buf,
} }
return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator) return load_from_context(z, buf, known_gzip_size, expected_output_size, allocator)
+3 -1
View File
@@ -54,9 +54,10 @@ Image :: struct {
*/ */
background: Maybe(RGB_Pixel_16), background: Maybe(RGB_Pixel_16),
metadata: Image_Metadata, metadata: Image_Metadata,
which: Which_File_Type,
} }
Image_Metadata :: union { Image_Metadata :: union #shared_nil {
^Netpbm_Info, ^Netpbm_Info,
^PNG_Info, ^PNG_Info,
^QOI_Info, ^QOI_Info,
@@ -172,6 +173,7 @@ General_Image_Error :: enum {
Unable_To_Write_File, Unable_To_Write_File,
// Invalid // Invalid
Unsupported_Format,
Invalid_Signature, Invalid_Signature,
Invalid_Input_Image, Invalid_Input_Image,
Image_Dimensions_Too_Large, Image_Dimensions_Too_Large,
+61
View File
@@ -0,0 +1,61 @@
package image
import "core:mem"
import "core:os"
import "core:bytes"
Loader_Proc :: #type proc(data: []byte, options: Options, allocator: mem.Allocator) -> (img: ^Image, err: Error)
Destroy_Proc :: #type proc(img: ^Image)
@(private)
_internal_loaders: [Which_File_Type]Loader_Proc
_internal_destroyers: [Which_File_Type]Destroy_Proc
register :: proc(kind: Which_File_Type, loader: Loader_Proc, destroyer: Destroy_Proc) {
assert(loader != nil)
assert(destroyer != nil)
assert(_internal_loaders[kind] == nil)
_internal_loaders[kind] = loader
assert(_internal_destroyers[kind] == nil)
_internal_destroyers[kind] = destroyer
}
load :: proc{
load_from_bytes,
load_from_file,
}
load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
loader := _internal_loaders[which(data)]
if loader == nil {
return nil, .Unsupported_Format
}
return loader(data, options, allocator)
}
load_from_file :: proc(filename: string, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
data, ok := os.read_entire_file(filename, allocator)
defer delete(data, allocator)
if ok {
return load_from_bytes(data, options, allocator)
} else {
return nil, .Unable_To_Read_File
}
}
destroy :: proc(img: ^Image, allocator := context.allocator) {
if img == nil {
return
}
context.allocator = allocator
destroyer := _internal_destroyers[img.which]
if destroyer != nil {
destroyer(img)
} else {
assert(img.metadata == nil)
bytes.buffer_destroy(&img.pixels)
free(img)
}
}
+15 -3
View File
@@ -28,7 +28,7 @@ BINARY :: Formats{.P4, .P5, .P6} + PAM + PFM
load :: proc { load :: proc {
load_from_file, load_from_file,
load_from_buffer, load_from_bytes,
} }
load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) { load_from_file :: proc(filename: string, allocator := context.allocator) -> (img: ^Image, err: Error) {
@@ -40,13 +40,14 @@ load_from_file :: proc(filename: string, allocator := context.allocator) -> (img
return return
} }
return load_from_buffer(data) return load_from_bytes(data)
} }
load_from_buffer :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) { load_from_bytes :: proc(data: []byte, allocator := context.allocator) -> (img: ^Image, err: Error) {
context.allocator = allocator context.allocator = allocator
img = new(Image) img = new(Image)
img.which = .NetPBM
header: Header; defer header_destroy(&header) header: Header; defer header_destroy(&header)
header_size: int header_size: int
@@ -749,3 +750,14 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for
// We couldn't find a suitable format // We couldn't find a suitable format
return {}, false return {}, false
} }
@(init, private)
_register :: proc() {
loader :: proc(data: []byte, options: image.Options, allocator: mem.Allocator) -> (img: ^Image, err: Error) {
return load_from_bytes(data, allocator)
}
destroyer :: proc(img: ^Image) {
_ = destroy(img)
}
image.register(.NetPBM, loader, destroyer)
}
+13 -9
View File
@@ -18,7 +18,6 @@ import "core:compress/zlib"
import "core:image" import "core:image"
import "core:os" import "core:os"
import "core:strings"
import "core:hash" import "core:hash"
import "core:bytes" import "core:bytes"
import "core:io" import "core:io"
@@ -318,13 +317,12 @@ read_header :: proc(ctx: ^$C) -> (image.PNG_IHDR, Error) {
} }
chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string { chunk_type_to_name :: proc(type: ^image.PNG_Chunk_Type) -> string {
t := transmute(^u8)type return string(([^]u8)(type)[:4])
return strings.string_from_ptr(t, 4)
} }
load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
ctx := &compress.Context_Memory_Input{ ctx := &compress.Context_Memory_Input{
input_data = slice, input_data = data,
} }
/* /*
@@ -344,10 +342,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
defer delete(data) defer delete(data)
if ok { if ok {
return load_from_slice(data, options) return load_from_bytes(data, options)
} else { } else {
img = new(Image) return nil, .Unable_To_Read_File
return img, .Unable_To_Read_File
} }
} }
@@ -375,6 +372,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
if img == nil { if img == nil {
img = new(Image) img = new(Image)
} }
img.which = .PNG
info := new(image.PNG_Info) info := new(image.PNG_Info)
img.metadata = info img.metadata = info
@@ -1639,4 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
return nil return nil
} }
load :: proc{load_from_file, load_from_slice, load_from_context} load :: proc{load_from_file, load_from_bytes, load_from_context}
@(init, private)
_register :: proc() {
image.register(.PNG, load_from_bytes, destroy)
}
+11 -6
View File
@@ -180,9 +180,9 @@ save_to_file :: proc(output: string, img: ^Image, options := Options{}, allocato
save :: proc{save_to_memory, save_to_file} save :: proc{save_to_memory, save_to_file}
load_from_slice :: proc(slice: []u8, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) { load_from_bytes :: proc(data: []byte, options := Options{}, allocator := context.allocator) -> (img: ^Image, err: Error) {
ctx := &compress.Context_Memory_Input{ ctx := &compress.Context_Memory_Input{
input_data = slice, input_data = data,
} }
img, err = load_from_context(ctx, options, allocator) img, err = load_from_context(ctx, options, allocator)
@@ -196,10 +196,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
defer delete(data) defer delete(data)
if ok { if ok {
return load_from_slice(data, options) return load_from_bytes(data, options)
} else { } else {
img = new(Image) return nil, .Unable_To_Read_File
return img, .Unable_To_Read_File
} }
} }
@@ -225,6 +224,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
if img == nil { if img == nil {
img = new(Image) img = new(Image)
} }
img.which = .QOI
if .return_metadata in options { if .return_metadata in options {
info := new(image.QOI_Info) info := new(image.QOI_Info)
@@ -359,7 +359,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
return return
} }
load :: proc{load_from_file, load_from_slice, load_from_context} load :: proc{load_from_file, load_from_bytes, load_from_context}
/* /*
Cleanup of image-specific data. Cleanup of image-specific data.
@@ -404,3 +404,8 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) {
return u8((i1 + i2 + i3 + i4) & 63) return u8((i1 + i2 + i3 + i4) & 63)
} }
@(init, private)
_register :: proc() {
image.register(.QOI, load_from_bytes, destroy)
}
-1
View File
@@ -12,7 +12,6 @@ package tga
import "core:mem" import "core:mem"
import "core:image" import "core:image"
import "core:compress"
import "core:bytes" import "core:bytes"
import "core:os" import "core:os"
+17 -11
View File
@@ -6,6 +6,7 @@ Which_File_Type :: enum {
Unknown, Unknown,
BMP, BMP,
DjVu, // AT&T DjVu file format
EXR, EXR,
FLIF, FLIF,
GIF, GIF,
@@ -14,7 +15,7 @@ Which_File_Type :: enum {
JPEG, JPEG,
JPEG_2000, JPEG_2000,
JPEG_XL, JPEG_XL,
PBM, PGM, PPM, PAM, PFM, // NetPBM family NetPBM, // NetPBM family
PIC, // Softimage PIC PIC, // Softimage PIC
PNG, // Portable Network Graphics PNG, // Portable Network Graphics
PSD, // Photoshop PSD PSD, // Photoshop PSD
@@ -88,6 +89,11 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
switch { switch {
case s[:2] == "BM": case s[:2] == "BM":
return .BMP return .BMP
case s[:8] == "AT&TFORM":
switch s[12:16] {
case "DJVU", "DJVM":
return .DjVu
}
case s[:4] == "\x76\x2f\x31\x01": case s[:4] == "\x76\x2f\x31\x01":
return .EXR return .EXR
case s[:6] == "GIF87a", s[:6] == "GIF89a": case s[:6] == "GIF87a", s[:6] == "GIF89a":
@@ -111,16 +117,16 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
switch s[2] { switch s[2] {
case '\t', '\n', '\r': case '\t', '\n', '\r':
switch s[1] { switch s[1] {
case '1', '4': case '1', '4': // PBM
return .PBM return .NetPBM
case '2', '5': case '2', '5': // PGM
return .PGM return .NetPBM
case '3', '6': case '3', '6': // PPM
return .PPM return .NetPBM
case '7': case '7': // PAM
return .PAM return .NetPBM
case 'F', 'f': case 'F', 'f': // PFM
return .PFM return .NetPBM
} }
} }
case s[:8] == "\x89PNG\r\n\x1a\n": case s[:8] == "\x89PNG\r\n\x1a\n":
+4
View File
@@ -62,8 +62,10 @@ import fmt "core:fmt"
import hash "core:hash" import hash "core:hash"
import image "core:image" import image "core:image"
import netpbm "core:image/netpbm"
import png "core:image/png" import png "core:image/png"
import qoi "core:image/qoi" import qoi "core:image/qoi"
import tga "core:image/tga"
import io "core:io" import io "core:io"
import log "core:log" import log "core:log"
@@ -164,8 +166,10 @@ _ :: xml
_ :: fmt _ :: fmt
_ :: hash _ :: hash
_ :: image _ :: image
_ :: netpbm
_ :: png _ :: png
_ :: qoi _ :: qoi
_ :: tga
_ :: io _ :: io
_ :: log _ :: log
_ :: math _ :: math
+10 -38
View File
@@ -4356,6 +4356,9 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) {
} }
String import_name = path_to_entity_name(id->import_name.string, id->fullpath, false); String import_name = path_to_entity_name(id->import_name.string, id->fullpath, false);
if (is_blank_ident(import_name)) {
force_use = true;
}
// NOTE(bill, 2019-05-19): If the directory path is not a valid entity name, force the user to assign a custom one // NOTE(bill, 2019-05-19): If the directory path is not a valid entity name, force the user to assign a custom one
// if (import_name.len == 0 || import_name == "_") { // if (import_name.len == 0 || import_name == "_") {
@@ -4363,17 +4366,13 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) {
// } // }
if (import_name.len == 0 || is_blank_ident(import_name)) { if (import_name.len == 0 || is_blank_ident(import_name)) {
if (id->is_using) { if (id->import_name.string == "") {
// TODO(bill): Should this be a warning? String invalid_name = id->fullpath;
} else { invalid_name = get_invalid_import_name(invalid_name);
if (id->import_name.string == "") {
String invalid_name = id->fullpath;
invalid_name = get_invalid_import_name(invalid_name);
error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import <new_name> \"%.*s\" ", LIT(invalid_name), LIT(invalid_name)); error(id->token, "Import name %.*s, is not a valid identifier. Perhaps you want to reference the package by a different name like this: import <new_name> \"%.*s\" ", LIT(invalid_name), LIT(invalid_name));
} else { } else {
error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string)); error(token, "Import name, %.*s, cannot be use as an import name as it is not a valid identifier", LIT(id->import_name.string));
}
} }
} else { } else {
GB_ASSERT(id->import_name.pos.line != 0); GB_ASSERT(id->import_name.pos.line != 0);
@@ -4383,38 +4382,11 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) {
scope); scope);
add_entity(ctx, parent_scope, nullptr, e); add_entity(ctx, parent_scope, nullptr, e);
if (force_use || id->is_using) { if (force_use) {
add_entity_use(ctx, nullptr, e); add_entity_use(ctx, nullptr, e);
} }
} }
if (id->is_using) {
if (parent_scope->flags & ScopeFlag_Global) {
error(id->import_name, "built-in package imports cannot use using");
return;
}
// NOTE(bill): Add imported entities to this file's scope
for_array(elem_index, scope->elements.entries) {
String name = scope->elements.entries[elem_index].key.string;
Entity *e = scope->elements.entries[elem_index].value;
if (e->scope == parent_scope) continue;
if (is_entity_exported(e, true)) {
Entity *found = scope_lookup_current(parent_scope, name);
if (found != nullptr) {
// NOTE(bill):
// Date: 2019-03-17
// The order has to be the other way around as `using` adds the entity into the that
// file scope otherwise the error would be the wrong way around
redeclaration_error(name, found, e);
} else {
add_entity_with_name(ctx, parent_scope, e->identifier, e, name);
}
}
}
}
scope->flags |= ScopeFlag_HasBeenImported; scope->flags |= ScopeFlag_HasBeenImported;
} }
+4 -10
View File
@@ -1160,11 +1160,10 @@ Ast *ast_package_decl(AstFile *f, Token token, Token name, CommentGroup *docs, C
return result; return result;
} }
Ast *ast_import_decl(AstFile *f, Token token, bool is_using, Token relpath, Token import_name, Ast *ast_import_decl(AstFile *f, Token token, Token relpath, Token import_name,
CommentGroup *docs, CommentGroup *comment) { CommentGroup *docs, CommentGroup *comment) {
Ast *result = alloc_ast_node(f, Ast_ImportDecl); Ast *result = alloc_ast_node(f, Ast_ImportDecl);
result->ImportDecl.token = token; result->ImportDecl.token = token;
result->ImportDecl.is_using = is_using;
result->ImportDecl.relpath = relpath; result->ImportDecl.relpath = relpath;
result->ImportDecl.import_name = import_name; result->ImportDecl.import_name = import_name;
result->ImportDecl.docs = docs; result->ImportDecl.docs = docs;
@@ -4382,7 +4381,6 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
CommentGroup *docs = f->lead_comment; CommentGroup *docs = f->lead_comment;
Token token = expect_token(f, Token_import); Token token = expect_token(f, Token_import);
Token import_name = {}; Token import_name = {};
bool is_using = kind != ImportDecl_Standard;
switch (f->curr_token.kind) { switch (f->curr_token.kind) {
case Token_Ident: case Token_Ident:
@@ -4393,22 +4391,18 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
break; break;
} }
if (!is_using && is_blank_ident(import_name)) {
syntax_error(import_name, "Illegal import name: '_'");
}
Token file_path = expect_token_after(f, Token_String, "import"); Token file_path = expect_token_after(f, Token_String, "import");
Ast *s = nullptr; Ast *s = nullptr;
if (f->curr_proc != nullptr) { if (f->curr_proc != nullptr) {
syntax_error(import_name, "You cannot use 'import' within a procedure. This must be done at the file scope"); syntax_error(import_name, "Cannot use 'import' within a procedure. This must be done at the file scope");
s = ast_bad_decl(f, import_name, file_path); s = ast_bad_decl(f, import_name, file_path);
} else { } else {
s = ast_import_decl(f, token, is_using, file_path, import_name, docs, f->line_comment); s = ast_import_decl(f, token, file_path, import_name, docs, f->line_comment);
array_add(&f->imports, s); array_add(&f->imports, s);
} }
if (is_using) { if (kind != ImportDecl_Standard) {
syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly"); syntax_error(import_name, "'using import' is not allowed, please use the import name explicitly");
} }
-1
View File
@@ -585,7 +585,6 @@ AST_KIND(_DeclBegin, "", bool) \
Token import_name; \ Token import_name; \
CommentGroup *docs; \ CommentGroup *docs; \
CommentGroup *comment; \ CommentGroup *comment; \
bool is_using; \
}) \ }) \
AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \ AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \
Token token; \ Token token; \