Browse Source

Image: Add improved blending method and test it.

Jeroen van Rijn 1 year ago
parent
commit
d2a2c1e74e

+ 17 - 0
core/image/common.odin

@@ -42,6 +42,23 @@ GA_Pixel      :: [2]u8
 G_Pixel_16    :: [1]u16
 G_Pixel_16    :: [1]u16
 GA_Pixel_16   :: [2]u16
 GA_Pixel_16   :: [2]u16
 
 
+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
+
+	r_a := u32(fg) * (MAX - u32(alpha)) + u32(bg) * (1 + u32(alpha))
+
+	return
+}
+
+blend :: proc{blend_single_channel, blend_pixel}
+
 Image :: struct {
 Image :: struct {
 	width:         int,
 	width:         int,
 	height:        int,
 	height:        int,

+ 9 - 11
core/image/png/png.odin

@@ -597,7 +597,7 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				dsc   := depth_scale_table
 				dsc   := depth_scale_table
 				scale := dsc[info.header.bit_depth]
 				scale := dsc[info.header.bit_depth]
 				if scale != 1 {
 				if scale != 1 {
-					key := mem.slice_data_cast([]u16be, c.data)[0] * u16be(scale)
+					key := (^u16be)(raw_data(c.data))^ * u16be(scale)
 					c.data = []u8{0, u8(key & 255)}
 					c.data = []u8{0, u8(key & 255)}
 				}
 				}
 			}
 			}
@@ -762,19 +762,18 @@ load_from_context :: proc(ctx: ^$C, options := Options{}, allocator := context.a
 				for w := 0; w < int(img.width);  w += 1 {
 				for w := 0; w < int(img.width);  w += 1 {
 					index := temp.buf[i]
 					index := temp.buf[i]
 
 
-					c     := _plte.entries[index]
-					a     := int(index) < len(trns.data) ? trns.data[index] : 255
-					alpha := f32(a) / 255.0
+					c := _plte.entries[index]
+					a := int(index) < len(trns.data) ? trns.data[index] : 255
 
 
 					if blend_background {
 					if blend_background {
-						c.r = u8((1.0 - alpha) * bg[0] + f32(c.r) * alpha)
-						c.g = u8((1.0 - alpha) * bg[1] + f32(c.g) * alpha)
-						c.b = u8((1.0 - alpha) * bg[2] + f32(c.b) * alpha)
+						c.r = image.blend(c.r, a, u8(u32(bg.r)))
+						c.g = image.blend(c.g, a, u8(u32(bg.g)))
+						c.b = image.blend(c.b, a, u8(u32(bg.b)))
 						a = 255
 						a = 255
 					} else if premultiply {
 					} else if premultiply {
-						c.r = u8(f32(c.r) * alpha)
-						c.g = u8(f32(c.g) * alpha)
-						c.b = u8(f32(c.b) * alpha)
+						c.r = image.blend(u8(0), a, c.r)
+						c.g = image.blend(u8(0), a, c.g)
+						c.b = image.blend(u8(0), a, c.b)
 					}
 					}
 
 
 					t.buf[j  ] = c.r
 					t.buf[j  ] = c.r
@@ -1627,7 +1626,6 @@ defilter :: proc(img: ^Image, filter_bytes: ^bytes.Buffer, header: ^image.PNG_IH
 	return nil
 	return nil
 }
 }
 
 
-
 @(init, private)
 @(init, private)
 _register :: proc() {
 _register :: proc() {
 	image.register(.PNG, load_from_bytes, destroy)
 	image.register(.PNG, load_from_bytes, destroy)

+ 36 - 0
tests/core/image/test_core_image.odin

@@ -2358,4 +2358,40 @@ run_bmp_suite :: proc(t: ^testing.T, suite: []Test) {
 		}
 		}
 	}
 	}
 	return
 	return
+}
+
+@test
+will_it_blend :: proc(t: ^testing.T) {
+	Pixel    :: image.RGB_Pixel
+	Pixel_16 :: image.RGB_Pixel_16
+
+	{
+		bg := Pixel{255, 255,   0}
+		fg := Pixel{  0,   0, 255}
+
+		for a in 0..=255 {
+			blended := Pixel{
+				image.blend(fg.r, u8(a), bg.r),
+				image.blend(fg.g, u8(a), bg.g),
+				image.blend(fg.b, u8(a), bg.b),
+			}
+			testing.expectf(t, blended.r == bg.r - u8(a),    "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u8(a), blended.r)
+			testing.expectf(t, blended.b == 255 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 255 - blended.r, blended.b)
+		}
+	}
+
+	{
+		bg := Pixel_16{65535, 65535,     0}
+		fg := Pixel_16{    0,     0, 65535}
+
+		for a in 0..=65535 {
+			blended := Pixel_16{
+				image.blend(fg.r, u16(a), bg.r),
+				image.blend(fg.g, u16(a), bg.g),
+				image.blend(fg.b, u16(a), bg.b),
+			}
+			testing.expectf(t, blended.r == bg.r - u16(a),     "Expected blend(%v, %3d, %v) = %v, got %v", fg.r, a, bg.r, bg.r - u16(a), blended.r)
+			testing.expectf(t, blended.b == 65535 - blended.r, "Expected blend(%v, %3d, %v) = %v, got %v", fg.b, a, bg.b, 65535 - blended.r, blended.b)
+		}
+	}
 }
 }

+ 0 - 1
tests/core/normal.odin

@@ -20,7 +20,6 @@ download_assets :: proc() {
 @(require) import "encoding/varint"
 @(require) import "encoding/varint"
 @(require) import "encoding/xml"
 @(require) import "encoding/xml"
 @(require) import "fmt"
 @(require) import "fmt"
-@(require) import "image"
 @(require) import "math"
 @(require) import "math"
 @(require) import "math/big"
 @(require) import "math/big"
 @(require) import "math/linalg/glsl"
 @(require) import "math/linalg/glsl"

+ 1 - 0
tests/core/speed.odin

@@ -3,3 +3,4 @@ package tests_core
 
 
 @(require) import "crypto"
 @(require) import "crypto"
 @(require) import "hash"
 @(require) import "hash"
+@(require) import "image"