aboutsummaryrefslogtreecommitdiff
path: root/core/compress/common.odin
diff options
context:
space:
mode:
authorJeroen van Rijn <Kelimion@users.noreply.github.com>2021-04-30 00:21:52 +0200
committerJeroen van Rijn <Kelimion@users.noreply.github.com>2021-04-30 00:21:52 +0200
commit58e023e0cf581db71b4bf3c341b781a2f09ff50a (patch)
treecf070b07edb9d8e3e0cb3dd849d26cf8228377c5 /core/compress/common.odin
parent222bab501ce886692f300b02d15b9e7099457406 (diff)
Add `compress` and `image` to core.
Diffstat (limited to 'core/compress/common.odin')
-rw-r--r--core/compress/common.odin203
1 files changed, 203 insertions, 0 deletions
diff --git a/core/compress/common.odin b/core/compress/common.odin
new file mode 100644
index 000000000..99b054903
--- /dev/null
+++ b/core/compress/common.odin
@@ -0,0 +1,203 @@
+package compress
+
+import "core:io"
+import "core:image"
+
+// Error helper, e.g. is_kind(err, General_Error.OK);
+is_kind :: proc(u: $U, x: $V) -> bool {
+ v, ok := u.(V);
+ return ok && v == x;
+}
+
+Error :: union {
+ General_Error,
+ Deflate_Error,
+ ZLIB_Error,
+ GZIP_Error,
+ ZIP_Error,
+ /*
+ This is here because png.load will return a this type of error union,
+ as it may involve an I/O error, a Deflate error, etc.
+ */
+ image.PNG_Error,
+}
+
+General_Error :: enum {
+ OK = 0,
+ File_Not_Found,
+ Cannot_Open_File,
+ File_Too_Short,
+ Stream_Too_Short,
+ Output_Too_Short,
+ Unknown_Compression_Method,
+ Checksum_Failed,
+ Incompatible_Options,
+ Unimplemented,
+}
+
+GZIP_Error :: enum {
+ Invalid_GZIP_Signature,
+ Reserved_Flag_Set,
+ Invalid_Extra_Data,
+ Original_Name_Too_Long,
+ Comment_Too_Long,
+ Payload_Length_Invalid,
+ Payload_CRC_Invalid,
+}
+
+ZIP_Error :: enum {
+ Invalid_ZIP_File_Signature,
+ Unexpected_Signature,
+ Insert_Next_Disk,
+ Expected_End_of_Central_Directory_Record,
+}
+
+ZLIB_Error :: enum {
+ Unsupported_Window_Size,
+ FDICT_Unsupported,
+ Unsupported_Compression_Level,
+ Code_Buffer_Malformed,
+}
+
+Deflate_Error :: enum {
+ Huffman_Bad_Sizes,
+ Huffman_Bad_Code_Lengths,
+ Inflate_Error,
+ Bad_Distance,
+ Bad_Huffman_Code,
+ Len_Nlen_Mismatch,
+ BType_3,
+}
+
+// General context for ZLIB, LZW, etc.
+Context :: struct {
+ code_buffer: u32,
+ num_bits: i8,
+ /*
+ num_bits will be set to -100 if the buffer is malformed
+ */
+ eof: b8,
+
+ input: io.Stream,
+ output: io.Stream,
+ bytes_written: i64,
+ // Used to update hash as we write instead of all at once
+ rolling_hash: u32,
+
+ // Sliding window buffer. Size must be a power of two.
+ window_size: i64,
+ last: ^[dynamic]byte,
+}
+
+// Stream helpers
+/*
+ TODO: These need to be optimized.
+
+ Streams should really only check if a certain method is available once, perhaps even during setup.
+
+ Bit and byte readers may be merged so that reading bytes will grab them from the bit buffer first.
+ This simplifies end-of-stream handling where bits may be left in the bit buffer.
+*/
+
+read_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
+ b := make([]u8, size_of(T), context.temp_allocator);
+ r, e1 := io.to_reader(c.input);
+ _, e2 := io.read(r, b);
+ if !e1 || e2 != .None {
+ return T{}, e2;
+ }
+
+ res = (^T)(raw_data(b))^;
+ return res, .None;
+}
+
+read_u8 :: #force_inline proc(z: ^Context) -> (res: u8, err: io.Error) {
+ return read_data(z, u8);
+}
+
+peek_data :: #force_inline proc(c: ^Context, $T: typeid) -> (res: T, err: io.Error) {
+ // Get current position to read from.
+ curr, e1 := c.input->impl_seek(0, .Current);
+ if e1 != .None {
+ return T{}, e1;
+ }
+ r, e2 := io.to_reader_at(c.input);
+ if !e2 {
+ return T{}, .Empty;
+ }
+ b := make([]u8, size_of(T), context.temp_allocator);
+ _, e3 := io.read_at(r, b, curr);
+ if e3 != .None {
+ return T{}, .Empty;
+ }
+
+ res = (^T)(raw_data(b))^;
+ return res, .None;
+}
+
+// Sliding window read back
+peek_back_byte :: proc(c: ^Context, offset: i64) -> (res: u8, err: io.Error) {
+ // Look back into the sliding window.
+ return c.last[offset % c.window_size], .None;
+}
+
+// Generalized bit reader LSB
+refill_lsb :: proc(z: ^Context, width := i8(24)) {
+ for {
+ if z.num_bits > width {
+ break;
+ }
+ if z.code_buffer == 0 && z.num_bits == -1 {
+ z.num_bits = 0;
+ }
+ if z.code_buffer >= 1 << uint(z.num_bits) {
+ // Code buffer is malformed.
+ z.num_bits = -100;
+ return;
+ }
+ c, err := read_u8(z);
+ if err != .None {
+ // This is fine at the end of the file.
+ z.num_bits = -42;
+ z.eof = true;
+ return;
+ }
+ z.code_buffer |= (u32(c) << u8(z.num_bits));
+ z.num_bits += 8;
+ }
+}
+
+consume_bits_lsb :: #force_inline proc(z: ^Context, width: u8) {
+ z.code_buffer >>= width;
+ z.num_bits -= i8(width);
+}
+
+peek_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
+ if z.num_bits < i8(width) {
+ refill_lsb(z);
+ }
+ // assert(z.num_bits >= i8(width));
+ return z.code_buffer & ~(~u32(0) << width);
+}
+
+peek_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
+ assert(z.num_bits >= i8(width));
+ return z.code_buffer & ~(~u32(0) << width);
+}
+
+read_bits_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
+ k := peek_bits_lsb(z, width);
+ consume_bits_lsb(z, width);
+ return k;
+}
+
+read_bits_no_refill_lsb :: #force_inline proc(z: ^Context, width: u8) -> u32 {
+ k := peek_bits_no_refill_lsb(z, width);
+ consume_bits_lsb(z, width);
+ return k;
+}
+
+discard_to_next_byte_lsb :: proc(z: ^Context) {
+ discard := u8(z.num_bits & 7);
+ consume_bits_lsb(z, discard);
+} \ No newline at end of file