mirror of
https://github.com/odin-lang/Odin.git
synced 2025-12-29 01:14:40 +00:00
1249 lines
34 KiB
Odin
1249 lines
34 KiB
Odin
/*
|
|
Copyright 2021 Jeroen van Rijn <nom@duclavier.com>.
|
|
Made available under Odin's BSD-3 license.
|
|
|
|
List of contributors:
|
|
Jeroen van Rijn: Initial implementation, optimization.
|
|
Ginger Bill: Cosmetic changes.
|
|
*/
|
|
|
|
// package image implements a general 2D image library to be used with other image related packages
|
|
package image
|
|
|
|
import "core:bytes"
|
|
import "core:mem"
|
|
import "core:compress"
|
|
import "base:runtime"
|
|
|
|
/*
|
|
67_108_864 pixels max by default.
|
|
|
|
For QOI, the Worst case scenario means all pixels will be encoded as RGBA literals, costing 5 bytes each.
|
|
This caps memory usage at 320 MiB.
|
|
|
|
The tunable is limited to 4_294_836_225 pixels maximum, or 4 GiB per 8-bit channel.
|
|
It is not advised to tune it this large.
|
|
|
|
The 64 Megapixel default is considered to be a decent upper bound you won't run into in practice,
|
|
except in very specific circumstances.
|
|
|
|
*/
|
|
MAX_DIMENSIONS :: min(#config(MAX_DIMENSIONS, 8192 * 8192), 65535 * 65535)
|
|
|
|
// Color
|
|
RGB_Pixel :: [3]u8
|
|
RGBA_Pixel :: [4]u8
|
|
RGB_Pixel_16 :: [3]u16
|
|
RGBA_Pixel_16 :: [4]u16
|
|
// Grayscale
|
|
G_Pixel :: [1]u8
|
|
GA_Pixel :: [2]u8
|
|
G_Pixel_16 :: [1]u16
|
|
GA_Pixel_16 :: [2]u16
|
|
|
|
Image :: struct {
|
|
width: int,
|
|
height: int,
|
|
channels: int,
|
|
depth: int, // Channel depth in bits, typically 8 or 16
|
|
pixels: bytes.Buffer `fmt:"-"`,
|
|
/*
|
|
Some image loaders/writers can return/take an optional background color.
|
|
For convenience, we return them as u16 so we don't need to switch on the type
|
|
in our viewer, and can just test against nil.
|
|
*/
|
|
background: Maybe(RGB_Pixel_16),
|
|
metadata: Image_Metadata,
|
|
which: Which_File_Type,
|
|
}
|
|
|
|
Image_Metadata :: union #shared_nil {
|
|
^Netpbm_Info,
|
|
^PNG_Info,
|
|
^QOI_Info,
|
|
^TGA_Info,
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
IMPORTANT: `.do_not_expand_*` options currently skip handling of the `alpha_*` options,
|
|
therefore Gray+Alpha will be returned as such even if you add `.alpha_drop_if_present`,
|
|
and `.alpha_add_if_missing` and keyed transparency will likewise be ignored.
|
|
|
|
The same goes for indexed images. This will be remedied in a near future update.
|
|
*/
|
|
|
|
/*
|
|
Image_Option:
|
|
`.info`
|
|
This option behaves as `.return_metadata` and `.do_not_decompress_image` and can be used
|
|
to gather an image's dimensions and color information.
|
|
|
|
`.return_header`
|
|
Fill out img.metadata.header with the image's format-specific header struct.
|
|
If we only care about the image specs, we can set `.return_header` +
|
|
`.do_not_decompress_image`, or `.info`.
|
|
|
|
`.return_metadata`
|
|
Returns all chunks not needed to decode the data.
|
|
It also returns the header as if `.return_header` was set.
|
|
|
|
`.do_not_decompress_image`
|
|
Skip decompressing IDAT chunk, defiltering and the rest.
|
|
|
|
`.do_not_expand_grayscale`
|
|
Do not turn grayscale (+ Alpha) images into RGB(A).
|
|
Returns just the 1 or 2 channels present, although 1, 2 and 4 bit are still scaled to 8-bit.
|
|
|
|
`.do_not_expand_indexed`
|
|
Do not turn indexed (+ Alpha) images into RGB(A).
|
|
Returns just the 1 or 2 (with `tRNS`) channels present.
|
|
Make sure to use `return_metadata` to also return the palette chunk so you can recolor it yourself.
|
|
|
|
`.do_not_expand_channels`
|
|
Applies both `.do_not_expand_grayscale` and `.do_not_expand_indexed`.
|
|
|
|
`.alpha_add_if_missing`
|
|
If the image has no alpha channel, it'll add one set to max(type).
|
|
Turns RGB into RGBA and Gray into Gray+Alpha
|
|
|
|
`.alpha_drop_if_present`
|
|
If the image has an alpha channel, drop it.
|
|
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`.
|
|
In this case it'll premultiply the specified pixels in question only,
|
|
as the others are implicitly fully opaque.
|
|
|
|
`.alpha_premultiply`
|
|
If the image has an alpha channel, returns image data as follows:
|
|
RGB *= A, Gray = Gray *= A
|
|
|
|
`.blend_background`
|
|
If a bKGD chunk is present in a PNG, we normally just set `img.background`
|
|
with its value and leave it up to the application to decide how to display the image,
|
|
as per the PNG specification.
|
|
|
|
With `.blend_background` selected, we blend the image against the background
|
|
color. As this negates the use for an alpha channel, we'll drop it _unless_
|
|
you also specify `.alpha_add_if_missing`.
|
|
|
|
Options that don't apply to an image format will be ignored by their loader.
|
|
*/
|
|
|
|
Option :: enum {
|
|
// LOAD OPTIONS
|
|
info = 0,
|
|
do_not_decompress_image,
|
|
return_header,
|
|
return_metadata,
|
|
alpha_add_if_missing, // Ignored for QOI. Always returns RGBA8.
|
|
alpha_drop_if_present, // Unimplemented for QOI. Returns error.
|
|
alpha_premultiply, // Unimplemented for QOI. Returns error.
|
|
blend_background, // Ignored for non-PNG formats
|
|
|
|
// Unimplemented
|
|
do_not_expand_grayscale,
|
|
do_not_expand_indexed,
|
|
do_not_expand_channels,
|
|
|
|
// SAVE OPTIONS
|
|
qoi_all_channels_linear, // QOI, informative only. If not set, defaults to sRGB with linear alpha.
|
|
}
|
|
Options :: distinct bit_set[Option]
|
|
|
|
Error :: union #shared_nil {
|
|
General_Image_Error,
|
|
Netpbm_Error,
|
|
PNG_Error,
|
|
QOI_Error,
|
|
|
|
compress.Error,
|
|
compress.General_Error,
|
|
compress.Deflate_Error,
|
|
compress.ZLIB_Error,
|
|
runtime.Allocator_Error,
|
|
}
|
|
|
|
General_Image_Error :: enum {
|
|
None = 0,
|
|
Unsupported_Option,
|
|
// File I/O
|
|
Unable_To_Read_File,
|
|
Unable_To_Write_File,
|
|
|
|
// Invalid
|
|
Unsupported_Format,
|
|
Invalid_Signature,
|
|
Invalid_Input_Image,
|
|
Image_Dimensions_Too_Large,
|
|
Invalid_Image_Dimensions,
|
|
Invalid_Number_Of_Channels,
|
|
Image_Does_Not_Adhere_to_Spec,
|
|
Invalid_Image_Depth,
|
|
Invalid_Bit_Depth,
|
|
Invalid_Color_Space,
|
|
|
|
// More data than pixels to decode into, for example.
|
|
Corrupt,
|
|
|
|
// Output buffer is the wrong size
|
|
Invalid_Output,
|
|
|
|
// Allocation
|
|
Unable_To_Allocate_Or_Resize,
|
|
}
|
|
|
|
/*
|
|
Netpbm-specific definitions
|
|
*/
|
|
Netpbm_Format :: enum {
|
|
P1, P2, P3, P4, P5, P6, P7, Pf, PF,
|
|
}
|
|
|
|
Netpbm_Header :: struct {
|
|
format: Netpbm_Format,
|
|
width: int,
|
|
height: int,
|
|
channels: int,
|
|
depth: int,
|
|
maxval: int,
|
|
tupltype: string,
|
|
scale: f32,
|
|
little_endian: bool,
|
|
}
|
|
|
|
Netpbm_Info :: struct {
|
|
header: Netpbm_Header,
|
|
}
|
|
|
|
Netpbm_Error :: enum {
|
|
None = 0,
|
|
|
|
// reading
|
|
Invalid_Header_Token_Character,
|
|
Incomplete_Header,
|
|
Invalid_Header_Value,
|
|
Duplicate_Header_Field,
|
|
Buffer_Too_Small,
|
|
Invalid_Buffer_ASCII_Token,
|
|
Invalid_Buffer_Value,
|
|
|
|
// writing
|
|
Invalid_Format,
|
|
}
|
|
|
|
/*
|
|
PNG-specific definitions
|
|
*/
|
|
PNG_Error :: enum {
|
|
None = 0,
|
|
IHDR_Not_First_Chunk,
|
|
IHDR_Corrupt,
|
|
IDAT_Missing,
|
|
IDAT_Must_Be_Contiguous,
|
|
IDAT_Corrupt,
|
|
IDAT_Size_Too_Large,
|
|
PLTE_Encountered_Unexpectedly,
|
|
PLTE_Invalid_Length,
|
|
PLTE_Missing,
|
|
TRNS_Encountered_Unexpectedly,
|
|
TNRS_Invalid_Length,
|
|
BKGD_Invalid_Length,
|
|
Unknown_Color_Type,
|
|
Invalid_Color_Bit_Depth_Combo,
|
|
Unknown_Filter_Method,
|
|
Unknown_Interlace_Method,
|
|
Requested_Channel_Not_Present,
|
|
Post_Processing_Error,
|
|
Invalid_Chunk_Length,
|
|
}
|
|
|
|
PNG_Info :: struct {
|
|
header: PNG_IHDR,
|
|
chunks: [dynamic]PNG_Chunk,
|
|
}
|
|
|
|
PNG_Chunk_Header :: struct #packed {
|
|
length: u32be,
|
|
type: PNG_Chunk_Type,
|
|
}
|
|
|
|
PNG_Chunk :: struct #packed {
|
|
header: PNG_Chunk_Header,
|
|
data: []byte,
|
|
crc: u32be,
|
|
}
|
|
|
|
PNG_Chunk_Type :: enum u32be {
|
|
// IHDR must come first in a file
|
|
IHDR = 'I' << 24 | 'H' << 16 | 'D' << 8 | 'R',
|
|
// PLTE must precede the first IDAT chunk
|
|
PLTE = 'P' << 24 | 'L' << 16 | 'T' << 8 | 'E',
|
|
bKGD = 'b' << 24 | 'K' << 16 | 'G' << 8 | 'D',
|
|
tRNS = 't' << 24 | 'R' << 16 | 'N' << 8 | 'S',
|
|
IDAT = 'I' << 24 | 'D' << 16 | 'A' << 8 | 'T',
|
|
|
|
iTXt = 'i' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
|
tEXt = 't' << 24 | 'E' << 16 | 'X' << 8 | 't',
|
|
zTXt = 'z' << 24 | 'T' << 16 | 'X' << 8 | 't',
|
|
|
|
iCCP = 'i' << 24 | 'C' << 16 | 'C' << 8 | 'P',
|
|
pHYs = 'p' << 24 | 'H' << 16 | 'Y' << 8 | 's',
|
|
gAMA = 'g' << 24 | 'A' << 16 | 'M' << 8 | 'A',
|
|
tIME = 't' << 24 | 'I' << 16 | 'M' << 8 | 'E',
|
|
|
|
sPLT = 's' << 24 | 'P' << 16 | 'L' << 8 | 'T',
|
|
sRGB = 's' << 24 | 'R' << 16 | 'G' << 8 | 'B',
|
|
hIST = 'h' << 24 | 'I' << 16 | 'S' << 8 | 'T',
|
|
cHRM = 'c' << 24 | 'H' << 16 | 'R' << 8 | 'M',
|
|
sBIT = 's' << 24 | 'B' << 16 | 'I' << 8 | 'T',
|
|
|
|
/*
|
|
eXIf tags are not part of the core spec, but have been ratified
|
|
in v1.5.0 of the PNG Ext register.
|
|
|
|
We will provide unprocessed chunks to the caller if `.return_metadata` is set.
|
|
Applications are free to implement an Exif decoder.
|
|
*/
|
|
eXIf = 'e' << 24 | 'X' << 16 | 'I' << 8 | 'f',
|
|
|
|
// PNG files must end with IEND
|
|
IEND = 'I' << 24 | 'E' << 16 | 'N' << 8 | 'D',
|
|
|
|
/*
|
|
XCode sometimes produces "PNG" files that don't adhere to the PNG spec.
|
|
We recognize them only in order to avoid doing further work on them.
|
|
|
|
Some tools like PNG Defry may be able to repair them, but we're not
|
|
going to reward Apple for producing proprietary broken files purporting
|
|
to be PNGs by supporting them.
|
|
|
|
*/
|
|
iDOT = 'i' << 24 | 'D' << 16 | 'O' << 8 | 'T',
|
|
CgBI = 'C' << 24 | 'g' << 16 | 'B' << 8 | 'I',
|
|
}
|
|
|
|
PNG_IHDR :: struct #packed {
|
|
width: u32be,
|
|
height: u32be,
|
|
bit_depth: u8,
|
|
color_type: PNG_Color_Type,
|
|
compression_method: u8,
|
|
filter_method: u8,
|
|
interlace_method: PNG_Interlace_Method,
|
|
}
|
|
PNG_IHDR_SIZE :: size_of(PNG_IHDR)
|
|
#assert (PNG_IHDR_SIZE == 13)
|
|
|
|
PNG_Color_Value :: enum u8 {
|
|
Paletted = 0, // 1 << 0 = 1
|
|
Color = 1, // 1 << 1 = 2
|
|
Alpha = 2, // 1 << 2 = 4
|
|
}
|
|
PNG_Color_Type :: distinct bit_set[PNG_Color_Value; u8]
|
|
|
|
PNG_Interlace_Method :: enum u8 {
|
|
None = 0,
|
|
Adam7 = 1,
|
|
}
|
|
|
|
/*
|
|
QOI-specific definitions
|
|
*/
|
|
QOI_Error :: enum {
|
|
None = 0,
|
|
Missing_Or_Corrupt_Trailer, // Image seemed to have decoded okay, but trailer is missing or corrupt.
|
|
}
|
|
|
|
QOI_Magic :: u32be(0x716f6966) // "qoif"
|
|
|
|
QOI_Color_Space :: enum u8 {
|
|
sRGB = 0,
|
|
Linear = 1,
|
|
}
|
|
|
|
QOI_Header :: struct #packed {
|
|
magic: u32be,
|
|
width: u32be,
|
|
height: u32be,
|
|
channels: u8,
|
|
color_space: QOI_Color_Space,
|
|
}
|
|
#assert(size_of(QOI_Header) == 14)
|
|
|
|
QOI_Info :: struct {
|
|
header: QOI_Header,
|
|
}
|
|
|
|
TGA_Data_Type :: enum u8 {
|
|
No_Image_Data = 0,
|
|
Uncompressed_Color_Mapped = 1,
|
|
Uncompressed_RGB = 2,
|
|
Uncompressed_Black_White = 3,
|
|
Compressed_Color_Mapped = 9,
|
|
Compressed_RGB = 10,
|
|
Compressed_Black_White = 11,
|
|
}
|
|
|
|
TGA_Header :: struct #packed {
|
|
id_length: u8,
|
|
color_map_type: u8,
|
|
data_type_code: TGA_Data_Type,
|
|
color_map_origin: u16le,
|
|
color_map_length: u16le,
|
|
color_map_depth: u8,
|
|
origin: [2]u16le,
|
|
dimensions: [2]u16le,
|
|
bits_per_pixel: u8,
|
|
image_descriptor: u8,
|
|
}
|
|
#assert(size_of(TGA_Header) == 18)
|
|
|
|
New_TGA_Signature :: "TRUEVISION-XFILE.\x00"
|
|
|
|
TGA_Footer :: struct #packed {
|
|
extension_area_offset: u32le,
|
|
developer_directory_offset: u32le,
|
|
signature: [18]u8 `fmt:"s,0"`, // Should match signature if New TGA.
|
|
}
|
|
#assert(size_of(TGA_Footer) == 26)
|
|
|
|
TGA_Extension :: struct #packed {
|
|
extension_size: u16le, // Size of this struct. If not 495 bytes it means it's an unsupported version.
|
|
author_name: [41]u8 `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
|
|
author_comments: [324]u8 `fmt:"s,0"`, // Author comments, formatted as 4 lines of 80 character lines, each zero terminated.
|
|
datetime: struct {month, day, year, hour, minute, second: u16le},
|
|
job_name: [41]u8 `fmt:"s,0"`, // Author name, ASCII. Zero-terminated
|
|
job_time: struct {hour, minute, second: u16le},
|
|
software_id: [41]u8 `fmt:"s,0"`, // Software ID name, ASCII. Zero-terminated
|
|
software_version: struct #packed {
|
|
number: u16le, // Version number * 100
|
|
letter: u8 `fmt:"r"`, // " " if not used
|
|
},
|
|
key_color: [4]u8, // ARGB key color used at time of production
|
|
aspect_ratio: [2]u16le, // Numerator / Denominator
|
|
gamma: [2]u16le, // Numerator / Denominator, range should be 0.0..10.0
|
|
color_correction_offset: u32le, // 0 if no color correction information
|
|
postage_stamp_offset: u32le, // 0 if no thumbnail
|
|
scanline_offset: u32le, // 0 if no scanline table
|
|
attributes: TGA_Alpha_Kind,
|
|
}
|
|
#assert(size_of(TGA_Extension) == 495)
|
|
|
|
TGA_Alpha_Kind :: enum u8 {
|
|
None,
|
|
Undefined_Ignore,
|
|
Undefined_Retain,
|
|
Useful,
|
|
Premultiplied,
|
|
}
|
|
|
|
TGA_Info :: struct {
|
|
header: TGA_Header,
|
|
image_id: string,
|
|
footer: Maybe(TGA_Footer),
|
|
extension: Maybe(TGA_Extension),
|
|
}
|
|
|
|
// 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
|
|
return
|
|
}
|
|
|
|
Channel :: enum u8 {
|
|
R = 1,
|
|
G = 2,
|
|
B = 3,
|
|
A = 4,
|
|
}
|
|
|
|
// When you have an RGB(A) image, but want a particular channel.
|
|
return_single_channel :: proc(img: ^Image, channel: Channel) -> (res: ^Image, ok: bool) {
|
|
// Were we actually given a valid image?
|
|
if img == nil {
|
|
return nil, false
|
|
}
|
|
|
|
ok = false
|
|
t: bytes.Buffer
|
|
|
|
idx := int(channel)
|
|
|
|
if img.channels == 2 && idx == 4 {
|
|
// Alpha requested, which in a two channel image is index 2: G.
|
|
idx = 2
|
|
}
|
|
|
|
if idx > img.channels {
|
|
return {}, false
|
|
}
|
|
|
|
switch img.depth {
|
|
case 8:
|
|
buffer_size := compute_buffer_size(img.width, img.height, 1, 8)
|
|
t = bytes.Buffer{}
|
|
resize(&t.buf, buffer_size)
|
|
|
|
i := bytes.buffer_to_bytes(&img.pixels)
|
|
o := bytes.buffer_to_bytes(&t)
|
|
|
|
for len(i) > 0 {
|
|
o[0] = i[idx]
|
|
i = i[img.channels:]
|
|
o = o[1:]
|
|
}
|
|
case 16:
|
|
buffer_size := compute_buffer_size(img.width, img.height, 1, 16)
|
|
t = bytes.Buffer{}
|
|
resize(&t.buf, buffer_size)
|
|
|
|
i := mem.slice_data_cast([]u16, img.pixels.buf[:])
|
|
o := mem.slice_data_cast([]u16, t.buf[:])
|
|
|
|
for len(i) > 0 {
|
|
o[0] = i[idx]
|
|
i = i[img.channels:]
|
|
o = o[1:]
|
|
}
|
|
case 1, 2, 4:
|
|
// We shouldn't see this case, as the loader already turns these into 8-bit.
|
|
return {}, false
|
|
}
|
|
|
|
res = new(Image)
|
|
res.width = img.width
|
|
res.height = img.height
|
|
res.channels = 1
|
|
res.depth = img.depth
|
|
res.pixels = t
|
|
res.background = img.background
|
|
res.metadata = img.metadata
|
|
|
|
return res, true
|
|
}
|
|
|
|
// Does the image have 1 or 2 channels, a valid bit depth (8 or 16),
|
|
// Is the pointer valid, are the dimensions valid?
|
|
is_valid_grayscale_image :: proc(img: ^Image) -> (ok: bool) {
|
|
// Were we actually given a valid image?
|
|
if img == nil {
|
|
return false
|
|
}
|
|
|
|
// Are we a Gray or Gray + Alpha image?
|
|
if img.channels != 1 && img.channels != 2 {
|
|
return false
|
|
}
|
|
|
|
// Do we have an acceptable bit depth?
|
|
if img.depth != 8 && img.depth != 16 {
|
|
return false
|
|
}
|
|
|
|
// This returns 0 if any of the inputs is zero.
|
|
bytes_expected := compute_buffer_size(img.width, img.height, img.channels, img.depth)
|
|
|
|
// If the dimensions are invalid or the buffer size doesn't match the image characteristics, bail.
|
|
if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Does the image have 3 or 4 channels, a valid bit depth (8 or 16),
|
|
// Is the pointer valid, are the dimensions valid?
|
|
is_valid_color_image :: proc(img: ^Image) -> (ok: bool) {
|
|
// Were we actually given a valid image?
|
|
if img == nil {
|
|
return false
|
|
}
|
|
|
|
// Are we an RGB or RGBA image?
|
|
if img.channels != 3 && img.channels != 4 {
|
|
return false
|
|
}
|
|
|
|
// Do we have an acceptable bit depth?
|
|
if img.depth != 8 && img.depth != 16 {
|
|
return false
|
|
}
|
|
|
|
// This returns 0 if any of the inputs is zero.
|
|
bytes_expected := compute_buffer_size(img.width, img.height, img.channels, img.depth)
|
|
|
|
// If the dimensions are invalid or the buffer size doesn't match the image characteristics, bail.
|
|
if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
// Does the image have 1..4 channels, a valid bit depth (8 or 16),
|
|
// Is the pointer valid, are the dimensions valid?
|
|
is_valid_image :: proc(img: ^Image) -> (ok: bool) {
|
|
// Were we actually given a valid image?
|
|
if img == nil {
|
|
return false
|
|
}
|
|
|
|
return is_valid_color_image(img) || is_valid_grayscale_image(img)
|
|
}
|
|
|
|
Alpha_Key :: union {
|
|
GA_Pixel,
|
|
RGBA_Pixel,
|
|
GA_Pixel_16,
|
|
RGBA_Pixel_16,
|
|
}
|
|
|
|
/*
|
|
Add alpha channel if missing, in-place.
|
|
|
|
Expects 1..4 channels (Gray, Gray + Alpha, RGB, RGBA).
|
|
Any other number of channels will be considered an error, returning `false` without modifying the image.
|
|
If the input image already has an alpha channel, it'll return `true` early (without considering optional keyed alpha).
|
|
|
|
If an image doesn't already have an alpha channel:
|
|
If the optional `alpha_key` is provided, it will be resolved as follows:
|
|
- For RGB, if pix = key.rgb -> pix = {0, 0, 0, key.a}
|
|
- For Gray, if pix = key.r -> pix = {0, key.g}
|
|
Otherwise, an opaque alpha channel will be added.
|
|
*/
|
|
alpha_add_if_missing :: proc(img: ^Image, alpha_key := Alpha_Key{}, allocator := context.allocator) -> (ok: bool) {
|
|
context.allocator = allocator
|
|
|
|
if !is_valid_image(img) {
|
|
return false
|
|
}
|
|
|
|
// We should now have a valid Image with 1..4 channels. Do we already have alpha?
|
|
if img.channels == 2 || img.channels == 4 {
|
|
// We're done.
|
|
return true
|
|
}
|
|
|
|
channels := img.channels + 1
|
|
bytes_wanted := compute_buffer_size(img.width, img.height, channels, img.depth)
|
|
|
|
buf := bytes.Buffer{}
|
|
|
|
// Can we allocate the return buffer?
|
|
if resize(&buf.buf, bytes_wanted) != nil {
|
|
delete(buf.buf)
|
|
return false
|
|
}
|
|
|
|
switch img.depth {
|
|
case 8:
|
|
switch channels {
|
|
case 2:
|
|
// Turn Gray into Gray + Alpha
|
|
inp := mem.slice_data_cast([]G_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]GA_Pixel, buf.buf[:])
|
|
|
|
if key, key_ok := alpha_key.(GA_Pixel); key_ok {
|
|
// We have keyed alpha.
|
|
o: GA_Pixel
|
|
for p in inp {
|
|
if p.r == key.r {
|
|
o = GA_Pixel{0, key.g}
|
|
} else {
|
|
o = GA_Pixel{p.r, 255}
|
|
}
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// No keyed alpha, just make all pixels opaque.
|
|
o := GA_Pixel{0, 255}
|
|
for p in inp {
|
|
o.r = p.r
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
}
|
|
|
|
case 4:
|
|
// Turn RGB into RGBA
|
|
inp := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:])
|
|
|
|
if key, key_ok := alpha_key.(RGBA_Pixel); key_ok {
|
|
// We have keyed alpha.
|
|
o: RGBA_Pixel
|
|
for p in inp {
|
|
if p == key.rgb {
|
|
o = RGBA_Pixel{0, 0, 0, key.a}
|
|
} else {
|
|
o = RGBA_Pixel{p.r, p.g, p.b, 255}
|
|
}
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// No keyed alpha, just make all pixels opaque.
|
|
o := RGBA_Pixel{0, 0, 0, 255}
|
|
for p in inp {
|
|
o.rgb = p
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
}
|
|
case:
|
|
// We shouldn't get here.
|
|
unreachable()
|
|
}
|
|
case 16:
|
|
switch channels {
|
|
case 2:
|
|
// Turn Gray into Gray + Alpha
|
|
inp := mem.slice_data_cast([]G_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]GA_Pixel_16, buf.buf[:])
|
|
|
|
if key, key_ok := alpha_key.(GA_Pixel_16); key_ok {
|
|
// We have keyed alpha.
|
|
o: GA_Pixel_16
|
|
for p in inp {
|
|
if p.r == key.r {
|
|
o = GA_Pixel_16{0, key.g}
|
|
} else {
|
|
o = GA_Pixel_16{p.r, 65535}
|
|
}
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// No keyed alpha, just make all pixels opaque.
|
|
o := GA_Pixel_16{0, 65535}
|
|
for p in inp {
|
|
o.r = p.r
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
}
|
|
|
|
case 4:
|
|
// Turn RGB into RGBA
|
|
inp := mem.slice_data_cast([]RGB_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:])
|
|
|
|
if key, key_ok := alpha_key.(RGBA_Pixel_16); key_ok {
|
|
// We have keyed alpha.
|
|
o: RGBA_Pixel_16
|
|
for p in inp {
|
|
if p == key.rgb {
|
|
o = RGBA_Pixel_16{0, 0, 0, key.a}
|
|
} else {
|
|
o = RGBA_Pixel_16{p.r, p.g, p.b, 65535}
|
|
}
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// No keyed alpha, just make all pixels opaque.
|
|
o := RGBA_Pixel_16{0, 0, 0, 65535}
|
|
for p in inp {
|
|
o.rgb = p
|
|
out[0] = o
|
|
out = out[1:]
|
|
}
|
|
}
|
|
case:
|
|
// We shouldn't get here.
|
|
unreachable()
|
|
}
|
|
}
|
|
|
|
// If we got here, that means we've now got a buffer with the alpha channel added.
|
|
// Destroy the old pixel buffer and replace it with the new one, and update the channel count.
|
|
bytes.buffer_destroy(&img.pixels)
|
|
img.pixels = buf
|
|
img.channels = channels
|
|
return true
|
|
}
|
|
alpha_apply_keyed_alpha :: alpha_add_if_missing
|
|
|
|
/*
|
|
Drop alpha channel if present, in-place.
|
|
|
|
Expects 1..4 channels (Gray, Gray + Alpha, RGB, RGBA).
|
|
Any other number of channels will be considered an error, returning `false` without modifying the image.
|
|
|
|
Of the `options`, the following are considered:
|
|
`.alpha_premultiply`
|
|
If the image has an alpha channel, returns image data as follows:
|
|
RGB *= A, Gray = Gray *= A
|
|
|
|
`.blend_background`
|
|
If `img.background` is set, it'll be blended in like this:
|
|
RGB = (1 - A) * Background + A * RGB
|
|
|
|
If an image has 1 (Gray) or 3 (RGB) channels, it'll return early without modifying the image,
|
|
with one exception: `alpha_key` and `img.background` are present, and `.blend_background` is set.
|
|
|
|
In this case a keyed alpha pixel will be replaced with the background color.
|
|
*/
|
|
alpha_drop_if_present :: proc(img: ^Image, options := Options{}, alpha_key := Alpha_Key{}, allocator := context.allocator) -> (ok: bool) {
|
|
context.allocator = allocator
|
|
|
|
if !is_valid_image(img) {
|
|
return false
|
|
}
|
|
|
|
// Do we have a background to blend?
|
|
will_it_blend := false
|
|
switch v in img.background {
|
|
case RGB_Pixel_16: will_it_blend = true if .blend_background in options else false
|
|
}
|
|
|
|
// Do we have keyed alpha?
|
|
keyed := false
|
|
switch v in alpha_key {
|
|
case GA_Pixel: keyed = true if img.channels == 1 && img.depth == 8 else false
|
|
case RGBA_Pixel: keyed = true if img.channels == 3 && img.depth == 8 else false
|
|
case GA_Pixel_16: keyed = true if img.channels == 1 && img.depth == 16 else false
|
|
case RGBA_Pixel_16: keyed = true if img.channels == 3 && img.depth == 16 else false
|
|
}
|
|
|
|
// We should now have a valid Image with 1..4 channels. Do we have alpha?
|
|
if img.channels == 1 || img.channels == 3 {
|
|
if !(will_it_blend && keyed) {
|
|
// We're done
|
|
return true
|
|
}
|
|
}
|
|
|
|
// # of destination channels
|
|
channels := 1 if img.channels < 3 else 3
|
|
|
|
bytes_wanted := compute_buffer_size(img.width, img.height, channels, img.depth)
|
|
buf := bytes.Buffer{}
|
|
|
|
// Can we allocate the return buffer?
|
|
if resize(&buf.buf, bytes_wanted) != nil {
|
|
delete(buf.buf)
|
|
return false
|
|
}
|
|
|
|
switch img.depth {
|
|
case 8:
|
|
switch img.channels {
|
|
case 1: // Gray to Gray, but we should have keyed alpha + background.
|
|
inp := mem.slice_data_cast([]G_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]G_Pixel, buf.buf[:])
|
|
|
|
key := alpha_key.(GA_Pixel).r
|
|
bg := G_Pixel{}
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, take just the red channel's topmost byte.
|
|
bg.r = u8(temp_bg.r >> 8)
|
|
}
|
|
|
|
for p in inp {
|
|
out[0] = bg if p.r == key else p
|
|
out = out[1:]
|
|
}
|
|
|
|
case 2: // Gray + Alpha to Gray, no keyed alpha but we can have a background.
|
|
inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]G_Pixel, buf.buf[:])
|
|
|
|
if will_it_blend {
|
|
// Blend with background "color", then drop alpha.
|
|
bg := f32(0.0)
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, take just the red channel's topmost byte.
|
|
bg = f32(temp_bg.r >> 8)
|
|
}
|
|
|
|
for p in inp {
|
|
a := f32(p.g) / 255.0
|
|
c := ((1.0 - a) * bg + a * f32(p.r))
|
|
out[0].r = u8(c)
|
|
out = out[1:]
|
|
}
|
|
|
|
} else if .alpha_premultiply in options {
|
|
// Premultiply component with alpha, then drop alpha.
|
|
for p in inp {
|
|
a := f32(p.g) / 255.0
|
|
c := f32(p.r) * a
|
|
out[0].r = u8(c)
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// Just drop alpha on the floor.
|
|
for p in inp {
|
|
out[0].r = p.r
|
|
out = out[1:]
|
|
}
|
|
}
|
|
|
|
case 3: // RGB to RGB, but we should have keyed alpha + background.
|
|
inp := mem.slice_data_cast([]RGB_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:])
|
|
|
|
key := alpha_key.(RGBA_Pixel)
|
|
bg := RGB_Pixel{}
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, squash down to 8 bits.
|
|
bg = {u8(temp_bg.r >> 8), u8(temp_bg.g >> 8), u8(temp_bg.b >> 8)}
|
|
}
|
|
|
|
for p in inp {
|
|
out[0] = bg if p == key.rgb else p
|
|
out = out[1:]
|
|
}
|
|
|
|
case 4: // RGBA to RGB, no keyed alpha but we can have a background or need to premultiply.
|
|
inp := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:])
|
|
|
|
if will_it_blend {
|
|
// Blend with background "color", then drop alpha.
|
|
bg := [3]f32{}
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, take just the red channel's topmost byte.
|
|
bg = {f32(temp_bg.r >> 8), f32(temp_bg.g >> 8), f32(temp_bg.b >> 8)}
|
|
}
|
|
|
|
for p in inp {
|
|
a := f32(p.a) / 255.0
|
|
rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)}
|
|
c := ((1.0 - a) * bg + a * rgb)
|
|
|
|
out[0] = {u8(c.r), u8(c.g), u8(c.b)}
|
|
out = out[1:]
|
|
}
|
|
|
|
} else if .alpha_premultiply in options {
|
|
// Premultiply component with alpha, then drop alpha.
|
|
for p in inp {
|
|
a := f32(p.a) / 255.0
|
|
rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)}
|
|
c := rgb * a
|
|
|
|
out[0] = {u8(c.r), u8(c.g), u8(c.b)}
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// Just drop alpha on the floor.
|
|
for p in inp {
|
|
out[0] = p.rgb
|
|
out = out[1:]
|
|
}
|
|
}
|
|
}
|
|
|
|
case 16:
|
|
switch img.channels {
|
|
case 1: // Gray to Gray, but we should have keyed alpha + background.
|
|
inp := mem.slice_data_cast([]G_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]G_Pixel_16, buf.buf[:])
|
|
|
|
key := alpha_key.(GA_Pixel_16).r
|
|
bg := G_Pixel_16{}
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, take just the red channel.
|
|
bg.r = temp_bg.r
|
|
}
|
|
|
|
for p in inp {
|
|
out[0] = bg if p.r == key else p
|
|
out = out[1:]
|
|
}
|
|
|
|
case 2: // Gray + Alpha to Gray, no keyed alpha but we can have a background.
|
|
inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]G_Pixel_16, buf.buf[:])
|
|
|
|
if will_it_blend {
|
|
// Blend with background "color", then drop alpha.
|
|
bg := f32(0.0)
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, take just the red channel.
|
|
bg = f32(temp_bg.r)
|
|
}
|
|
|
|
for p in inp {
|
|
a := f32(p.g) / 65535.0
|
|
c := ((1.0 - a) * bg + a * f32(p.r))
|
|
out[0].r = u16(c)
|
|
out = out[1:]
|
|
}
|
|
|
|
} else if .alpha_premultiply in options {
|
|
// Premultiply component with alpha, then drop alpha.
|
|
for p in inp {
|
|
a := f32(p.g) / 65535.0
|
|
c := f32(p.r) * a
|
|
out[0].r = u16(c)
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// Just drop alpha on the floor.
|
|
for p in inp {
|
|
out[0].r = p.r
|
|
out = out[1:]
|
|
}
|
|
}
|
|
|
|
case 3: // RGB to RGB, but we should have keyed alpha + background.
|
|
inp := mem.slice_data_cast([]RGB_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:])
|
|
|
|
key := alpha_key.(RGBA_Pixel_16)
|
|
bg := img.background.(RGB_Pixel_16)
|
|
|
|
for p in inp {
|
|
out[0] = bg if p == key.rgb else p
|
|
out = out[1:]
|
|
}
|
|
|
|
case 4: // RGBA to RGB, no keyed alpha but we can have a background or need to premultiply.
|
|
inp := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:])
|
|
|
|
if will_it_blend {
|
|
// Blend with background "color", then drop alpha.
|
|
bg := [3]f32{}
|
|
if temp_bg, temp_bg_ok := img.background.(RGB_Pixel_16); temp_bg_ok {
|
|
// Background is RGB 16-bit, convert to [3]f32 to blend.
|
|
bg = {f32(temp_bg.r), f32(temp_bg.g), f32(temp_bg.b)}
|
|
}
|
|
|
|
for p in inp {
|
|
a := f32(p.a) / 65535.0
|
|
rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)}
|
|
c := ((1.0 - a) * bg + a * rgb)
|
|
|
|
out[0] = {u16(c.r), u16(c.g), u16(c.b)}
|
|
out = out[1:]
|
|
}
|
|
|
|
} else if .alpha_premultiply in options {
|
|
// Premultiply component with alpha, then drop alpha.
|
|
for p in inp {
|
|
a := f32(p.a) / 65535.0
|
|
rgb := [3]f32{f32(p.r), f32(p.g), f32(p.b)}
|
|
c := rgb * a
|
|
|
|
out[0] = {u16(c.r), u16(c.g), u16(c.b)}
|
|
out = out[1:]
|
|
}
|
|
} else {
|
|
// Just drop alpha on the floor.
|
|
for p in inp {
|
|
out[0] = p.rgb
|
|
out = out[1:]
|
|
}
|
|
}
|
|
}
|
|
|
|
case:
|
|
unreachable()
|
|
}
|
|
|
|
// If we got here, that means we've now got a buffer with the alpha channel dropped.
|
|
// Destroy the old pixel buffer and replace it with the new one, and update the channel count.
|
|
bytes.buffer_destroy(&img.pixels)
|
|
img.pixels = buf
|
|
img.channels = channels
|
|
return true
|
|
}
|
|
|
|
// Apply palette to 8-bit single-channel image and return an 8-bit RGB image, in-place.
|
|
// If the image given is not a valid 8-bit single channel image, the procedure will return `false` early.
|
|
apply_palette_rgb :: proc(img: ^Image, palette: [256]RGB_Pixel, allocator := context.allocator) -> (ok: bool) {
|
|
context.allocator = allocator
|
|
|
|
if img == nil || img.channels != 1 || img.depth != 8 {
|
|
return false
|
|
}
|
|
|
|
bytes_expected := compute_buffer_size(img.width, img.height, 1, 8)
|
|
if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS {
|
|
return false
|
|
}
|
|
|
|
// Can we allocate the return buffer?
|
|
buf := bytes.Buffer{}
|
|
bytes_wanted := compute_buffer_size(img.width, img.height, 3, 8)
|
|
if resize(&buf.buf, bytes_wanted) != nil {
|
|
delete(buf.buf)
|
|
return false
|
|
}
|
|
|
|
out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:])
|
|
|
|
// Apply the palette
|
|
for p, i in img.pixels.buf {
|
|
out[i] = palette[p]
|
|
}
|
|
|
|
// If we got here, that means we've now got a buffer with the alpha channel dropped.
|
|
// Destroy the old pixel buffer and replace it with the new one, and update the channel count.
|
|
bytes.buffer_destroy(&img.pixels)
|
|
img.pixels = buf
|
|
img.channels = 3
|
|
return true
|
|
}
|
|
|
|
// Apply palette to 8-bit single-channel image and return an 8-bit RGBA image, in-place.
|
|
// If the image given is not a valid 8-bit single channel image, the procedure will return `false` early.
|
|
apply_palette_rgba :: proc(img: ^Image, palette: [256]RGBA_Pixel, allocator := context.allocator) -> (ok: bool) {
|
|
context.allocator = allocator
|
|
|
|
if img == nil || img.channels != 1 || img.depth != 8 {
|
|
return false
|
|
}
|
|
|
|
bytes_expected := compute_buffer_size(img.width, img.height, 1, 8)
|
|
if bytes_expected == 0 || bytes_expected != len(img.pixels.buf) || img.width * img.height > MAX_DIMENSIONS {
|
|
return false
|
|
}
|
|
|
|
// Can we allocate the return buffer?
|
|
buf := bytes.Buffer{}
|
|
bytes_wanted := compute_buffer_size(img.width, img.height, 4, 8)
|
|
if resize(&buf.buf, bytes_wanted) != nil {
|
|
delete(buf.buf)
|
|
return false
|
|
}
|
|
|
|
out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:])
|
|
|
|
// Apply the palette
|
|
for p, i in img.pixels.buf {
|
|
out[i] = palette[p]
|
|
}
|
|
|
|
// If we got here, that means we've now got a buffer with the alpha channel dropped.
|
|
// Destroy the old pixel buffer and replace it with the new one, and update the channel count.
|
|
bytes.buffer_destroy(&img.pixels)
|
|
img.pixels = buf
|
|
img.channels = 4
|
|
return true
|
|
}
|
|
apply_palette :: proc{apply_palette_rgb, apply_palette_rgba}
|
|
|
|
|
|
// Replicates grayscale values into RGB(A) 8- or 16-bit images as appropriate.
|
|
// Returns early with `false` if already an RGB(A) image.
|
|
expand_grayscale :: proc(img: ^Image, allocator := context.allocator) -> (ok: bool) {
|
|
context.allocator = allocator
|
|
|
|
if !is_valid_grayscale_image(img) {
|
|
return false
|
|
}
|
|
|
|
// We should have 1 or 2 channels of 8- or 16 bits now. We need to turn that into 3 or 4.
|
|
// Can we allocate the return buffer?
|
|
buf := bytes.Buffer{}
|
|
bytes_wanted := compute_buffer_size(img.width, img.height, img.channels + 2, img.depth)
|
|
if resize(&buf.buf, bytes_wanted) != nil {
|
|
delete(buf.buf)
|
|
return false
|
|
}
|
|
|
|
switch img.depth {
|
|
case 8:
|
|
switch img.channels {
|
|
case 1: // Turn Gray into RGB
|
|
out := mem.slice_data_cast([]RGB_Pixel, buf.buf[:])
|
|
|
|
for p in img.pixels.buf {
|
|
out[0] = p // Broadcast gray value into RGB components.
|
|
out = out[1:]
|
|
}
|
|
|
|
case 2: // Turn Gray + Alpha into RGBA
|
|
inp := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGBA_Pixel, buf.buf[:])
|
|
|
|
for p in inp {
|
|
out[0].rgb = p.r // Gray component.
|
|
out[0].a = p.g // Alpha component.
|
|
}
|
|
|
|
case:
|
|
unreachable()
|
|
}
|
|
|
|
case 16:
|
|
switch img.channels {
|
|
case 1: // Turn Gray into RGB
|
|
inp := mem.slice_data_cast([]u16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGB_Pixel_16, buf.buf[:])
|
|
|
|
for p in inp {
|
|
out[0] = p // Broadcast gray value into RGB components.
|
|
out = out[1:]
|
|
}
|
|
|
|
case 2: // Turn Gray + Alpha into RGBA
|
|
inp := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:])
|
|
out := mem.slice_data_cast([]RGBA_Pixel_16, buf.buf[:])
|
|
|
|
for p in inp {
|
|
out[0].rgb = p.r // Gray component.
|
|
out[0].a = p.g // Alpha component.
|
|
}
|
|
|
|
case:
|
|
unreachable()
|
|
}
|
|
|
|
case:
|
|
unreachable()
|
|
}
|
|
|
|
|
|
// If we got here, that means we've now got a buffer with the extra alpha channel.
|
|
// Destroy the old pixel buffer and replace it with the new one, and update the channel count.
|
|
bytes.buffer_destroy(&img.pixels)
|
|
img.pixels = buf
|
|
img.channels += 2
|
|
return true
|
|
}
|
|
|
|
/*
|
|
Helper functions to read and write data from/to a Context, etc.
|
|
*/
|
|
@(optimization_mode="speed")
|
|
read_data :: proc(z: $C, $T: typeid) -> (res: T, err: compress.General_Error) {
|
|
if r, e := compress.read_data(z, T); e != .None {
|
|
return {}, .Stream_Too_Short
|
|
} else {
|
|
return r, nil
|
|
}
|
|
}
|
|
|
|
@(optimization_mode="speed")
|
|
read_u8 :: proc(z: $C) -> (res: u8, err: compress.General_Error) {
|
|
if r, e := compress.read_u8(z); e != .None {
|
|
return {}, .Stream_Too_Short
|
|
} else {
|
|
return r, nil
|
|
}
|
|
}
|
|
|
|
write_bytes :: proc(buf: ^bytes.Buffer, data: []u8) -> (err: compress.General_Error) {
|
|
if len(data) == 0 {
|
|
return nil
|
|
} else if len(data) == 1 {
|
|
if bytes.buffer_write_byte(buf, data[0]) != nil {
|
|
return .Resize_Failed
|
|
}
|
|
} else if n, _ := bytes.buffer_write(buf, data); n != len(data) {
|
|
return .Resize_Failed
|
|
}
|
|
return nil
|
|
}
|