diff --git a/vlib/gg/image.c.v b/vlib/gg/image.c.v index 768867d943c65a..5b4510291c3bba 100644 --- a/vlib/gg/image.c.v +++ b/vlib/gg/image.c.v @@ -16,6 +16,7 @@ pub mut: width int height int nr_channels int + nr_mipmaps int ok bool data voidptr ext string @@ -26,6 +27,13 @@ pub mut: texture_filter TextureFilter = .linear } +@[params] +pub struct ImageConfig { +pub: + texture_filter ?TextureFilter + max_mipmaps int = 1 // a value below 1 means as many as possible +} + // destroy GPU resources associated with the image fn (image &Image) destroy() { if image.ok { @@ -38,17 +46,18 @@ fn (image &Image) destroy() { } } -// create_image creates an `Image` from `file`. -pub fn (mut ctx Context) create_image(file string) !Image { - return ctx.create_image_with_filter(file, ctx.config.texture_filter) -} - // create_image_with_filter creates an `Image` from `file` with the requested texture filter. +@[deprecated: 'use Context.create_image instead'] pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter TextureFilter) !Image { + return ctx.create_image(file, texture_filter: texture_filter)! +} + +// create_image creates an `Image` from `file`. +pub fn (mut ctx Context) create_image(file string, cfg ImageConfig) !Image { if !os.exists(file) { $if android { image_data := os.read_apk_asset(file)! - mut image := ctx.create_image_from_byte_array_with_filter(image_data, texture_filter)! + mut image := ctx.create_image_from_byte_array(image_data, cfg)! image.path = file @@ -57,15 +66,11 @@ pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter Te return error('image file "${file}" not found') } } - $if macos { if ctx.native_rendering { - // return C.darwin_create_image(file) mut img := C.darwin_create_image(file) - img.texture_filter = texture_filter + img.texture_filter = cfg.texture_filter or { ctx.config.texture_filter } - // println('created macos image: ${img.path} w=${img.width}') - // C.printf('p = %p\n', img.data) img.id = ctx.image_cache.len unsafe { ctx.image_cache << img @@ -74,33 +79,83 @@ pub fn (mut ctx Context) create_image_with_filter(file string, texture_filter Te } } - if !gfx.is_valid() { - // Sokol is not initialized yet, add stbi object to a queue/cache - // ctx.image_queue << file - mut img := load_image_with_filter(file, texture_filter)! - img.ok = false - img.id = ctx.image_cache.len - unsafe { - ctx.image_cache << img - } - return img - } - mut img := create_image(file, texture_filter)! + mut img := load_image(file)! img.id = ctx.image_cache.len + img.texture_filter = cfg.texture_filter or { ctx.config.texture_filter } unsafe { ctx.image_cache << img } + if gfx.is_valid() { + img.init_sokol_image() + } return img } +// adapted from https://github.com/floooh/sokol/issues/102#issuecomment-2926566603 +fn sokol_mipmap(mut simg_desc gfx.ImageDesc, max_mipmaps int) { + mut levels := 0 + mut size := 0 + width := simg_desc.width + height := simg_desc.height + mipmaps := if max_mipmaps < gfx.sg_max_mipmaps && max_mipmaps > 0 { + max_mipmaps + } else { + gfx.sg_max_mipmaps + } + for i in 1 .. mipmaps { + w := width >> i + h := height >> i + if w < 1 || h < 1 { break + } + size += w * h * 4 // 4 = img.nr_channels + levels++ + } + // use unsafe to access the data because we have to anyway + mut target := unsafe { malloc(size) } + mut src_width := width + mut src_height := height + for level in 1 .. levels { + src := &u8(simg_desc.data.subimage[0][level - 1].ptr) + target_width := src_width / 2 + target_height := src_height / 2 + for x in 0 .. target_width { + for y in 0 .. target_height { + // channels RGBA + chans := 4 + for ch in 0 .. 4 { + // avererage colors in a 2x2 square over the source image where (x*2|y*2) is the lower left corner + mut color := 0 + sx := x * 2 + sy := y * 2 + color += unsafe { src[(sx + src_width * sy) * chans + ch] } + color += unsafe { src[(sx + src_width * (sy + 1)) * chans + ch] } + color += unsafe { src[((sx + 1) + src_width * (sy + 1)) * chans + ch] } + color += unsafe { src[((sx + 1) + src_width * sy) * chans + ch] } + color /= 4 + unsafe { + target[(x + y * target_width) * chans + ch] = u8(color) + } + } + } + } + src_width = target_width + src_height = target_height + simg_desc.data.subimage[0][level].ptr = target + simg_desc.data.subimage[0][level].size = usize(target_width * target_height * 4) + unsafe { + target += target_width * target_height * 4 + } + } + simg_desc.num_mipmaps = levels +} + // init_sokol_image initializes this `Image` for use with the // sokol graphical backend system. pub fn (mut img Image) init_sokol_image() &Image { // println('\n init sokol image ${img.path} ok=${img.simg_ok}') mut img_desc := gfx.ImageDesc{ - width: img.width - height: img.height - num_mipmaps: 0 + width: img.width + height: img.height // wrap_u: .clamp_to_edge // XTODO SAMPLER // wrap_v: .clamp_to_edge label: &char(img.path.str) @@ -123,14 +178,17 @@ pub fn (mut img Image) init_sokol_image() &Image { ptr: img.data size: img_size } + sokol_mipmap(mut img_desc, img.nr_mipmaps) + img.nr_mipmaps = img_desc.num_mipmaps img.simg = gfx.make_image(&img_desc) gfx_filter := img.texture_filter.gfx_filter() mut smp_desc := gfx.SamplerDesc{ - min_filter: gfx_filter - mag_filter: gfx_filter - wrap_u: .clamp_to_edge - wrap_v: .clamp_to_edge + min_filter: gfx_filter + mag_filter: gfx_filter + mipmap_filter: .linear + wrap_u: .clamp_to_edge + wrap_v: .clamp_to_edge } img.ssmp = gfx.make_sampler(&smp_desc) @@ -216,7 +274,8 @@ pub fn (mut ctx Context) create_image_with_size(file string, width int, height i if !gfx.is_valid() { // Sokol is not initialized yet, add stbi object to a queue/cache // ctx.image_queue << file - mut img := load_image_with_filter(file, ctx.config.texture_filter) or { return Image{} } + mut img := load_image(file) or { return Image{} } + img.texture_filter = ctx.config.texture_filter img.width = width img.height = height img.ok = false @@ -224,25 +283,7 @@ pub fn (mut ctx Context) create_image_with_size(file string, width int, height i ctx.image_cache << img return img } - mut img := create_image(file, ctx.config.texture_filter) or { return Image{} } - img.id = ctx.image_cache.len - ctx.image_cache << img - return img -} - -// create_image creates an `Image` from `file`. -// -// TODO: remove this -fn create_image(file string, texture_filter TextureFilter) !Image { - mut img := load_image_with_filter(file, texture_filter)! - img.init_sokol_image() - return img -} - -fn load_image_with_filter(file string, texture_filter TextureFilter) !Image { - mut img := load_image(file)! - img.texture_filter = texture_filter - return img + return ctx.create_image(file) or { Image{} } } fn load_image(file string) !Image { @@ -265,12 +306,7 @@ fn load_image(file string) !Image { // memory buffer `buf` of size `bufsize`. // // See also: create_image_from_byte_array -pub fn (mut ctx Context) create_image_from_memory(buf &u8, bufsize int) !Image { - return ctx.create_image_from_memory_with_filter(buf, bufsize, ctx.config.texture_filter) -} - -// create_image_from_memory_with_filter creates an `Image` from `buf` with the requested texture filter. -pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize int, texture_filter TextureFilter) !Image { +pub fn (mut ctx Context) create_image_from_memory(buf &u8, bufsize int, cfg ImageConfig) !Image { stb_img := stbi.load_from_memory(buf, bufsize)! mut img := Image{ width: stb_img.width @@ -280,26 +316,33 @@ pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize i data: stb_img.data ext: stb_img.ext id: ctx.image_cache.len - texture_filter: texture_filter + texture_filter: cfg.texture_filter or { ctx.config.texture_filter } } - if gfx.is_valid() && !ctx.native_rendering { + if gfx.is_valid() { img.init_sokol_image() } ctx.image_cache << img return img } +// create_image_from_memory_with_filter creates an `Image` from `buf` with the requested texture filter. +@[deprecated: 'use Context.create_image_from_memory instead'] +pub fn (mut ctx Context) create_image_from_memory_with_filter(buf &u8, bufsize int, texture_filter TextureFilter) !Image { + return ctx.create_image_from_memory(buf, bufsize, texture_filter: texture_filter) +} + // create_image_from_byte_array creates an `Image` from the // byte array `b`. // // See also: create_image_from_memory -pub fn (mut ctx Context) create_image_from_byte_array(b []u8) !Image { - return ctx.create_image_from_byte_array_with_filter(b, ctx.config.texture_filter) +pub fn (mut ctx Context) create_image_from_byte_array(b []u8, cfg ImageConfig) !Image { + return ctx.create_image_from_memory(b.data, b.len, cfg) } // create_image_from_byte_array_with_filter creates an `Image` from `b` with the requested texture filter. +@[deprecated: 'use Context.create_image_from_byte_array instead'] pub fn (mut ctx Context) create_image_from_byte_array_with_filter(b []u8, texture_filter TextureFilter) !Image { - return ctx.create_image_from_memory_with_filter(b.data, b.len, texture_filter) + return ctx.create_image_from_memory(b.data, b.len, texture_filter: texture_filter) } pub struct StreamingImageConfig { diff --git a/vlib/gg/image_test.v b/vlib/gg/image_test.v index d1bf201e5597a3..364fb240114245 100644 --- a/vlib/gg/image_test.v +++ b/vlib/gg/image_test.v @@ -79,7 +79,7 @@ fn test_create_image_from_byte_array_uses_context_texture_filter() { fn test_create_image_from_byte_array_with_filter_overrides_context_default() { mut ctx := gg.new_context(width: 100) background_bytes := os.read_bytes(background_path)! - img := ctx.create_image_from_byte_array_with_filter(background_bytes, .nearest)! + img := ctx.create_image_from_byte_array(background_bytes, texture_filter: .nearest)! assert img.texture_filter == .nearest assert ctx.get_cached_image_by_idx(img.id).texture_filter == .nearest }