@@ -83,6 +83,70 @@ impl CfaPattern {
8383 Self { pattern : XTRANS_DEFAULT , width : 6 , height : 6 }
8484 }
8585
86+ /// Create a Quad Bayer RGGB pattern (4x4 repeating tile).
87+ ///
88+ /// ```text
89+ /// R R G G
90+ /// R R G G
91+ /// G G B B
92+ /// G G B B
93+ /// ```
94+ pub fn quad_bayer_rggb ( ) -> Self {
95+ Self :: quad_bayer ( [ Red , Green , Green , Blue ] )
96+ }
97+
98+ /// Create a Quad Bayer BGGR pattern (4x4 repeating tile).
99+ ///
100+ /// ```text
101+ /// B B G G
102+ /// B B G G
103+ /// G G R R
104+ /// G G R R
105+ /// ```
106+ pub fn quad_bayer_bggr ( ) -> Self {
107+ Self :: quad_bayer ( [ Blue , Green , Green , Red ] )
108+ }
109+
110+ /// Create a Quad Bayer GRBG pattern (4x4 repeating tile).
111+ ///
112+ /// ```text
113+ /// G G R R
114+ /// G G R R
115+ /// B B G G
116+ /// B B G G
117+ /// ```
118+ pub fn quad_bayer_grbg ( ) -> Self {
119+ Self :: quad_bayer ( [ Green , Red , Blue , Green ] )
120+ }
121+
122+ /// Create a Quad Bayer GBRG pattern (4x4 repeating tile).
123+ ///
124+ /// ```text
125+ /// G G B B
126+ /// G G B B
127+ /// R R G G
128+ /// R R G G
129+ /// ```
130+ pub fn quad_bayer_gbrg ( ) -> Self {
131+ Self :: quad_bayer ( [ Green , Blue , Red , Green ] )
132+ }
133+
134+ fn quad_bayer ( base : [ Channel ; 4 ] ) -> Self {
135+ let mut pattern = [ Green ; 36 ] ;
136+ for r in 0 ..2 {
137+ for c in 0 ..2 {
138+ let ch = base[ r * 2 + c] ;
139+ let r0 = 2 * r;
140+ let c0 = 2 * c;
141+ pattern[ r0 * 4 + c0] = ch;
142+ pattern[ r0 * 4 + c0 + 1 ] = ch;
143+ pattern[ ( r0 + 1 ) * 4 + c0] = ch;
144+ pattern[ ( r0 + 1 ) * 4 + c0 + 1 ] = ch;
145+ }
146+ }
147+ Self { pattern, width : 4 , height : 4 }
148+ }
149+
86150 /// Return a shifted view of this pattern.
87151 ///
88152 /// The shift (dy, dx) follows the additive convention:
@@ -119,10 +183,31 @@ impl CfaPattern {
119183 self . width == 2
120184 }
121185
186+ /// Returns `true` if this is a 4x4 Quad Bayer pattern.
187+ pub fn is_quad_bayer ( & self ) -> bool {
188+ self . width == 4 && self . height == 4
189+ }
190+
122191 /// Returns `true` if this is a 6x6 X-Trans pattern.
123192 pub fn is_xtrans ( & self ) -> bool {
124193 self . width == 6
125194 }
195+
196+ /// For a Quad Bayer pattern, derive the corresponding 2x2 Bayer pattern
197+ /// that results from 2x2 binning.
198+ ///
199+ /// Returns `None` if this is not a Quad Bayer pattern.
200+ pub fn quad_to_bayer ( & self ) -> Option < CfaPattern > {
201+ if !self . is_quad_bayer ( ) {
202+ return None ;
203+ }
204+ Some ( Self :: bayer ( [
205+ self . color_at ( 0 , 0 ) ,
206+ self . color_at ( 0 , 2 ) ,
207+ self . color_at ( 2 , 0 ) ,
208+ self . color_at ( 2 , 2 ) ,
209+ ] ) )
210+ }
126211}
127212
128213impl fmt:: Display for CfaPattern {
@@ -132,6 +217,10 @@ impl fmt::Display for CfaPattern {
132217 write ! ( f, "{}" , self . pattern[ i] ) ?;
133218 }
134219 Ok ( ( ) )
220+ } else if self . is_quad_bayer ( ) {
221+ write ! ( f, "Quad Bayer " ) ?;
222+ // Show the 2x2 base pattern (top-left of each quadrant).
223+ write ! ( f, "{}{}{}{}" , self . pattern[ 0 ] , self . pattern[ 2 ] , self . pattern[ 8 ] , self . pattern[ 10 ] )
135224 } else {
136225 write ! ( f, "X-Trans 6x6" )
137226 }
@@ -177,6 +266,117 @@ mod tests {
177266 assert_eq ! ( shifted. color_at( 2 , 3 ) , cfa. color_at( 3 , 4 ) ) ;
178267 }
179268
269+ #[ test]
270+ fn quad_bayer_rggb_pattern ( ) {
271+ let cfa = CfaPattern :: quad_bayer_rggb ( ) ;
272+ assert_eq ! ( cfa. width( ) , 4 ) ;
273+ assert_eq ! ( cfa. height( ) , 4 ) ;
274+ assert ! ( cfa. is_quad_bayer( ) ) ;
275+ assert ! ( !cfa. is_bayer( ) ) ;
276+ assert ! ( !cfa. is_xtrans( ) ) ;
277+
278+ // Row 0: R R G G
279+ assert_eq ! ( cfa. color_at( 0 , 0 ) , Red ) ;
280+ assert_eq ! ( cfa. color_at( 0 , 1 ) , Red ) ;
281+ assert_eq ! ( cfa. color_at( 0 , 2 ) , Green ) ;
282+ assert_eq ! ( cfa. color_at( 0 , 3 ) , Green ) ;
283+ // Row 1: R R G G
284+ assert_eq ! ( cfa. color_at( 1 , 0 ) , Red ) ;
285+ assert_eq ! ( cfa. color_at( 1 , 1 ) , Red ) ;
286+ assert_eq ! ( cfa. color_at( 1 , 2 ) , Green ) ;
287+ assert_eq ! ( cfa. color_at( 1 , 3 ) , Green ) ;
288+ // Row 2: G G B B
289+ assert_eq ! ( cfa. color_at( 2 , 0 ) , Green ) ;
290+ assert_eq ! ( cfa. color_at( 2 , 1 ) , Green ) ;
291+ assert_eq ! ( cfa. color_at( 2 , 2 ) , Blue ) ;
292+ assert_eq ! ( cfa. color_at( 2 , 3 ) , Blue ) ;
293+ // Row 3: G G B B
294+ assert_eq ! ( cfa. color_at( 3 , 0 ) , Green ) ;
295+ assert_eq ! ( cfa. color_at( 3 , 1 ) , Green ) ;
296+ assert_eq ! ( cfa. color_at( 3 , 2 ) , Blue ) ;
297+ assert_eq ! ( cfa. color_at( 3 , 3 ) , Blue ) ;
298+
299+ // Tiling
300+ assert_eq ! ( cfa. color_at( 4 , 0 ) , Red ) ;
301+ assert_eq ! ( cfa. color_at( 0 , 4 ) , Red ) ;
302+ assert_eq ! ( cfa. color_at( 6 , 6 ) , Blue ) ;
303+ }
304+
305+ #[ test]
306+ fn quad_bayer_all_variants ( ) {
307+ let rggb = CfaPattern :: quad_bayer_rggb ( ) ;
308+ let bggr = CfaPattern :: quad_bayer_bggr ( ) ;
309+ let grbg = CfaPattern :: quad_bayer_grbg ( ) ;
310+ let gbrg = CfaPattern :: quad_bayer_gbrg ( ) ;
311+
312+ // Top-left 2x2 block color
313+ assert_eq ! ( rggb. color_at( 0 , 0 ) , Red ) ;
314+ assert_eq ! ( bggr. color_at( 0 , 0 ) , Blue ) ;
315+ assert_eq ! ( grbg. color_at( 0 , 0 ) , Green ) ;
316+ assert_eq ! ( gbrg. color_at( 0 , 0 ) , Green ) ;
317+
318+ // Bottom-right 2x2 block color
319+ assert_eq ! ( rggb. color_at( 2 , 2 ) , Blue ) ;
320+ assert_eq ! ( bggr. color_at( 2 , 2 ) , Red ) ;
321+ assert_eq ! ( grbg. color_at( 2 , 2 ) , Green ) ;
322+ assert_eq ! ( gbrg. color_at( 2 , 2 ) , Green ) ;
323+
324+ // All should be quad Bayer
325+ for cfa in & [ & rggb, & bggr, & grbg, & gbrg] {
326+ assert ! ( cfa. is_quad_bayer( ) ) ;
327+ }
328+ }
329+
330+ #[ test]
331+ fn quad_bayer_shift ( ) {
332+ let cfa = CfaPattern :: quad_bayer_rggb ( ) ;
333+ let shifted = cfa. shift ( 1 , 1 ) ;
334+ // shifted.color_at(0,0) == cfa.color_at(1,1)
335+ assert_eq ! ( shifted. color_at( 0 , 0 ) , cfa. color_at( 1 , 1 ) ) ;
336+ assert_eq ! ( shifted. color_at( 2 , 3 ) , cfa. color_at( 3 , 0 ) ) ;
337+ }
338+
339+ #[ test]
340+ fn quad_to_bayer_mapping ( ) {
341+ assert_eq ! (
342+ CfaPattern :: quad_bayer_rggb( ) . quad_to_bayer( ) . unwrap( ) ,
343+ CfaPattern :: bayer_rggb( )
344+ ) ;
345+ assert_eq ! (
346+ CfaPattern :: quad_bayer_bggr( ) . quad_to_bayer( ) . unwrap( ) ,
347+ CfaPattern :: bayer_bggr( )
348+ ) ;
349+ assert_eq ! (
350+ CfaPattern :: quad_bayer_grbg( ) . quad_to_bayer( ) . unwrap( ) ,
351+ CfaPattern :: bayer_grbg( )
352+ ) ;
353+ assert_eq ! (
354+ CfaPattern :: quad_bayer_gbrg( ) . quad_to_bayer( ) . unwrap( ) ,
355+ CfaPattern :: bayer_gbrg( )
356+ ) ;
357+ }
358+
359+ #[ test]
360+ fn quad_to_bayer_returns_none_for_non_quad ( ) {
361+ assert ! ( CfaPattern :: bayer_rggb( ) . quad_to_bayer( ) . is_none( ) ) ;
362+ assert ! ( CfaPattern :: xtrans_default( ) . quad_to_bayer( ) . is_none( ) ) ;
363+ }
364+
365+ #[ test]
366+ fn quad_bayer_color_count_per_tile ( ) {
367+ // Each 4x4 tile should have 4R, 8G, 4B
368+ let cfa = CfaPattern :: quad_bayer_rggb ( ) ;
369+ let mut counts = [ 0u32 ; 3 ] ;
370+ for r in 0 ..4 {
371+ for c in 0 ..4 {
372+ counts[ cfa. color_at ( r, c) as usize ] += 1 ;
373+ }
374+ }
375+ assert_eq ! ( counts[ Red as usize ] , 4 ) ;
376+ assert_eq ! ( counts[ Green as usize ] , 8 ) ;
377+ assert_eq ! ( counts[ Blue as usize ] , 4 ) ;
378+ }
379+
180380 #[ test]
181381 fn xtrans_every_3x3_has_all_colors ( ) {
182382 let cfa = CfaPattern :: xtrans_default ( ) ;
0 commit comments