Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 104 additions & 61 deletions vlib/gg/image.c.v
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pub mut:
width int
height int
nr_channels int
nr_mipmaps int
ok bool
data voidptr
ext string
Expand All @@ -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 {
Expand All @@ -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

Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -216,33 +274,16 @@ 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
img.id = ctx.image_cache.len
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 {
Expand All @@ -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
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion vlib/gg/image_test.v
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Loading