blob: 143b3c157ba3630799843420d2db74f119d2ded9 [file] [log] [blame]
// Copyright 2025 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::selectors::TABLES;
#[derive(Debug, PartialEq, Eq)]
pub struct BlockMetadata {
pub base: [[i32; 3]; 2],
pub table_idx_1: u32,
pub table_idx_2: u32,
pub flip: bool,
}
// Extract base color in the individual mode.
// Return [base_color1, base_color2]
// See "Khronos Data Format Specification v1.4.0", Section 21, "In the
// 'individual mode'..."
fn parse_individual_base(patch: u64) -> [[i32; 3]; 2] {
// The bit layout for individual mode is:
// 63..60: base color 1 R
// 59..56: base color 2 R
// 55..52: base color 1 G
// 51..48: base color 2 G
// 49..44: base color 1 B
// 43..40: base color 2 B
// The remaining bits are handled in 'parse_block_metadata'
let base = [0, 1].map(|j| {
// i = 0,1,2 maps to [R, G, B] respectively
[0, 1, 2].map(|i| {
let shift = 60 - 4 * (i * 2 + j);
let read_4bit = ((patch >> shift) & 0b1111) as i32;
scale_4bit_to_8bit(read_4bit)
})
});
return base;
}
// Expands a 4-bit color component to an 8-bit component
// according to the specification.
//
// We want to scale value in [0, 15] to value in [0, 255].
// The ideal scaling is (input / 15) * 255 = input * 17.
// The bitwise operation of this function:
// (input << 4) | input
// is equivalent to:
// (input * 2^4) + input = input * 17.
// Thus, the two results are identical.
//
// In addition, the operation of this function is hardware-friendly and
// correctly maps the endpoints:
// - Minimum: 0b_0000(0) becomes 0b_00000000(0).
// - Maximum: 0b_1111(15) becomes 0b_11111111(255).
pub fn scale_4bit_to_8bit(input: i32) -> i32 {
assert!(input >= 0 && input < 16);
input << 4 | input
}
// Extract base color in the differential mode.
// Return [base_color1, base_color2], where base_color2 is base_color1 + delta.
// See "Khronos Data Format Specification v1.4.0", Section 21, "In the
// 'differential mode'..."
fn parse_differential_base(patch: u64) -> [[i32; 3]; 2] {
// The bit layout for differential mode is
// 63..59: base color R
// 58..56: color delta R
// 55..51: base color G
// 50..48: color delta G
// 47..43: base color B
// 42..40: color delta B
// The remaining bits are handled in 'parse_block_metadata'
// i = 0,1,2 maps to [R, G, B] respectively
let base_1_5bit = [0, 1, 2].map(|i| {
let shift = 59 - 8 * i;
((patch >> shift) & 0b11111) as i32
});
let base_1 = base_1_5bit.map(|i| scale_5bit_to_8bit(i));
let base_2 = [0, 1, 2].map(|i| {
let shift = 56 - 8 * i;
let delta = read_delta_bits(((patch >> shift) & 0b111) as u32);
let base_2_5bit = (base_1_5bit[i] + delta).clamp(0, 31);
scale_5bit_to_8bit(base_2_5bit)
});
[base_1, base_2]
}
// Parses a 3-bit binary string as a two's complement signed integer.
// The range covered is [-4,3].
pub fn read_delta_bits(input: u32) -> i32 {
assert!(input < 8);
if input >= 4 {
(input as i32) - 8
} else {
input as i32
}
}
// Expands a 5-bit color component to an 8-bit component
// according to the specification.
//
// We want to scale value in [0, 31] to value in [0, 255].
// The ideal scaling is (input / 31) * 255 ~ input * 8.22580...
// The bitwise operation of this function:
// (input <<3) | (input >> 2)
// is equivalent to:
// (input * 2^3) + floor(input / 2^2) ~ input * 8.25.
// The scaling value (8.25) approximates the ideal scaling (8.225...).
//
// In addition, the operation of this function is hardware-friendly and
// correctly maps the endpoints:
// - Minimum: 0b_00000(0) becomes 0b_00000000(0).
// - Maximum: 0b_11111(31) becomes 0b_11111111(255).
pub fn scale_5bit_to_8bit(input: i32) -> i32 {
assert!(input >= 0 && input < 32);
input << 3 | input >> 2
}
// Extract block meta-data from the upper half of 64 bits.
// (See "Khronos Data Format Specification v1.4.0", Section21, Table 138)
// There are 2 ways to extract the base colors.
// One is 'individual' mode (See Table 138 part a), and the other is
// 'differential' mode (See Table 138 part b).
pub fn parse_block_metadata(etc1_block: u64) -> BlockMetadata {
let diff0 = ((etc1_block >> 33) & 0b1) == 0;
let base =
if diff0 { parse_individual_base(etc1_block) } else { parse_differential_base(etc1_block) };
BlockMetadata {
base,
table_idx_1: ((etc1_block >> 37) & 0b111) as u32,
table_idx_2: ((etc1_block >> 34) & 0b111) as u32,
flip: ((etc1_block >> 32) & 0b1) == 1,
}
}
// Compute the final pixel color using the block's base color and per-pixel
// bits. From the Khronos Data Format Specification v1.4.0
// The modifier value comes from Table 142. and Table 143.
// - `base`: should be the base color as an [R, G, B].
// - `table_idx`: should be the index for the modifier table. (range 0-7).
// - `negative`: If true, the modifier value is treated as negative.
// - `large`: If true, we select the large value from the table.
// The return value is a u32 packed in the 0xAABBGGRR layout.
pub fn apply_modifier(base: [i32; 3], table_idx: u32, negative: bool, large: bool) -> u32 {
let base_delta = (TABLES[table_idx as usize][large as usize]) as i32;
let modifier_delta = if negative { -base_delta } else { base_delta };
let mut output_rgba: u32 = 0xFF000000; // Set alpha channel to 1.0
for i in 0..3 {
let channel_color = (base[i] + modifier_delta).clamp(0, 255) as u32;
// Pack the R,G,B values into a single 32-bit
let shift = i * 8;
output_rgba |= channel_color << shift;
}
return output_rgba;
}
pub fn decode_etc1_block(input_etc1: u64) -> [[u32; 4]; 4] {
let metadata = parse_block_metadata(input_etc1);
// The output layout is
// [[ a e i m ],
// [ b f j n ],
// [ c g k o ],
// [ d h l p ]].
let mut output = [[0 as u32; 4]; 4];
for col in 0..4 {
for row in 0..4 {
// When flip bit = 0, the block is divided into two 2×4 subblocks.
// - Pixels a,b,c,d, e,f,g,h (columns 0, 1) use base color 1.
// - Pixels i,j,k,l, m,n,o,p (columns 2, 3) use base color 2.
//
// When flip bit = 1, the block is divided into two 4×2 subblocks.
// - Pixels a,e,i,m, b,f,j,n (rows 0, 1) use base color 1.
// - Pixels c,g,k,o, d,h,l,p (rows 2, 3) use base color 2.
let left_half = col <= 1;
let top_half = row <= 1;
let use_base_color_1 = if metadata.flip { top_half } else { left_half };
// The lower-32 bit contains indices for the color modifiers of all 16 pixels
// (a-p).
//
// Bits 16-31 (the upper 16 bit) decides whether the modifier value is negative
// or not. Bit 16 corresponds to pixel 'a', ..., Bit 31 to pixel 'p'
//
// Bits 0-15 (the lower 16 bit) decides whether the modifier value is large or
// not. Bit 0 corresponds to pixel 'a', ..., Bit 15 to pixel 'p'
// The modifier table provides pairs of [small, large] for each table index.
// The two bits extracted for a pixel select one of four possible deltas.
// For example, TABLES[2] is [9,29]
// - negative = 1, large = 1 : -large (ex. -29)
// - negative = 1, large = 0 : -small (ex. -9)
// - negative = 0, large = 0 : +small (ex. 9)
// - negative = 0, large = 1 : +large (ex. 29)
let shift = row + col * 4;
let large = (input_etc1 >> shift) & 0b1 == 1;
let negative = (input_etc1 >> shift + 16) & 0b1 == 1;
// TODO: base[0] actually means base color 1, and base[1] means base color 2.
// Refactor this section for clarity.
output[row][col] = if use_base_color_1 {
apply_modifier(metadata.base[0], metadata.table_idx_1, negative, large)
} else {
apply_modifier(metadata.base[1], metadata.table_idx_2, negative, large)
};
}
}
return output;
}