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 {
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 {
stdout("Displaying test vector: ")
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))
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) {
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
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
}
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
z := &compress.Context_Memory_Input{
input_data = slice,
input_data = data,
output = buf,
}
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),
metadata: Image_Metadata,
which: Which_File_Type,
}
Image_Metadata :: union {
Image_Metadata :: union #shared_nil {
^Netpbm_Info,
^PNG_Info,
^QOI_Info,
@@ -172,6 +173,7 @@ General_Image_Error :: enum {
Unable_To_Write_File,
// Invalid
Unsupported_Format,
Invalid_Signature,
Invalid_Input_Image,
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_from_file,
load_from_buffer,
load_from_bytes,
}
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 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
img = new(Image)
img.which = .NetPBM
header: Header; defer header_destroy(&header)
header_size: int
@@ -748,4 +749,15 @@ autoselect_pbm_format_from_image :: proc(img: ^Image, prefer_binary := true, for
// We couldn't find a suitable format
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:os"
import "core:strings"
import "core:hash"
import "core:bytes"
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 {
t := transmute(^u8)type
return strings.string_from_ptr(t, 4)
return string(([^]u8)(type)[: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{
input_data = slice,
input_data = data,
}
/*
@@ -344,10 +342,9 @@ load_from_file :: proc(filename: string, options := Options{}, allocator := cont
defer delete(data)
if ok {
return load_from_slice(data, options)
return load_from_bytes(data, options)
} else {
img = new(Image)
return img, .Unable_To_Read_File
return nil, .Unable_To_Read_File
}
}
@@ -375,6 +372,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
if img == nil {
img = new(Image)
}
img.which = .PNG
info := new(image.PNG_Info)
img.metadata = info
@@ -1639,4 +1637,10 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
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}
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{
input_data = slice,
input_data = data,
}
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)
if ok {
return load_from_slice(data, options)
return load_from_bytes(data, options)
} else {
img = new(Image)
return img, .Unable_To_Read_File
return nil, .Unable_To_Read_File
}
}
@@ -225,6 +224,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
if img == nil {
img = new(Image)
}
img.which = .QOI
if .return_metadata in options {
info := new(image.QOI_Info)
@@ -359,7 +359,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
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.
@@ -403,4 +403,9 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) {
i4 := u16(pixel.a) * 11
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:image"
import "core:compress"
import "core:bytes"
import "core:os"
+17 -11
View File
@@ -6,6 +6,7 @@ Which_File_Type :: enum {
Unknown,
BMP,
DjVu, // AT&T DjVu file format
EXR,
FLIF,
GIF,
@@ -14,7 +15,7 @@ Which_File_Type :: enum {
JPEG,
JPEG_2000,
JPEG_XL,
PBM, PGM, PPM, PAM, PFM, // NetPBM family
NetPBM, // NetPBM family
PIC, // Softimage PIC
PNG, // Portable Network Graphics
PSD, // Photoshop PSD
@@ -88,6 +89,11 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
switch {
case s[:2] == "BM":
return .BMP
case s[:8] == "AT&TFORM":
switch s[12:16] {
case "DJVU", "DJVM":
return .DjVu
}
case s[:4] == "\x76\x2f\x31\x01":
return .EXR
case s[:6] == "GIF87a", s[:6] == "GIF89a":
@@ -111,16 +117,16 @@ which_bytes :: proc(data: []byte) -> Which_File_Type {
switch s[2] {
case '\t', '\n', '\r':
switch s[1] {
case '1', '4':
return .PBM
case '2', '5':
return .PGM
case '3', '6':
return .PPM
case '7':
return .PAM
case 'F', 'f':
return .PFM
case '1', '4': // PBM
return .NetPBM
case '2', '5': // PGM
return .NetPBM
case '3', '6': // PPM
return .NetPBM
case '7': // PAM
return .NetPBM
case 'F', 'f': // PFM
return .NetPBM
}
}
case s[:8] == "\x89PNG\r\n\x1a\n":
+4
View File
@@ -62,8 +62,10 @@ import fmt "core:fmt"
import hash "core:hash"
import image "core:image"
import netpbm "core:image/netpbm"
import png "core:image/png"
import qoi "core:image/qoi"
import tga "core:image/tga"
import io "core:io"
import log "core:log"
@@ -164,8 +166,10 @@ _ :: xml
_ :: fmt
_ :: hash
_ :: image
_ :: netpbm
_ :: png
_ :: qoi
_ :: tga
_ :: io
_ :: log
_ :: 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);
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
// 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 (id->is_using) {
// TODO(bill): Should this be a warning?
} else {
if (id->import_name.string == "") {
String invalid_name = id->fullpath;
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));
} 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(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 {
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 {
GB_ASSERT(id->import_name.pos.line != 0);
@@ -4383,38 +4382,11 @@ void check_add_import_decl(CheckerContext *ctx, Ast *decl) {
scope);
add_entity(ctx, parent_scope, nullptr, e);
if (force_use || id->is_using) {
if (force_use) {
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;
}
+4 -10
View File
@@ -1160,11 +1160,10 @@ Ast *ast_package_decl(AstFile *f, Token token, Token name, CommentGroup *docs, C
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) {
Ast *result = alloc_ast_node(f, Ast_ImportDecl);
result->ImportDecl.token = token;
result->ImportDecl.is_using = is_using;
result->ImportDecl.relpath = relpath;
result->ImportDecl.import_name = import_name;
result->ImportDecl.docs = docs;
@@ -4382,7 +4381,6 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
CommentGroup *docs = f->lead_comment;
Token token = expect_token(f, Token_import);
Token import_name = {};
bool is_using = kind != ImportDecl_Standard;
switch (f->curr_token.kind) {
case Token_Ident:
@@ -4393,22 +4391,18 @@ Ast *parse_import_decl(AstFile *f, ImportDeclKind kind) {
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");
Ast *s = 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);
} 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);
}
if (is_using) {
if (kind != ImportDecl_Standard) {
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; \
CommentGroup *docs; \
CommentGroup *comment; \
bool is_using; \
}) \
AST_KIND(ForeignImportDecl, "foreign import declaration", struct { \
Token token; \