mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2026-06-12 19:11:42 -07:00
impl Worker for handling png cropping of tiles
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
let aladin;
|
let aladin;
|
||||||
A.init.then(() => {
|
A.init.then(() => {
|
||||||
aladin = A.aladin('#aladin-lite-div', {survey: 'data/hips/CDS_P_DSS2_color', target: '05 34 30.89 +22 00 52.9', fov: 0.3, log: false, showSettingsControl: true});
|
aladin = A.aladin('#aladin-lite-div', {survey: 'data/hips/CDS_P_DSS2_color', target: '05 34 30.89 +22 00 52.9', fov: 0.3, log: false, showSettingsControl: true});
|
||||||
var cat = A.catalogFromURL('data/votable/labels.vot', {sourceSize:12, color: '#cc99bb', displayLabel: true, labelColumn: 'main_id', labelColor: '#ae4', labelFont: '9px sans-serif'});
|
var cat = A.catalogFromURL('data/votable/labels.vot', {sourceSize:20, shape: 'cross', color: '#cc99bb', displayLabel: true, labelColumn: 'main_id', labelColor: '#ae4', labelFont: '9px sans-serif'});
|
||||||
aladin.addCatalog(cat);
|
aladin.addCatalog(cat);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
+208
-130
@@ -62,6 +62,7 @@ pub struct App {
|
|||||||
//ui: GuiRef,
|
//ui: GuiRef,
|
||||||
shaders: ShaderManager,
|
shaders: ShaderManager,
|
||||||
pub camera: CameraViewPort,
|
pub camera: CameraViewPort,
|
||||||
|
worker: Worker,
|
||||||
|
|
||||||
downloader: Rc<RefCell<Downloader>>,
|
downloader: Rc<RefCell<Downloader>>,
|
||||||
tile_fetcher: TileFetcherQueue,
|
tile_fetcher: TileFetcherQueue,
|
||||||
@@ -105,6 +106,9 @@ pub struct App {
|
|||||||
|
|
||||||
pub projection: ProjectionType,
|
pub projection: ProjectionType,
|
||||||
|
|
||||||
|
cubic_tile_send: async_channel::Sender<WorkerResponse>,
|
||||||
|
cubic_tile_recv: async_channel::Receiver<WorkerResponse>,
|
||||||
|
|
||||||
// Async data receivers
|
// Async data receivers
|
||||||
//img_send: async_channel::Sender<ImageLayer>,
|
//img_send: async_channel::Sender<ImageLayer>,
|
||||||
img_recv: async_channel::Receiver<ImageLayer>,
|
img_recv: async_channel::Receiver<ImageLayer>,
|
||||||
@@ -199,8 +203,7 @@ impl App {
|
|||||||
|
|
||||||
let (_, img_recv) = async_channel::unbounded::<ImageLayer>();
|
let (_, img_recv) = async_channel::unbounded::<ImageLayer>();
|
||||||
let (ack_img_send, _) = async_channel::unbounded::<ImageParams>();
|
let (ack_img_send, _) = async_channel::unbounded::<ImageParams>();
|
||||||
|
let (cubic_tile_send, cubic_tile_recv) = async_channel::unbounded::<WorkerResponse>();
|
||||||
//let line_renderer = RasterizedLineRenderer::new(&gl)?;
|
|
||||||
|
|
||||||
let dist_dragging = 0.0;
|
let dist_dragging = 0.0;
|
||||||
let time_start_dragging = Time::now();
|
let time_start_dragging = Time::now();
|
||||||
@@ -210,6 +213,49 @@ impl App {
|
|||||||
let browser_features_support = BrowserFeaturesSupport::new();
|
let browser_features_support = BrowserFeaturesSupport::new();
|
||||||
|
|
||||||
let vel_history = vec![];
|
let vel_history = vec![];
|
||||||
|
let worker = create_worker()?;
|
||||||
|
// Send the ack to the js promise so that she finished
|
||||||
|
let cubic_tile_send2 = cubic_tile_send.clone();
|
||||||
|
|
||||||
|
let onmessage = Closure::<
|
||||||
|
dyn FnMut(web_sys::MessageEvent),
|
||||||
|
>::new(
|
||||||
|
move |event: web_sys::MessageEvent| {
|
||||||
|
let data = event.data();
|
||||||
|
|
||||||
|
let bytes_js = js_sys::Reflect::get(&data, &"bytes".into())
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<js_sys::Uint8Array>()
|
||||||
|
.expect("is not a uint8 buffer");
|
||||||
|
|
||||||
|
// Zero-copy clone from JS memory
|
||||||
|
let mut bytes = vec![0u8; bytes_js.length() as usize];
|
||||||
|
bytes_js.copy_to(&mut bytes);
|
||||||
|
|
||||||
|
let meta: WorkerResponseMeta = serde_wasm_bindgen::from_value(data).unwrap();
|
||||||
|
|
||||||
|
let response = WorkerResponse {
|
||||||
|
bytes,
|
||||||
|
tile_size: meta.tile_size,
|
||||||
|
tile_depth: meta.tile_depth,
|
||||||
|
time_request: meta.time_request,
|
||||||
|
cell: meta.cell,
|
||||||
|
hips_cdid: meta.hips_cdid
|
||||||
|
};
|
||||||
|
let cubic_tile_send3 = cubic_tile_send2.clone();
|
||||||
|
|
||||||
|
wasm_bindgen_futures::spawn_local(async move {
|
||||||
|
cubic_tile_send3.send(response).await.unwrap_throw();
|
||||||
|
})
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
worker.set_onmessage(Some(
|
||||||
|
onmessage.as_ref().unchecked_ref(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// 🚨 VERY IMPORTANT: prevent the closure from being dropped
|
||||||
|
onmessage.forget();
|
||||||
Ok(App {
|
Ok(App {
|
||||||
gl,
|
gl,
|
||||||
//ui,
|
//ui,
|
||||||
@@ -247,6 +293,7 @@ impl App {
|
|||||||
time_mouse_high_vel,
|
time_mouse_high_vel,
|
||||||
dragging,
|
dragging,
|
||||||
vel_history,
|
vel_history,
|
||||||
|
worker,
|
||||||
|
|
||||||
prev_cam_position,
|
prev_cam_position,
|
||||||
out_of_fov,
|
out_of_fov,
|
||||||
@@ -262,6 +309,8 @@ impl App {
|
|||||||
//img_send,
|
//img_send,
|
||||||
img_recv,
|
img_recv,
|
||||||
ack_img_send,
|
ack_img_send,
|
||||||
|
cubic_tile_send,
|
||||||
|
cubic_tile_recv,
|
||||||
|
|
||||||
browser_features_support, //ack_img_recv,
|
browser_features_support, //ack_img_recv,
|
||||||
})
|
})
|
||||||
@@ -543,6 +592,19 @@ impl App {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Ok(WorkerResponse { cell, hips_cdid, tile_size, tile_depth, bytes, time_request, .. }) = self.cubic_tile_recv.try_recv() {
|
||||||
|
if let Some(HiPS::D3(hips)) = self.layers.get_mut_hips_from_cdid(&hips_cdid) {
|
||||||
|
hips.push_tile_from_png(
|
||||||
|
&cell,
|
||||||
|
bytes.into_boxed_slice(),
|
||||||
|
(tile_size, tile_size, tile_depth),
|
||||||
|
Time::now(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.request_redraw = true;
|
||||||
|
}
|
||||||
|
|
||||||
let has_camera_moved = self.camera.has_moved();
|
let has_camera_moved = self.camera.has_moved();
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -692,140 +754,46 @@ impl App {
|
|||||||
ImageType::ImageRgba8u {
|
ImageType::ImageRgba8u {
|
||||||
image: Bitmap { image, .. },
|
image: Bitmap { image, .. },
|
||||||
} => {
|
} => {
|
||||||
let worker = create_worker()?;
|
|
||||||
|
|
||||||
//attach_onmessage(&worker);
|
|
||||||
let msg = js_sys::Object::new();
|
let msg = js_sys::Object::new();
|
||||||
js_sys::Reflect::set(
|
js_sys::Reflect::set(
|
||||||
&msg,
|
&msg,
|
||||||
&"bitmap".into(),
|
&"bitmap".into(),
|
||||||
&image,
|
&image,
|
||||||
)?;
|
)?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&msg,
|
||||||
|
&"tileSize".into(),
|
||||||
|
&JsValue::from_f64(*tile_size as f64),
|
||||||
|
)?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&msg,
|
||||||
|
&"tileDepth".into(),
|
||||||
|
&JsValue::from_f64(*tile_depth as f64),
|
||||||
|
)?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&msg,
|
||||||
|
&"timeRequest".into(),
|
||||||
|
&JsValue::from_f64(tile.request.time_request.as_millis() as f64),
|
||||||
|
)?;
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&msg,
|
||||||
|
&"cell".into(),
|
||||||
|
&serde_wasm_bindgen::to_value(&cell)
|
||||||
|
.expect("Failed to serialize")
|
||||||
|
)?;
|
||||||
|
|
||||||
|
js_sys::Reflect::set(
|
||||||
|
&msg,
|
||||||
|
&"HiPS".into(),
|
||||||
|
&JsValue::from_str(&tile.hips_cdid),
|
||||||
|
)?;
|
||||||
|
|
||||||
// Transfer ownership (zero-copy)
|
// Transfer ownership (zero-copy)
|
||||||
let transfer = js_sys::Array::of1(&image);
|
let transfer = js_sys::Array::of1(&image);
|
||||||
worker.post_message_with_transfer(
|
|
||||||
|
self.worker.post_message_with_transfer(
|
||||||
&msg, &transfer,
|
&msg, &transfer,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let onmessage = Closure::<
|
|
||||||
dyn FnMut(web_sys::MessageEvent),
|
|
||||||
>::new(
|
|
||||||
move |event: web_sys::MessageEvent| {
|
|
||||||
let data = event.data();
|
|
||||||
|
|
||||||
let width = js_sys::Reflect::get(
|
|
||||||
&data,
|
|
||||||
&"width".into(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let height = js_sys::Reflect::get(
|
|
||||||
&data,
|
|
||||||
&"height".into(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let width: u32 =
|
|
||||||
width.as_f64().unwrap() as u32;
|
|
||||||
let height: u32 =
|
|
||||||
height.as_f64().unwrap() as u32;
|
|
||||||
|
|
||||||
web_sys::console::log_2(
|
|
||||||
&"Worker replied:".into(),
|
|
||||||
&format!("{width} x {height}")
|
|
||||||
.into(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
worker.set_onmessage(Some(
|
|
||||||
onmessage.as_ref().unchecked_ref(),
|
|
||||||
));
|
|
||||||
|
|
||||||
// 🚨 VERY IMPORTANT: prevent the closure from being dropped
|
|
||||||
onmessage.forget();
|
|
||||||
|
|
||||||
let tile_size = *tile_size;
|
|
||||||
let tile_depth = *tile_depth;
|
|
||||||
|
|
||||||
let num_cols = image.width() / tile_size;
|
|
||||||
// Cut the png in several tile images. See page 3 of
|
|
||||||
// https://aladin.cds.unistra.fr/java/DocTechHiPS3D.pdf
|
|
||||||
let num_rows = ((tile_depth as f32)
|
|
||||||
/ (num_cols as f32))
|
|
||||||
.ceil()
|
|
||||||
as u32;
|
|
||||||
|
|
||||||
let canvas = web_sys::OffscreenCanvas::new(
|
|
||||||
image.width(),
|
|
||||||
image.height(),
|
|
||||||
)?;
|
|
||||||
let context = canvas
|
|
||||||
.get_context("2d")?
|
|
||||||
.unwrap_abort()
|
|
||||||
.dyn_into::<web_sys::OffscreenCanvasRenderingContext2d>()?;
|
|
||||||
// Get the data once for all for the whole image
|
|
||||||
// This takes time so better do it once and not repeatly
|
|
||||||
context.draw_image_with_image_bitmap(
|
|
||||||
image, 0.0, 0.0,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let bytes = context
|
|
||||||
.get_image_data(
|
|
||||||
0_f64,
|
|
||||||
0_f64,
|
|
||||||
(num_cols * tile_size) as f64,
|
|
||||||
(num_rows * tile_size) as f64,
|
|
||||||
)?
|
|
||||||
.data()
|
|
||||||
.0;
|
|
||||||
|
|
||||||
let mut decoded_bytes = vec![
|
|
||||||
0_u8;
|
|
||||||
(tile_size * tile_size * tile_depth * 2)
|
|
||||||
as usize
|
|
||||||
];
|
|
||||||
|
|
||||||
let mut k = 0;
|
|
||||||
let mut num_tiles_cropped = 0;
|
|
||||||
for y in 0..num_rows {
|
|
||||||
let sy = y * tile_size;
|
|
||||||
|
|
||||||
for x in 0..num_cols {
|
|
||||||
let sx = x * tile_size;
|
|
||||||
|
|
||||||
for i in sy..(sy + tile_size) {
|
|
||||||
for j in sx..(sx + tile_size) {
|
|
||||||
let id_byte = (j + i
|
|
||||||
* num_cols
|
|
||||||
* tile_size)
|
|
||||||
* 4;
|
|
||||||
|
|
||||||
decoded_bytes[k] =
|
|
||||||
bytes[id_byte as usize];
|
|
||||||
decoded_bytes[k + 1] =
|
|
||||||
bytes[id_byte as usize + 3];
|
|
||||||
k += 2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
num_tiles_cropped += 1;
|
|
||||||
|
|
||||||
if num_tiles_cropped == tile_depth {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if num_tiles_cropped == tile_depth {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hips.push_tile_from_png(
|
|
||||||
cell,
|
|
||||||
decoded_bytes.into_boxed_slice(),
|
|
||||||
(tile_size, tile_size, tile_depth),
|
|
||||||
tile.request.time_request,
|
|
||||||
)?;
|
|
||||||
}
|
}
|
||||||
ImageType::ImageRgb8u {
|
ImageType::ImageRgb8u {
|
||||||
image: Bitmap { image, .. },
|
image: Bitmap { image, .. },
|
||||||
@@ -1890,16 +1858,126 @@ impl App {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WorkerResponse {
|
||||||
|
#[serde(rename = "tileSize")]
|
||||||
|
pub tile_size: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "tileDepth")]
|
||||||
|
pub tile_depth: u32,
|
||||||
|
|
||||||
|
pub bytes: Vec<u8>,
|
||||||
|
|
||||||
|
#[serde(rename = "timeRequest")]
|
||||||
|
pub time_request: f32,
|
||||||
|
|
||||||
|
#[serde(rename = "HiPSCDid")]
|
||||||
|
pub hips_cdid: String,
|
||||||
|
|
||||||
|
pub cell: HEALPixFreqCell,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Debug)]
|
||||||
|
pub struct WorkerResponseMeta {
|
||||||
|
#[serde(rename = "tileSize")]
|
||||||
|
pub tile_size: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "tileDepth")]
|
||||||
|
pub tile_depth: u32,
|
||||||
|
|
||||||
|
#[serde(rename = "timeRequest")]
|
||||||
|
pub time_request: f32,
|
||||||
|
|
||||||
|
#[serde(rename = "HiPSCDid")]
|
||||||
|
pub hips_cdid: String,
|
||||||
|
|
||||||
|
pub cell: HEALPixFreqCell,
|
||||||
|
}
|
||||||
|
|
||||||
use web_sys::{Worker, WorkerOptions};
|
use web_sys::{Worker, WorkerOptions};
|
||||||
pub fn create_worker() -> Result<Worker, JsValue> {
|
pub fn create_worker() -> Result<Worker, JsValue> {
|
||||||
// JS source code of the worker
|
// JS source code of the worker
|
||||||
let worker_source = r#"
|
let worker_source = r#"
|
||||||
self.onmessage = (e) => {
|
self.onmessage = (e) => {
|
||||||
const { bitmap } = e.data;
|
const { bitmap, tileDepth, tileSize, timeRequest, HiPS, cell } = e.data;
|
||||||
self.postMessage({
|
|
||||||
width: bitmap.width,
|
// Compute tiling layout
|
||||||
height: bitmap.height,
|
const numCols = Math.floor(bitmap.width / tileSize);
|
||||||
});
|
|
||||||
|
// See HiPS3D doc
|
||||||
|
const numRows = Math.ceil(tileDepth / numCols);
|
||||||
|
|
||||||
|
// Create OffscreenCanvas
|
||||||
|
const canvas = new OffscreenCanvas(bitmap.width, bitmap.height);
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Draw full image once
|
||||||
|
context.drawImage(bitmap, 0, 0);
|
||||||
|
|
||||||
|
// Extract full region needed
|
||||||
|
const imageData = context.getImageData(
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
numCols * tileSize,
|
||||||
|
numRows * tileSize
|
||||||
|
);
|
||||||
|
|
||||||
|
const bytes = imageData.data; // Uint8ClampedArray (RGBA)
|
||||||
|
|
||||||
|
// Allocate output buffer (2 bytes per pixel)
|
||||||
|
const decodedBytes = new Uint8Array(
|
||||||
|
tileSize * tileSize * tileDepth * 2
|
||||||
|
);
|
||||||
|
|
||||||
|
let k = 0;
|
||||||
|
let numTilesCropped = 0;
|
||||||
|
|
||||||
|
for (let y = 0; y < numRows; y++) {
|
||||||
|
const sy = y * tileSize;
|
||||||
|
|
||||||
|
for (let x = 0; x < numCols; x++) {
|
||||||
|
const sx = x * tileSize;
|
||||||
|
|
||||||
|
for (let i = sy; i < sy + tileSize; i++) {
|
||||||
|
for (let j = sx; j < sx + tileSize; j++) {
|
||||||
|
|
||||||
|
const idByte = (j + i * numCols * tileSize) * 4;
|
||||||
|
|
||||||
|
// Copy R channel
|
||||||
|
decodedBytes[k] = bytes[idByte];
|
||||||
|
|
||||||
|
// Copy A channel
|
||||||
|
decodedBytes[k + 1] = bytes[idByte + 3];
|
||||||
|
|
||||||
|
k += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numTilesCropped++;
|
||||||
|
|
||||||
|
if (numTilesCropped === tileDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (numTilesCropped === tileDepth) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.postMessage(
|
||||||
|
{
|
||||||
|
tileSize,
|
||||||
|
tileDepth,
|
||||||
|
timeRequest,
|
||||||
|
HiPSCDid: HiPS,
|
||||||
|
cell,
|
||||||
|
bytes: decodedBytes,
|
||||||
|
},
|
||||||
|
[decodedBytes.buffer] // transfer of ownership
|
||||||
|
);
|
||||||
};
|
};
|
||||||
"#;
|
"#;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct HEALPixCell(pub u8, pub u64);
|
pub struct HEALPixCell(pub u8, pub u64);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -493,6 +494,7 @@ impl Ord for HEALPixCell {
|
|||||||
|
|
||||||
/// A simple object describing a cubic tile of a HiPS3D
|
/// A simple object describing a cubic tile of a HiPS3D
|
||||||
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
|
#[derive(Eq, Hash, PartialEq, Clone, Debug)]
|
||||||
|
#[derive(serde::Deserialize, serde::Serialize)]
|
||||||
pub struct HEALPixFreqCell {
|
pub struct HEALPixFreqCell {
|
||||||
pub hpx: HEALPixCell,
|
pub hpx: HEALPixCell,
|
||||||
pub f_hash: u64,
|
pub f_hash: u64,
|
||||||
|
|||||||
+4810
-4810
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1121,7 +1121,7 @@ export let Catalog = (function () {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fillText(label, s.x, s.y);
|
ctx.fillText(label, s.x + this.sourceSize / 2, s.y);
|
||||||
};
|
};
|
||||||
|
|
||||||
// callback function to be called when the status of one of the sources has changed
|
// callback function to be called when the status of one of the sources has changed
|
||||||
|
|||||||
Reference in New Issue
Block a user