Browse Source

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

Generalized `core:image` loader
gingerBill 3 years ago
parent
commit
a2c771876e

+ 1 - 1
core/compress/gzip/example.odin

@@ -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
core/compress/gzip/gzip.odin

@@ -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
core/image/common.odin

@@ -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 - 0
core/image/general_loader.odin

@@ -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
core/image/netpbm/netpbm.odin

@@ -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
@@ -748,4 +749,15 @@ 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
core/image/png/png.odin

@@ -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 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{
 	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 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 {
 	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
core/image/qoi/qoi.odin

@@ -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 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 {
 	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.
@@ -403,4 +403,9 @@ qoi_hash :: #force_inline proc(pixel: RGBA_Pixel) -> (index: u8) {
 	i4 := u16(pixel.a) * 11
 	i4 := u16(pixel.a) * 11
 
 
 	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)
 }
 }

+ 0 - 1
core/image/tga/tga.odin

@@ -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
core/image/which.odin

@@ -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':
-				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":
 	case s[:8] == "\x89PNG\r\n\x1a\n":

+ 4 - 0
examples/all/all_main.odin

@@ -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
src/checker.cpp

@@ -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) {
-			// 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 {
 	} 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
src/parser.cpp

@@ -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");
 	}
 	}
 
 

+ 0 - 1
src/parser.hpp

@@ -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;           \