Skip to content

Commit

Permalink
add mosaic tiling for large image support
Browse files Browse the repository at this point in the history
  • Loading branch information
Kl4rry committed Jan 22, 2024
1 parent ec0fc37 commit 2b8ef72
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 70 deletions.
4 changes: 2 additions & 2 deletions src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -700,11 +700,11 @@ impl App {
});
}

pub fn update(&mut self, wgpu: &WgpuState) -> (bool, Duration) {
pub fn update(&mut self, _wgpu: &WgpuState) -> (bool, Duration) {
self.delay = Duration::MAX;

if let Some(ref mut image) = self.image_view {
self.delay = self.delay.min(image.animate(wgpu));
self.delay = self.delay.min(image.animate());
}

if let Some(ref mut image) = self.image_view {
Expand Down
70 changes: 8 additions & 62 deletions src/app/image_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ use std::{

use cgmath::{Deg, Matrix4, Ortho, Vector3, Vector4};
use image::GenericImageView;
use wgpu::util::DeviceExt;
use winit::event_loop::EventLoopProxy;

use self::image_renderer::Vertex;
use self::mosaic::Mosaic;
use super::op_queue::{Output, UserEventLoopProxyExt};
use crate::{
max,
Expand All @@ -23,13 +22,15 @@ use crate::{

pub mod crop_renderer;
pub mod image_renderer;
pub mod mosaic;

mod crop;
use crop::Crop;

mod texture;

pub struct ImageView {
pub mosaic: Vec<Mosaic>,
pub size: Vec2<f32>,
pub position: Vec2<f32>,
pub scale: f32,
Expand All @@ -47,28 +48,17 @@ pub struct ImageView {
pub grayscale: bool,
pub invert: bool,
pub crop: Crop,
vertices: wgpu::Buffer,
indices: wgpu::Buffer,
texture: texture::Texture,
}

impl ImageView {
pub fn new(wgpu: &WgpuState, image_data: Arc<ImageData>, path: Option<PathBuf>) -> Self {
let frames = &image_data.frames;
let image = frames[0].buffer();
let (width, height) = image.dimensions();
let texture = texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None);

let indices: &[u32] = &[0, 1, 2, 2, 1, 3];
let indices = wgpu
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Image Index Buffer"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});
let mosaic = Mosaic::from_images(wgpu, image_data.clone());

Self {
mosaic,
size: Vec2::new(width as f32, height as f32),
position: Vec2::default(),
scale: 1.0,
Expand All @@ -79,9 +69,6 @@ impl ImageView {
horizontal_flip: false,
vertical_flip: false,
crop: Crop::new(),
vertices: get_vertex_buffer(wgpu, width as f32, height as f32),
indices,
texture,
path,
hue: 0.0,
contrast: 0.0,
Expand Down Expand Up @@ -137,14 +124,6 @@ impl ImageView {
}
}

pub fn get_buffers(&self) -> (&wgpu::Buffer, &wgpu::Buffer) {
(&self.vertices, &self.indices)
}

pub fn get_texture(&self) -> &texture::Texture {
&self.texture
}

pub fn scaled(&self) -> Vec2<f32> {
self.size * self.scale
}
Expand Down Expand Up @@ -187,7 +166,7 @@ impl ImageView {
self.vertical_flip = !self.vertical_flip;
}

pub fn animate(&mut self, wgpu: &WgpuState) -> Duration {
pub fn animate(&mut self) -> Duration {
let guard = self.image_data.read().unwrap();
let frames = &guard.frames;
if frames.len() > 1 {
Expand All @@ -201,9 +180,6 @@ impl ImageView {
self.index = 0;
}

drop(guard);
self.update_image_data(wgpu);

self.last_frame = now;

delay
Expand Down Expand Up @@ -251,16 +227,8 @@ impl ImageView {
mem::swap(&mut Arc::make_mut(&mut *guard).frames, frames);
let (width, height) = guard.frames[0].buffer().dimensions();
drop(guard);
self.update_image_data(wgpu);
self.vertices = get_vertex_buffer(wgpu, width as f32, height as f32);
}

fn update_image_data(&mut self, wgpu: &WgpuState) {
let guard = self.image_data.read().unwrap();
let frames = &guard.frames;
let image = frames[self.index].buffer();
self.size = Vec2::new(image.width() as f32, image.height() as f32);
self.texture = texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None);
self.mosaic = Mosaic::from_images(wgpu, self.image_data.read().unwrap().clone());
self.size = Vec2::new(width as f32, height as f32);
}

pub fn rotation(&self) -> i32 {
Expand Down Expand Up @@ -324,25 +292,3 @@ impl ImageView {
}
}
}

fn get_vertex_buffer(wgpu: &WgpuState, width: f32, height: f32) -> wgpu::Buffer {
let texture_cords = (
Vec2::new(1.0, 1.0),
Vec2::new(1.0, 0.0),
Vec2::new(0.0, 1.0),
Vec2::new(0.0, 0.0),
);
let shape = [
Vertex::new(0.0, 0.0, texture_cords.0.x(), texture_cords.0.y()),
Vertex::new(0.0, height, texture_cords.1.x(), texture_cords.1.y()),
Vertex::new(width, 0.0, texture_cords.2.x(), texture_cords.2.y()),
Vertex::new(width, height, texture_cords.3.x(), texture_cords.3.y()),
];

wgpu.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Image Vertex Buffer"),
contents: bytemuck::cast_slice(shape.as_slice()),
usage: wgpu::BufferUsages::VERTEX,
})
}
14 changes: 8 additions & 6 deletions src/app/image_view/image_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,13 +211,15 @@ impl Renderer {
rpass: &mut wgpu::RenderPass<'rpass>,
image_view: &'rpass ImageView,
) {
let (vertices, indices) = image_view.get_buffers();
let texture = image_view.get_texture();
rpass.set_pipeline(&self.pipeline);
rpass.set_bind_group(0, &self.uniform_bind_group, &[]);
rpass.set_bind_group(1, &texture.diffuse_bind_group, &[]);
rpass.set_vertex_buffer(0, vertices.slice(..));
rpass.set_index_buffer(indices.slice(..), wgpu::IndexFormat::Uint32);
rpass.draw_indexed(0..6, 0, 0..1);
let mosaic = &image_view.mosaic[image_view.index];
rpass.set_index_buffer(mosaic.indices.slice(..), wgpu::IndexFormat::Uint32);

for tile in &mosaic.tiles {
rpass.set_bind_group(1, &tile.texture.diffuse_bind_group, &[]);
rpass.set_vertex_buffer(0, tile.vertices.slice(..));
rpass.draw_indexed(0..6, 0, 0..1);
}
}
}
142 changes: 142 additions & 0 deletions src/app/image_view/mosaic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
use std::{iter, sync::Arc};

use image::{DynamicImage, SubImage};
use wgpu::{util::DeviceExt, Limits};

use super::{image_renderer::Vertex, texture};
use crate::{util::ImageData, vec2::Vec2, WgpuState};

pub struct Tile {
pub vertices: wgpu::Buffer,
pub texture: texture::Texture,
}

pub struct Mosaic {
pub tiles: Vec<Tile>,
pub indices: wgpu::Buffer,
}

impl Mosaic {
pub fn from_images(wgpu: &WgpuState, images: Arc<ImageData>) -> Vec<Self> {
let limit = Limits::default().max_texture_dimension_2d;

let mut output = Vec::new();
for image in &images.frames {
let mut tiles = Vec::new();
let image = &image.image;

let tile_width = (image.width() / limit) + 1;
let tile_height = (image.height() / limit) + 1;

// TODO: make parallel
for x in 0..tile_width {
for y in 0..tile_height {
let start_x = x * limit;
let start_y = y * limit;
let end_x = ((x + 1) * limit).min(image.width());
let end_y = ((y + 1) * limit).min(image.height());

if end_x.saturating_sub(start_x) == 0 || end_x.saturating_sub(start_x) == 0 {
continue;
}

let vertices = get_vertex_buffer(
wgpu,
start_x as f32,
start_y as f32,
end_x as f32,
end_y as f32,
);

// Fast path for small images
let texture = if tile_height == 1 && tile_width == 1 {
texture::Texture::from_image(&wgpu.device, &wgpu.queue, image, None)
} else {
let sub_image =
get_tile(image, start_x, start_y, end_x - start_x, end_y - start_y);
texture::Texture::from_image(&wgpu.device, &wgpu.queue, &sub_image, None)
};

tiles.push(Tile { vertices, texture });
}
}

let indices: &[u32] = &[0, 1, 2, 2, 1, 3];
let indices = wgpu
.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Image Index Buffer"),
contents: bytemuck::cast_slice(indices),
usage: wgpu::BufferUsages::INDEX | wgpu::BufferUsages::COPY_DST,
});

output.push(Mosaic { tiles, indices })
}
wgpu.queue.submit(iter::empty());
output
}
}

fn get_tile(image: &DynamicImage, x: u32, y: u32, width: u32, height: u32) -> DynamicImage {
match image {
DynamicImage::ImageLuma8(image) => {
DynamicImage::ImageLuma8(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageLumaA8(image) => {
DynamicImage::ImageLumaA8(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgb8(image) => {
DynamicImage::ImageRgb8(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgba8(image) => {
DynamicImage::ImageRgba8(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageLuma16(image) => {
DynamicImage::ImageLuma16(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageLumaA16(image) => {
DynamicImage::ImageLumaA16(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgb16(image) => {
DynamicImage::ImageRgb16(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgba16(image) => {
DynamicImage::ImageRgba16(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgb32F(image) => {
DynamicImage::ImageRgb32F(SubImage::new(image, x, y, width, height).to_image())
}
DynamicImage::ImageRgba32F(image) => {
DynamicImage::ImageRgba32F(SubImage::new(image, x, y, width, height).to_image())
}
image => DynamicImage::ImageRgba8(SubImage::new(image, x, y, width, height).to_image()),
}
}

fn get_vertex_buffer(
wgpu: &WgpuState,
start_x: f32,
start_y: f32,
end_x: f32,
end_y: f32,
) -> wgpu::Buffer {
let texture_cords = (
Vec2::new(1.0, 1.0),
Vec2::new(1.0, 0.0),
Vec2::new(0.0, 1.0),
Vec2::new(0.0, 0.0),
);
let shape = [
Vertex::new(start_x, start_y, texture_cords.0.x(), texture_cords.0.y()),
Vertex::new(start_x, end_y, texture_cords.1.x(), texture_cords.1.y()),
Vertex::new(end_x, start_y, texture_cords.2.x(), texture_cords.2.y()),
Vertex::new(end_x, end_y, texture_cords.3.x(), texture_cords.3.y()),
];

wgpu.device
.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Image Vertex Buffer"),
contents: bytemuck::cast_slice(shape.as_slice()),
usage: wgpu::BufferUsages::VERTEX,
})
}
1 change: 1 addition & 0 deletions src/app/image_view/texture.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl Texture {
size,
);

// TODO: add mip maps
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
let sampler = device.create_sampler(&wgpu::SamplerDescriptor {
address_mode_u: wgpu::AddressMode::ClampToEdge,
Expand Down

0 comments on commit 2b8ef72

Please sign in to comment.