| 1 | // Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved. |
| 2 | // Use of this source code is governed by an MIT license |
| 3 | // that can be found in the LICENSE file. |
| 4 | |
| 5 | module stbi |
| 6 | |
| 7 | import os |
| 8 | |
| 9 | @[if trace_stbi_allocations ?] |
| 10 | fn trace_allocation(message string) { |
| 11 | eprintln(message) |
| 12 | } |
| 13 | |
| 14 | @[export: 'stbi__callback_malloc'] |
| 15 | fn cb_malloc(s usize) voidptr { |
| 16 | res := unsafe { malloc(isize(s)) } |
| 17 | trace_allocation('> stbi__callback_malloc: ${s} => ${ptr_str(res)}') |
| 18 | return res |
| 19 | } |
| 20 | |
| 21 | @[export: 'stbi__callback_realloc'] |
| 22 | fn cb_realloc(p voidptr, s usize) voidptr { |
| 23 | res := unsafe { v_realloc(p, isize(s)) } |
| 24 | trace_allocation('> stbi__callback_realloc: ${ptr_str(p)} , ${s} => ${ptr_str(res)}') |
| 25 | return res |
| 26 | } |
| 27 | |
| 28 | @[export: 'stbi__callback_free'] |
| 29 | fn cb_free(p voidptr) { |
| 30 | trace_allocation('> stbi__callback_free: ${ptr_str(p)}') |
| 31 | unsafe { free(p) } |
| 32 | } |
| 33 | |
| 34 | #flag -I @VEXEROOT/thirdparty/stb_image |
| 35 | #include "stb_image.h" |
| 36 | #include "stb_image_write.h" |
| 37 | #include "stb_image_resize2.h" |
| 38 | #include "stb_v_header.h" |
| 39 | #flag @VEXEROOT/thirdparty/stb_image/stbi.o |
| 40 | |
| 41 | // Image represents an image loaded from file or memory, or an image, produced after resizing |
| 42 | pub struct Image { |
| 43 | pub mut: |
| 44 | width int // the width in pixels in the .data |
| 45 | height int // the height in pixels in the .data |
| 46 | nr_channels int // the number of color channels in the .data |
| 47 | ok bool // if the image was loaded successfully |
| 48 | data &u8 = unsafe { nil } // the actual data/pixels in the image, after reading and potentially converting the image |
| 49 | ext string // the extension of the file, from which the image was loaded |
| 50 | // |
| 51 | original_nr_channels int // when loaded from memory/disk, this field will contain the original number of channels, based on the data, prior to any conversions. Use only as metadata, not for further conversions. |
| 52 | } |
| 53 | |
| 54 | //----------------------------------------------------------------------------- |
| 55 | // |
| 56 | // Configuration functions |
| 57 | // |
| 58 | //----------------------------------------------------------------------------- |
| 59 | fn C.stbi_set_flip_vertically_on_load(should_flip i32) |
| 60 | fn C.stbi_flip_vertically_on_write(flag i32) |
| 61 | fn C.set_png_compression_level(level i32) |
| 62 | fn C.write_force_png_filter(level i32) |
| 63 | fn C.write_tga_with_rle(level i32) |
| 64 | |
| 65 | pub fn set_flip_vertically_on_load(val bool) { |
| 66 | C.stbi_set_flip_vertically_on_load(val) |
| 67 | } |
| 68 | |
| 69 | pub fn set_flip_vertically_on_write(val bool) { |
| 70 | C.stbi_flip_vertically_on_write(val) |
| 71 | } |
| 72 | |
| 73 | // set_png_compression_level set the PNG compression level during the writing process |
| 74 | // defaults to 8; set to higher for more compression |
| 75 | pub fn set_png_compression_level(level int) { |
| 76 | C.set_png_compression_level(level) |
| 77 | } |
| 78 | |
| 79 | // write_force_png_filter defaults to -1; set to 0..5 to force a filter mode |
| 80 | // the filter algorithms that can be applied before compression. The purpose of these filters is to prepare the image data for optimum compression. |
| 81 | // Type Name |
| 82 | // |
| 83 | // 0 None |
| 84 | // 1 Sub |
| 85 | // 2 Up |
| 86 | // 3 Average |
| 87 | // 4 Paeth |
| 88 | pub fn write_force_png_filter(level int) { |
| 89 | C.write_force_png_filter(level) |
| 90 | } |
| 91 | |
| 92 | // stbi_write_tga_with_rle enable/disable the TGA RLE during the writing process |
| 93 | // defaults to true; set to false to disable RLE in tga |
| 94 | pub fn write_tga_with_rle(flag bool) { |
| 95 | C.write_tga_with_rle(if flag { 1 } else { 0 }) |
| 96 | } |
| 97 | |
| 98 | //----------------------------------------------------------------------------- |
| 99 | // |
| 100 | // Utility functions |
| 101 | // |
| 102 | //----------------------------------------------------------------------------- |
| 103 | fn C.stbi_image_free(retval_from_stbi_load &u8) |
| 104 | |
| 105 | pub fn (img &Image) free() { |
| 106 | C.stbi_image_free(img.data) |
| 107 | } |
| 108 | |
| 109 | //----------------------------------------------------------------------------- |
| 110 | // |
| 111 | // Load functions |
| 112 | // |
| 113 | //----------------------------------------------------------------------------- |
| 114 | fn C.stbi_load(filename &char, x &int, y &int, channels_in_file &int, desired_channels i32) &u8 |
| 115 | fn C.stbi_load_from_file(f voidptr, x &int, y &int, channels_in_file &int, desired_channels i32) &u8 |
| 116 | fn C.stbi_load_from_memory(buffer &u8, len i32, x &int, y &int, channels_in_file &int, desired_channels i32) &u8 |
| 117 | |
| 118 | pub const C.STBI_rgb_alpha int |
| 119 | |
| 120 | @[params] |
| 121 | pub struct LoadParams { |
| 122 | pub: |
| 123 | desired_channels int = C.STBI_rgb_alpha // 4 by default (RGBA); desired_channels is the number of color channels, that will be used for representing the image in memory. If set to 0, stbi will figure out the number of channels, based on the original image data. |
| 124 | } |
| 125 | |
| 126 | // load loads an image from `path` |
| 127 | // If you do not pass desired_channels: explicitly, it will default to 4 (RGBA), |
| 128 | // The image, will get converted into that internal format, no matter what it was on disk. |
| 129 | // Use desired_channels:0, if you need to keep the channels of the image on disk. |
| 130 | // Note that displaying such an image, with gg/sokol later, can be a problem. |
| 131 | // Converting/resizing it, should work fine though. |
| 132 | pub fn load(path string, params LoadParams) !Image { |
| 133 | ext := path.all_after_last('.') |
| 134 | $if windows { |
| 135 | $if clang { |
| 136 | // stb's file-based loader crashes on Windows when compiled with clang. |
| 137 | // Read the file through V and decode the in-memory bytes instead. |
| 138 | file_bytes := os.read_bytes(path) or { |
| 139 | return error('stbi_image failed to load from "${path}"') |
| 140 | } |
| 141 | mut img := load_from_memory(file_bytes.data, file_bytes.len, params) or { |
| 142 | return error('stbi_image failed to load from "${path}"') |
| 143 | } |
| 144 | img.ext = ext |
| 145 | return img |
| 146 | } |
| 147 | } |
| 148 | mut res := Image{ |
| 149 | ok: true |
| 150 | ext: ext |
| 151 | nr_channels: params.desired_channels |
| 152 | } |
| 153 | res.data = C.stbi_load(&char(path.str), &res.width, &res.height, &res.original_nr_channels, |
| 154 | params.desired_channels) |
| 155 | if params.desired_channels == 0 { |
| 156 | res.nr_channels = res.original_nr_channels |
| 157 | } |
| 158 | if isnil(res.data) { |
| 159 | return error('stbi_image failed to load from "${path}"') |
| 160 | } |
| 161 | return res |
| 162 | } |
| 163 | |
| 164 | // load_from_memory load an image from a memory buffer |
| 165 | // If you do not pass desired_channels: explicitly, it will default to 4 (RGBA), |
| 166 | // and the image will get converted into that internal format, no matter what it was originally. |
| 167 | // Use desired_channels:0, if you need to keep the channels of the image as they were. |
| 168 | // Note that displaying such an image, with gg/sokol later, can be a problem. |
| 169 | // Converting/resizing it, should work fine though. |
| 170 | pub fn load_from_memory(buf &u8, bufsize int, params LoadParams) !Image { |
| 171 | mut res := Image{ |
| 172 | ok: true |
| 173 | nr_channels: params.desired_channels |
| 174 | } |
| 175 | res.data = C.stbi_load_from_memory(buf, bufsize, &res.width, &res.height, |
| 176 | &res.original_nr_channels, params.desired_channels) |
| 177 | if params.desired_channels == 0 { |
| 178 | res.nr_channels = res.original_nr_channels |
| 179 | } |
| 180 | if isnil(res.data) { |
| 181 | return error('stbi_image failed to load from memory') |
| 182 | } |
| 183 | return res |
| 184 | } |
| 185 | |
| 186 | //----------------------------------------------------------------------------- |
| 187 | // |
| 188 | // Resize functions |
| 189 | // |
| 190 | //----------------------------------------------------------------------------- |
| 191 | fn C.stbir_resize_uint8_linear(input_pixels &u8, input_w i32, input_h i32, input_stride_in_bytes i32, output_pixels &u8, |
| 192 | output_w i32, output_h i32, output_stride_in_bytes i32, num_channels i32) i32 |
| 193 | |
| 194 | // resize_uint8 resizes `img` to dimensions of `output_w` and `output_h` |
| 195 | pub fn resize_uint8(img &Image, output_w int, output_h int) !Image { |
| 196 | mut res := Image{ |
| 197 | ok: true |
| 198 | ext: img.ext |
| 199 | width: output_w |
| 200 | height: output_h |
| 201 | nr_channels: img.nr_channels |
| 202 | original_nr_channels: img.original_nr_channels // preserve the metadata of the original, during resizes |
| 203 | } |
| 204 | |
| 205 | res.data = cb_malloc(usize(output_w * output_h * img.nr_channels)) |
| 206 | if res.data == 0 { |
| 207 | return error('stbi_image failed to resize file') |
| 208 | } |
| 209 | |
| 210 | if 0 == C.stbir_resize_uint8_linear(img.data, img.width, img.height, 0, res.data, output_w, |
| 211 | output_h, 0, img.nr_channels) { |
| 212 | return error('stbi_image failed to resize file') |
| 213 | } |
| 214 | return res |
| 215 | } |
| 216 | |
| 217 | //----------------------------------------------------------------------------- |
| 218 | // |
| 219 | // Write functions |
| 220 | // |
| 221 | //----------------------------------------------------------------------------- |
| 222 | fn C.stbi_write_png(filename &char, w i32, h i32, nr_channels i32, buffer &u8, stride_in_bytes i32) i32 |
| 223 | fn C.stbi_write_bmp(filename &char, w i32, h i32, nr_channels i32, buffer &u8) i32 |
| 224 | fn C.stbi_write_tga(filename &char, w i32, h i32, nr_channels i32, buffer &u8) i32 |
| 225 | fn C.stbi_write_jpg(filename &char, w i32, h i32, nr_channels i32, buffer &u8, quality i32) i32 |
| 226 | |
| 227 | // fn C.stbi_write_hdr(filename &char, w int, h int, nr_channels int, buffer &u8) int // buffer &u8 => buffer &f32 |
| 228 | |
| 229 | // stbi_write_png writes a PNG file to `path`. |
| 230 | // `nr_channels` is the number of channels in `buf`. |
| 231 | // `row_stride_in_bytes` is usually `w * nr_channels`. |
| 232 | pub fn stbi_write_png(path string, w int, h int, nr_channels int, buf &u8, row_stride_in_bytes int) ! { |
| 233 | if 0 == C.stbi_write_png(&char(path.str), w, h, nr_channels, buf, row_stride_in_bytes) { |
| 234 | return error('stbi_image failed to write png file to "${path}"') |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | // stbi_write_bmp writes a BMP file to `path`. |
| 239 | // `nr_channels` is the number of channels in `buf`. |
| 240 | pub fn stbi_write_bmp(path string, w int, h int, nr_channels int, buf &u8) ! { |
| 241 | if 0 == C.stbi_write_bmp(&char(path.str), w, h, nr_channels, buf) { |
| 242 | return error('stbi_image failed to write bmp file to "${path}"') |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | // stbi_write_tga writes a TGA file to `path`. |
| 247 | // `nr_channels` is the number of channels in `buf`. |
| 248 | pub fn stbi_write_tga(path string, w int, h int, nr_channels int, buf &u8) ! { |
| 249 | if 0 == C.stbi_write_tga(&char(path.str), w, h, nr_channels, buf) { |
| 250 | return error('stbi_image failed to write tga file to "${path}"') |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | // stbi_write_jpg writes a JPG file to `path`. |
| 255 | // `nr_channels` is the number of channels in `buf`. |
| 256 | // `quality` controls the JPG compression quality and must be between 1 and 100. |
| 257 | pub fn stbi_write_jpg(path string, w int, h int, nr_channels int, buf &u8, quality int) ! { |
| 258 | if 0 == C.stbi_write_jpg(&char(path.str), w, h, nr_channels, buf, quality) { |
| 259 | return error('stbi_image failed to write jpg file to "${path}"') |
| 260 | } |
| 261 | } |
| 262 | |
| 263 | /* |
| 264 | pub fn stbi_write_hdr(path string, w int, h int, nr_channels int, buf &u8) ! { |
| 265 | if 0 == C.stbi_write_hdr(&char(path.str), w , h , nr_channels , buf){ |
| 266 | return error('stbi_image failed to write hdr file to "${path}"') |
| 267 | } |
| 268 | } |
| 269 | */ |
| 270 | |