diff --git a/src/core/al-core/src/image/fits.rs b/src/core/al-core/src/image/fits.rs index 1ba8f27a..9976af55 100644 --- a/src/core/al-core/src/image/fits.rs +++ b/src/core/al-core/src/image/fits.rs @@ -9,6 +9,7 @@ use fitsrs::WCS; use fitsrs::{Fits, HDU}; use std::fmt::Debug; use std::io::Cursor; +use std::ops::Range; use wasm_bindgen::JsValue; #[derive(Debug)] @@ -31,6 +32,8 @@ pub struct FitsImage<'a> { pub blank: Option, // optional wcs pub wcs: Option, + // bytes offset where the data bytes are located inside the fits + pub data_byte_offset: Range, // raw bytes of the data image (in Big-Endian) pub raw_bytes: &'a [u8], } @@ -76,7 +79,8 @@ impl<'a> FitsImage<'a> { let off = hdu.get_data_unit_byte_offset() as usize; let len = hdu.get_data_unit_byte_size() as usize; - let raw_bytes = &bytes[off..(off + len)]; + let data_byte_offset = off..(off + len); + let raw_bytes = &bytes[data_byte_offset.clone()]; let wcs = hdu.wcs().ok(); @@ -92,6 +96,7 @@ impl<'a> FitsImage<'a> { wcs, bzero, blank, + data_byte_offset, raw_bytes, }); } @@ -117,10 +122,6 @@ impl Image for FitsImage<'_> { // An offset to write the image in the texture array offset: &Vector3, ) -> Result<(), JsValue> { - crate::log(&format!( - "{0}, {1}, {2}", - self.width, self.height, self.depth - )); let view = unsafe { R8U::view(self.raw_bytes) }; textures.tex_sub_image_3d_with_opt_array_buffer_view( offset.x + self.trim1 as i32, @@ -136,6 +137,7 @@ impl Image for FitsImage<'_> { } fn get_size(&self) -> (u32, u32, u32) { + // The true image size is given by ONAXISi keywords (self.width, self.height, self.depth) } } diff --git a/src/core/al-core/src/image/mod.rs b/src/core/al-core/src/image/mod.rs index 085bd18e..c0bd5ee6 100644 --- a/src/core/al-core/src/image/mod.rs +++ b/src/core/al-core/src/image/mod.rs @@ -311,10 +311,10 @@ impl Image for ImageType { ImageType::Canvas { canvas } => canvas.insert_into_3d_texture(textures, offset)?, ImageType::ImageRgba8u { image } => image.insert_into_3d_texture(textures, offset)?, ImageType::ImageRgb8u { image } => image.insert_into_3d_texture(textures, offset)?, - ImageType::HTMLImageRgba8u { image } => { + ImageType::HTMLImageRgba8u { image, .. } => { image.insert_into_3d_texture(textures, offset)? } - ImageType::HTMLImageRgb8u { image } => { + ImageType::HTMLImageRgb8u { image, .. } => { image.insert_into_3d_texture(textures, offset)? } ImageType::RawRgb8u { image } => image.insert_into_3d_texture(textures, offset)?, diff --git a/src/core/al-core/src/image/raw.rs b/src/core/al-core/src/image/raw.rs index 618d9e3e..a4700162 100644 --- a/src/core/al-core/src/image/raw.rs +++ b/src/core/al-core/src/image/raw.rs @@ -8,8 +8,8 @@ pub struct ImageBuffer where T: TextureFormat, { - pub data: Vec<<::P as Pixel>::Item>, - pub size: Vector2, + pub data: Box<[<::P as Pixel>::Item]>, + pub size: (u32, u32, u32), } use crate::texture::format::Bytes; @@ -26,21 +26,22 @@ where T: TextureFormat, { pub fn new( - data: Vec<<::P as Pixel>::Item>, - width: i32, - height: i32, + data: Box<[<::P as Pixel>::Item]>, + width: u32, + height: u32, + depth: u32, ) -> Self { - let size_buf = width * height * (T::NUM_CHANNELS as i32); - debug_assert!(size_buf == data.len() as i32); + let size_buf = width * height * depth * (T::NUM_CHANNELS as u32); + debug_assert!(size_buf == data.len() as u32); //let buf = <::P as Pixel>::Container::new(buf); - let size = Vector2::new(width, height); + let size = (width, height, depth); Self { data, size } } pub fn from_encoded_raw_bytes( raw_bytes: &[u8], - width: i32, - height: i32, + width: u32, + height: u32, ) -> Result { let mut decoded_bytes = match T::decode(raw_bytes).map_err(JsValue::from_str)? { Bytes::Borrowed(bytes) => bytes.to_vec(), @@ -55,29 +56,33 @@ where std::mem::transmute::, Vec<<::P as Pixel>::Item>>( decoded_bytes, ) + .into_boxed_slice() }; - Ok(Self::new(decoded_pixels, width, height)) + Ok(Self::new(decoded_pixels, width, height, 1)) } - pub fn from_raw_bytes(mut raw_bytes: Vec, width: i32, height: i32) -> Self { - let size_buf = width * height * (std::mem::size_of::() as i32); - debug_assert!(size_buf == raw_bytes.len() as i32); + pub fn from_raw_bytes(mut raw_bytes: Vec, width: u32, height: u32) -> Self { + let size_buf = width * height * (std::mem::size_of::() as u32); + debug_assert!(size_buf == raw_bytes.len() as u32); let decoded_pixels = unsafe { raw_bytes.set_len(raw_bytes.len() / std::mem::size_of::<::Item>()); - std::mem::transmute::, Vec<::Item>>(raw_bytes) + std::mem::transmute::, Vec<::Item>>(raw_bytes).into_boxed_slice() }; - Self::new(decoded_pixels, width, height) + Self::new(decoded_pixels, width, height, 1) } pub fn empty() -> Self { - let size = Vector2::new(0, 0); - Self { data: vec![], size } + let size = (0, 0, 0); + Self { + data: Box::new([]), + size, + } } - pub fn allocate(pixel_fill: &T::P, width: i32, height: i32) -> ImageBuffer { + pub fn allocate(pixel_fill: &T::P, width: u32, height: u32) -> ImageBuffer { let size_buf = ((width * height) as usize) * (T::NUM_CHANNELS); let data = pixel_fill @@ -86,9 +91,10 @@ where .cloned() .cycle() .take(size_buf) - .collect::>(); + .collect::>() + .into_boxed_slice(); - ImageBuffer::::new(data, width, height) + ImageBuffer::::new(data, width, height, 1) } pub fn tex_sub(&mut self, src: &Self, s: &ImageBufferView, d: &ImageBufferView) { @@ -97,8 +103,8 @@ where for ix in s.x..(s.x + s.w) { for iy in s.y..(s.y + s.h) { - let s_idx = (iy * src.width() + ix) as usize; - let d_idx = (di * self.width() + dj) as usize; + let s_idx = ((iy * src.width() as i32) + ix) as usize; + let d_idx = ((di * self.width() as i32) + dj) as usize; for i in 0..T::NUM_CHANNELS { let si = s_idx * T::NUM_CHANNELS + i; @@ -124,12 +130,12 @@ where &self.data } - pub fn width(&self) -> i32 { - self.size.x + pub fn width(&self) -> u32 { + self.size.0 } - pub fn height(&self) -> i32 { - self.size.y + pub fn height(&self) -> u32 { + self.size.1 } } @@ -144,7 +150,7 @@ pub enum ImageBufferType { } use crate::image::{ArrayBuffer, Image}; -use cgmath::{Vector2, Vector3}; +use cgmath::Vector3; impl Image for ImageBuffer where I: TextureFormat, @@ -161,9 +167,9 @@ where offset.x, offset.y, offset.z, - self.width(), - self.height(), - 1, + self.width() as i32, + self.height() as i32, + self.size.2 as i32, Some(js_array.as_ref()), ); @@ -172,6 +178,6 @@ where // The size of the image fn get_size(&self) -> (u32, u32, u32) { - (self.size.x as u32, self.size.y as u32, 1) + self.size.clone() } } diff --git a/src/core/src/app.rs b/src/core/src/app.rs index 12044d2b..78cac348 100644 --- a/src/core/src/app.rs +++ b/src/core/src/app.rs @@ -563,7 +563,7 @@ impl App { let rscs_received = self.downloader.borrow_mut().get_received_resources(); - //let mut tile_copied = false; + let mut tile_copied = false; for rsc in rscs_received { match rsc { @@ -608,15 +608,6 @@ impl App { // 2. Add the tile to its HiPS if let Some(img) = &*image.borrow() { - /*if tile_copied { - self.downloader - .borrow_mut() - .delay(Resource::Tile(tile)); - continue; - } - tile_copied = true; - */ - // For PNG/JPEG cubic tiles, all the slices are in the lonely image match (&tile.cell, hips) { (CellDesc::HiPS2D { cell, tile_size }, HiPS::D2(hips)) => { @@ -654,6 +645,17 @@ impl App { }, HiPS::D3(hips), ) => { + // As the decoding and copying to the GPU of cubic tile is more costly + // (not that much but there is more because they are smaller) + // then we delay their treatment through the frames + if tile_copied { + self.downloader + .borrow_mut() + .delay(RequestType::Tile(tile)); + continue; + } + tile_copied = true; + // TODO PNG/JPG case to handle here match img { ImageType::HTMLImageRgba8u { @@ -671,6 +673,11 @@ impl App { .ceil() as u32; + let tile_size = *tile_size; + let mut decoded_bytes = Vec::with_capacity( + (tile_size * tile_size * *tile_depth) + as usize, + ); for x in 0..num_rows { for y in 0..num_cols { let document = web_sys::window() @@ -680,45 +687,52 @@ impl App { let canvas = document .create_element("canvas")? .dyn_into::()?; - canvas.set_width(*tile_size); - canvas.set_height(*tile_size); + canvas.set_width(tile_size); + canvas.set_height(tile_size); let context = canvas .get_context("2d")? .unwrap_abort() .dyn_into::()?; - let sx = (y * *tile_size) as f64; - let sy = (x * *tile_size) as f64; - let sw = *tile_size as f64; - let sh = *tile_size as f64; + let sx = (y * tile_size) as f64; + let sy = (x * tile_size) as f64; + let sw = tile_size as f64; + let sh = tile_size as f64; let dx = 0.0; let dy = 0.0; - let dw = *tile_size as f64; - let dh = *tile_size as f64; + let dw = tile_size as f64; + let dh = tile_size as f64; context.draw_image_with_html_image_element_and_sw_and_sh_and_dx_and_dy_and_dw_and_dh(image, sx, sy, sw, sh, dx, dy, dw, dh)?; - let slice_img = ImageType::Canvas { - canvas: Canvas::::new( - canvas, - ), - }; - let slice_idx = y + x * num_cols; + let slice_bytes = context + .get_image_data(dx, dy, dw, dh)? + .data(); - hips.push_tile_slice( - cell, - slice_img, - tile.request.time_request, - slice_idx as u16, - )? + decoded_bytes.extend( + slice_bytes + .0 + .chunks(4) + .map(|p| p[0]), + ); } } + + hips.push_tile_from_jpeg( + cell, + decoded_bytes.into_boxed_slice(), + (tile_size, tile_size, *tile_depth), + tile.request.time_request, + )?; } - _ => hips.push_tile( - cell, - img, - tile.request.time_request, - )?, + ImageType::FitsRawBytes { raw_bytes, size } => hips + .push_tile_from_fits( + cell, + raw_bytes.clone(), + *size, + tile.request.time_request, + )?, + _ => unreachable!(), } } _ => unreachable!(), diff --git a/src/core/src/downloader/mod.rs b/src/core/src/downloader/mod.rs index 2a30539f..0fbcbc72 100644 --- a/src/core/src/downloader/mod.rs +++ b/src/core/src/downloader/mod.rs @@ -95,18 +95,12 @@ impl Downloader { self.queried_list.contains(id) } - /*pub fn delay(&mut self, r: RequestType) { + pub fn delay(&mut self, r: RequestType) { match r { - Resource::Tile(tile) => { - let k = format!( - "{:?}{:?}/{:?}", - tile.get_hips_cdid(), - tile.cell.depth(), - tile.cell.idx() - ); - self.cache.insert(k, Resource::Tile(tile)); + RequestType::Tile(tile) => { + self.cache.insert(tile.id.clone(), RequestType::Tile(tile)); } _ => unimplemented!(), } - }*/ + } } diff --git a/src/core/src/downloader/request/allsky.rs b/src/core/src/downloader/request/allsky.rs index 0359c362..a614667a 100644 --- a/src/core/src/downloader/request/allsky.rs +++ b/src/core/src/downloader/request/allsky.rs @@ -63,7 +63,7 @@ async fn query_allsky( let raw_bytes = image_data.data(); - Ok(ImageBuffer::from_raw_bytes(raw_bytes.0, w as i32, h as i32)) + Ok(ImageBuffer::from_raw_bytes(raw_bytes.0, w, h)) } impl From for AllskyRequest { @@ -95,12 +95,18 @@ impl From for AllskyRequest { .map(|image| { let ImageBuffer { data, size } = image; let data = data - .into_iter() + .iter() .enumerate() .filter(|&(i, _)| i % 4 != 3) - .map(|(_, v)| v) - .collect(); - let image = ImageBuffer::new(data, size.x, size.y); + .map(|(_, v)| *v) + .collect::>(); + + let image = ImageBuffer::new( + data.into_boxed_slice(), + size.0, + size.1, + size.2, + ); ImageType::RawRgb8u { image } }) @@ -226,8 +232,11 @@ fn handle_allsky_file( let mut src_idx = 0; let tiles = (0..12).map(move |_| { - let mut base_tile = - ImageBuffer::::allocate(&F::P::BLACK, allsky_tile_size, allsky_tile_size); + let mut base_tile = ImageBuffer::::allocate( + &F::P::BLACK, + allsky_tile_size as u32, + allsky_tile_size as u32, + ); for idx_tile in 0..64 { let (x, y) = crate::utils::unmortonize(idx_tile as u64); let dx = x * (d3_tile_allsky_size as u32); @@ -274,8 +283,14 @@ fn handle_allsky_fits( .rev() .flatten() .copied() - .collect::>(); - let image = ImageBuffer::::new(reversed_rows_data, width_allsky_px, height_allsky_px); + .collect::>() + .into_boxed_slice(); + let image = ImageBuffer::::new( + reversed_rows_data, + width_allsky_px as u32, + height_allsky_px as u32, + 1, + ); let allsky_tiles_iter = handle_allsky_file::(image, allsky_tile_size, tile_size)?.map(move |image| { @@ -292,7 +307,12 @@ fn handle_allsky_fits( .cloned() .collect(); - ImageBuffer::::new(new_image_data, allsky_tile_size, allsky_tile_size) + ImageBuffer::::new( + new_image_data, + allsky_tile_size as u32, + allsky_tile_size as u32, + 1, + ) }); Ok(allsky_tiles_iter) diff --git a/src/core/src/healpix/cell.rs b/src/core/src/healpix/cell.rs index 743822fe..6e213f63 100644 --- a/src/core/src/healpix/cell.rs +++ b/src/core/src/healpix/cell.rs @@ -10,11 +10,13 @@ pub struct CellVertices { const BIT_MASK_ALL_ONE_EXCEPT_FIRST: u32 = !0x1; +use cgmath::BaseFloat; use healpix::compass_point::Cardinal; use healpix::compass_point::MainWind; use healpix::compass_point::Ordinal; use healpix::compass_point::OrdinalMap; +use crate::math::lonlat::LonLatT; use crate::utils; impl HEALPixCell { @@ -495,6 +497,22 @@ use crate::math::spectra::Freq; use crate::math::spectra::SpectralUnit; impl HEALPixFreqCell { + pub fn from_lonlat(lonlat: LonLatT, freq: Freq, s_depth: u8, f_depth: u8) -> Self { + let hpx = HEALPixCell::new( + s_depth, + lonlat.lon().to_radians(), + lonlat.lat().to_radians(), + ); + + let f_hash = freq.hash(f_depth); + + Self { + hpx, + f_hash, + f_depth, + } + } + pub fn new(hpx: HEALPixCell, f_hash: u64, f_depth: u8) -> Self { Self { hpx, diff --git a/src/core/src/renderable/hips/d3/cube.rs b/src/core/src/renderable/hips/d3/cube.rs index f01a7c9f..c0b155ab 100644 --- a/src/core/src/renderable/hips/d3/cube.rs +++ b/src/core/src/renderable/hips/d3/cube.rs @@ -101,16 +101,17 @@ impl HiPS3DBuffer { let texture = self.textures.get_mut(cell).unwrap_abort(); // And copy the image in that cubic tile - texture.append_tile_slice(image, slice_idx, &self.config, &self.gl)?; + texture.append_tile_slice(image, slice_idx)?; self.available_tiles_during_frame = true; Ok(()) } - pub fn push_tile( + pub fn push_tile_from_fits( &mut self, cell: &HEALPixFreqCell, - image: I, + raw_bytes: js_sys::Uint8Array, + size: (u32, u32, u32), time_request: Time, ) -> Result<(), JsValue> { self.push_cell(cell, time_request)?; @@ -118,7 +119,25 @@ impl HiPS3DBuffer { let texture = self.textures.get_mut(cell).unwrap_abort(); // And copy the image in that cubic tile - texture.append_tile(image, &self.config, &self.gl)?; + texture.set_data_from_fits(raw_bytes, size)?; + self.available_tiles_during_frame = true; + + Ok(()) + } + + pub fn push_tile_from_jpeg( + &mut self, + cell: &HEALPixFreqCell, + decoded_bytes: Box<[u8]>, + size: (u32, u32, u32), + time_request: Time, + ) -> Result<(), JsValue> { + self.push_cell(cell, time_request)?; + + let texture = self.textures.get_mut(cell).unwrap_abort(); + + // And copy the image in that cubic tile + texture.set_data_from_jpeg(decoded_bytes, size)?; self.available_tiles_during_frame = true; Ok(()) diff --git a/src/core/src/renderable/hips/d3/mod.rs b/src/core/src/renderable/hips/d3/mod.rs index f815b4f9..0f0a330e 100644 --- a/src/core/src/renderable/hips/d3/mod.rs +++ b/src/core/src/renderable/hips/d3/mod.rs @@ -3,7 +3,10 @@ pub mod texture; use crate::downloader::request::allsky::AllskyRequest; use crate::healpix::moc::FreqSpaceMoc; +use crate::math::angle::ToAngle; +use crate::math::lonlat::LonLatT; use crate::math::spectra::SpectralUnit; +use crate::LonLat; use crate::tile_fetcher::TileFetcherQueue; use al_api::hips::DataproductType; @@ -78,18 +81,7 @@ pub fn get_raster_shader<'a>( crate::shader::get_shader(gl, shaders, "hips3d_raster.vert", "hips3d_f32.frag") } // color case - _ => { - if cmap.label() == "native" { - crate::shader::get_shader(gl, shaders, "hips3d_raster.vert", "hips3d_rgba.frag") - } else { - crate::shader::get_shader( - gl, - shaders, - "hips3d_raster.vert", - "hips3d_rgba2cmap.frag", - ) - } - } + _ => crate::shader::get_shader(gl, shaders, "hips3d_raster.vert", "hips3d_red.frag"), } } @@ -122,6 +114,8 @@ pub struct HiPS3D { cells: Vec, // flag to forcing the mesh to be rebuilt move_freq: bool, + // The location of the cursor to extract the spectra + cursor_location: LonLatT, } use super::HpxTileBuffer; @@ -176,6 +170,7 @@ impl HiPS3D { let moc = None; let hpx_cells_in_view = vec![]; let move_freq = false; + let cursor_location = LonLatT::new(0.0.to_angle(), 0.0.to_angle()); // request the allsky texture Ok(Self { // The image survey texture buffer @@ -198,6 +193,7 @@ impl HiPS3D { cells, num_indices, move_freq, + cursor_location, }) } @@ -344,6 +340,29 @@ impl HiPS3D { } } + /// Read the spectra under the cursor location + pub fn read_spectra(&self, camera: &CameraViewPort) { + // 1. Get the HEALPixFreq cell containing the cursor location + let s_order = camera.get_tile_depth(); + let f_max_order = self.get_config().max_depth_freq.unwrap_abort(); + let s_max_order = self.get_config().max_depth_tile; + + let f_order = f_max_order - (s_max_order - s_order); + + let cell = HEALPixFreqCell::from_lonlat(self.cursor_location, self.freq, s_order, f_order); + + // 2. Iterate through the cells on the frequency axis at that spatial location to construct the spectra around the (cursor, freq) point + let f_hash_min = (cell.f_hash as i64 - 4).max(0) as u64; + let f_hash_max = (cell.f_hash + 4) + .max(Frequency::::n_cells_max() >> (Frequency::::MAX_DEPTH - f_order)); + + //(f_hash_min..f_hash_max).map(|f_hash| {}) + } + + pub fn set_cursor_location(&mut self, lonlat: LonLatT) { + self.cursor_location = lonlat; + } + pub fn set_freq(&mut self, f: Freq) { self.freq = f; @@ -788,19 +807,29 @@ impl HiPS3D { .push_tile_slice(cell, image, time_request, slice_idx) } - pub fn push_tile( + pub fn push_tile_from_fits( &mut self, cell: &HEALPixFreqCell, // the image slice - cube: I, + data: js_sys::Uint8Array, + size: (u32, u32, u32), time_request: Time, ) -> Result<(), JsValue> { - self.buffer.push_tile(cell, cube, time_request) + self.buffer + .push_tile_from_fits(cell, data, size, time_request) } - /*pub fn add_allsky(&mut self, allsky: AllskyRequest) -> Result<(), JsValue> { - self.buffer.push_allsky(allsky) - }*/ + pub fn push_tile_from_jpeg( + &mut self, + cell: &HEALPixFreqCell, + // the image slice + data: Box<[u8]>, + size: (u32, u32, u32), + time_request: Time, + ) -> Result<(), JsValue> { + self.buffer + .push_tile_from_jpeg(cell, data, size, time_request) + } /* Accessors */ #[inline] diff --git a/src/core/src/renderable/hips/d3/texture.rs b/src/core/src/renderable/hips/d3/texture.rs index 6d7406d3..2d3ffb67 100644 --- a/src/core/src/renderable/hips/d3/texture.rs +++ b/src/core/src/renderable/hips/d3/texture.rs @@ -4,13 +4,83 @@ use crate::{healpix::cell::HEALPixCell, time::Time}; use crate::renderable::hips::config::HiPSConfig; use crate::Abort; use crate::WebGlContext; -use al_core::image::Image; +use al_core::image::fits::FitsImage; +use al_core::image::raw::ImageBuffer; +use al_core::image::{Image, ImageType}; use al_core::texture::format::{PixelType, R16I, R32F, R32I, R8U, RGB8U, RGBA8U}; use al_core::texture::Texture3D; use al_core::webgl_ctx::WebGlRenderingCtx; use cgmath::Vector3; +use fitsrs::hdu::header::extension::bintable::L; +use fitsrs::hdu::header::Bitpix; use std::cmp::Ordering; -use wasm_bindgen::JsValue; +use std::ops::Range; +use wasm_bindgen::{Clamped, JsValue}; + +pub enum HpxFreqData { + Fits { + // The raw bytes of the whole cubic FITS file, data big endian + raw_bytes: Box<[u8]>, + // Offset to the data bytes of the cubic tile + data_byte_offset: Range, + // Number of bytes per pixel (deduced from the bitpix) + bitpix: Bitpix, + // Triming offset indices when reading the data + trim: (u32, u32, u32), + // Naxis + naxis: (u32, u32, u32), + }, + Jpeg { + data: Box<[u8]>, + size: (u32, u32, u32), + }, +} + +enum Pixel { + F32(f32), + I32(i32), + I16(i16), + U8(u8), +} + +impl HpxFreqData { + pub fn read_pixel(&self, x: u32, y: u32, z: u32) -> Pixel { + match self { + HpxFreqData::Fits { + raw_bytes, + data_byte_offset, + bitpix, + trim, + naxis, + } => { + let x = x - trim.0; + let y = y - trim.1; + let z = z - trim.2; + + let data_raw_bytes = &raw_bytes[data_byte_offset.clone()]; + + let pixel_bytes_off = (x + y * naxis.0 + z * (naxis.0 * naxis.1)) as usize; + + let bytes_per_pixel = bitpix.byte_size(); + let p = &data_raw_bytes[pixel_bytes_off..(pixel_bytes_off + bytes_per_pixel)]; + match bitpix { + Bitpix::U8 => Pixel::U8(p[0]), + Bitpix::I16 => Pixel::I16(i16::from_be_bytes([p[0], p[1]])), + Bitpix::I32 => Pixel::I32(i32::from_be_bytes([p[0], p[1], p[2], p[3]])), + Bitpix::F32 => Pixel::F32(f32::from_be_bytes([p[0], p[1], p[2], p[3]])), + // Texture are converted to + _ => unreachable!(), + } + } + HpxFreqData::Jpeg { data, size } => { + let pixel_bytes_off = (x + y * size.0 + z * (size.0 * size.1)) as usize; + + let p = data[pixel_bytes_off]; + Pixel::U8(p) + } + } + } +} pub struct HpxFreqTex { pub cell: HEALPixFreqCell, @@ -39,6 +109,10 @@ pub struct HpxFreqTex { /// The webgl2 3D texture of the cubic tile pub texture: Texture3D, + data: Option, + + // The real image data for accessing the pixel values + //data: ImageType, /// A bitvector keeping track of the slices that have been inserted into the 3D texture /// It is limited to a cube depth of 256 (~ to the max texture size). slice_idx: [u32; 8], @@ -49,270 +123,6 @@ pub struct HpxFreqTex { num_stored_slices: u16, } -/* -impl HpxFreqTex { - pub fn new(cell: HEALPixCell, time_request: Time) -> Self { - let start_time = None; - let uniq = cell.uniq(); - let textures = std::iter::repeat_n(None, 512).collect(); - let blocks = [0; 512]; - let block_indices = Vec::new(); - Self { - cell, - uniq, - time_request, - start_time, - textures, - blocks, - block_indices, - } - } - - pub fn find_nearest_slice(&self, slice: u16) -> Option { - let block_idx = (slice >> 5) as usize; - - match self.block_indices.binary_search(&block_idx) { - Ok(_) => { - if self.contains_slice(slice) { - Some(slice) - } else { - // the slice is not present but we know there is one in the block - let block = self.blocks[block_idx]; - - let slice_idx = (slice & 0x1f) as u32; - - let m2 = if slice_idx == 31 { - 0 - } else { - 0xffffffff >> (slice_idx + 1) - }; - let m1 = (!m2) & !(1 << (31 - slice_idx)); - - let lb = (block & m1) >> (32 - slice_idx); - let rb = block & m2; - - let lb_trailing_zeros = (lb.trailing_zeros() as u16).min(slice_idx as u16); - let rb_leading_zeros = (rb.leading_zeros() - slice_idx - 1) as u16; - - let no_more_left_bits = slice_idx - (lb_trailing_zeros as u32) == 0; - let no_more_right_bits = slice_idx + (rb_leading_zeros as u32) == 31; - - match (no_more_left_bits, no_more_right_bits) { - (false, false) => { - if lb_trailing_zeros <= rb_leading_zeros { - Some(slice - lb_trailing_zeros - 1) - } else { - Some(slice + rb_leading_zeros + 1) - } - } - (false, true) => { - if lb_trailing_zeros <= rb_leading_zeros { - Some(slice - lb_trailing_zeros - 1) - } else { - // explore next block - if block_idx == self.blocks.len() - 1 { - // no after block - Some(slice - lb_trailing_zeros - 1) - } else { - // get the next block - let next_block = self.blocks[block_idx + 1]; - - let num_bits_to_next_block = - next_block.leading_zeros() as u16 + rb_leading_zeros; - - if num_bits_to_next_block < lb_trailing_zeros { - Some(slice + num_bits_to_next_block + 1) - } else { - Some(slice - lb_trailing_zeros - 1) - } - } - } - } - (true, false) => { - if rb_leading_zeros <= lb_trailing_zeros { - Some(slice + rb_leading_zeros + 1) - } else { - // explore previous block - if block_idx == 0 { - // no after block - Some(slice + rb_leading_zeros + 1) - } else { - // get the next block - let prev_block = self.blocks[block_idx - 1]; - - let num_bits_from_prev_block = - prev_block.trailing_zeros() as u16 + lb_trailing_zeros; - if num_bits_from_prev_block < rb_leading_zeros { - Some(slice - num_bits_from_prev_block - 1) - } else { - Some(slice + rb_leading_zeros + 1) - } - } - } - } - (true, true) => unreachable!(), - } - } - } - Err(i) => { - let prev_block = if i > 0 { - self.block_indices.get(i - 1) - } else { - None - }; - - let cur_block = self.block_indices.get(i); - match (prev_block, cur_block) { - (Some(b_idx_1), Some(b_idx_2)) => { - let b1 = self.blocks[*b_idx_1]; - let b2 = self.blocks[*b_idx_2]; - - let b1_tz = b1.trailing_zeros() as usize; - let b2_lz = b2.leading_zeros() as usize; - - let slice_b1 = ((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16; - let slice_b2 = ((*b_idx_2 << 5) + b2_lz) as u16; - if slice - slice_b1 <= slice_b2 - slice { - // the nearest slice is in b1 - Some(slice_b1) - } else { - // the nearest slice is in b2 - Some(slice_b2) - } - } - (None, Some(b_idx_2)) => { - let b2 = self.blocks[*b_idx_2]; - let b2_lz = b2.leading_zeros() as usize; - - Some(((*b_idx_2 << 5) + b2_lz) as u16) - } - (Some(b_idx_1), None) => { - let b1 = self.blocks[*b_idx_1]; - let b1_tz = b1.trailing_zeros() as usize; - - Some(((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16) - } - (None, None) => None, - } - } - } - } - - pub fn get_3d_block_from_slice(&self, slice: u16) -> Option<&Texture3D> { - let block_idx = (slice >> 5) as usize; - - self.textures[block_idx].as_ref() - } - - pub fn extract_2d_slice_texture(&self, slice: u16) -> Option { - // Find the good sub cube containing the slice - let block_idx = (slice >> 5) as usize; - let slice_idx = (slice & 0x1f) as u8; - - // check the texture is there - if self.blocks[block_idx] & (1 << (31 - slice_idx)) != 0 { - Some(HpxTex::new(&self.cell, slice_idx as i32, self.time_request)) - } else { - None - } - } - - // Panic if cell is not contained in the texture - // Do nothing if the texture is full - // Return true if the tile is newly added - pub fn append( - &mut self, - image: I, - slice: u16, - cfg: &HiPSConfig, - gl: &WebGlContext, - ) -> Result<(), JsValue> { - let block_idx = (slice >> 5) as usize; - - let texture = if let Some(texture) = self.textures[block_idx].as_ref() { - texture - } else { - let tile_size = cfg.get_tile_size(); - let params = &[ - ( - WebGlRenderingCtx::TEXTURE_MIN_FILTER, - WebGlRenderingCtx::NEAREST, - ), - ( - WebGlRenderingCtx::TEXTURE_MAG_FILTER, - WebGlRenderingCtx::NEAREST, - ), - // Prevents s-coordinate wrapping (repeating) - ( - WebGlRenderingCtx::TEXTURE_WRAP_S, - WebGlRenderingCtx::CLAMP_TO_EDGE, - ), - // Prevents t-coordinate wrapping (repeating) - ( - WebGlRenderingCtx::TEXTURE_WRAP_T, - WebGlRenderingCtx::CLAMP_TO_EDGE, - ), - // Prevents r-coordinate wrapping (repeating) - ( - WebGlRenderingCtx::TEXTURE_WRAP_R, - WebGlRenderingCtx::CLAMP_TO_EDGE, - ), - ]; - - let texture = match cfg.get_format().get_pixel_format() { - PixelType::RGBA8U => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - PixelType::RGB8U => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - PixelType::R32F => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - PixelType::R8U => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - PixelType::R16I => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - PixelType::R32I => { - Texture3D::create_empty::(gl, tile_size, tile_size, 32, params) - } - }; - self.textures[block_idx] = Some(texture?); - - self.textures[block_idx].as_ref().unwrap() - }; - - let slice_idx = slice & 0x1f; - - // if there is already something, do not tex sub - if self.blocks[block_idx] & (1 << (31 - slice_idx)) == 0 { - image.insert_into_3d_texture(texture, &Vector3::::new(0, 0, slice_idx as i32))?; - - match self.block_indices.binary_search(&block_idx) { - Ok(_) => {} // element already in vector @ `pos` - Err(i) => self.block_indices.insert(i, block_idx), - } - - self.blocks[block_idx] |= 1 << (31 - slice_idx); - } - - self.start_time = Some(Time::now()); - - Ok(()) - } - - // Cell must be contained in the texture - pub fn contains_slice(&self, slice: u16) -> bool { - let block_idx = (slice >> 5) as usize; - let idx_in_block = slice & 0x1f; - - (self.blocks[block_idx] >> (31 - idx_in_block)) & 0x1 == 1 - } -} -*/ - const TEX_PARAMS: &[(u32, u32)] = &[ ( WebGlRenderingCtx::TEXTURE_MIN_FILTER, @@ -358,14 +168,14 @@ impl HpxFreqTex { let start_time = None; let texture = match pixel_format { - PixelType::RGBA8U => Texture3D::create_empty::( + PixelType::RGBA8U => Texture3D::create_empty::( gl, tile_size as i32, tile_size as i32, num_slices as i32, TEX_PARAMS, ), - PixelType::RGB8U => Texture3D::create_empty::( + PixelType::RGB8U => Texture3D::create_empty::( gl, tile_size as i32, tile_size as i32, @@ -400,8 +210,11 @@ impl HpxFreqTex { num_slices as i32, TEX_PARAMS, ), + // No color cubes + _ => unreachable!(), }?; + let data = None; let num_stored_slices = 0; let slice_idx = [0x0; 8]; Ok(Self { @@ -409,151 +222,68 @@ impl HpxFreqTex { slice_idx, time_request, start_time, + data, texture, num_slices, num_stored_slices, }) } - /*pub fn find_nearest_slice(&self, slice: u16) -> Option { - let block_idx = (slice >> 5) as usize; - - match self.block_indices.binary_search(&block_idx) { - Ok(_) => { - if self.contains_slice(slice) { - Some(slice) - } else { - // the slice is not present but we know there is one in the block - let block = self.blocks[block_idx]; - - let slice_idx = (slice & 0x1f) as u32; - - let m2 = if slice_idx == 31 { - 0 - } else { - 0xffffffff >> (slice_idx + 1) - }; - let m1 = (!m2) & !(1 << (31 - slice_idx)); - - let lb = (block & m1) >> (32 - slice_idx); - let rb = block & m2; - - let lb_trailing_zeros = (lb.trailing_zeros() as u16).min(slice_idx as u16); - let rb_leading_zeros = (rb.leading_zeros() - slice_idx - 1) as u16; - - let no_more_left_bits = slice_idx - (lb_trailing_zeros as u32) == 0; - let no_more_right_bits = slice_idx + (rb_leading_zeros as u32) == 31; - - match (no_more_left_bits, no_more_right_bits) { - (false, false) => { - if lb_trailing_zeros <= rb_leading_zeros { - Some(slice - lb_trailing_zeros - 1) - } else { - Some(slice + rb_leading_zeros + 1) - } - } - (false, true) => { - if lb_trailing_zeros <= rb_leading_zeros { - Some(slice - lb_trailing_zeros - 1) - } else { - // explore next block - if block_idx == self.blocks.len() - 1 { - // no after block - Some(slice - lb_trailing_zeros - 1) - } else { - // get the next block - let next_block = self.blocks[block_idx + 1]; - - let num_bits_to_next_block = - next_block.leading_zeros() as u16 + rb_leading_zeros; - - if num_bits_to_next_block < lb_trailing_zeros { - Some(slice + num_bits_to_next_block + 1) - } else { - Some(slice - lb_trailing_zeros - 1) - } - } - } - } - (true, false) => { - if rb_leading_zeros <= lb_trailing_zeros { - Some(slice + rb_leading_zeros + 1) - } else { - // explore previous block - if block_idx == 0 { - // no after block - Some(slice + rb_leading_zeros + 1) - } else { - // get the next block - let prev_block = self.blocks[block_idx - 1]; - - let num_bits_from_prev_block = - prev_block.trailing_zeros() as u16 + lb_trailing_zeros; - if num_bits_from_prev_block < rb_leading_zeros { - Some(slice - num_bits_from_prev_block - 1) - } else { - Some(slice + rb_leading_zeros + 1) - } - } - } - } - (true, true) => unreachable!(), - } - } - } - Err(i) => { - let prev_block = if i > 0 { - self.block_indices.get(i - 1) - } else { - None - }; - - let cur_block = self.block_indices.get(i); - match (prev_block, cur_block) { - (Some(b_idx_1), Some(b_idx_2)) => { - let b1 = self.blocks[*b_idx_1]; - let b2 = self.blocks[*b_idx_2]; - - let b1_tz = b1.trailing_zeros() as usize; - let b2_lz = b2.leading_zeros() as usize; - - let slice_b1 = ((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16; - let slice_b2 = ((*b_idx_2 << 5) + b2_lz) as u16; - if slice - slice_b1 <= slice_b2 - slice { - // the nearest slice is in b1 - Some(slice_b1) - } else { - // the nearest slice is in b2 - Some(slice_b2) - } - } - (None, Some(b_idx_2)) => { - let b2 = self.blocks[*b_idx_2]; - let b2_lz = b2.leading_zeros() as usize; - - Some(((*b_idx_2 << 5) + b2_lz) as u16) - } - (Some(b_idx_1), None) => { - let b1 = self.blocks[*b_idx_1]; - let b1_tz = b1.trailing_zeros() as usize; - - Some(((*b_idx_1 << 5) + 32 - b1_tz - 1) as u16) - } - (None, None) => None, - } - } - } - }*/ - - pub fn append_tile( + pub fn set_data_from_fits( &mut self, - // the tile image - image: I, - cfg: &HiPSConfig, - gl: &WebGlContext, + // the tile image of the whole cubic tile + raw_bytes: js_sys::Uint8Array, + // size of the cube + size: (u32, u32, u32), ) -> Result<(), JsValue> { - image.insert_into_3d_texture(&self.texture, &Vector3::::new(0, 0, 0))?; + let raw_bytes = raw_bytes.to_vec().into_boxed_slice(); + let (trim1, trim2, trim3, width, height, depth, bitpix, data_byte_offset) = { + let fits = FitsImage::from_raw_bytes(&raw_bytes[..])?; + fits[0].insert_into_3d_texture(&self.texture, &Vector3::::new(0, 0, 0))?; + + ( + fits[0].trim1, + fits[0].trim2, + fits[0].trim3, + fits[0].width, + fits[0].height, + fits[0].depth, + fits[0].bitpix, + fits[0].data_byte_offset.clone(), + ) + }; + + let trim = (trim1, trim2, trim3); + let naxis = (width, height, depth); + + self.data = Some(HpxFreqData::Fits { + raw_bytes, + data_byte_offset: data_byte_offset.clone(), + bitpix, + trim, + naxis, + }); + self.num_stored_slices = self.num_slices; + self.start_time = Some(Time::now()); + + Ok(()) + } + + pub fn set_data_from_jpeg( + &mut self, + // the tile image of the whole cubic tile + decoded_bytes: Box<[u8]>, + // size of the cube + size: (u32, u32, u32), + ) -> Result<(), JsValue> { + let cubic_tile = ImageBuffer::::new(decoded_bytes, size.0, size.1, size.2); + cubic_tile.insert_into_3d_texture(&self.texture, &Vector3::::new(0, 0, 0))?; + + self.data = Some(HpxFreqData::Jpeg { + data: cubic_tile.data, + size, + }); self.num_stored_slices = self.num_slices; self.start_time = Some(Time::now()); @@ -563,14 +293,13 @@ impl HpxFreqTex { // Panic if cell is not contained in the texture // Do nothing if the texture is full // Return true if the tile is newly added + // Used by HiPS Cubes pub fn append_tile_slice( &mut self, - // the tile image + // the tile image of 1 slice image: I, // the slice offset in the cubic tile offset: u16, - cfg: &HiPSConfig, - gl: &WebGlContext, ) -> Result<(), JsValue> { // If there is already something, do not tex sub let block_idx = (offset >> 5) as usize; diff --git a/src/glsl/webgl2/hips/color.glsl b/src/glsl/webgl2/hips/color.glsl index 262e95c3..e2f19435 100644 --- a/src/glsl/webgl2/hips/color.glsl +++ b/src/glsl/webgl2/hips/color.glsl @@ -13,8 +13,19 @@ uniform float reversed; #include ../decode.glsl; ///////////////////////////////////////////// -/// RGBA sampler +/// RED sampler +vec4 uvw2c_r(vec3 uv) { + float v = texture(tex, uv).r; + v = transfer_func(H, v, min_value, max_value); + + // apply reversed + v = mix(v, 1.0 - v, reversed); + + return apply_tonal(colormap_f(v)); +} + +/// RGBA sampler vec4 uvw2c_rgba(vec3 uv) { vec4 c = texture(tex, uv).rgba; diff --git a/src/glsl/webgl2/hips3d/rgba.frag b/src/glsl/webgl2/hips3d/red.frag similarity index 90% rename from src/glsl/webgl2/hips3d/rgba.frag rename to src/glsl/webgl2/hips3d/red.frag index aff3d470..38936612 100644 --- a/src/glsl/webgl2/hips3d/rgba.frag +++ b/src/glsl/webgl2/hips3d/red.frag @@ -13,7 +13,7 @@ uniform float opacity; void main() { vec3 uv = vec3(frag_uv.xyz); - vec4 color = uvw2c_rgba(uv); + vec4 color = uvw2c_r(uv); out_frag_color = color; out_frag_color.a = opacity * out_frag_color.a; diff --git a/src/glsl/webgl2/hips3d/rgba2cmap.frag b/src/glsl/webgl2/hips3d/rgba2cmap.frag deleted file mode 100644 index c7568081..00000000 --- a/src/glsl/webgl2/hips3d/rgba2cmap.frag +++ /dev/null @@ -1,20 +0,0 @@ -#version 300 es -precision lowp float; -precision lowp sampler3D; - -uniform sampler3D tex; - -in vec3 frag_uv; - -out vec4 out_frag_color; -uniform float opacity; - -#include ../hips/color.glsl; - -void main() { - vec3 uv = vec3(frag_uv.xyz); - vec4 color = uvw2cmap_rgba(uv); - - out_frag_color = color; - out_frag_color.a = opacity * out_frag_color.a; -} \ No newline at end of file