You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Nannou Rust框架中获取wgpu纹理的平均/突出颜色?

从nannou::wgpu::Texture获取平均/突出颜色的实现方案

第一步:从WGPU纹理中读取像素数据

WGPU的Texture是GPU端资源,无法直接访问像素值,必须先把数据下载到CPU内存:

  • 创建Buffer,设置用途为COPY_DST和MAP_READ,大小与纹理像素数据匹配(比如RGBA8格式为 宽×高×4字节)
  • 通过CommandEncoder发起拷贝命令,将Texture数据复制到Buffer
  • 等待Buffer映射完成,读取切片并解析为归一化的颜色数组

示例代码:

use nannou::wgpu::{self, Device, Queue, Texture, TextureViewDescriptor};

fn download_texture_pixels(
    device: &Device,
    queue: &Queue,
    texture: &Texture,
) -> Vec<[f32; 4]> {
    let format = texture.format();
    let width = texture.size().width;
    let height = texture.size().height;
    let bytes_per_pixel = format.block_size(None).unwrap() as usize;
    let buffer_size = (width * height * bytes_per_pixel as u32) as usize;

    // 创建接收数据的Buffer
    let buffer = device.create_buffer(&wgpu::BufferDescriptor {
        label: Some("texture_download_buffer"),
        size: buffer_size as u64,
        usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
        mapped_at_creation: false,
    });

    // 创建纹理视图
    let texture_view = texture.create_view(&TextureViewDescriptor::default());

    // 编码拷贝命令
    let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
        label: Some("texture_copy_encoder"),
    });
    encoder.copy_texture_to_buffer(
        wgpu::ImageCopyTexture {
            texture,
            mip_level: 0,
            origin: wgpu::Origin3d::ZERO,
            aspect: wgpu::TextureAspect::All,
        },
        wgpu::ImageCopyBuffer {
            buffer: &buffer,
            layout: wgpu::ImageDataLayout {
                offset: 0,
                bytes_per_row: Some(width * bytes_per_pixel as u32),
                rows_per_image: Some(height),
            },
        },
        texture.size(),
    );

    // 提交命令并等待映射完成
    queue.submit(Some(encoder.finish()));
    let buffer_slice = buffer.slice(..);
    let (sender, receiver) = futures_intrusive::channel::shared::oneshot_channel();
    buffer_slice.map_async(wgpu::MapMode::Read, move |result| {
        sender.send(result).unwrap();
    });
    device.poll(wgpu::Maintain::Wait);
    receiver.receive().unwrap().unwrap();

    // 解析为归一化RGBA颜色数组
    let data = buffer_slice.get_mapped_range();
    let pixels = data
        .chunks_exact(bytes_per_pixel)
        .map(|chunk| {
            let [r, g, b, a] = chunk.try_into().unwrap();
            [
                r as f32 / 255.0,
                g as f32 / 255.0,
                b as f32 / 255.0,
                a as f32 / 255.0,
            ]
        })
        .collect();
    drop(data);
    buffer.unmap();
    pixels
}

第二步:计算平均颜色

拿到像素数组后,对RGBA四个分量分别求和,再除以总像素数即可:

fn calculate_average_color(pixels: &[[f32; 4]]) -> [f32; 4] {
    let total = pixels.len() as f32;
    let mut sum_r = 0.0;
    let mut sum_g = 0.0;
    let mut sum_b = 0.0;
    let mut sum_a = 0.0;

    for &[r, g, b, a] in pixels {
        sum_r += r;
        sum_g += g;
        sum_b += b;
        sum_a += a;
    }

    [sum_r / total, sum_g / total, sum_b / total, sum_a / total]
}

第三步:提取突出颜色

方案1:高频颜色统计(快速实现)

先过滤低饱和度像素避免灰黑白干扰,再统计出现次数最多的颜色:

use std::collections::HashMap;

fn get_dominant_color(pixels: &[[f32; 4]], saturation_threshold: f32) -> [f32; 4] {
    // 过滤低饱和度像素
    let filtered: Vec<_> = pixels
        .iter()
        .filter(|&&[r, g, b, _]| {
            let max = *[r, g, b].iter().max_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
            let min = *[r, g, b].iter().min_by(|a, b| a.partial_cmp(b).unwrap()).unwrap();
            let saturation = if max == 0.0 { 0.0 } else { (max - min) / max };
            saturation >= saturation_threshold
        })
        .collect();

    if filtered.is_empty() {
        return [0.5, 0.5, 0.5, 1.0]; // 默认灰色
    }

    // 量化颜色并统计频率
    let mut counts = HashMap::new();
    for &color in &filtered {
        let key = (
            (color[0] * 255.0) as u8,
            (color[1] * 255.0) as u8,
            (color[2] * 255.0) as u8,
            (color[3] * 255.0) as u8,
        );
        *counts.entry(key).or_insert(0) += 1;
    }

    // 返回出现次数最多的颜色
    let (most_common, _) = counts.into_iter().max_by_key(|&(_, cnt)| cnt).unwrap();
    [
        most_common.0 as f32 / 255.0,
        most_common.1 as f32 / 255.0,
        most_common.2 as f32 / 255.0,
        most_common.3 as f32 / 255.0,
    ]
}

方案2:简化K-Means聚类(精准调色板)

如果需要多个突出颜色,用K-Means将像素分为K类,每类的中心即为一个核心颜色:

fn k_means_clustering(pixels: &[[f32; 4]], k: usize, iterations: usize) -> Vec<[f32; 4]> {
    // 随机选取K个初始中心
    let mut centers: Vec<_> = pixels.iter().cloned().take(k).collect();
    for _ in 0..iterations {
        // 将像素分配到最近的中心
        let mut clusters: Vec<Vec<[f32; 4]>> = vec![vec![]; k];
        for &pixel in pixels {
            let mut min_dist = f32::INFINITY;
            let mut closest_idx = 0;
            for (i, center) in centers.iter().enumerate() {
                let dist = (pixel[0]-center[0]).powi(2) + (pixel[1]-center[1]).powi(2)
                    + (pixel[2]-center[2]).powi(2) + (pixel[3]-center[3]).powi(2);
                if dist < min_dist {
                    min_dist = dist;
                    closest_idx = i;
                }
            }
            clusters[closest_idx].push(pixel);
        }
        // 更新聚类中心
        for i in 0..k {
            if clusters[i].is_empty() { continue; }
            let count = clusters[i].len() as f32;
            let mut sum_r = 0.0;
            let mut sum_g = 0.0;
            let mut sum_b = 0.0;
            let mut sum_a = 0.0;
            for &[r,g,b,a] in &clusters[i] {
                sum_r += r; sum_g += g; sum_b += b; sum_a += a;
            }
            centers[i] = [sum_r/count, sum_g/count, sum_b/count, sum_a/count];
        }
    }
    centers
}

注意事项

  • 大尺寸纹理建议先通过WGPU降采样缩小分辨率,减少CPU计算压力
  • 处理透明像素时,可根据alpha值加权计算(比如平均颜色时乘以alpha再求和)
  • 若追求极致性能,可写WGSL shader在GPU端直接计算平均颜色,无需下载数据到CPU

内容的提问来源于stack exchange,提问作者Luctins

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 01:30:29