1
+ use once_cell:: sync:: Lazy ;
1
2
use serde:: Serialize ;
2
3
use std:: str:: FromStr ;
3
4
4
- #[ derive( Clone ) ]
5
- pub struct Rgba {
6
- r : f32 ,
7
- g : f32 ,
8
- b : f32 ,
9
- a : f32 ,
10
- }
11
-
12
- impl Rgba {
13
- pub fn into_array ( self ) -> [ u8 ; 4 ] {
14
- [
15
- self . r . round ( ) as u8 ,
16
- self . g . round ( ) as u8 ,
17
- self . b . round ( ) as u8 ,
18
- self . a . round ( ) as u8 ,
19
- ]
20
- }
5
+ pub static ALPHA_TABLE : Lazy < [ u8 ; 256 * 256 ] > = Lazy :: new ( || {
6
+ let mut table = [ 0u8 ; 256 * 256 ] ;
21
7
22
- pub fn from_array ( rgba : & [ u8 ] ) -> Rgba {
23
- Self {
24
- r : rgba[ 0 ] as f32 ,
25
- g : rgba[ 1 ] as f32 ,
26
- b : rgba[ 2 ] as f32 ,
27
- a : rgba[ 3 ] as f32 ,
8
+ for dst in 0 ..256 {
9
+ for src in 0 ..256 {
10
+ let index = dst * 256 + src;
11
+ let value = ( ( src as f32 ) * ( dst as f32 / 255.0 ) + 0.5 ) . floor ( ) as i32 ;
12
+ table[ index] = if ( 0 ..256 ) . contains ( & value) {
13
+ value as u8
14
+ } else {
15
+ 0xFF
16
+ } ;
28
17
}
29
18
}
30
19
31
- fn map_each < F , T > ( color : & Rgba , color2 : & Rgba , rgb_fn : F , a_fn : T ) -> Rgba
32
- where
33
- F : Fn ( f32 , f32 ) -> f32 ,
34
- T : Fn ( f32 , f32 ) -> f32 ,
35
- {
36
- Rgba {
37
- r : rgb_fn ( color. r , color2. r ) ,
38
- g : rgb_fn ( color. g , color2. g ) ,
39
- b : rgb_fn ( color. b , color2. b ) ,
40
- a : a_fn ( color. a , color2. a ) ,
41
- }
42
- }
43
-
44
- fn map_each_a < F , T > ( color : & Rgba , color2 : & Rgba , rgb_fn : F , a_fn : T ) -> Rgba
45
- where
46
- F : Fn ( f32 , f32 , f32 , f32 ) -> f32 ,
47
- T : Fn ( f32 , f32 ) -> f32 ,
48
- {
49
- Rgba {
50
- r : rgb_fn ( color. r , color2. r , color. a , color2. a ) ,
51
- g : rgb_fn ( color. g , color2. g , color. a , color2. a ) ,
52
- b : rgb_fn ( color. b , color2. b , color. a , color2. a ) ,
53
- a : a_fn ( color. a , color2. a ) ,
54
- }
55
- }
56
-
57
- /// Takes two [u8; 4]s, converts them to Rgba structs, then blends them according to blend_mode by calling blend().
58
- pub fn blend_u8 ( color : & [ u8 ] , other_color : & [ u8 ] , blend_mode : & BlendMode ) -> [ u8 ; 4 ] {
59
- Rgba :: from_array ( color)
60
- . blend ( & Rgba :: from_array ( other_color) , blend_mode)
61
- . into_array ( )
62
- }
63
-
64
- /// Blends two colors according to blend_mode.
65
- pub fn blend ( & self , other_color : & Rgba , blend_mode : & BlendMode ) -> Rgba {
66
- match blend_mode {
67
- BlendMode :: Add => Rgba :: map_each ( self , other_color, |c1, c2| c1 + c2, f32:: min) ,
68
- BlendMode :: Subtract => Rgba :: map_each ( self , other_color, |c1, c2| c1 - c2, f32:: min) ,
69
- BlendMode :: Multiply => Rgba :: map_each (
70
- self ,
71
- other_color,
72
- |c1, c2| c1 * c2 / 255.0 ,
73
- |a1 : f32 , a2 : f32 | a1 * a2 / 255.0 ,
74
- ) ,
75
- BlendMode :: Overlay => Rgba :: map_each_a (
76
- self ,
77
- other_color,
78
- |c1, c2, c1_a, c2_a| {
79
- if c1_a == 0.0 {
80
- return c2;
81
- }
82
- c1 + ( c2 - c1) * c2_a / 255.0
83
- } ,
84
- |a1, a2| {
85
- let high = f32:: max ( a1, a2) ;
86
- let low = f32:: min ( a1, a2) ;
87
- high + ( high * low / 255.0 )
88
- } ,
89
- ) ,
90
- BlendMode :: Underlay => Rgba :: map_each_a (
91
- other_color,
92
- self ,
93
- |c1, c2, c1_a, c2_a| {
94
- if c1_a == 0.0 {
95
- return c2;
96
- }
97
- c1 + ( c2 - c1) * c2_a / 255.0
98
- } ,
99
- |a1, a2| {
100
- let high = f32:: max ( a1, a2) ;
101
- let low = f32:: min ( a1, a2) ;
102
- high + ( high * low / 255.0 )
103
- } ,
104
- ) ,
105
- }
106
- }
107
- }
20
+ table
21
+ } ) ;
108
22
109
23
// The numbers correspond to BYOND ICON_X blend modes. https://www.byond.com/docs/ref/#/icon/proc/Blend
110
24
#[ derive( Clone , Hash , Eq , PartialEq , Serialize ) ]
@@ -114,6 +28,8 @@ pub enum BlendMode {
114
28
Subtract = 1 ,
115
29
Multiply = 2 ,
116
30
Overlay = 3 ,
31
+ And = 4 ,
32
+ Or = 5 ,
117
33
Underlay = 6 ,
118
34
}
119
35
@@ -124,10 +40,94 @@ impl BlendMode {
124
40
1 => Ok ( BlendMode :: Subtract ) ,
125
41
2 => Ok ( BlendMode :: Multiply ) ,
126
42
3 => Ok ( BlendMode :: Overlay ) ,
43
+ 4 => Ok ( BlendMode :: And ) ,
44
+ 5 => Ok ( BlendMode :: Or ) ,
127
45
6 => Ok ( BlendMode :: Underlay ) ,
128
46
_ => Err ( format ! ( "blend_mode '{blend_mode}' is not supported!" ) ) ,
129
47
}
130
48
}
49
+
50
+ pub fn blend_u8 ( & self , color : & [ u8 ] , other_color : & [ u8 ] ) -> [ u8 ; 4 ] {
51
+ let ( r1, g1, b1, a1) = ( color[ 0 ] , color[ 1 ] , color[ 2 ] , color[ 3 ] ) ;
52
+ let ( r2, g2, b2, a2) = (
53
+ other_color[ 0 ] ,
54
+ other_color[ 1 ] ,
55
+ other_color[ 2 ] ,
56
+ other_color[ 3 ] ,
57
+ ) ;
58
+
59
+ let add_channel = |c_src : u8 , c_dst : u8 | c_src. saturating_add ( c_dst) ;
60
+ let subtract_channel = |c_src : u8 , c_dst : u8 | c_src. saturating_sub ( c_dst) ;
61
+ let multiply_channel = |c_src : u8 , c_dst : u8 | ( ( c_src as u16 * c_dst as u16 ) / 255 ) as u8 ;
62
+ let overlay_channel = |c_src : u8 , c_dst : u8 , a_src : u8 , a_dst : u8 | {
63
+ if a_src == 0 {
64
+ c_dst
65
+ } else {
66
+ let delta = ( c_dst as i32 - c_src as i32 ) * a_dst as i32 / 255 ;
67
+ ( c_src as i32 + delta) . clamp ( 0 , 255 ) as u8
68
+ }
69
+ } ;
70
+ let overlay_alpha = |a_src : u8 , a_dst : u8 | {
71
+ let a_src = a_src as f32 / 255.0 ;
72
+ let a_dst = a_dst as f32 / 255.0 ;
73
+ ( ( a_src + a_dst * ( 1.0 - a_src) ) * 255.0 )
74
+ . round ( )
75
+ . clamp ( 0.0 , 255.0 ) as u8
76
+ } ;
77
+
78
+ let alpha_lookup =
79
+ |a_src : u8 , a_dst : u8 | ALPHA_TABLE [ a_dst as usize + ( a_src as usize ) * 256 ] ;
80
+
81
+ match self {
82
+ BlendMode :: Add | BlendMode :: And => [
83
+ add_channel ( r1, r2) ,
84
+ add_channel ( g1, g2) ,
85
+ add_channel ( b1, b2) ,
86
+ alpha_lookup ( a1, a2) ,
87
+ ] ,
88
+ BlendMode :: Subtract => [
89
+ subtract_channel ( r1, r2) ,
90
+ subtract_channel ( g1, g2) ,
91
+ subtract_channel ( b1, b2) ,
92
+ alpha_lookup ( a1, a2) ,
93
+ ] ,
94
+ BlendMode :: Multiply => [
95
+ multiply_channel ( r1, r2) ,
96
+ multiply_channel ( g1, g2) ,
97
+ multiply_channel ( b1, b2) ,
98
+ alpha_lookup ( a1, a2) ,
99
+ ] ,
100
+ BlendMode :: Overlay => [
101
+ overlay_channel ( r1, r2, a1, a2) ,
102
+ overlay_channel ( g1, g2, a1, a2) ,
103
+ overlay_channel ( b1, b2, a1, a2) ,
104
+ overlay_alpha ( a1, a2) ,
105
+ ] ,
106
+ BlendMode :: Or => {
107
+ if a1 == 0 {
108
+ return [ r2, g2, b2, a2] ;
109
+ }
110
+ if a2 == 0 {
111
+ return [ r1, g1, b1, a1] ;
112
+ }
113
+ [
114
+ add_channel ( r1, r2) ,
115
+ add_channel ( g1, g2) ,
116
+ add_channel ( b1, b2) ,
117
+ !ALPHA_TABLE [ 0x10000usize
118
+ . wrapping_sub ( a1 as usize )
119
+ . wrapping_sub ( a2 as usize * 256 )
120
+ . min ( 65535 ) ] ,
121
+ ]
122
+ }
123
+ BlendMode :: Underlay => [
124
+ overlay_channel ( r2, r1, a2, a1) ,
125
+ overlay_channel ( g2, g1, a2, a1) ,
126
+ overlay_channel ( b2, b1, a2, a1) ,
127
+ overlay_alpha ( a2, a1) ,
128
+ ] ,
129
+ }
130
+ }
131
131
}
132
132
133
133
impl FromStr for BlendMode {
@@ -139,6 +139,8 @@ impl FromStr for BlendMode {
139
139
"subtract" => Ok ( BlendMode :: Subtract ) ,
140
140
"multiply" => Ok ( BlendMode :: Multiply ) ,
141
141
"overlay" => Ok ( BlendMode :: Overlay ) ,
142
+ "and" => Ok ( BlendMode :: And ) ,
143
+ "or" => Ok ( BlendMode :: Or ) ,
142
144
"underlay" => Ok ( BlendMode :: Underlay ) ,
143
145
_ => Err ( format ! ( "blend_mode '{blend_mode}' is not supported!" ) ) ,
144
146
}
0 commit comments