Skip to content

Commit

Permalink
Add browser-dependent mipmap calculation for textures
Browse files Browse the repository at this point in the history
  • Loading branch information
Asaaj committed Jun 25, 2024
1 parent a66aa44 commit dc08681
Show file tree
Hide file tree
Showing 23 changed files with 126 additions and 17 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ target/
*.exe

scripts/data
.DS_Store


## DEPLOY_TAG
# Everything below here is removed from the gitignore when deploying. Hacky, but effective
Expand Down
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[workspace]
resolver = "2"
members = [
"ghg",
"ghg-common",
Expand Down
11 changes: 6 additions & 5 deletions ghg-data-processing/src/bin/export_shapefile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ fn main() -> std::io::Result<()> {
let args: Vec<String> = env::args().collect();
assert_eq!(args.len(), 2);

let output_root = Path::new("ghg/www/images/countries");
let output_root = Path::new("ghg/www/images/countries/0");
assert!(output_root.exists());

let data_source = Path::new(&args[1]);
Expand All @@ -28,9 +28,10 @@ fn main() -> std::io::Result<()> {
println!(" - {file:?}");
}

// let metadata = ShapefileMetadata { width: 200, height: 100 };
let metadata = ShapefileMetadata { width: 10800, height: 5400 };
// let metadata = ShapefileMetadata { width: 21600, height: 10800 };
// let metadata = ShapefileMetadata { width: 2700, height: 1350 }; // Level 3
// let metadata = ShapefileMetadata { width: 5400, height: 2700 }; // Level 2
// let metadata = ShapefileMetadata { width: 10800, height: 5400 }; // Level 1
let metadata = ShapefileMetadata { width: 21600, height: 10800 }; // Level 0

for file in &data_files {
let geometry_universe: GeometryUniverse = Shp::<f64>::open(file, metadata)
Expand All @@ -40,7 +41,7 @@ fn main() -> std::io::Result<()> {
let geometry_map = geometry_universe.into_geometry_map(metadata.width, metadata.height);
let image = geometry_map.to_image();

let output_name = output_root.join("country_map.png");
let output_name = output_root.join("original.png");
image.save(output_name).expect("Failed to save image data");
}

Expand Down
1 change: 1 addition & 0 deletions ghg-data-processing/src/export/geometry_map.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#![cfg(feature = "read_shapefile")]
use std::collections::HashMap;

use euclid::approxeq::ApproxEq;
Expand Down
5 changes: 5 additions & 0 deletions ghg-data-processing/src/export/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ use image::{
GrayAlphaImage, GrayImage, ImageBuffer, Luma, LumaA, Pixel, Rgb, RgbImage, Rgba, RgbaImage,
};
use itertools::izip;

#[cfg(feature = "read_shapefile")]
use shapefile::Shape;

use crate::export::data_2d_statistics::{Data2dStatistics, DataType};
#[cfg(feature = "read_shapefile")]
use crate::export::geometry_map::{GeometryMap, Identity};
#[cfg(feature = "read_shapefile")]
use crate::file_type::Shp;

pub trait ToImage<P: Pixel> {
Expand Down Expand Up @@ -186,6 +190,7 @@ where
}
}

#[cfg(feature = "read_shapefile")]
impl ToImage<Luma<u8>> for Shp<f64> {
type Data = f64;

Expand Down
4 changes: 4 additions & 0 deletions ghg-data-processing/src/file_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,22 @@ pub trait ToStatistics<TData: DataType, TVar: VariableDescriptor> {
}

/// *.shp + *.shx + *.dbf files (a.k.a. Shapefiles)
#[cfg(feature = "read_shapefile")]
pub struct Shp<T: DataType> {
pub(crate) reader: RefCell<shapefile::Reader<BufReader<File>, BufReader<File>>>,
pub(crate) width: usize,
pub(crate) height: usize,
phantom: PhantomData<T>,
}

#[cfg(feature = "read_shapefile")]
#[derive(Copy, Clone, Debug)]
pub struct ShapefileMetadata {
pub width: usize,
pub height: usize,
}

#[cfg(feature = "read_shapefile")]
impl<T: DataType> DataFile<T, ShapefileMetadata> for Shp<T> {
fn extension() -> &'static OsStr { OsStr::new("shp") }

Expand All @@ -53,6 +56,7 @@ impl<T: DataType> DataFile<T, ShapefileMetadata> for Shp<T> {
}
}

#[cfg(feature = "read_shapefile")]
impl Shp<f64> {
const NUM_CHANNELS: usize = 1;

Expand Down
6 changes: 5 additions & 1 deletion ghg/src/application/country.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use async_std::task;
use image::LumaA;
use wasm_bindgen::JsValue;
use web_sys::WebGl2RenderingContext;
use crate::application::image_utility::biggest_mipmap_level;

use crate::application::shaders::ShaderContext;
use crate::render_core::animation_params::AnimationParams;
Expand All @@ -15,12 +16,15 @@ use crate::render_core::uniform;
use crate::request_data::fetch_bytes;
use crate::utils::prelude::*;

const COUNTRY_IMAGE_MAX_SIZE: usize = 21_600;

async fn load_country_data(
shader_context: ShaderContext,
texture_index: u32,
) -> Result<(), JsValue> {
let mipmap_level = biggest_mipmap_level(shader_context.context.clone(), COUNTRY_IMAGE_MAX_SIZE)?;
let country_root = Path::new("images/countries");
let country_map_image = country_root.join("country_map.png");
let country_map_image = country_root.join(format!("{mipmap_level}/full.png").as_str());

let texture = fetch_bytes(country_map_image.to_str().unwrap()).await?;
shader_context.use_shader();
Expand Down
55 changes: 55 additions & 0 deletions ghg/src/application/image_utility.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use wasm_bindgen::__rt::IntoJsResult;
use wasm_bindgen::JsValue;
use web_sys::WebGl2RenderingContext;

const MAX_MIPMAP_LEVEL: usize = 10;

pub fn biggest_mipmap_level(context: WebGl2RenderingContext, level_0_max_size: usize) -> Result<usize, JsValue> {
let max_texture = context
.get_parameter(WebGl2RenderingContext::MAX_TEXTURE_SIZE)?
.as_f64()
.ok_or(JsValue::from_str("Max texture size is not a float"))?;

compute_biggest_level(max_texture as usize, level_0_max_size)
.ok_or(format!("No mipmap level found before {MAX_MIPMAP_LEVEL}").into_js_result().unwrap())
}

fn compute_biggest_level(max_texture: usize, level_0_max_size: usize) -> Option<usize> {
for attempted_level in 0..MAX_MIPMAP_LEVEL {
let level_dimension = level_0_max_size / (1 << attempted_level);
if level_dimension <= max_texture {
return Some(attempted_level);
}
}
return None;
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_max_larger_than_level_0() {
let result = compute_biggest_level(100, 90);
assert_eq!(result, Some(0));
}


#[test]
fn test_max_between_levels_0_and_1() {
let result = compute_biggest_level(75, 100);
assert_eq!(result, Some(1));
}

#[test]
fn test_max_on_level_2() {
let result = compute_biggest_level(32, 128);
assert_eq!(result, Some(2));
}

#[test]
fn test_max_smaller_than_level_2() {
let result = compute_biggest_level(31, 128);
assert_eq!(result, Some(3));
}
}
1 change: 1 addition & 0 deletions ghg/src/application/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod planet;
pub mod shaders;
pub mod sphere;
pub mod vertex;
mod image_utility;
16 changes: 12 additions & 4 deletions ghg/src/application/planet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use image::{Luma, Rgb};
use single_thread_executor::Spawner;
use wasm_bindgen::JsValue;
use web_sys::WebGl2RenderingContext;
use crate::application::image_utility::biggest_mipmap_level;

use crate::application::lighting::LightParameters;
use crate::application::shaders::ShaderContext;
Expand All @@ -26,11 +27,14 @@ use crate::request_data::fetch_bytes;
#[allow(unused_imports)]
use crate::utils::prelude::*;

const IMAGE_MAX_SIZE: usize = 21_600;

async fn load_planet_terrain(
context: WebGl2RenderingContext,
texture_index: u32,
mipmap_level: usize,
) -> Result<(), JsValue> {
let texture = fetch_bytes("images/earth_height/2/full.png").await?;
let texture = fetch_bytes(format!("images/earth_height/{mipmap_level}/full.png").as_str()).await?;
load_into_texture::<Luma<u8>>(
context,
&texture,
Expand All @@ -41,8 +45,9 @@ async fn load_planet_terrain(
async fn load_planet_color(
context: WebGl2RenderingContext,
texture_index: u32,
mipmap_level: usize,
) -> Result<(), JsValue> {
let texture = fetch_bytes("images/earth_color/2/full.png").await?;
let texture = fetch_bytes(format!("images/earth_color/{mipmap_level}/full.png").as_str()).await?;
load_into_texture::<Rgb<u8>>(
context,
&texture,
Expand All @@ -57,9 +62,12 @@ async fn load_all_textures(
terrain_index: u32,
color_index: u32,
) {
let texture_mipmap_level = biggest_mipmap_level(context.clone(), IMAGE_MAX_SIZE)
.expect("Failed to get max texture size");

let (color_result, terrain_result) = join!(
load_planet_color(context.clone(), color_index),
load_planet_terrain(context.clone(), terrain_index)
load_planet_color(context.clone(), color_index, texture_mipmap_level),
load_planet_terrain(context.clone(), terrain_index, texture_mipmap_level)
)
.await;

Expand Down
37 changes: 31 additions & 6 deletions ghg/src/bin/texture_splitter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use image::imageops::{resize, FilterType};
use image::io::Reader;
use image::{
ColorType, DynamicImage, EncodableLayout, GenericImageView, GrayImage, ImageBuffer, Luma,
Pixel, Rgb,
LumaA, Pixel, Rgb,
};

// TODO: Consolidate with render_core::image::LoadableImageType? At least some
Expand All @@ -31,6 +31,16 @@ impl ColorMapping for Rgb<u8> {
fn force_bytes(b: &ImageBuffer<Self, Vec<Self::Subpixel>>) -> &[u8] { b.as_bytes() }
}

impl ColorMapping for LumaA<u8> {
fn color_type() -> ColorType { ColorType::La8 }

fn dynamic_to_specific(d: DynamicImage) -> ImageBuffer<Self, Vec<Self::Subpixel>> {
d.to_luma_alpha8()
}

fn force_bytes(b: &ImageBuffer<Self, Vec<Self::Subpixel>>) -> &[u8] { b.as_bytes() }
}

impl ColorMapping for Luma<u8> {
fn color_type() -> ColorType { ColorType::L8 }

Expand Down Expand Up @@ -211,13 +221,14 @@ fn create_subimages<Map: ColorMapping + 'static>(
enum WhichToGenerate {
Color,
Height,
Borders,
}

fn main() {
// Expects to be run with CWD in the project root
let image_root = Path::new("./www/images");

const GENERATE: WhichToGenerate = WhichToGenerate::Height;
const GENERATE: WhichToGenerate = WhichToGenerate::Color;
match GENERATE {
WhichToGenerate::Color => {
let color_image = read_image(&image_root.join("earth_color/0/full.png"));
Expand All @@ -226,8 +237,8 @@ fn main() {
let (width_0, height_0) = original.dimensions();
println!("Loaded image: {} x {}", width_0, height_0);

create_downscaled_originals(image_root, "earth_color", 2, original);
create_subimages::<Rgb<u8>>(image_root, "earth_color", 2, 4, 2);
create_downscaled_originals(image_root, "earth_color", 3, original);
create_subimages::<Rgb<u8>>(image_root, "earth_color", 3, 4, 2);
}
WhichToGenerate::Height => {
let original_dynamic = image::open(image_root.join("earth_height/0/full.png"))
Expand All @@ -239,8 +250,22 @@ fn main() {
let (width_0, height_0) = original.dimensions();
println!("Loaded image: {} x {}", width_0, height_0);

create_downscaled_originals(image_root, "earth_height", 2, original);
create_subimages::<Luma<u8>>(image_root, "earth_height", 2, 4, 2);
create_downscaled_originals(image_root, "earth_height", 3, original);
create_subimages::<Luma<u8>>(image_root, "earth_height", 3, 4, 2);
}
WhichToGenerate::Borders => {
let original_dynamic = image::open(image_root.join("countries/0/full.png"))
.map_err(|e| e.to_string())
.expect("Failed to load image!");

let original =
original_dynamic.as_luma_alpha8().expect("Failed to get LumaAlpha8 Image!");

let (width_0, height_0) = original.dimensions();
println!("Loaded image: {} x {}", width_0, height_0);

create_downscaled_originals(image_root, "countries", 3, original);
create_subimages::<LumaA<u8>>(image_root, "countries", 3, 4, 2);
}
}
}
2 changes: 2 additions & 0 deletions ghg/src/request_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ use wasm_bindgen_futures::JsFuture;
use web_sys::{Blob, Response};

use crate::render_core::canvas::window;
use crate::utils::prelude::*;

pub async fn fetch_bytes(url: &str) -> Result<Vec<u8>, JsValue> {
ghg_log!("Fetching {url}");
let blob = fetch_blob(url).await?;
let bytes = blob_to_bytes(blob).await?;
Ok(bytes)
Expand Down
Binary file added ghg/www/images/countries/1/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ghg/www/images/countries/2/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ghg/www/images/countries/3/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed ghg/www/images/countries/country_map.png
Binary file not shown.
Binary file added ghg/www/images/earth_color/1/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ghg/www/images/earth_color/2/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ghg/www/images/earth_color/3/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ghg/www/images/earth_height/1/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified ghg/www/images/earth_height/2/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ghg/www/images/earth_height/3/full.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion single-thread-executor/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ repository = "https://github.com/asaaj/ghg"
license-file = "LICENSE_MIT"

[dependencies]
async-channel = "1.8"
async-channel = "2.3"
futures = "0.3"
js-sys = "0.3"
wasm-bindgen = "0.2"
Expand Down

0 comments on commit dc08681

Please sign in to comment.