use crate::cache::filter_blob_removed_events;
use crate::image_info::StoredBlobCacheKey;
use crate::{Cache, CacheEntryAccess, ImageInfo};
use ahash::HashMap;
use re_byte_size::SizeBytes as _;
use re_byte_size::{MemUsageTree, MemUsageTreeCapture};
use re_chunk_store::ChunkStoreEvent;
use re_entity_db::EntityDb;
use std::sync::Arc;
#[derive(Clone, Debug)]
pub struct Rgb8Histogram {
pub bins: [[u64; 256]; 3],
}
impl re_byte_size::SizeBytes for Rgb8Histogram {
fn heap_size_bytes(&self) -> u64 {
0
}
fn is_pod() -> bool {
true
}
}
impl Rgb8Histogram {
pub fn from_rgb8(rgb: &[u8]) -> Self {
re_tracing::profile_function!();
let mut bins_r = [0_u64; 256];
let mut bins_g = [0_u64; 256];
let mut bins_b = [0_u64; 256];
let (chunks, _remainder) = rgb.as_chunks::<3>();
for &[r, g, b] in chunks {
bins_r[r as usize] += 1;
bins_g[g as usize] += 1;
bins_b[b as usize] += 1;
}
Self {
bins: [bins_r, bins_g, bins_b],
}
}
}
#[derive(Default)]
pub struct ImageHistogramCache(HashMap<StoredBlobCacheKey, Arc<Rgb8Histogram>>);
impl ImageHistogramCache {
pub fn entry(&mut self, image: &ImageInfo) -> Arc<Rgb8Histogram> {
self.0
.entry(image.buffer_content_hash)
.or_insert_with(|| Arc::new(Rgb8Histogram::from_rgb8(&image.buffer)))
.clone()
}
}
impl CacheEntryAccess<ImageInfo, Arc<Rgb8Histogram>> for ImageHistogramCache {
fn read(&self, image: &ImageInfo) -> Option<Arc<Rgb8Histogram>> {
self.0.get(&image.buffer_content_hash).cloned()
}
fn compute(&mut self, image: &ImageInfo) -> Arc<Rgb8Histogram> {
self.entry(image)
}
}
impl Cache for ImageHistogramCache {
fn name(&self) -> &'static str {
"ImageHistogramCache"
}
fn purge_memory(&mut self) {
}
fn on_store_events(&mut self, events: &[&ChunkStoreEvent], _entity_db: &EntityDb) {
let removed = filter_blob_removed_events(events);
if removed.is_empty() {
return;
}
self.0.retain(|key, _| !removed.contains(key));
}
}
impl MemUsageTreeCapture for ImageHistogramCache {
fn capture_mem_usage_tree(&self) -> MemUsageTree {
MemUsageTree::Bytes(self.0.total_size_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_buffer_yields_zero_bins() {
let hist = Rgb8Histogram::from_rgb8(&[]);
for channel in &hist.bins {
assert!(channel.iter().all(|&c| c == 0));
}
}
#[test]
fn single_pixel_increments_one_bin_per_channel() {
let hist = Rgb8Histogram::from_rgb8(&[10, 20, 30]);
assert_eq!(hist.bins[0][10], 1);
assert_eq!(hist.bins[1][20], 1);
assert_eq!(hist.bins[2][30], 1);
let total: u64 = hist.bins.iter().flatten().sum();
assert_eq!(total, 3);
}
#[test]
fn trailing_bytes_are_ignored() {
let hist = Rgb8Histogram::from_rgb8(&[1, 2, 3, 99]);
assert_eq!(hist.bins[0][1], 1);
assert_eq!(hist.bins[1][2], 1);
assert_eq!(hist.bins[2][3], 1);
assert_eq!(hist.bins[0][99], 0);
assert_eq!(hist.bins[1][99], 0);
assert_eq!(hist.bins[2][99], 0);
}
#[test]
fn many_pixels_count_correctly() {
let buffer: Vec<u8> = (0..100).flat_map(|_| [5, 6, 7]).collect();
let hist = Rgb8Histogram::from_rgb8(&buffer);
assert_eq!(hist.bins[0][5], 100);
assert_eq!(hist.bins[1][6], 100);
assert_eq!(hist.bins[2][7], 100);
for channel in 0..3 {
for bin in 0..256 {
let expected = match (channel, bin) {
(0, 5) | (1, 6) | (2, 7) => 100,
_ => 0,
};
assert_eq!(
hist.bins[channel][bin], expected,
"channel {channel} bin {bin}"
);
}
}
}
}