diff --git a/examples/al-aas225.html b/examples/al-aas225.html index e80257b8..ef7cbbaf 100644 --- a/examples/al-aas225.html +++ b/examples/al-aas225.html @@ -17,7 +17,7 @@ let aladin; A.init.then(() => { aladin = A.aladin('#aladin-lite-div', { fov:0.15, target: 'Arp 240', showReticle: false, fullScreen: true }); - aladin.setBaseImageLayer(aladin.newImageSurvey('P/SDSS9/g', {colormap: "rainbow", stretch: "Linear"})); + aladin.setBaseImageLayer(aladin.newImageSurvey('P/SDSS9/g', {colormap: "sinebow", stretch: "Linear"})); var simbad = A.catalog({name: 'Simbad', sourceSize: 16, color: '#4050F0'}); aladin.addCatalog(simbad); diff --git a/examples/al-additive-blend.html b/examples/al-additive-blend.html index b6393b11..711cd6b0 100644 --- a/examples/al-additive-blend.html +++ b/examples/al-additive-blend.html @@ -12,11 +12,12 @@ A.init.then(() => { let aladin = A.aladin('#aladin-lite-div', {projection: "TAN", survey: "P/HSC/DR2/deep/g", target: '02 21 36.529 -05 31 20.16', fov: 0.1}); let HSCGreenSurvey = aladin.getBaseImageLayer(); - HSCGreenSurvey.changeImageFormat("fits"); - HSCGreenSurvey.setColor([0, 1.0, 0, 1.0], { stretch: "asinh" }); + HSCGreenSurvey.setImageFormat("fits"); + HSCGreenSurvey.setColormap("green", { stretch: "asinh" }); + HSCGreenSurvey.setCuts(-0.2186, 5.30322); - const HSCRedSurvey = aladin.createImageSurvey('HSC-r', "HSC red", "CDS/P/HSC/DR2/deep/r", null, null, {imgFormat: 'fits', color: [1.0, 0.0, 0.0, 1.0], additive: true, stretch: "asinh"}); - const HSCBlueSurvey = aladin.createImageSurvey('HSC-b', "HSC blue", "CDS/P/HSC/DR2/deep/z", null, null, {imgFormat: 'fits', color: [0.0, 0.0, 1.0, 1.0], additive: true, stretch: "asinh"}); + const HSCRedSurvey = aladin.createImageSurvey('HSC-r', "HSC red", "CDS/P/HSC/DR2/deep/r", null, null, {imgFormat: 'fits', colormap: "red", minCut: 0.34228, maxCut: 2.75785, additive: true, stretch: "asinh"}); + const HSCBlueSurvey = aladin.createImageSurvey('HSC-b', "HSC blue", "CDS/P/HSC/DR2/deep/z", null, null, {imgFormat: 'fits', colormap: "blue", minCut: -0.01218, maxCut: 2.27397, additive: true, stretch: "asinh"}); aladin.setOverlayImageLayer('HSC-r', 'hsc red layer'); aladin.setOverlayImageLayer('HSC-b', 'hsc blue layer'); diff --git a/examples/al-animation-CS-CDS-2022.html b/examples/al-animation-CS-CDS-2022.html index 60a458af..d3c3a339 100644 --- a/examples/al-animation-CS-CDS-2022.html +++ b/examples/al-animation-CS-CDS-2022.html @@ -24,11 +24,10 @@ A.init.then(() => { aladin = A.aladin('#aladin-lite-div', {cooFrame: 'galactic', fov: 400, fullScreen: true, showCooGrid: false, showReticle: false}) aladin.gotoRaDec(79.9525321, -69.2742586) - const gaiaFlux = aladin.createImageSurvey('P/DM/flux-G/I/355/gaiadr3', "GaiaDR3GFlux", null, null, null) + const gaiaFlux = aladin.createImageSurvey('P/DM/flux-G/I/355/gaiadr3', "GaiaDR3GFlux", null, null, null, {imgFormat: 'fits', stretch: 'log'}) aladin.setBaseImageLayer(gaiaFlux) - gaiaFlux.setOptions({'imgFormat': 'fits', 'stretch': 'log'}) - gaiaFlux.setCuts([3e5, 1e8]) - + gaiaFlux.setCuts(3e5, 1e8) + aladin.setProjection('MOL') @@ -306,7 +305,7 @@ let cut0 = -0.0004 * lambda + (1 - lambda) * -0.00132 let cut1 = 0.005 * lambda + (1 - lambda) * 0.05759 - meerkat.setCuts([cut0, cut1]) + meerkat.setCuts(cut0, cut1) } } diff --git a/examples/al-chinavo.html b/examples/al-chinavo.html index 94f5d780..e4173af9 100644 --- a/examples/al-chinavo.html +++ b/examples/al-chinavo.html @@ -17,7 +17,7 @@ // Start up Aladin Lite aladin = A.aladin('#aladin-lite-div', {target: 'M81', fov: 360, showCooGrid: true}); aladin.setImageSurvey( - aladin.newImageSurvey('https://hips.china-vo.org/change2-moon-7m-dom', {imgFormat: 'fits', colormap: "redtemperature"}) + aladin.newImageSurvey('https://hips.china-vo.org/change2-moon-7m-dom', {imgFormat: 'png', colormap: "redtemperature"}) ); }); diff --git a/examples/al-set-colormap.html b/examples/al-set-colormap.html index 81368133..d9913746 100644 --- a/examples/al-set-colormap.html +++ b/examples/al-set-colormap.html @@ -15,8 +15,8 @@ let aladin; A.init.then(() => { aladin = A.aladin('#aladin-lite-div', {cooFrame: 'galactic', target: 'galactic center', survey: 'P/Finkbeiner'}); - // possible values are 'blues', 'cividis', 'cubehelix', 'eosb', 'grayscale', 'inferno', 'magma', 'native', 'parula', 'plasma', 'rainbow', - // 'rdbu', 'rdyibu', 'redtemperature', 'spectral', 'summer', 'viridis', 'yignbu' and 'yiorbr' + // possible values are 'blues', 'cividis', 'cubehelix', 'eosb', 'grayscale', 'inferno', 'magma', 'native', 'parula', 'plasma', + // 'rdbu', 'rdylbu', 'redtemperature', 'sinebow', 'spectral', 'summer', 'viridis', 'ylgnbu' and 'ylorbr' aladin.getBaseImageLayer().setColormap("cubehelix"); //aladin.getBaseImageLayer().setColor([1.0, 0.0, 1.0, 1.0], { tf: 'Linear'} ); }); diff --git a/examples/al-stephan-quintet.html b/examples/al-stephan-quintet.html index ed32fbdc..af3811f6 100644 --- a/examples/al-stephan-quintet.html +++ b/examples/al-stephan-quintet.html @@ -18,7 +18,7 @@ '#aladin-lite-div', { projection: 'AIT', // set a projection - fov: 0.02, // initial field of view in degrees + fov: 0.1, // initial field of view in degrees target: '338.98958 33.96', // initial target cooFrame: 'equatorial', // set galactic frame showCooGrid: true, // set the grid diff --git a/examples/al-zoom-meerkat.html b/examples/al-zoom-meerkat.html index 6fb075ea..c106e92d 100644 --- a/examples/al-zoom-meerkat.html +++ b/examples/al-zoom-meerkat.html @@ -34,7 +34,7 @@ let cut0 = -0.0004 * lambda + (1 - lambda) * -0.00132; let cut1 = 0.005 * lambda + (1 - lambda) * 0.05759; - meerkat.setCuts([cut0, cut1]); + meerkat.setCuts(cut0, cut1); }) }); diff --git a/src/core/Cargo.toml b/src/core/Cargo.toml index b8c61139..63a905c0 100644 --- a/src/core/Cargo.toml +++ b/src/core/Cargo.toml @@ -39,6 +39,7 @@ al-task-exec = { path = "./al-task-exec" } al-api = { path = "./al-api" } mapproj = "0.3.0" wcs = "0.1.1" +colorgrad = "0.6.2" [features] webgl1 = [ diff --git a/src/core/al-api/Cargo.toml b/src/core/al-api/Cargo.toml index a31050f5..291f3dfd 100644 --- a/src/core/al-api/Cargo.toml +++ b/src/core/al-api/Cargo.toml @@ -10,6 +10,7 @@ cgmath = "*" serde = { version = "^1.0.59", features = ["derive"] } serde-wasm-bindgen = "0.4" wasm-bindgen = "0.2.79" +colorgrad = "0.6.2" [features] webgl1 = [ diff --git a/src/core/al-api/src/color.rs b/src/core/al-api/src/color.rs index 7e742acd..c73f5191 100644 --- a/src/core/al-api/src/color.rs +++ b/src/core/al-api/src/color.rs @@ -6,6 +6,8 @@ extern "C" { #[wasm_bindgen(static_method_of = Color)] pub fn hexToRgb(hex: String) -> JsValue; + #[wasm_bindgen(static_method_of = Color)] + pub fn hexToRgba(hex: String) -> JsValue; } #[derive(Debug, Clone, Copy)] @@ -81,3 +83,17 @@ impl TryFrom for ColorRGB { Ok(c) } } + +impl TryFrom for ColorRGBA { + type Error = JsValue; + + fn try_from(rgb: JsValue) -> Result { + let mut c: ColorRGBA = serde_wasm_bindgen::from_value(rgb)?; + c.r /= 255.0; + c.g /= 255.0; + c.b /= 255.0; + c.a /= 255.0; + + Ok(c) + } +} \ No newline at end of file diff --git a/src/core/al-api/src/colormap.rs b/src/core/al-api/src/colormap.rs index 81bd1ac6..7d3abe5e 100644 --- a/src/core/al-api/src/colormap.rs +++ b/src/core/al-api/src/colormap.rs @@ -1,26 +1,9 @@ -use wasm_bindgen::prelude::*; - use serde::{Deserialize, Serialize}; -#[wasm_bindgen] -#[derive(Clone, Debug, Copy, PartialEq, Deserialize, Serialize)] -#[serde(rename_all = "camelCase")] -pub enum Colormap { - Blues = 0, - Cividis = 1, - Cubehelix = 2, - Eosb = 3, - Grayscale = 4, - Inferno = 5, - Magma = 6, - Parula = 7, - Plasma = 8, - Rainbow = 9, - Rdbu = 10, - Rdyibu = 11, - Redtemperature = 12, - Spectral = 13, - Summer = 14, - Viridis = 15, - Ylgnbu = 16, - Ylorbr = 17, +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct CmapLabel(String); + +impl AsRef for CmapLabel { + fn as_ref(&self) -> &str { + self.0.as_ref() + } } diff --git a/src/core/al-api/src/hips.rs b/src/core/al-api/src/hips.rs index cf756a05..86c267c9 100644 --- a/src/core/al-api/src/hips.rs +++ b/src/core/al-api/src/hips.rs @@ -25,25 +25,10 @@ pub struct SimpleHiPS { /// The HiPS metadata pub properties: HiPSProperties, - + /// Its color pub meta: ImageSurveyMeta, - - pub img_format: HiPSTileFormat, } -/*#[wasm_bindgen] -impl SimpleHiPS { - #[wasm_bindgen(constructor)] - pub fn new(layer: String, properties: HiPSProperties, meta: ImageSurveyMeta) -> Self { - Self { - layer, - properties, - meta, - backend: None - } - } -}*/ - impl SimpleHiPS { pub fn get_layer(&self) -> String { self.layer.clone() @@ -68,6 +53,7 @@ pub struct HiPSProperties { formats: Vec, sky_fraction: f32, min_order: u8, + colored: bool, hips_initial_fov: Option, hips_initial_ra: Option, @@ -79,38 +65,6 @@ pub struct HiPSProperties { } impl HiPSProperties { - /*pub fn new( - url: String, - max_order: u8, - frame: CooSystem, - tile_size: i32, - min_cutout: Option, - max_cutout: Option, - bitpix: Option, - formats: Vec, - sky_fraction: f32, - min_order: u8, - hips_initial_fov: Option, - hips_initial_ra: Option, - hips_initial_dec: Option, - ) -> Self { - Self { - url, - max_order, - min_order, - frame, - tile_size, - formats, - bitpix, - min_cutout, - max_cutout, - sky_fraction, - hips_initial_fov, - hips_initial_dec, - hips_initial_ra, - } - }*/ - #[inline] pub fn get_url(&self) -> String { self.url.clone() @@ -165,6 +119,11 @@ impl HiPSProperties { pub fn get_initial_dec(&self) -> Option { self.hips_initial_dec } + + #[inline] + pub fn is_colored(&self) -> bool { + self.colored + } } #[derive(Deserialize, Debug, Clone, Copy, PartialEq)] @@ -177,26 +136,6 @@ pub enum HiPSTileFormat { } use serde::Serialize; -/*#[wasm_bindgen] -#[derive(Deserialize, Serialize, Debug)] -#[derive(Clone, Copy)] -#[serde(rename_all = "camelCase")] -pub struct GrayscaleParameter { - pub h: TransferFunction, - pub min_value: f32, - pub max_value: f32, -} - -impl Default for GrayscaleParameter { - fn default() -> Self { - Self { - h: TransferFunction::Asinh, - min_value: 0.0, - max_value: 1.0 - } - } -}*/ - use wasm_bindgen::prelude::*; #[wasm_bindgen] #[derive(Clone, Copy, PartialEq, Debug, Deserialize, Serialize)] @@ -237,47 +176,27 @@ impl From for TransferFunction { } } -use crate::colormap::Colormap; -#[derive(Deserialize, Debug, Clone, Copy)] +use crate::colormap::CmapLabel; +#[derive(Deserialize, Debug, Clone)] #[serde(rename_all = "camelCase")] -pub enum HiPSColor { - // FITS tile - Grayscale { - #[serde(rename = "stretch")] - tf: TransferFunction, - #[serde(rename = "minCut")] - min_cut: Option, - #[serde(rename = "maxCut")] - max_cut: Option, - - color: GrayscaleColor, - }, - // JPG/PNG tile - Color, +pub struct HiPSColor { + // transfer function called before evaluating the colormap + pub stretch: TransferFunction, + // low cut + pub min_cut: Option, + // high cut + pub max_cut: Option, + // flag to tell the colormap is queried reversed + pub reversed: bool, + // the colormap + pub cmap_name: CmapLabel, + /// tonal color tuning factors + pub k_gamma: f32, + pub k_saturation: f32, + pub k_contrast: f32, + pub k_brightness: f32, } -#[derive(Deserialize, Debug)] -#[serde(rename_all = "camelCase")] -#[derive(Clone, Copy)] -pub enum GrayscaleColor { - Colormap { reversed: bool, name: Colormap }, - Color([f32; 4]), -} -/* -impl Default for HiPSColor { - fn default() -> Self { - HiPSColor::Grayscale2Color { - color: [1.0, 0.0, 0.0], - param: GrayscaleParameter { - h: TransferFunction::Asinh, - min_value: 0.0, - max_value: 1.0, - }, - k: 1.0 - } - } -}*/ - #[derive(Deserialize, Debug)] #[serde(rename_all = "camelCase")] #[derive(Clone)] @@ -293,6 +212,8 @@ pub struct ImageSurveyMeta { #[serde(default = "default_opacity")] pub opacity: f32, pub longitude_reversed: bool, + /// the current format chosen + pub img_format: HiPSTileFormat, } fn default_opacity() -> f32 { @@ -313,71 +234,72 @@ impl ImageSurveyMeta { pub fn color(&self) -> JsValue { let js_color_obj = js_sys::Object::new(); - let color = match &self.color { - HiPSColor::Color => JsValue::from_str("Colored"), - HiPSColor::Grayscale { - tf, - min_cut, - max_cut, - color, - } => { - let js_grayscale = js_sys::Object::new(); + let HiPSColor { + stretch, + min_cut, + max_cut, + reversed, + cmap_name, + k_gamma, + k_saturation, + k_brightness, + k_contrast, + } = &self.color; - js_sys::Reflect::set( - &js_grayscale, - &"stretch".into(), - &serde_wasm_bindgen::to_value(&tf).unwrap_abort(), - ) - .unwrap_abort(); - js_sys::Reflect::set( - &js_grayscale, - &"minCut".into(), - &serde_wasm_bindgen::to_value(&min_cut).unwrap_abort(), - ) - .unwrap_abort(); - js_sys::Reflect::set( - &js_grayscale, - &"maxCut".into(), - &serde_wasm_bindgen::to_value(&max_cut).unwrap_abort(), - ) - .unwrap_abort(); - - let js_color = match color { - GrayscaleColor::Color(color) => { - let js_color = js_sys::Object::new(); - js_sys::Reflect::set( - &js_color, - &"color".into(), - &serde_wasm_bindgen::to_value(&color).unwrap_abort(), - ) - .unwrap_abort(); - - js_color - } - GrayscaleColor::Colormap { reversed, name } => { - let js_colormap = js_sys::Object::new(); - js_sys::Reflect::set( - &js_colormap, - &"reversed".into(), - &JsValue::from_bool(*reversed), - ) - .unwrap_abort(); - js_sys::Reflect::set( - &js_colormap, - &"colormap".into(), - &serde_wasm_bindgen::to_value(&name).unwrap_abort(), - ) - .unwrap_abort(); - - js_colormap - } - }; - js_sys::Reflect::set(&js_grayscale, &"color".into(), &js_color).unwrap_abort(); - - js_grayscale.into() - } - }; - js_sys::Reflect::set(&js_color_obj, &"color".into(), &color).unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"stretch".into(), + &serde_wasm_bindgen::to_value(&stretch).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"minCut".into(), + &serde_wasm_bindgen::to_value(&min_cut).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"maxCut".into(), + &serde_wasm_bindgen::to_value(&max_cut).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"kGamma".into(), + &serde_wasm_bindgen::to_value(&k_gamma).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"kSaturation".into(), + &serde_wasm_bindgen::to_value(&k_saturation).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"kBrightness".into(), + &serde_wasm_bindgen::to_value(&k_brightness).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"kContrast".into(), + &serde_wasm_bindgen::to_value(&k_contrast).unwrap_abort(), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"reversed".into(), + &JsValue::from_bool(*reversed), + ) + .unwrap_abort(); + js_sys::Reflect::set( + &js_color_obj, + &"colormap".into(), + &serde_wasm_bindgen::to_value(&cmap_name).unwrap_abort(), + ) + .unwrap_abort(); js_color_obj.into() } diff --git a/src/core/al-core/Cargo.toml b/src/core/al-core/Cargo.toml index 25d05684..13a21f3d 100644 --- a/src/core/al-core/Cargo.toml +++ b/src/core/al-core/Cargo.toml @@ -16,6 +16,7 @@ serde_json = "1.0" serde-wasm-bindgen = "0.4" wasm-streams = "0.3.0" futures = "0.3.25" +colorgrad = "0.6.2" [dependencies.wasm-bindgen] version = "0.2.79" diff --git a/src/core/al-core/src/colormap.rs b/src/core/al-core/src/colormap.rs new file mode 100644 index 00000000..fee257e1 --- /dev/null +++ b/src/core/al-core/src/colormap.rs @@ -0,0 +1,249 @@ +use std::collections::HashMap; + +use al_api::resources::Resources; +use colorgrad::Color; + +use crate::Texture2D; +use crate::WebGlContext; +use crate::image::format; +use crate::shader::SendUniformsWithParams; + +use wasm_bindgen::JsValue; +use crate::webgl_ctx::WebGlRenderingCtx; + +const WIDTH_CMAP_TEX: usize = 256; + +type Label = String; + +pub struct Colormap { + label: Label, + grad: colorgrad::Gradient, +} +impl Colormap { + pub fn new(label: &str, grad: colorgrad::Gradient) -> Self { + Self { label: label.to_string(), grad } + } + + pub fn label(&self) -> &Label { + &self.label + } +} + +fn build_cmaps_texture(gl: &WebGlContext, cmaps: &[Colormap]) -> Result { + let tex_bytes: Vec = cmaps.iter() + .map(|cmap| { + let mut values = [0_u8; 1024]; + for ix in 0..WIDTH_CMAP_TEX { + let rgba = cmap.grad.at(ix as f64 / WIDTH_CMAP_TEX as f64).to_rgba8(); + let ptr = values[4*ix..].as_mut_ptr() as *mut [u8; 4]; + unsafe { *ptr = rgba; } + } + + values + }) + .flatten() + .collect(); + let tex_params = &[ + ( + WebGlRenderingCtx::TEXTURE_MIN_FILTER, + WebGlRenderingCtx::LINEAR, + ), + ( + WebGlRenderingCtx::TEXTURE_MAG_FILTER, + WebGlRenderingCtx::LINEAR, + ), + // 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, + ), + ]; + + Texture2D::create_from_raw_pixels::( + gl, + WIDTH_CMAP_TEX as i32, + cmaps.len() as i32, + tex_params, + Some(&tex_bytes[..]) + ) +} + +pub struct Colormaps { + cmaps: Vec, + indices: HashMap, + + cmaps_tex: Texture2D, + + labels: Vec