| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615 |
- /*
- Copyright 2021 Jeroen van Rijn <[email protected]>.
- 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:io"
- 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,
- ^BMP_Info,
- ^JPEG_Info,
- }
- Exif :: struct {
- byte_order: enum {
- little_endian,
- big_endian,
- },
- data: []u8 `fmt:"-"`,
- }
- /*
- 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,
- BMP_Error,
- JPEG_Error,
- compress.Error,
- compress.General_Error,
- compress.Deflate_Error,
- compress.ZLIB_Error,
- io.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,
- }
- /*
- BMP-specific
- */
- BMP_Error :: enum {
- None = 0,
- Invalid_File_Size,
- Unsupported_BMP_Version,
- Unsupported_OS2_File,
- Unsupported_Compression,
- Unsupported_BPP,
- Invalid_Stride,
- Invalid_Color_Count,
- Implausible_File_Size,
- Bitfield_Version_Unhandled, // We don't (yet) handle bit fields for this BMP version.
- Bitfield_Sum_Exceeds_BPP, // Total mask bit count > bpp
- Bitfield_Overlapped, // Channel masks overlap
- }
- // img.metadata is wrapped in a struct in case we need to add to it later
- // without putting it in BMP_Header
- BMP_Info :: struct {
- info: BMP_Header,
- }
- BMP_Magic :: enum u16le {
- Bitmap = 0x4d42, // 'BM'
- OS2_Bitmap_Array = 0x4142, // 'BA'
- OS2_Icon = 0x4349, // 'IC',
- OS2_Color_Icon = 0x4943, // 'CI'
- OS2_Pointer = 0x5450, // 'PT'
- OS2_Color_Pointer = 0x5043, // 'CP'
- }
- // See: http://justsolve.archiveteam.org/wiki/BMP#Well-known_versions
- BMP_Version :: enum u32le {
- OS2_v1 = 12, // BITMAPCOREHEADER (Windows V2 / OS/2 version 1.0)
- OS2_v2 = 64, // BITMAPCOREHEADER2 (OS/2 version 2.x)
- V3 = 40, // BITMAPINFOHEADER
- V4 = 108, // BITMAPV4HEADER
- V5 = 124, // BITMAPV5HEADER
- ABBR_16 = 16, // Abbreviated
- ABBR_24 = 24, // ..
- ABBR_48 = 48, // ..
- ABBR_52 = 52, // ..
- ABBR_56 = 56, // ..
- }
- BMP_Header :: struct #packed {
- // File header
- magic: BMP_Magic,
- size: u32le,
- _res1: u16le, // Reserved; must be zero
- _res2: u16le, // Reserved; must be zero
- pixel_offset: u32le, // Offset in bytes, from the beginning of BMP_Header to the pixel data
- // V3
- info_size: BMP_Version,
- width: i32le,
- height: i32le,
- planes: u16le,
- bpp: u16le,
- compression: BMP_Compression,
- image_size: u32le,
- pels_per_meter: [2]u32le,
- colors_used: u32le,
- colors_important: u32le, // OS2_v2 is equal up to here
- // V4
- masks: [4]u32le `fmt:"32b"`,
- colorspace: BMP_Logical_Color_Space,
- endpoints: BMP_CIEXYZTRIPLE,
- gamma: [3]BMP_GAMMA16_16,
- // V5
- intent: BMP_Gamut_Mapping_Intent,
- profile_data: u32le,
- profile_size: u32le,
- reserved: u32le,
- }
- #assert(size_of(BMP_Header) == 138)
- OS2_Header :: struct #packed {
- // BITMAPCOREHEADER minus info_size field
- width: i16le,
- height: i16le,
- planes: u16le,
- bpp: u16le,
- }
- #assert(size_of(OS2_Header) == 8)
- BMP_Compression :: enum u32le {
- RGB = 0x0000,
- RLE8 = 0x0001,
- RLE4 = 0x0002,
- Bit_Fields = 0x0003, // If Windows
- Huffman1D = 0x0003, // If OS2v2
- JPEG = 0x0004, // If Windows
- RLE24 = 0x0004, // If OS2v2
- PNG = 0x0005,
- Alpha_Bit_Fields = 0x0006,
- CMYK = 0x000B,
- CMYK_RLE8 = 0x000C,
- CMYK_RLE4 = 0x000D,
- }
- BMP_Logical_Color_Space :: enum u32le {
- CALIBRATED_RGB = 0x00000000,
- sRGB = 0x73524742, // 'sRGB'
- WINDOWS_COLOR_SPACE = 0x57696E20, // 'Win '
- }
- BMP_FXPT2DOT30 :: u32le
- BMP_CIEXYZ :: [3]BMP_FXPT2DOT30
- BMP_CIEXYZTRIPLE :: [3]BMP_CIEXYZ
- BMP_GAMMA16_16 :: [2]u16le
- BMP_Gamut_Mapping_Intent :: enum u32le {
- INVALID = 0x00000000, // If not V5, this field will just be zero-initialized and not valid.
- ABS_COLORIMETRIC = 0x00000008,
- BUSINESS = 0x00000001,
- GRAPHICS = 0x00000002,
- IMAGES = 0x00000004,
- }
- /*
- 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),
- }
- /*
- JPEG-specific
- */
- JFIF_Magic := [?]byte{0x4A, 0x46, 0x49, 0x46} // "JFIF"
- JFXX_Magic := [?]byte{0x4A, 0x46, 0x58, 0x58} // "JFXX"
- Exif_Magic := [?]byte{0x45, 0x78, 0x69, 0x66} // "Exif"
- JPEG_Error :: enum {
- None = 0,
- Duplicate_SOI_Marker,
- Invalid_JFXX_Extension_Code,
- Encountered_SOS_Before_SOF,
- Invalid_Quantization_Table_Precision,
- Invalid_Quantization_Table_Index,
- Invalid_Huffman_Coefficient_Type,
- Invalid_Huffman_Table_Index,
- Unsupported_Frame_Type,
- Invalid_Frame_Bit_Depth_Combo,
- Invalid_Sampling_Factor,
- Unsupported_12_Bit_Depth,
- Multiple_SOS_Markers,
- Encountered_RST_Marker_Outside_ECS,
- Extra_Data_After_SOS, // Image seemed to have decoded okay, but there's more data after SOS
- Invalid_Thumbnail_Size,
- Huffman_Symbols_Exceeds_Max,
- }
- JFIF_Unit :: enum byte {
- None = 0,
- Dots_Per_Inch = 1,
- Dots_Per_Centimeter = 2,
- }
- JFIF_APP0 :: struct {
- version: u16be,
- x_density: u16be,
- y_density: u16be,
- units: JFIF_Unit,
- x_thumbnail: u8,
- y_thumbnail: u8,
- greyscale_thumbnail: bool,
- thumbnail: []RGB_Pixel `fmt:"-"`,
- }
- JFXX_APP0 :: struct {
- extension_code: JFXX_Extension_Code,
- x_thumbnail: u8,
- y_thumbnail: u8,
- thumbnail: []byte `fmt:"-"`,
- }
- JFXX_Extension_Code :: enum u8 {
- Thumbnail_JPEG = 0x10,
- Thumbnail_1_Byte_Palette = 0x11,
- Thumbnail_3_Byte_RGB = 0x13,
- }
- JPEG_Marker :: enum u8 {
- SOF0 = 0xC0, // Baseline sequential DCT
- SOF1 = 0xC1, // Extended sequential DCT
- SOF2 = 0xC2, // Progressive DCT
- SOF3 = 0xC3, // Lossless (sequential)
- SOF5 = 0xC5, // Differential sequential DCT
- SOF6 = 0xC6, // Differential progressive DCT
- SOF7 = 0xC7, // Differential lossless (sequential)
- SOF9 = 0xC9, // Extended sequential DCT, Arithmetic coding
- SOF10 = 0xCA, // Progressive DCT, Arithmetic coding
- SOF11 = 0xCB, // Lossless (sequential), Arithmetic coding
- SOF13 = 0xCD, // Differential sequential DCT, Arithmetic coding
- SOF14 = 0xCE, // Differential progressive DCT, Arithmetic coding
- SOF15 = 0xCF, // Differential lossless (sequential), Arithmetic coding
- DHT = 0xC4,
- JPG = 0xC8,
- DAC = 0xCC,
- RST0 = 0xD0,
- RST1 = 0xD1,
- RST2 = 0xD2,
- RST3 = 0xD3,
- RST4 = 0xD4,
- RST5 = 0xD5,
- RST6 = 0xD6,
- RST7 = 0xD7,
- SOI = 0xD8,
- EOI = 0xD9,
- SOS = 0xDA,
- DQT = 0xDB,
- DNL = 0xDC,
- DRI = 0xDD,
- DHP = 0xDE,
- EXP = 0xDF,
- APP0 = 0xE0,
- APP1 = 0xE1,
- APP2 = 0xE2,
- APP3 = 0xE3,
- APP4 = 0xE4,
- APP5 = 0xE5,
- APP6 = 0xE6,
- APP7 = 0xE7,
- APP8 = 0xE8,
- APP9 = 0xE9,
- APP10 = 0xEA,
- APP11 = 0xEB,
- APP12 = 0xEC,
- APP13 = 0xED,
- APP14 = 0xEE,
- APP15 = 0xEF,
- JPG0 = 0xF0,
- JPG1 = 0xF1,
- JPG2 = 0xF2,
- JPG3 = 0xF3,
- JPG4 = 0xF4,
- JPG5 = 0xF5,
- JPG6 = 0xF6,
- JPG7 = 0xF7,
- JPG8 = 0xF8,
- JPG9 = 0xF9,
- JPG10 = 0xFA,
- JPG11 = 0xFB,
- JPG12 = 0xFC,
- JPG13 = 0xFD,
- COM = 0xFE,
- TEM = 0x01,
- }
- JPEG_Info :: struct {
- jfif_app0: Maybe(JFIF_APP0),
- jfxx_app0: Maybe(JFXX_APP0),
- comments: [dynamic]string,
- exif: [dynamic]Exif,
- frame_type: JPEG_Marker,
- }
- // 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,
- }
- // Take a slice of pixels (`[]RGBA_Pixel`, etc), and return an `Image`
- // Don't call `destroy` on the resulting `Image`. Instead, delete the original `pixels` slice.
- pixels_to_image :: proc(pixels: [][$N]$E, width: int, height: int) -> (img: Image, ok: bool) where E == u8 || E == u16, N >= 1, N <= 4 {
- if len(pixels) != width * height {
- return {}, false
- }
- img.height = height
- img.width = width
- img.depth = 8 when E == u8 else 16
- img.channels = N
- s := transmute(runtime.Raw_Slice)pixels
- d := runtime.Raw_Dynamic_Array{
- data = s.data,
- len = s.len * size_of(E) * N,
- cap = s.len * size_of(E) * N,
- allocator = runtime.nil_allocator(),
- }
- img.pixels = bytes.Buffer{
- buf = transmute([dynamic]u8)d,
- }
- return img, true
- }
- // 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}
- blend_single_channel :: #force_inline proc(fg, alpha, bg: $T) -> (res: T) where T == u8 || T == u16 {
- MAX :: 256 when T == u8 else 65536
- c := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha))
- return T(c & (MAX - 1))
- }
- blend_pixel :: #force_inline proc(fg: [$N]$T, alpha: T, bg: [N]T) -> (res: [N]T) where (T == u8 || T == u16), N >= 1, N <= 4 {
- MAX :: 256 when T == u8 else 65536
- when N == 1 {
- r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
- return {T(r & (MAX - 1))}
- }
- when N == 2 {
- r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
- g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
- return {T(r & (MAX - 1)), T(g & (MAX - 1))}
- }
- when N == 3 || N == 4 {
- r := u32(fg.r) * (MAX - u32(alpha)) + u32(bg.r) * (1 + u32(alpha))
- g := u32(fg.g) * (MAX - u32(alpha)) + u32(bg.g) * (1 + u32(alpha))
- b := u32(fg.b) * (MAX - u32(alpha)) + u32(bg.b) * (1 + u32(alpha))
- when N == 3 {
- return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1))}
- } else {
- return {T(r & (MAX - 1)), T(g & (MAX - 1)), T(b & (MAX - 1)), MAX - 1}
- }
- }
- unreachable()
- }
- blend :: proc{blend_single_channel, blend_pixel}
- // For all pixels of the image, multiplies R, G and B by Alpha. This is useful mainly for games rendering anti-aliased transparent sprites.
- // Grayscale with alpha images are supported as well.
- // Note that some image formats like QOI explicitly do NOT support premultiplied alpha, so you will end up with a non-standard file.
- premultiply_alpha :: proc(img: ^Image) -> (ok: bool) {
- switch {
- case img.channels == 2 && img.depth == 8:
- pixels := mem.slice_data_cast([]GA_Pixel, img.pixels.buf[:])
- for &pixel in pixels {
- pixel.r = u8(u32(pixel.r) * u32(pixel.g) / 0xFF)
- }
- return true
- case img.channels == 2 && img.depth == 16:
- pixels := mem.slice_data_cast([]GA_Pixel_16, img.pixels.buf[:])
- for &pixel in pixels {
- pixel.r = u16(u32(pixel.r) * u32(pixel.g) / 0xFFFF)
- }
- return true
- case img.channels == 4 && img.depth == 8:
- pixels := mem.slice_data_cast([]RGBA_Pixel, img.pixels.buf[:])
- for &pixel in pixels {
- pixel.r = u8(u32(pixel.r) * u32(pixel.a) / 0xFF)
- pixel.g = u8(u32(pixel.g) * u32(pixel.a) / 0xFF)
- pixel.b = u8(u32(pixel.b) * u32(pixel.a) / 0xFF)
- }
- return true
- case img.channels == 4 && img.depth == 16:
- pixels := mem.slice_data_cast([]RGBA_Pixel_16, img.pixels.buf[:])
- for &pixel in pixels {
- pixel.r = u16(u32(pixel.r) * u32(pixel.a) / 0xFFFF)
- pixel.g = u16(u32(pixel.g) * u32(pixel.a) / 0xFFFF)
- pixel.b = u16(u32(pixel.b) * u32(pixel.a) / 0xFFFF)
- }
- return true
- case: return false
- }
- }
- // 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.
- out = out[1:]
- }
- 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.
- out = out[1:]
- }
- 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="favor_size")
- 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="favor_size")
- 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
- }
|