mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Add JPEG, PNG, and mixed compression for terrain.
This commit is contained in:
parent
a4dc52eeb2
commit
b855c2bf97
7
Cargo.lock
generated
7
Cargo.lock
generated
@ -2472,6 +2472,7 @@ dependencies = [
|
||||
"bytemuck",
|
||||
"byteorder",
|
||||
"color_quant",
|
||||
"jpeg-decoder",
|
||||
"num-iter",
|
||||
"num-rational 0.3.2",
|
||||
"num-traits",
|
||||
@ -2616,6 +2617,12 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jpeg-decoder"
|
||||
version = "0.1.22"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.50"
|
||||
|
@ -19,7 +19,7 @@ bincode = "1.3.1"
|
||||
bitvec = "0.22"
|
||||
enum-iterator = "0.6"
|
||||
fxhash = "0.2.1"
|
||||
image = { version = "0.23.12", default-features = false, features = ["png"] }
|
||||
image = { version = "0.23.12", default-features = false, features = ["png", "jpeg"] }
|
||||
itertools = "0.10"
|
||||
vek = { version = "0.14.1", features = ["serde"] }
|
||||
noise = { version = "0.7", default-features = false }
|
||||
|
@ -1,12 +1,17 @@
|
||||
use common::{
|
||||
spiral::Spiral2d,
|
||||
terrain::{chonk::Chonk, Block, BlockKind, SpriteKind},
|
||||
vol::{IntoVolIterator, RectVolSize, SizedVol, WriteVol},
|
||||
volumes::dyna::{Access, ColumnAccess, Dyna},
|
||||
vol::{BaseVol, IntoVolIterator, ReadVol, RectVolSize, SizedVol, WriteVol},
|
||||
volumes::{
|
||||
dyna::{Access, ColumnAccess, Dyna},
|
||||
vol_grid_2d::VolGrid2d,
|
||||
},
|
||||
};
|
||||
use hashbrown::HashMap;
|
||||
use std::{
|
||||
fmt::Debug,
|
||||
io::{Read, Write},
|
||||
sync::Arc,
|
||||
time::Instant,
|
||||
};
|
||||
use tracing::{debug, trace};
|
||||
@ -110,6 +115,254 @@ fn channelize_dyna<M: Clone, A: Access>(
|
||||
(blocks, r, g, b, sprites)
|
||||
}
|
||||
|
||||
/// Formula for packing voxel data into a 2d array
|
||||
pub trait PackingFormula {
|
||||
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32);
|
||||
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32);
|
||||
}
|
||||
|
||||
/// A tall, thin image, with no wasted space, but which most image viewers don't
|
||||
/// handle well. Z levels increase from top to bottom, xy-slices are stacked
|
||||
/// vertically.
|
||||
pub struct TallPacking {
|
||||
/// Making the borders go back and forth based on z-parity preserves spatial
|
||||
/// locality better, but is more confusing to look at
|
||||
pub flip_y: bool,
|
||||
}
|
||||
|
||||
impl PackingFormula for TallPacking {
|
||||
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) { (dims.x, dims.y * dims.z) }
|
||||
|
||||
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
|
||||
let i = x;
|
||||
let j0 = if self.flip_y {
|
||||
if z % 2 == 0 { y } else { dims.y - y - 1 }
|
||||
} else {
|
||||
y
|
||||
};
|
||||
let j = z * dims.y + j0;
|
||||
(i, j)
|
||||
}
|
||||
}
|
||||
|
||||
/// A grid of the z levels, left to right, top to bottom, like English prose.
|
||||
/// Convenient for visualizing terrain, but wastes space if the number of z
|
||||
/// levels isn't a perfect square.
|
||||
pub struct GridLtrPacking;
|
||||
|
||||
impl PackingFormula for GridLtrPacking {
|
||||
fn dimensions(&self, dims: Vec3<u32>) -> (u32, u32) {
|
||||
let rootz = (dims.z as f64).sqrt().ceil() as u32;
|
||||
(dims.x * rootz, dims.y * rootz)
|
||||
}
|
||||
|
||||
fn index(&self, dims: Vec3<u32>, x: u32, y: u32, z: u32) -> (u32, u32) {
|
||||
let rootz = (dims.z as f64).sqrt().ceil() as u32;
|
||||
let i = x + (z % rootz) * dims.x;
|
||||
let j = y + (z / rootz) * dims.y;
|
||||
(i, j)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VoxelImageEncoding {
|
||||
type Workspace;
|
||||
type Output;
|
||||
fn create(width: u32, height: u32) -> Self::Workspace;
|
||||
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>);
|
||||
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind);
|
||||
fn finish(ws: &Self::Workspace) -> Self::Output;
|
||||
}
|
||||
|
||||
pub struct PngEncoding;
|
||||
|
||||
impl VoxelImageEncoding for PngEncoding {
|
||||
type Output = Vec<u8>;
|
||||
type Workspace = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
|
||||
|
||||
fn create(width: u32, height: u32) -> Self::Workspace {
|
||||
use image::{ImageBuffer, Rgba};
|
||||
ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height)
|
||||
}
|
||||
|
||||
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
|
||||
ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8]));
|
||||
}
|
||||
|
||||
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind) {
|
||||
ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255]));
|
||||
}
|
||||
|
||||
fn finish(ws: &Self::Workspace) -> Self::Output {
|
||||
use image::codecs::png::{CompressionType, FilterType};
|
||||
let mut buf = Vec::new();
|
||||
let png = image::codecs::png::PngEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
CompressionType::Fast,
|
||||
FilterType::Up,
|
||||
);
|
||||
png.encode(
|
||||
&*ws.as_raw(),
|
||||
ws.width(),
|
||||
ws.height(),
|
||||
image::ColorType::Rgba8,
|
||||
)
|
||||
.unwrap();
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
pub struct JpegEncoding;
|
||||
|
||||
impl VoxelImageEncoding for JpegEncoding {
|
||||
type Output = Vec<u8>;
|
||||
type Workspace = image::ImageBuffer<image::Rgba<u8>, Vec<u8>>;
|
||||
|
||||
fn create(width: u32, height: u32) -> Self::Workspace {
|
||||
use image::{ImageBuffer, Rgba};
|
||||
ImageBuffer::<Rgba<u8>, Vec<u8>>::new(width, height)
|
||||
}
|
||||
|
||||
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
|
||||
ws.put_pixel(x, y, image::Rgba([rgb.r, rgb.g, rgb.b, 255 - kind as u8]));
|
||||
}
|
||||
|
||||
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind) {
|
||||
ws.put_pixel(x, y, image::Rgba([kind as u8, sprite as u8, 255, 255]));
|
||||
}
|
||||
|
||||
fn finish(ws: &Self::Workspace) -> Self::Output {
|
||||
let mut buf = Vec::new();
|
||||
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
|
||||
jpeg.encode_image(ws).unwrap();
|
||||
buf
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MixedEncoding;
|
||||
|
||||
impl VoxelImageEncoding for MixedEncoding {
|
||||
type Output = (Vec<u8>, usize);
|
||||
type Workspace = (
|
||||
image::ImageBuffer<image::LumaA<u8>, Vec<u8>>,
|
||||
image::ImageBuffer<image::Rgb<u8>, Vec<u8>>,
|
||||
);
|
||||
|
||||
fn create(width: u32, height: u32) -> Self::Workspace {
|
||||
use image::ImageBuffer;
|
||||
(
|
||||
ImageBuffer::new(width, height),
|
||||
ImageBuffer::new(width, height),
|
||||
)
|
||||
}
|
||||
|
||||
fn put_solid(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, rgb: Rgb<u8>) {
|
||||
ws.0.put_pixel(x, y, image::LumaA([kind as u8, 0]));
|
||||
ws.1.put_pixel(x, y, image::Rgb([rgb.r, rgb.g, rgb.b]));
|
||||
}
|
||||
|
||||
fn put_sprite(ws: &mut Self::Workspace, x: u32, y: u32, kind: BlockKind, sprite: SpriteKind) {
|
||||
ws.0.put_pixel(x, y, image::LumaA([kind as u8, sprite as u8]));
|
||||
ws.1.put_pixel(x, y, image::Rgb([0; 3]));
|
||||
}
|
||||
|
||||
fn finish(ws: &Self::Workspace) -> Self::Output {
|
||||
let mut buf = Vec::new();
|
||||
use image::codecs::png::{CompressionType, FilterType};
|
||||
let png = image::codecs::png::PngEncoder::new_with_quality(
|
||||
&mut buf,
|
||||
CompressionType::Fast,
|
||||
FilterType::Up,
|
||||
);
|
||||
png.encode(
|
||||
&*ws.0.as_raw(),
|
||||
ws.0.width(),
|
||||
ws.0.height(),
|
||||
image::ColorType::La8,
|
||||
)
|
||||
.unwrap();
|
||||
let index = buf.len();
|
||||
let mut jpeg = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut buf, 1);
|
||||
jpeg.encode_image(&ws.1).unwrap();
|
||||
//println!("Mixed {} {}", index, buf.len());
|
||||
(buf, index)
|
||||
}
|
||||
}
|
||||
|
||||
fn image_terrain_chonk<S: RectVolSize, M: Clone, P: PackingFormula, VIE: VoxelImageEncoding>(
|
||||
vie: VIE,
|
||||
packing: P,
|
||||
chonk: &Chonk<Block, S, M>,
|
||||
) -> VIE::Output {
|
||||
image_terrain(
|
||||
vie,
|
||||
packing,
|
||||
chonk,
|
||||
Vec3::new(0, 0, chonk.get_min_z() as u32),
|
||||
Vec3::new(S::RECT_SIZE.x, S::RECT_SIZE.y, chonk.get_max_z() as u32),
|
||||
)
|
||||
}
|
||||
|
||||
fn image_terrain_volgrid<
|
||||
S: RectVolSize + Debug,
|
||||
M: Clone + Debug,
|
||||
P: PackingFormula,
|
||||
VIE: VoxelImageEncoding,
|
||||
>(
|
||||
vie: VIE,
|
||||
packing: P,
|
||||
volgrid: &VolGrid2d<Chonk<Block, S, M>>,
|
||||
) -> VIE::Output {
|
||||
let mut lo = Vec3::broadcast(i32::MAX);
|
||||
let mut hi = Vec3::broadcast(i32::MIN);
|
||||
for (pos, chonk) in volgrid.iter() {
|
||||
lo.x = lo.x.min(pos.x * S::RECT_SIZE.x as i32);
|
||||
lo.y = lo.y.min(pos.y * S::RECT_SIZE.y as i32);
|
||||
lo.z = lo.z.min(chonk.get_min_z());
|
||||
|
||||
hi.x = hi.x.max((pos.x + 1) * S::RECT_SIZE.x as i32);
|
||||
hi.y = hi.y.max((pos.y + 1) * S::RECT_SIZE.y as i32);
|
||||
hi.z = hi.z.max(chonk.get_max_z());
|
||||
}
|
||||
println!("{:?} {:?}", lo, hi);
|
||||
|
||||
image_terrain(vie, packing, volgrid, lo.as_(), hi.as_())
|
||||
}
|
||||
|
||||
fn image_terrain<V: BaseVol<Vox = Block> + ReadVol, P: PackingFormula, VIE: VoxelImageEncoding>(
|
||||
_: VIE,
|
||||
packing: P,
|
||||
vol: &V,
|
||||
lo: Vec3<u32>,
|
||||
hi: Vec3<u32>,
|
||||
) -> VIE::Output {
|
||||
let dims = hi - lo;
|
||||
|
||||
let (width, height) = packing.dimensions(dims);
|
||||
let mut image = VIE::create(width, height);
|
||||
//println!("jpeg dims: {:?}", dims);
|
||||
for z in 0..dims.z {
|
||||
for y in 0..dims.y {
|
||||
for x in 0..dims.x {
|
||||
let (i, j) = packing.index(dims, x, y, z);
|
||||
//println!("{:?} {:?}", (x, y, z), (i, j));
|
||||
|
||||
let block = *vol
|
||||
.get(Vec3::new(x + lo.x, y + lo.y, z + lo.z).as_())
|
||||
.unwrap_or(&Block::empty());
|
||||
//println!("{} {} {} {:?}", x, y, z, block);
|
||||
if let Some(rgb) = block.get_color() {
|
||||
VIE::put_solid(&mut image, i, j, *block, rgb);
|
||||
} else {
|
||||
let sprite = block.get_sprite().unwrap();
|
||||
VIE::put_sprite(&mut image, i, j, *block, sprite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
VIE::finish(&image)
|
||||
}
|
||||
|
||||
fn histogram_to_dictionary(histogram: &HashMap<Vec<u8>, usize>, dictionary: &mut Vec<u8>) {
|
||||
let mut tmp: Vec<(Vec<u8>, usize)> = histogram.iter().map(|(k, v)| (k.clone(), *v)).collect();
|
||||
tmp.sort_by_key(|(_, count)| *count);
|
||||
@ -142,9 +395,10 @@ fn main() {
|
||||
let mut dictionary2 = vec![0xffu8; 1 << 16];
|
||||
let k = 32;
|
||||
let sz = world.sim().get_size();
|
||||
let mut totals = [0.0; 5];
|
||||
let mut total_timings = [0.0; 2];
|
||||
let mut totals = [0.0; 10];
|
||||
let mut total_timings = [0.0; 7];
|
||||
let mut count = 0;
|
||||
let mut volgrid = VolGrid2d::new().unwrap();
|
||||
for (i, (x, y)) in Spiral2d::new()
|
||||
.radius(20)
|
||||
.map(|v| (v.x + sz.x as i32 / 2, v.y + sz.y as i32 / 2))
|
||||
@ -182,6 +436,36 @@ fn main() {
|
||||
let deflate_dyna = do_deflate(&*ser_dyna);
|
||||
let deflate_channeled_dyna =
|
||||
do_deflate_flate2(&bincode::serialize(&channelize_dyna(&dyna)).unwrap());
|
||||
|
||||
let jpegchonkgrid_pre = Instant::now();
|
||||
let jpegchonkgrid = image_terrain_chonk(JpegEncoding, GridLtrPacking, &chunk);
|
||||
let jpegchonkgrid_post = Instant::now();
|
||||
|
||||
if false {
|
||||
use std::fs::File;
|
||||
let mut f = File::create(&format!("chonkjpegs/tmp_{}_{}.jpg", x, y)).unwrap();
|
||||
f.write_all(&*jpegchonkgrid).unwrap();
|
||||
}
|
||||
|
||||
let jpegchonktall_pre = Instant::now();
|
||||
let jpegchonktall =
|
||||
image_terrain_chonk(JpegEncoding, TallPacking { flip_y: false }, &chunk);
|
||||
let jpegchonktall_post = Instant::now();
|
||||
|
||||
let jpegchonkflip_pre = Instant::now();
|
||||
let jpegchonkflip =
|
||||
image_terrain_chonk(JpegEncoding, TallPacking { flip_y: true }, &chunk);
|
||||
let jpegchonkflip_post = Instant::now();
|
||||
|
||||
let mixedchonk_pre = Instant::now();
|
||||
let mixedchonk =
|
||||
image_terrain_chonk(MixedEncoding, TallPacking { flip_y: true }, &chunk);
|
||||
let mixedchonk_post = Instant::now();
|
||||
|
||||
let pngchonk_pre = Instant::now();
|
||||
let pngchonk = image_terrain_chonk(PngEncoding, GridLtrPacking, &chunk);
|
||||
let pngchonk_post = Instant::now();
|
||||
|
||||
let n = uncompressed.len();
|
||||
let sizes = [
|
||||
lz4_chonk.len() as f32 / n as f32,
|
||||
@ -189,6 +473,11 @@ fn main() {
|
||||
lz4_dyna.len() as f32 / n as f32,
|
||||
deflate_dyna.len() as f32 / n as f32,
|
||||
deflate_channeled_dyna.len() as f32 / n as f32,
|
||||
jpegchonkgrid.len() as f32 / n as f32,
|
||||
jpegchonktall.len() as f32 / n as f32,
|
||||
jpegchonkflip.len() as f32 / n as f32,
|
||||
mixedchonk.0.len() as f32 / n as f32,
|
||||
pngchonk.len() as f32 / n as f32,
|
||||
];
|
||||
let best_idx = sizes
|
||||
.iter()
|
||||
@ -204,6 +493,11 @@ fn main() {
|
||||
let timings = [
|
||||
(lz4chonk_post - lz4chonk_pre).subsec_nanos(),
|
||||
(deflatechonk_post - deflatechonk_pre).subsec_nanos(),
|
||||
(jpegchonkgrid_post - jpegchonkgrid_pre).subsec_nanos(),
|
||||
(jpegchonktall_post - jpegchonktall_pre).subsec_nanos(),
|
||||
(jpegchonkflip_post - jpegchonkflip_pre).subsec_nanos(),
|
||||
(mixedchonk_post - mixedchonk_pre).subsec_nanos(),
|
||||
(pngchonk_post - pngchonk_pre).subsec_nanos(),
|
||||
];
|
||||
trace!(
|
||||
"{} {}: uncompressed: {}, {:?} {} {:?}",
|
||||
@ -214,13 +508,24 @@ fn main() {
|
||||
best_idx,
|
||||
timings
|
||||
);
|
||||
for j in 0..5 {
|
||||
for j in 0..totals.len() {
|
||||
totals[j] += sizes[j];
|
||||
}
|
||||
for j in 0..2 {
|
||||
for j in 0..total_timings.len() {
|
||||
total_timings[j] += timings[j] as f32;
|
||||
}
|
||||
count += 1;
|
||||
let _ = volgrid.insert(Vec2::new(x, y), Arc::new(chunk));
|
||||
|
||||
if (1usize..10)
|
||||
.into_iter()
|
||||
.any(|i| (2 * i + 1) * (2 * i + 1) == count)
|
||||
{
|
||||
use std::fs::File;
|
||||
let mut f = File::create(&format!("chonkjpegs/volgrid_{}.jpg", count)).unwrap();
|
||||
let jpeg_volgrid = image_terrain_volgrid(JpegEncoding, GridLtrPacking, &volgrid);
|
||||
f.write_all(&*jpeg_volgrid).unwrap();
|
||||
}
|
||||
}
|
||||
if i % 64 == 0 {
|
||||
println!("Chunks processed: {}\n", count);
|
||||
@ -232,6 +537,11 @@ fn main() {
|
||||
"Average deflate_channeled_dyna: {}",
|
||||
totals[4] / count as f32
|
||||
);
|
||||
println!("Average jpeggridchonk: {}", totals[5] / count as f32);
|
||||
println!("Average jpegtallchonk: {}", totals[6] / count as f32);
|
||||
println!("Average jpegflipchonk: {}", totals[7] / count as f32);
|
||||
println!("Average mixedchonk: {}", totals[8] / count as f32);
|
||||
println!("Average pngchonk: {}", totals[9] / count as f32);
|
||||
println!("");
|
||||
println!(
|
||||
"Average lz4_chonk nanos : {:02}",
|
||||
@ -241,6 +551,26 @@ fn main() {
|
||||
"Average deflate_chonk nanos: {:02}",
|
||||
total_timings[1] / count as f32
|
||||
);
|
||||
println!(
|
||||
"Average jpeggridchonk nanos: {:02}",
|
||||
total_timings[2] / count as f32
|
||||
);
|
||||
println!(
|
||||
"Average jpegtallchonk nanos: {:02}",
|
||||
total_timings[3] / count as f32
|
||||
);
|
||||
println!(
|
||||
"Average jpegflipchonk nanos: {:02}",
|
||||
total_timings[4] / count as f32
|
||||
);
|
||||
println!(
|
||||
"Average mixedchonk nanos: {:02}",
|
||||
total_timings[5] / count as f32
|
||||
);
|
||||
println!(
|
||||
"Average pngchonk nanos: {:02}",
|
||||
total_timings[6] / count as f32
|
||||
);
|
||||
println!("-----");
|
||||
}
|
||||
if i % 256 == 0 {
|
||||
|
Loading…
Reference in New Issue
Block a user