Compare commits

...

2 Commits

Author SHA1 Message Date
bmatthieu3
b275ac70ec remove rust warning 2025-05-07 16:35:39 +02:00
bmatthieu3
4bc8c81ad4 feat: color picker and read pixel
This PR targets: #253, #76 and to some extent, maybe #208

It features:
* api: readPixel on a HiPS object method taking x, y pixel values on the
  screen. When no screen coo are given, the one of the center screen is
used.
* api: HiPS.probe a general method using HiPS.readPixel under the hood.
  It allow to probe the survey pixel values on a pixel, along a screen line and
along a great circle arc on the sky
* fix: readPixel could throw an exception if the tile has not been
  received. If so, now, we return a JS null value
* fix: retrieve pixels on fits HiPS bitpix=-32
* feat: a new aladin mode called TOOL_COLOR_PICKER.
* tool: showColorPickerControl: when clicking on it, enter the
  TOOL_COLOR_PICKER mode. The user can move the mouse on a pixel to know
its value. With a click, the pixel value is copied to its clipboard
* fix: restore samp button to connect to a hub
* fix: call redraw when calling gotoRaDec to update instantly the
  imageCanvas #208
* a new global readCanvas method on the Aladin object that
will simply read the final image Canvas. User can give a pixel coo, a
line (2 pixel coos), a rect (2 pixel coos for the box)
2025-05-07 16:29:55 +02:00
32 changed files with 1042 additions and 303 deletions

View File

@@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
<svg width="800px" height="800px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<rect x="0" fill="none" width="20" height="20"/>
<g>

After

Width:  |  Height:  |  Size: 529 B

View File

@@ -10,8 +10,6 @@ import A from '../src/js/A.js';
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});
aladin.reverseLongitude(true)
let hscGreenSurvey = aladin.getBaseImageLayer();
hscGreenSurvey.setImageFormat("fits");
hscGreenSurvey.setColormap("green", { stretch: "asinh" });

123
examples/al-read-pixel.html Normal file
View File

@@ -0,0 +1,123 @@
<!doctype html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.9.4/Chart.js"></script>
</head>
<body>
<div id="aladin-lite-div" style="width: 768px; height: 512px"></div>
<canvas id="myChart" style="width:100%;max-width:600px"></canvas>
<script>let aladin;</script>
<script type="module">
function getPixelsOnLine(startX, startY, endX, endY){
const pixelCols = [];
var x = Math.floor(startX);
var y = Math.floor(startY);
const xx = Math.floor(endX);
const yy = Math.floor(endY);
const dx = Math.abs(xx - x);
const sx = x < xx ? 1 : -1;
const dy = -Math.abs(yy - y);
const sy = y < yy ? 1 : -1;
var err = dx + dy;
var e2;
var end = false;
while (!end) {
pixelCols.push([x,y]);
if ((x === xx && y === yy)) {
end = true;
} else {
e2 = 2 * err;
if (e2 >= dy) {
err += dy;
x += sx;
}
if (e2 <= dx) {
err += dx;
y += sy;
}
}
}
return pixelCols;
}
import A from '../src/js/A.js';
A.init.then(() => {
aladin = A.aladin(
'#aladin-lite-div',
{
showSimbadPointerControl: true,
survey: 'P/allWISE/color', // set initial image survey
projection: 'AIT', // set a projection
fov: 360, // initial field of view in degrees
target: 'orion', // initial target
cooFrame: 'icrs', // set galactic frame
reticleColor: '#ff89ff', // change reticle color
reticleSize: 64, // change reticle size
showContextMenu: true,
showShareControl: true,
showFrame: true,
showZoomControl:true,
showSettingsControl:true,
showColorPickerControl: true,
showCooGrid: true,
fullScreen: true,
samp: true,
realFullscreen: true,
}
);
let base = aladin.getBaseImageLayer();
aladin.select('line', p => {
let xValues = [];
let rValues = [];
let gValues = [];
let bValues = [];
let i = 0;
for(var [r, g, b] of base.probe({type: 'line', x1: p.a.x, y1: p.a.y, x2: p.b.x, y2: p.b.y})) {
xValues.push(i)
rValues.push(r)
gValues.push(g)
bValues.push(b)
i++;
}
new Chart("myChart", {
type: "line",
data: {
labels: xValues,
datasets: [{
fill: false,
lineTension: 0,
backgroundColor: "rgba(255,0,0,1.0)",
data: rValues
},
{
fill: false,
lineTension: 0,
backgroundColor: "rgba(0,255,0,1.0)",
data: gValues
},
{
fill: false,
lineTension: 0,
backgroundColor: "rgba(0,0,255,1.0)",
data: bValues
}]
},
options: {
legend: {display: false},
scales: {
yAxes: [{ticks: {min: 0, max:255}}],
}
}
});
})
});
</script>
</body>
</html>

View File

@@ -56,7 +56,8 @@ impl Texture3D {
height: height as u32,
internal_format: F::INTERNAL_FORMAT,
format: F::FORMAT,
type_: F::TYPE,
ty: F::TYPE,
channel_type: F::CHANNEL_TYPE
})));
Ok(Texture3D {
@@ -135,7 +136,7 @@ impl<'a> Texture3DBound<'a> {
image.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 3d");
@@ -162,7 +163,7 @@ impl<'a> Texture3DBound<'a> {
canvas.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
canvas,
)
.expect("Sub texture 2d");
@@ -189,7 +190,7 @@ impl<'a> Texture3DBound<'a> {
image.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -219,7 +220,7 @@ impl<'a> Texture3DBound<'a> {
h,
d,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -249,7 +250,7 @@ impl<'a> Texture3DBound<'a> {
h,
d,
metadata.format,
metadata.type_,
metadata.ty,
pixels,
)
.expect("Sub texture 2d");

View File

@@ -11,6 +11,7 @@ use std::cell::RefCell;
use std::rc::Rc;
use wasm_bindgen::prelude::*;
use web_sys::HtmlImageElement;
use crate::texture::ChannelType;
pub struct Texture2DArray {
gl: WebGlContext,
@@ -54,7 +55,8 @@ impl Texture2DArray {
height: height as u32,
internal_format: F::INTERNAL_FORMAT,
format: F::FORMAT,
type_: F::TYPE,
ty: F::TYPE,
channel_type: F::CHANNEL_TYPE
})));
Ok(Texture2DArray {
@@ -115,28 +117,30 @@ impl Texture2DArray {
.viewport(0, 0, metadata.width as i32, metadata.height as i32);
#[cfg(feature = "webgl2")]
let value = match (metadata.format, metadata.type_) {
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::UNSIGNED_BYTE) => {
let value = match metadata.channel_type {
ChannelType::R8UI => {
let p = <[u8; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::SHORT) => {
ChannelType::R16I => {
let p = <[i16; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::INT) => {
ChannelType::R32I => {
let p = <[i32; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED, WebGlRenderingCtx::FLOAT) => {
ChannelType::R32F => {
let p = <[f32; 1]>::read_pixel(&self.gl, x, y)?;
crate::log(&format!("{:?}", p));
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RGB, WebGlRenderingCtx::UNSIGNED_BYTE) => {
ChannelType::RGB8U => {
let p = <[u8; 3]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p)?)
}
(WebGlRenderingCtx::RGBA, WebGlRenderingCtx::UNSIGNED_BYTE) => {
ChannelType::RGBA8U => {
let p = <[u8; 4]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p)?)
}
@@ -216,7 +220,7 @@ impl<'a> Texture2DArrayBound<'a> {
image.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 3d");
@@ -243,7 +247,7 @@ impl<'a> Texture2DArrayBound<'a> {
canvas.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
canvas,
)
.expect("Sub texture 2d");
@@ -270,7 +274,7 @@ impl<'a> Texture2DArrayBound<'a> {
image.height() as i32,
1,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -299,7 +303,7 @@ impl<'a> Texture2DArrayBound<'a> {
h,
1,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -328,7 +332,7 @@ impl<'a> Texture2DArrayBound<'a> {
h,
1,
metadata.format,
metadata.type_,
metadata.ty,
pixels,
)
.expect("Sub texture 2d");

View File

@@ -11,6 +11,7 @@ pub use mod_3d::Texture3D;
use web_sys::HtmlCanvasElement;
use web_sys::WebGlTexture;
use crate::image::format::ChannelType;
use crate::webgl_ctx::WebGlContext;
use crate::webgl_ctx::WebGlRenderingCtx;
use wasm_bindgen::prelude::*;
@@ -24,7 +25,8 @@ pub static mut CUR_IDX_TEX_UNIT: u8 = 0;
pub struct Texture2DMeta {
pub format: u32,
pub internal_format: i32,
pub type_: u32,
pub ty: u32,
pub channel_type: ChannelType,
pub width: u32,
pub height: u32,
@@ -76,7 +78,8 @@ impl Texture2D {
height: height,
internal_format: F::INTERNAL_FORMAT,
format: F::FORMAT,
type_: F::TYPE,
ty: F::TYPE,
channel_type: F::CHANNEL_TYPE
}));
#[cfg(feature = "webgl2")]
@@ -204,7 +207,8 @@ impl Texture2D {
height: height as u32,
internal_format: F::INTERNAL_FORMAT,
format: F::FORMAT,
type_: F::TYPE,
ty: F::TYPE,
channel_type: F::CHANNEL_TYPE
})));
Ok(Texture2D {
@@ -244,7 +248,8 @@ impl Texture2D {
height: height as u32,
internal_format: F::INTERNAL_FORMAT,
format: F::FORMAT,
type_: F::TYPE,
ty: F::TYPE,
channel_type: F::CHANNEL_TYPE
})));
Ok(Texture2D {
texture,
@@ -331,28 +336,30 @@ impl Texture2D {
.viewport(0, 0, metadata.width as i32, metadata.height as i32);
#[cfg(feature = "webgl2")]
let value = match (metadata.format, metadata.type_) {
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::UNSIGNED_BYTE) => {
let value = match metadata.channel_type {
ChannelType::R8UI => {
let p = <[u8; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::SHORT) => {
ChannelType::R16I => {
let p = <[i16; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED_INTEGER, WebGlRenderingCtx::INT) => {
ChannelType::R32I => {
let p = <[i32; 1]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RED, WebGlRenderingCtx::FLOAT) => {
ChannelType::R32F => {
let p = <[f32; 1]>::read_pixel(&self.gl, x, y)?;
crate::log(&format!("{:?}", p));
Ok(serde_wasm_bindgen::to_value(&p[0])?)
}
(WebGlRenderingCtx::RGB, WebGlRenderingCtx::UNSIGNED_BYTE) => {
ChannelType::RGB8U => {
let p = <[u8; 3]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p)?)
}
(WebGlRenderingCtx::RGBA, WebGlRenderingCtx::UNSIGNED_BYTE) => {
ChannelType::RGBA8U => {
let p = <[u8; 4]>::read_pixel(&self.gl, x, y)?;
Ok(serde_wasm_bindgen::to_value(&p)?)
}
@@ -417,7 +424,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -430,7 +437,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -454,7 +461,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
canvas,
)
.expect("Sub texture 2d");
@@ -467,7 +474,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
canvas,
)
.expect("Sub texture 2d");
@@ -491,7 +498,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -504,7 +511,7 @@ impl<'a> Texture2DBound<'a> {
dx,
dy,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -530,7 +537,7 @@ impl<'a> Texture2DBound<'a> {
width,
height,
metadata.format,
metadata.type_,
metadata.ty,
image,
)
.expect("Sub texture 2d");
@@ -556,7 +563,7 @@ impl<'a> Texture2DBound<'a> {
width,
height,
metadata.format,
metadata.type_,
metadata.ty,
pixels,
)
.expect("Sub texture 2d");

View File

@@ -91,29 +91,23 @@ impl Pixel for [f32; 1] {
const BLACK: Self = [std::f32::NAN];
fn read_pixel(gl: &WebGlContext, x: i32, y: i32) -> Result<Self, JsValue> {
let pixels = js_sys::Float32Array::new_with_length(1);
#[cfg(feature = "webgl2")]
let p = js_sys::Uint8Array::new_with_length(4);
gl.read_pixels_with_opt_array_buffer_view(
x,
y,
1,
1,
WebGlRenderingCtx::RED,
WebGlRenderingCtx::FLOAT,
Some(&pixels),
)?;
#[cfg(feature = "webgl1")]
gl.read_pixels_with_opt_array_buffer_view(
x,
y,
1,
1,
WebGlRenderingCtx::LUMINANCE_ALPHA,
WebGlRenderingCtx::FLOAT,
Some(&pixels),
WebGlRenderingCtx::RGBA,
WebGlRenderingCtx::UNSIGNED_BYTE,
Some(&p),
)?;
Ok([pixels.to_vec()[0]])
Ok([f32::from_le_bytes([
p.at(0).unwrap(),
p.at(1).unwrap(),
p.at(2).unwrap(),
p.at(3).unwrap(),
])])
}
}
/*use crate::image::ArrayF64;

View File

@@ -789,20 +789,25 @@ impl App {
Ok(has_camera_moved)
}
pub(crate) fn read_pixel(&self, pos: &Vector2<f64>, layer: &str) -> Result<JsValue, JsValue> {
if let Some(lonlat) = self.screen_to_world(pos) {
if let Some(hips) = self.layers.get_hips_from_layer(layer) {
hips.read_pixel(&lonlat, &self.camera)
} else if let Some(_image) = self.layers.get_image_from_layer(layer) {
Err(JsValue::from_str("TODO: read pixel value"))
} else {
Err(JsValue::from_str("Survey not found"))
}
pub(crate) fn read_pixel(&self, x: f64, y: f64, layer: &str) -> Result<JsValue, JsValue> {
if let Some(hips) = self.layers.get_hips_from_layer(layer) {
hips.read_pixel(x, y, &self.camera, &self.projection)
} else if let Some(_image) = self.layers.get_image_from_layer(layer) {
// FIXME handle the case of an image
Ok(JsValue::null())
} else {
Err(JsValue::from_str(&"position is out of projection"))
Err(JsValue::from_str("Survey not found"))
}
}
pub(crate) fn read_line_of_pixels(&self, x1: f64, y1: f64, x2: f64, y2: f64, layer: &str) -> Result<Vec<JsValue>, JsValue> {
let pixels = crate::math::utils::bresenham(x1, y1, x2, y2)
.map(|(x, y)| self.read_pixel(x, y, layer))
.collect::<Result<Vec<_>, _>>()?;
Ok(pixels)
}
pub(crate) fn draw_grid_labels(&mut self) -> Result<(), JsValue> {
self.grid.draw_labels()
}

View File

@@ -225,21 +225,6 @@ impl HEALPixCell {
}
}
#[inline(always)]
pub(crate) fn has_7_neigh(&self) -> bool {
let base_cell = self.ancestor(self.depth());
let nside_minus_one = (self.nside() - 1) as u32;
let (x, y) = self.offset_in_parent(&base_cell);
match base_cell.idx() {
0..=3 => (x == 0 && y == nside_minus_one) || (y == 0 && x == nside_minus_one),
4..=7 => (x == 0 && y == 0) || (x == nside_minus_one && y == nside_minus_one),
8..=11 => (x == 0 && y == nside_minus_one) || (y == 0 && x == nside_minus_one),
_ => unreachable!()
}
}
#[inline(always)]
pub(crate) fn is_on_base_cell_edges(&self) -> bool {
let base_cell = self.ancestor(self.depth());

View File

@@ -900,7 +900,7 @@ impl WebClient {
Ok(())
}
/// Project a line to the screen
/// Project a great circle arc on the screen
///
/// # Returns
///
@@ -915,23 +915,31 @@ impl WebClient {
/// * `lat1` - The latitude in degrees of the starting line point
/// * `lon2` - The longitude in degrees of the ending line point
/// * `lat2` - The latitude in degrees of the ending line point
/*#[wasm_bindgen(js_name = projectLine)]
pub fn project_line(
#[wasm_bindgen(js_name = projectGreatCircleArc)]
pub fn project_great_circle_arc(
&self,
lon1: f64,
lat1: f64,
lon2: f64,
lat2: f64,
) -> Result<Box<[f64]>, JsValue> {
let vertices = self.app.project_line(lon1, lat1, lon2, lat2);
let vertices = crate::renderable::line::great_circle_arc::project(
lon1.to_radians(), lat1.to_radians(),
lon2.to_radians(), lat2.to_radians(),
&self.app.camera,
&self.app.projection
);
let vertices = vertices
.into_iter()
.flat_map(|v| vec![v.x, v.y])
.flat_map(|ndc| {
let sxy = crate::math::projection::ndc_to_screen_space(&ndc, &self.app.camera);
[sxy.x, sxy.y]
})
.collect::<Vec<_>>();
Ok(vertices.into_boxed_slice())
}*/
}
/// Get the list of colormap supported
///
@@ -1005,10 +1013,14 @@ impl WebClient {
/// * `x` - The x screen coordinate in pixels
/// * `y` - The y screen coordinate in pixels
/// * `base_url` - The base url of the hips identifying it
#[wasm_bindgen(js_name = readPixel)]
pub fn read_pixel(&self, x: f64, y: f64, layer: String) -> Result<JsValue, JsValue> {
let pixel = self.app.read_pixel(&Vector2::new(x, y), layer.as_str())?;
Ok(pixel)
#[wasm_bindgen(js_name = probePixel)]
pub fn probe_pixel(&self, x: f64, y: f64, layer: String) -> Result<JsValue, JsValue> {
self.app.read_pixel(x, y, layer.as_str())
}
#[wasm_bindgen(js_name = probeLineOfPixels)]
pub fn probe_line_of_pixels(&self, x1: f64, y1: f64, x2: f64, y2: f64, layer: String) -> Result<Vec<JsValue>, JsValue> {
self.app.read_line_of_pixels(x1, y1, x2, y2, layer.as_str())
}
#[wasm_bindgen(js_name = getVisibleCells)]

View File

@@ -114,3 +114,67 @@ pub fn ccw_tri<S: BaseFloat>(a: &[S; 2], b: &[S; 2], c: &[S; 2]) -> bool {
a[0] * b[1] + a[1] * c[0] + b[0] * c[1] - c[0] * b[1] - c[1] * a[0] - b[0] * a[1] >= S::zero()
}
struct PixelBresenhamIter {
x: i32,
y: i32,
xx: i32,
yy: i32,
dx: i32,
sx: i32,
dy: i32,
sy: i32,
err: i32,
end: bool,
}
impl PixelBresenhamIter {
fn new(sx: f64, sy: f64, ex: f64, ey: f64) -> Self {
let x = sx.floor() as i32;
let y = sy.floor() as i32;
let xx = ex.floor() as i32;
let yy = ey.floor() as i32;
let dx = (xx - x).abs();
let sx = if x < xx { 1 } else { -1 };
let dy = -(yy - y).abs();
let sy = if y < yy { 1 } else { -1 };
let err = dx + dy;
let end = false;
Self { x, y, xx, yy, dx, sx, dy, sy, err, end }
}
}
impl Iterator for PixelBresenhamIter {
type Item = (f64, f64);
fn next(&mut self) -> Option<Self::Item> {
if self.end {
None
} else {
let item = (self.x as f64, self.y as f64);
if self.x == self.xx && self.y == self.yy {
self.end = true;
} else {
let e2 = 2 * self.err;
if e2 >= self.dy {
self.err += self.dy;
self.x += self.sx;
}
if e2 <= self.dx {
self.err += self.dx;
self.y += self.sy;
}
}
Some(item)
}
}
}
pub fn bresenham(sx: f64, sy: f64, ex: f64, ey: f64) -> impl Iterator<Item = (f64, f64)> {
PixelBresenhamIter::new(sx, sy, ex, ey)
}

View File

@@ -10,9 +10,6 @@ use cgmath::Vector3;
use al_api::hips::ImageExt;
use al_core::webgl_ctx::WebGlRenderingCtx;
use crate::math::lonlat::LonLat;
use crate::CameraViewPort;
use crate::LonLatT;
use al_core::image::format::ImageFormat;
use al_core::image::format::{R16I, R32F, R32I, R64F, R8UI, RGB8U, RGBA8U};
use al_core::image::Image;
@@ -349,6 +346,10 @@ impl HiPS2DBuffer {
Ok(())
}
pub fn get_texture(&self) -> &Texture2DArray {
&self.texture_2d_array
}
}
impl HpxTileBuffer for HiPS2DBuffer {
@@ -495,72 +496,6 @@ impl HpxTileBuffer for HiPS2DBuffer {
fn config_mut(&mut self) -> &mut HiPSConfig {
&mut self.config
}
fn read_pixel(&self, pos: &LonLatT<f64>, camera: &CameraViewPort) -> Result<JsValue, JsValue> {
// 1. Convert it to the hips frame system
let cfg = self.config();
let camera_frame = camera.get_coo_system();
let hips_frame = cfg.get_frame();
let pos: LonLatT<f64> =
crate::coosys::apply_coo_system(camera_frame, hips_frame, &pos.vector()).lonlat();
// Get the array of textures from that survey
let depth = camera.get_texture_depth().min(cfg.get_max_depth_texture());
// compute the tex
let (pix, dx, dy) = crate::healpix::utils::hash_with_dxdy(depth, &pos);
let texture_cell = HEALPixCell(depth, pix);
if let Some(texture) = self.get(&texture_cell) {
let cfg = self.config();
// Index of the texture in the total set of textures
let texture_idx = texture.idx();
// The size of the global texture containing the tiles
let texture_size = cfg.get_texture_size();
// Offset in the slice in pixels
let mut pos_tex = Vector3::new(
(dy * (texture_size as f64)) as i32,
(dx * (texture_size as f64)) as i32,
texture_idx,
);
// Offset in the slice in pixels
if cfg.tex_storing_fits {
let texture_size = cfg.get_texture_size() as f32;
let mut uvy = pos_tex.y as f32 / texture_size;
uvy = cfg.size_tile_uv + 2.0 * cfg.size_tile_uv * (uvy / cfg.size_tile_uv).floor()
- uvy;
pos_tex.y = (uvy * texture_size) as i32;
}
let mut value = self
.texture_2d_array
.read_pixel(pos_tex.x, pos_tex.y, pos_tex.z)?;
if cfg.tex_storing_fits {
// scale the value
let f64_v = value
.as_f64()
.ok_or_else(|| JsValue::from_str("Error unwraping the pixel read value."))?;
let scale = cfg.scale as f64;
let offset = cfg.offset as f64;
value = JsValue::from_f64(f64_v * scale + offset);
}
Ok(value)
} else {
Err(JsValue::from_str(&format!(
"{:?} not loaded in the GPU, please wait before trying again.",
texture_cell
)))
}
}
}
fn send_to_gpu<I: Image>(

View File

@@ -9,7 +9,7 @@ use al_core::colormap::Colormap;
use al_core::colormap::Colormaps;
use al_core::image::format::ChannelType;
use cgmath::Vector3;
use cgmath::Vector2;
use crate::math::angle::ToAngle;
use crate::downloader::query;
@@ -28,7 +28,7 @@ use crate::ProjectionType;
use crate::camera::CameraViewPort;
use crate::shader::ShaderManager;
use crate::{math::lonlat::LonLatT, utils};
use crate::utils;
use crate::downloader::request::allsky::Allsky;
use crate::healpix::{cell::HEALPixCell, coverage::HEALPixCoverage};
@@ -36,6 +36,7 @@ use crate::time::Time;
use super::config::HiPSConfig;
use std::collections::HashSet;
use crate::math::lonlat::LonLat;
// Recursively compute the number of subdivision needed for a cell
// to not be too much skewed
@@ -427,13 +428,78 @@ impl HiPS2D {
self.buffer.config().is_allsky
}
// Position given is in the camera space
pub fn read_pixel(
&self,
p: &LonLatT<f64>,
x: f64,
y: f64,
camera: &CameraViewPort,
proj: &ProjectionType
) -> Result<JsValue, JsValue> {
self.buffer.read_pixel(p, camera)
if let Some(xyz) = proj.screen_to_model_space(&Vector2::new(x, y), camera) {
// 1. Convert it to the hips frame system
let cfg = self.buffer.config();
let camera_frame = camera.get_coo_system();
let hips_frame = cfg.get_frame();
let lonlat =
crate::coosys::apply_coo_system(camera_frame, hips_frame, &xyz).lonlat();
// Get the array of textures from that survey
let depth = camera.get_texture_depth().min(cfg.get_max_depth_texture());
// compute the tex
let (pix, dx, dy) = crate::healpix::utils::hash_with_dxdy(depth, &lonlat);
let texture_cell = HEALPixCell(depth, pix);
let value = if let Some(texture) = self.buffer.get(&texture_cell) {
// Index of the texture in the total set of textures
let texture_idx = texture.idx();
// The size of the global texture containing the tiles
let texture_size = cfg.get_texture_size();
// Offset in the slice in pixels
let mut pos_tex = Vector3::new(
(dy * (texture_size as f64)) as i32,
(dx * (texture_size as f64)) as i32,
texture_idx,
);
// Offset in the slice in pixels
if cfg.tex_storing_fits {
let texture_size = cfg.get_texture_size() as f32;
let mut uvy = pos_tex.y as f32 / texture_size;
uvy = cfg.size_tile_uv + 2.0 * cfg.size_tile_uv * (uvy / cfg.size_tile_uv).floor()
- uvy;
pos_tex.y = (uvy * texture_size) as i32;
}
let mut value = self
.buffer
.get_texture()
.read_pixel(pos_tex.x, pos_tex.y, pos_tex.z)?;
if cfg.tex_storing_fits {
// scale the value
let f64_v = value
.as_f64()
.ok_or_else(|| "Error unwraping the pixel read value.")?;
let scale = cfg.scale as f64;
let offset = cfg.offset as f64;
value = JsValue::from_f64(f64_v * scale + offset);
}
value
} else {
JsValue::null()
};
Ok(value)
} else {
Err(JsValue::from_str("Out of projection"))
}
}
fn recompute_vertices(&mut self, camera: &mut CameraViewPort, projection: &ProjectionType) {

View File

@@ -1,7 +1,5 @@
use std::collections::HashMap;
use crate::CameraViewPort;
use crate::LonLatT;
use al_core::image::Image;
use al_core::WebGlContext;
@@ -162,10 +160,6 @@ impl HpxTileBuffer for HiPS3DBuffer {
Ok(())
}
fn read_pixel(&self, _pos: &LonLatT<f64>, _camera: &CameraViewPort) -> Result<JsValue, JsValue> {
todo!();
}
// Tell if a texture is available meaning all its sub tiles
// must have been written for the GPU
fn contains(&self, cell: &HEALPixCell) -> bool {

View File

@@ -24,7 +24,6 @@ use crate::camera::CameraViewPort;
use crate::downloader::query;
use crate::shader::ShaderManager;
use crate::math::lonlat::LonLatT;
use crate::downloader::request::allsky::Allsky;
use crate::healpix::{cell::HEALPixCell, coverage::HEALPixCoverage};
@@ -505,13 +504,13 @@ impl HiPS3D {
}
// Position given is in the camera space
pub fn read_pixel(
/*pub fn read_pixel(
&self,
p: &LonLatT<f64>,
camera: &CameraViewPort,
) -> Result<JsValue, JsValue> {
self.buffer.read_pixel(p, camera)
}
}*/
fn draw_internal(
&self,

View File

@@ -14,7 +14,6 @@ use crate::time::Time;
use crate::CameraViewPort;
use crate::HEALPixCell;
use crate::HEALPixCoverage;
use crate::LonLatT;
use crate::WebGlContext;
use al_api::hips::ImageExt;
use wasm_bindgen::JsValue;
@@ -70,8 +69,6 @@ pub(crate) trait HpxTileBuffer {
fn config_mut(&mut self) -> &mut HiPSConfig;
fn config(&self) -> &HiPSConfig;
fn read_pixel(&self, pos: &LonLatT<f64>, camera: &CameraViewPort) -> Result<JsValue, JsValue>;
}
use crate::downloader::query;
@@ -98,12 +95,15 @@ impl HiPS {
// Position given is in the camera space
pub fn read_pixel(
&self,
p: &LonLatT<f64>,
x: f64,
y: f64,
camera: &CameraViewPort,
proj: &ProjectionType
) -> Result<JsValue, JsValue> {
match self {
D2(hips) => hips.read_pixel(p, camera),
D3(hips) => hips.read_pixel(p, camera),
D2(hips) => hips.read_pixel(x, y, camera, proj),
// FIXME todo
D3(_) => Ok(JsValue::null()),
}
}

View File

@@ -12,8 +12,18 @@
/*container-type: inline-size;*/
font-size: 0.9rem;
/* Aladin lite default color */
--aladin-color: #b232b2;
--aladin-color-border: #fff;
}
.aladin-dark-theme {
background-color: black;
color: white;
}
.aladin-imageCanvas {
position: absolute;
left: 0;
@@ -973,8 +983,6 @@ otherwise it fits its content options. If those are too big the select can go ou
pointer-events: none;
cursor: default;
visibility: hidden;
background-color: white;
color: black;
width: max-content;
@@ -994,11 +1002,6 @@ otherwise it fits its content options. If those are too big the select can go ou
transition-delay: 100ms;
}
.aladin-tooltip-container .aladin-tooltip.aladin-dark-theme {
background-color: #333;
color: white;
}
/* Show the tooltip text when you mouse over the tooltip container */
.aladin-tooltip-container:hover .aladin-tooltip {
visibility: visible;
@@ -1046,6 +1049,24 @@ otherwise it fits its content options. If those are too big the select can go ou
transform: translateY(-100%);
}
.aladin-container .aladin-color-picker {
transform: translate(10px, 10px);
position: fixed;
pointer-events: none;
}
.aladin-container .aladin-view-label {
font-weight: bold;
font-family: monospace;
background-color: #00000000;
font-size: 1rem;
color: var(--aladin-color);
text-shadow: 1px 0 var(--aladin-color-border), -1px 0 var(--aladin-color-border), 0 1px var(--aladin-color-border), 0 -1px var(--aladin-color-border),
1px 1px var(--aladin-color-border), -1px -1px var(--aladin-color-border), 1px -1px var(--aladin-color-border), -1px 1px var(--aladin-color-border);
}
/* *********************************************** */
/* Cursors */
@@ -1088,6 +1109,13 @@ otherwise it fits its content options. If those are too big the select can go ou
left: 0.2rem;
}
.aladin-colorPicker-control {
position: absolute;
top: 15rem;
left: 0.2rem;
}
.aladin-cooFrame {
position: absolute;
top: 0.2rem;
@@ -1158,8 +1186,6 @@ otherwise it fits its content options. If those are too big the select can go ou
bottom: 0.2rem;
left: 0.2rem;
background-color: red;
font-family: monospace;
border-radius: 0.4rem;

View File

@@ -68,6 +68,7 @@ import { ProjectionActionButton } from "./gui/Button/Projection.js";
// features
import { SettingsButton } from "./gui/Button/Settings";
import { SimbadPointer } from "./gui/Button/SimbadPointer";
import { ColorPicker } from "./gui/Button/ColorPicker";
import { OverlayStackButton } from "./gui/Button/OverlayStack";
import { GridEnabler } from "./gui/Button/GridEnabler";
import { CooFrame } from "./gui/Input/CooFrame";
@@ -94,19 +95,21 @@ import { Polyline } from "./shapes/Polyline";
* @property {string} [backgroundColor="rgb(60, 60, 60)"] - Background color in RGB format.
*
* @property {boolean} [showZoomControl=true] - Whether to show the zoom control toolbar.
* This element belongs to the FoV UI thus its CSS class is `aladin-fov`
* This element belongs to the FoV UI thus its CSS class is `aladin-fov`
* @property {boolean} [showLayersControl=true] - Whether to show the layers control toolbar.
* CSS class for that button is `aladin-stack-control`
* CSS class for that button is `aladin-stack-control`
* @property {boolean} [expandLayersControl=false] - Whether to show the stack box opened at starting
* CSS class for the stack box is `aladin-stack-box`
* @property {boolean} [showFullscreenControl=true] - Whether to show the fullscreen control toolbar.
* CSS class for that button is `aladin-fullScreen-control`
* CSS class for that button is `aladin-fullScreen-control`
* @property {boolean} [showSimbadPointerControl=false] - Whether to show the Simbad pointer control toolbar.
* CSS class for that button is `aladin-simbadPointer-control`
* CSS class for that button is `aladin-simbadPointer-control`
* @property {boolean} [showCooGridControl=false] - Whether to show the coordinate grid control toolbar.
* CSS class for that button is `aladin-grid-control`
* CSS class for that button is `aladin-grid-control`
* @property {boolean} [showSettingsControl=false] - Whether to show the settings control toolbar.
* CSS class for that button is `aladin-settings-control`
* @property {boolean} [showColorPickerControl=false] - Whether to show the color picker tool.
* CSS class for that button is `aladin-colorPicker-control`
* @property {boolean} [showShareControl=false] - Whether to show the share control toolbar.
* CSS class for that button is `aladin-share-control`
* @property {boolean} [showStatusBar=true] - Whether to show the status bar. Enabled by default.
@@ -187,7 +190,6 @@ import { Polyline } from "./shapes/Polyline";
/**
* @typedef {Object} CircleSelection
* @description Options for configuring the Aladin Lite instance.
*
* @property {number} x - x coordinate of the center's circle in pixels
* @property {number} y - y coordinate of the center's circle in pixels
@@ -198,7 +200,6 @@ import { Polyline } from "./shapes/Polyline";
/**
* @typedef {Object} RectSelection
* @description Options for configuring the Aladin Lite instance.
*
* @property {number} x - top left x coordinate of the rectangle in pixels
* @property {number} y - top left y coordinate of the rectangle in pixels
@@ -208,9 +209,19 @@ import { Polyline } from "./shapes/Polyline";
* @property {function} bbox - returns the bbox of the selection in pixels
*/
/**
* @typedef {Object} LineSelection
*
* @property {Object} a - start point vertex
* @property {number} [a.x] - x coo screen in pixels
* @property {number} [a.y] - y coo screen in pixels
* @property {Object} b - end point vertex
* @property {number} [b.x] - x coo screen in pixels
* @property {number} [b.y] - y coo screen in pixels
*/
/**
* @typedef {Object} PolygonSelection
* @description Options for configuring the Aladin Lite instance.
*
* @property {Object[]} vertices - vertices of the polygon selection in pixels. Each vertex has a x and y key in pixels.
* @property {function} contains - function taking a {x, y} object telling if the vertex is contained in the selection or not
@@ -418,6 +429,10 @@ export let Aladin = (function () {
this.hipsCache.append(hipsObj.id, hipsObj)
}
if (options.samp) {
this.samp = new SAMPConnector(this);
}
this._setupUI(options);
ALEvent.FAVORITE_HIPS_LIST_UPDATED.dispatchedTo(document.body, this.hipsFavorites);
@@ -502,10 +517,6 @@ export let Aladin = (function () {
);
}
if (options.samp) {
this.samp = new SAMPConnector(this);
}
// lockNorthUp option
this.lockNorthUp = options.lockNorthUp || false;
if (this.lockNorthUp) {
@@ -552,10 +563,12 @@ export let Aladin = (function () {
////////////////////////////////////////////////////
let stack = new OverlayStackButton(this);
let simbad = new SimbadPointer(this);
let colorPicker = new ColorPicker(this);
let grid = new GridEnabler(this);
this.addUI(stack);
this.addUI(simbad);
this.addUI(grid);
this.addUI(colorPicker)
// Add the layers control
if (!options.showLayersControl) {
@@ -573,6 +586,12 @@ export let Aladin = (function () {
grid._hide();
}
// Add the projection control
// Add the coo grid control
if (!options.showColorPickerControl) {
colorPicker._hide();
}
// Settings control
if (options.showSettingsControl) {
let settings = new SettingsButton(this, {
@@ -675,6 +694,7 @@ export let Aladin = (function () {
showSimbadPointerControl: false,
showCooGridControl: false,
showSettingsControl: false,
showColorPickerControl: false,
// Share toolbar
showShareControl: false,
@@ -712,7 +732,12 @@ export let Aladin = (function () {
manualSelection: false
};
// realFullscreen: AL div expands not only to the size of its parent, but takes the whole available screen estate
/**
* Toggle the fullscreen of the Aladin Lite view
*
* @memberof Aladin
* @param {boolean} realFullscreen - If true, AL div expands not only to the size of its parent, but takes the whole available screen estate
*/
Aladin.prototype.toggleFullscreen = function (realFullscreen) {
let self = this;
@@ -838,7 +863,7 @@ export let Aladin = (function () {
* @memberof Aladin
* @param {number} zoomFactor - Scaling screen factor
*/
Aladin.prototype.setZoomFactor = function (zoomFactor) {
Aladin.prototype.setZoomFactor = function (zoomFactor) {
this.view.setZoomFactor(zoomFactor);
};
@@ -851,6 +876,35 @@ export let Aladin = (function () {
return this.view.zoomFactor;
};
/**
* Read pixels inside the Aladin Lite canvas
*
* @description
* Returns the rgba pixels composing the current view.
* Please keep in mind that this method returns the actual colors that you see in the screen, it is not intended to return values coming from the progenitors.
* For a knowing exactly the values of a specific HiPS (e.g. the real FITS values from HiPS FITS tiles) please use the method {@link HiPS#readPixel}.
*
* @memberof Aladin
* @param {PixelProber|RectProber} [prober] - A prob object. Can be unique prober or a list of it. By default, the center of the view is probed, i.e. the pixel under the reticle.
* @returns {ImageData} A {@link https://developer.mozilla.org/fr/docs/Web/API/ImageData| ImageData} JS object coming from the canvas probing. Its `data` field stores the byte pixel array containing a list of 4 bytes RGBA values.
*/
Aladin.prototype.readCanvas = function (prober) {
prober = prober || {x: this.view.width / 2, y: this.view.height / 2};
let probers = [].concat(prober)
let pixels = []
for (var prober of probers) {
pixels.push(this.view.readPixel(prober))
}
if (probers.length === 1) {
return pixels[0]
} else {
return pixels;
}
};
// @API
// (experimental) try to adjust the FoV to the given object name. Does nothing if object is not known from Simbad
Aladin.prototype.adjustFovForObject = function (objectName) {
@@ -929,6 +983,23 @@ export let Aladin = (function () {
}
};
/**
* Change the default color of Aladin Lite. By default, #b232b2
*
* @memberof Aladin
* @param {string} color - A color given as a string. Hex, `rgb(r, g, b)` or css label colored e.g. `orange` are accepted
*/
Aladin.prototype.setDefaultColor = function(color) {
let aladinColor = new Color(color)
this.reticle.update({color: aladinColor.toHex()})
this.aladinDiv.style.setProperty('--aladin-color', aladinColor.toHex())
let aladinBorderColor = Color.getLabelColorForBackground(`rgb(${aladinColor.r}, ${aladinColor.g}, ${aladinColor.b})`);
this.aladinDiv.style.setProperty('--aladin-color-border', aladinBorderColor)
console.log(aladinBorderColor)
};
/**
* Sets the projection of the Aladin instance to the specified type.
*
@@ -2249,12 +2320,13 @@ export let Aladin = (function () {
* Enters selection mode
*
* @memberof Aladin
* @param {string} [mode='rect'] - The mode of selection, can be either, 'rect', 'poly', or 'circle'
* @param {'circle'|'rect'|'poly'|'line'} [mode='rect'] - The mode of selection, can be either, 'rect', 'poly', or 'circle'
* @param {function} [callback] - A function called once the selection has been done
* The callback accepts one parameter depending of the mode used: <br/>
* - If mode='circle' that parameter is of type {@link CircleSelection} <br/>
* - If mode='rect' that parameter is of type {@link RectSelection} <br/>
* - If mode='poly' that parameter is of type {@link PolygonSelection}
* - If mode='poly' that parameter is of type {@link PolygonSelection} <br/>
* - If mode='line' the selection resolves into a {@link LineSelection} object
*
* @example
* // Creates and add a MOC from the user polygonal selection
@@ -2286,10 +2358,11 @@ export let Aladin = (function () {
Aladin.prototype.fire = function (what, params) {
if (what === "selectstart") {
const { mode, callback } = params;
this.view.startSelection(mode, callback);
this.view.setMode(View.SELECT, params)
} else if (what === "simbad") {
this.view.setMode(View.TOOL_SIMBAD_POINTER);
} else if (what === "colorpicker") {
this.view.setMode(View.TOOL_COLOR_PICKER);
} else if (what === "default") {
this.view.setMode(View.PAN);
}
@@ -2359,7 +2432,6 @@ export let Aladin = (function () {
// TODO : integrate somehow into API ?
Aladin.prototype.exportAsPNG = function (downloadFile = false) {
(async () => {
const url = await this.getViewDataURL();
if (downloadFile) {

View File

@@ -37,7 +37,6 @@ export class CircleSelect extends FSM {
let start = (params) => {
const {callback} = params;
this.callback = callback;
view.setMode(View.SELECT)
}
let mousedown = (params) => {

View File

@@ -0,0 +1,168 @@
// Copyright 2015 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
import { FSM } from "../FiniteStateMachine";
import { View } from "../View";
/******************************************************************************
* Aladin Lite project
*
* Class Selector
*
* A line selector, used for example by the sky distance measuring tool or to retrieve pixels along a line
*
* Author: Matthieu Baumann[CDS]
*
*****************************************************************************/
export class LineSelect extends FSM {
// constructor
constructor(options, view) {
// Off initial state
let off = () => {
view.aladin.showReticle(true)
view.setMode(View.PAN)
view.setCursor('default');
// in case of a mouseout we would like to erase the selection draw
// in the canvas
view.requestRedraw();
view.aladin.removeStatusBarMessage('selector')
}
let mouseout = (params) => {
let {e, coo} = params;
self.dispatch('mousemove', {coo});
};
let start = (params) => {
const {callback} = params;
this.callback = callback;
// reset the coo
this.coos = [];
}
let mousedown = (params) => {
const {coo} = params;
this.coos.push(coo);
};
let mouseup = (params) => {
const {coo} = params;
this.coos.push(coo);
self.dispatch('finish');
};
let mousemove = (params) => {
const {coo} = params;
this.moveCoo = coo;
view.requestRedraw();
};
let draw = () => {
let ctx = view.catalogCtx;
// draw the selection
ctx.save();
let colorValue = (typeof options.color === 'function') ? options.color() : options.color;
ctx.strokeStyle = colorValue;
ctx.lineWidth = options.lineWidth;
ctx.beginPath();
const startCoo = this.coos[0];
const endCoo = this.moveCoo;
// Unproject the coordinates
let [lon1, lat1] = view.aladin.pix2world(endCoo.x, endCoo.y);
let [lon2, lat2] = view.aladin.pix2world(startCoo.x, startCoo.y);
let vertices = view.wasm.projectGreatCircleArc(lon1, lat1, lon2, lat2)
for (var i = 0; i < vertices.length; i+=4) {
ctx.moveTo(vertices[i], vertices[i+1]);
ctx.lineTo(vertices[i+2], vertices[i+3]);
}
ctx.stroke();
ctx.restore();
}
let finish = () => {
// finish the selection
let s = {
a: this.coos[0],
b: this.coos[1],
label: 'line',
};
(typeof this.callback === 'function') && this.callback(s);
this.coos = [];
// TODO execute general callback
view.requestRedraw();
this.dispatch('off');
};
let fsm = {
state: 'off',
transitions: {
off: {
start,
},
start: {
mousedown
},
mousedown: {
mousemove,
draw,
},
mouseout: {
start,
mousemove,
draw,
},
mousemove: {
draw,
mouseup,
},
draw: {
mouseout,
mousemove,
mouseup,
},
mouseup: {
finish
},
finish: {
off
}
}
};
super(fsm)
let self = this;
this.coos = [];
};
}

View File

@@ -66,8 +66,6 @@ export class PolySelect extends FSM {
let start = (params) => {
const {callback} = params;
view.setMode(View.SELECT)
this.callback = callback;
// reset the coo
this.coos = [];

View File

@@ -38,7 +38,6 @@ export class RectSelect extends FSM {
const {callback} = params;
this.callback = callback;
view.setMode(View.SELECT)
}
let mousedown = (params) => {

View File

@@ -176,6 +176,45 @@ PropertyParser.isPlanetaryBody = function (properties) {
* - 'include' - always send, even for cross-origin requests.
*/
/**
* Screen pixel prober type
*
* @typedef {Object} PixelProber
* @property {number} [x] - x screen coordinate. Default is set to the view center, i.e. half the width in pixels of the aladin lite div.
* @property {number} [y] - y screen coordinate. Default is set to the view center, i.e. half the height in pixels of the aladin lite div.
*/
/**
* Screen line prober type
*
* @typedef {Object} LineProber
* @property {number} [x1] - x start point screen coordinate
* @property {number} [y1] - y start point screen coordinate
* @property {number} [x2] - x end point screen coordinate
* @property {number} [y2] - y end point screen coordinate
*/
/**
* Sky great circle arc prober type
*
* @typedef {Object} GreatCircleArcProber
* @property {number} [ra1] - ra first point sky coordinate (in icrs) frame
* @property {number} [dec1] - dec first point sky coordinate (in icrs) frame
* @property {number} [ra2] - ra end point sky coordinate (in icrs) frame
* @property {number} [dec2] - dec end point sky coordinate (in icrs) frame
*/
/**
* Screen rectangular prober type
*
* @typedef {Object} RectProber
* @property {number} [top] - top screen pixel coordinate
* @property {number} [left] - left screen pixel coordinate
* @property {number} [w] - width in screen pixel
* @property {number} [h] - height in screen pixel
*/
/**
* JS {@link https://developer.mozilla.org/fr/docs/Web/API/FileList| FileList} API type
*
@@ -189,7 +228,6 @@ PropertyParser.isPlanetaryBody = function (properties) {
* @property {File} properties - The local properties file of the HiPS
*/
export let HiPS = (function () {
/**
* The object describing an image survey
@@ -859,16 +897,66 @@ export let HiPS = (function () {
HiPS.prototype.getAlpha = HiPS.prototype.getOpacity;
/**
* Read a specific screen pixel value
* Probe the HiPS at a screen pixel location.
*
* @description
* Returns the true pixel value for the pixel located at the given (x, y) pixel screen position.
* This method returns the true value coming from the tiles (color or 1 channel fits). It does not take into
* account the apply of a transfer function, a colormap, cuts etc... It only returns the true pixel value coming from the tile
*
* If you want to retrieve the pixels after apply of a transfer function, colormap, etc... i.e. if you are not looking for
* the real HiPS pixel values, then you might be more interested in {@link Aladin#readPixel} instead.
*
* @todo This has not yet been implemented
* @memberof HiPS
* @param {number} x - x axis in screen pixels to probe
* @param {number} y - y axis in screen pixels to probe
* @returns {number} the value of that pixel
* @param {number} [x] - x screen pixel coordinate. Default is set to the view center, i.e. half the width in pixels of the aladin lite div.
* @param {number} [y] - y screen pixel coordinate. Default is set to the view center, i.e. half the height in pixels of the aladin lite div.
* @returns {number} - The pixel value coming directly from the tiles
*/
HiPS.prototype.readPixel = function (x, y) {
return this.view.wasm.readPixel(x, y, this.layer);
x = x || (this.view.width / 2);
y = y || (this.view.height / 2);
return this.view.wasm.probePixel(x, y, this.layer);
};
/**
* Probe the HiPS true pixels
*
* @description
* Returns the true pixels composing this HiPS.
* This method returns the true value coming from the tiles (whether it refers to colored or 1 channel fits ones). It does not take into
* account the apply of a transfer function, a colormap, cuts etc... i.e. it returns the true pixel values coming from the tiles.
*
* This method is called by {@link HiPS#readPixel} with a pixel prober on the view center.
*
* If you want to retrieve the pixels you directly see on the screen, then you might be more interested in {@link Aladin#readCanvas} instead.
*
* @memberof HiPS
* @param {PixelProber|LineProber|GreatCircleArcProber} prober - A prob object. Only, `pixel`, `line` or `arc` are accepted.
* @returns {number[]} The pixel value(s) probed.
*/
HiPS.prototype.probe = function (prober) {
if (Utils.isNumber(prober.x) && Utils.isNumber(prober.y)) {
// pixel probing
return this.readPixel(prober.x, prober.y);
} else if (Utils.isNumber(prober.x1) && Utils.isNumber(prober.y1) && Utils.isNumber(prober.x2) && Utils.isNumber(prober.y2)) {
// line probing
return this.view.wasm.probeLineOfPixels(prober.x1, prober.y1, prober.x2, prober.y2, this.layer);
} else if (Utils.isNumber(prober.ra1) && Utils.isNumber(prober.dec1) && Utils.isNumber(prober.ra2) && Utils.isNumber(prober.dec2)) {
// get the vertices along the great circle arc
let pixelsAlongArc = view.wasm.projectGreatCircleArc(prober.ra1, prober.dec1, prober.ra2, prober.dec2);
let pixels = []
for (var i = 0; i < pixelsAlongArc.length; i+=4) {
pixels = pixels.concat(this.probe({
x1: pixelsAlongArc[i],
y1: pixelsAlongArc[i+1],
x2: pixelsAlongArc[i+2],
y2: pixelsAlongArc[i+3],
}))
}
return pixels
}
};
HiPS.prototype._setView = function (view) {

View File

@@ -20,8 +20,10 @@
import { Color } from "./Color";
import { CircleSelect } from "./FiniteStateMachine/CircleSelect";
import { PolySelect } from "./FiniteStateMachine/PolySelect";
import { LineSelect } from "./FiniteStateMachine/LineSelect";
import { RectSelect } from "./FiniteStateMachine/RectSelect";
import { ALEvent } from "./events/ALEvent";
import { View } from "./View";
/******************************************************************************
* Aladin Lite project
*
@@ -63,30 +65,32 @@ export class Selector {
setMode(mode) {
if (mode) {
let options = {
color: this.color,
lineWidth: this.lineWidth
};
if (mode === 'circle') {
this.select = new CircleSelect(options, this.view)
} else if (mode === 'rect') {
this.select = new RectSelect(options, this.view)
} else if (mode === 'poly') {
this.select = new PolySelect(options, this.view)
}
}
}
start(mode, callback) {
this.view.aladin.removeStatusBarMessage('selector')
this.view.aladin.addStatusBarMessage({
id: 'selector',
message: 'You entered the selection mode',
type: 'info'
})
this.setMode(mode);
let options = {
color: this.color,
lineWidth: this.lineWidth
};
if (mode === 'circle') {
this.select = new CircleSelect(options, this.view)
} else if (mode === 'rect') {
this.select = new RectSelect(options, this.view)
} else if (mode === 'poly') {
this.select = new PolySelect(options, this.view)
} else if (mode === 'line') {
this.select = new LineSelect(options, this.view)
}
this.dispatch('start', {callback})
}

View File

@@ -272,6 +272,21 @@ export let View = (function () {
this.fadingLatestUpdate = null;
this.dateRequestRedraw = null;
let colorPickerElement = document.getElementById('aladin-picker-tooltip');
if (!colorPickerElement) {
colorPickerElement = document.createElement('span');
colorPickerElement.classList.add('aladin-color-picker')
colorPickerElement.classList.add('aladin-view-label')
colorPickerElement.classList.add('aladin-dark-theme')
this.aladin.aladinDiv.appendChild(colorPickerElement);
}
this.colorPickerTool = {
domElement: colorPickerElement,
probedValue: null
};
init(this);
// listen to window resize and reshape canvases
this.resizeTimer = null;
@@ -283,6 +298,7 @@ export let View = (function () {
self.resizeObserver.observe(this.aladinDiv)
self.fixLayoutDimensions();
self.redraw()
};
@@ -290,7 +306,7 @@ export let View = (function () {
View.PAN = 0;
View.SELECT = 1;
View.TOOL_SIMBAD_POINTER = 2;
View.TOOL_COLOR_PICKER = 3;
// TODO: should be put as an option at layer level
View.DRAW_SOURCES_WHILE_DRAGGING = true;
@@ -435,7 +451,6 @@ export let View = (function () {
this.aladinDiv.style.removeProperty('line-height');
this.throttledDivResized();
};
var pixelateCanvasContext = function (ctx, pixelateFlag) {
@@ -447,11 +462,14 @@ export let View = (function () {
ctx.oImageSmoothingEnabled = enableSmoothing;
}
View.prototype.startSelection = function(mode, callback) {
this.selector.start(mode, callback);
}
View.prototype.setMode = function (mode, params) {
// hide the picker tooltip
this.colorPickerTool.domElement.style.display = "none";
// in case we are in the selection mode
this.requestRedraw();
this.aladin.removeStatusBarMessage('selector')
View.prototype.setMode = function (mode) {
this.mode = mode;
if (this.mode == View.TOOL_SIMBAD_POINTER) {
@@ -465,6 +483,13 @@ export let View = (function () {
else if (this.mode == View.SELECT) {
this.setCursor('crosshair');
this.aladin.showReticle(false)
const { mode, callback } = params;
this.selector.start(mode, callback);
} else if (this.mode == View.TOOL_COLOR_PICKER) {
this.colorPickerTool.domElement.style.display = "block";
this.setCursor('crosshair');
this.aladin.showReticle(false)
}
ALEvent.MODE.dispatchedTo(this.aladin.aladinDiv, {mode});
@@ -478,18 +503,7 @@ export let View = (function () {
this.catalogCanvas.style.cursor = cursor;
};
View.prototype.getCanvas = async function (imgType, width, height, withLogo=true) {
const loadImage = function (url) {
return new Promise((resolve, reject) => {
const image = document.createElement("img")
image.src = url
image.onload = () => resolve(image)
image.onerror = () => reject(new Error('could not load image'))
})
}
imgType = imgType || "image/png";
View.prototype.getRawPixelsCanvas = function(width, height) {
const canvas = this.wasm.canvas();
const c = document.createElement('canvas');
@@ -501,6 +515,22 @@ export let View = (function () {
ctx.drawImage(canvas, 0, 0, c.width, c.height);
ctx.drawImage(this.catalogCanvas, 0, 0, c.width, c.height);
return c;
};
View.prototype.getCanvas = async function (width, height, withLogo=true) {
const loadImage = function (url) {
return new Promise((resolve, reject) => {
const image = document.createElement("img")
image.src = url
image.onload = () => resolve(image)
image.onerror = () => reject(new Error('could not load image'))
})
}
const c = this.getRawPixelsCanvas(width, height)
let ctx = c.getContext("2d");
// draw the reticle if it is on the view
let reticle = this.aladin.reticle;
if (reticle.isVisible()) {
@@ -521,13 +551,13 @@ export let View = (function () {
}
return c;
}
};
/**
* Return dataURL string corresponding to the current view
*/
View.prototype.getCanvasDataURL = async function (imgType, width, height, withLogo=true) {
const c = await this.getCanvas(imgType, width, height, withLogo);
const c = await this.getCanvas(width, height, withLogo);
return c.toDataURL(imgType);
};
@@ -535,26 +565,20 @@ export let View = (function () {
* Return ArrayBuffer corresponding to the current view
*/
View.prototype.getCanvasArrayBuffer = async function (imgType, width, height, withLogo=true) {
const c = await this.getCanvas(imgType, width, height, withLogo);
return new Promise((resolve, reject) => {
c.toBlob(blob => {
if (blob) {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = () => reject(new Error('Error reading blob as ArrayBuffer'));
reader.readAsArrayBuffer(blob);
} else {
reject(new Error('Canvas toBlob failed'));
}
}, imgType);
});
return this.getCanvasBlob(imgType, width, height, withLogo)
.then((blob) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = () => reject(new Error('Error reading blob as ArrayBuffer'));
reader.readAsArrayBuffer(blob);
});
}
/**
* Return Blob corresponding to the current view
*/
View.prototype.getCanvasBlob = async function (imgType, width, height, withLogo=true) {
const c = await this.getCanvas(imgType, width, height, withLogo);
const c = await this.getCanvas(width, height, withLogo);
return new Promise((resolve, reject) => {
c.toBlob(blob => {
if (blob) {
@@ -562,7 +586,7 @@ export let View = (function () {
} else {
reject(new Error('Canvas toBlob failed'));
}
});
}, imgType);
});
}
@@ -825,7 +849,6 @@ export let View = (function () {
ev: e,
});
if (e.type === 'touchend' || e.type === 'touchcancel') {
if (longTouchTimer) {
if (view.aladin.statusBar) {
@@ -895,6 +918,20 @@ export let View = (function () {
return; // when in TOOL_SIMBAD_POINTER mode, we do not call the listeners
}
if (view.mode == View.TOOL_COLOR_PICKER) {
Utils.copy2Clipboard(view.colorPickerTool.probedValue)
.then(() => {
if (view.aladin.statusBar) {
view.aladin.statusBar.appendMessage({
message: `${view.colorPickerTool.probedValue} copied into your clipboard`,
duration: 1500,
type: 'info'
})
}
})
return; // listeners are not called
}
// popup to show ?
if (!wasDragging || e.type === "touchend") {
if (e.type === "touchend") {
@@ -935,6 +972,44 @@ export let View = (function () {
var lastHoveredObject; // save last object hovered by mouse
var lastMouseMovePos = null;
const pickColor = (xymouse) => {
const layers = view.aladin.getStackLayers()
let lastImageLayer = view.aladin.getOverlayImageLayer(layers[layers.length - 1])
try {
let probedValue = lastImageLayer.readPixel(xymouse.x, xymouse.y);
view.colorPickerTool.domElement.style.display = "block"
if (probedValue !== null && probedValue.length === 3) {
// rgb color
const r = probedValue[0];
const g = probedValue[1];
const b = probedValue[2];
view.colorPickerTool.probedValue = Color.rgbToHex(r, g, b);
view.colorPickerTool.domElement.innerText = view.colorPickerTool.probedValue
} else if (probedValue !== null && probedValue.length === 4) {
// rgba color
const r = probedValue[0];
const g = probedValue[1];
const b = probedValue[2];
const a = probedValue[3];
view.colorPickerTool.probedValue = Color.rgbaToHex(r, g, b, a);
view.colorPickerTool.domElement.innerText = view.colorPickerTool.probedValue
} else {
// 1-channel color
view.colorPickerTool.probedValue = probedValue;
view.colorPickerTool.domElement.innerText = probedValue
}
} catch(e) {
console.warn("Pixel color reading: " + e)
// out of the projection, we probe no pixel
view.colorPickerTool.domElement.style.display = "none"
}
view.colorPickerTool.domElement.style.left = `${xymouse.x}px`;
view.colorPickerTool.domElement.style.top = `${xymouse.y}px`;
}
Utils.on(view.catalogCanvas, "mousemove touchmove", function (e) {
e.preventDefault();
@@ -1127,6 +1202,10 @@ export let View = (function () {
view.selector.dispatch('mousemove', {coo: xymouse})
}
if (view.mode === View.TOOL_COLOR_PICKER) {
pickColor(xymouse);
}
if (!view.dragging) {
return;
}
@@ -1197,6 +1276,10 @@ export let View = (function () {
}
view.throttledTouchPadZoom();
if (view.mode === View.TOOL_COLOR_PICKER) {
pickColor(xymouse);
}
}
return false;
@@ -1285,7 +1368,6 @@ export let View = (function () {
this.throttledPositionChanged(false);
}
////// 2. Draw catalogues////////
const isViewRendering = this.wasm.isRendering();
if (isViewRendering || this.needRedraw) {
@@ -1417,6 +1499,30 @@ export let View = (function () {
return pixList;
};
View.prototype.readPixel = function(prober) {
// Hide the coo grid
this.aladin.hideCooGrid();
// Ask for the redraw to make the coo grid hiding effective
this.redraw()
let c = this.getRawPixelsCanvas(null, null);
let ctx = c.getContext("2d");
let imageData;
if (Utils.isNumber(prober.x) && Utils.isNumber(prober.y)) {
imageData = ctx.getImageData(prober.x, prober.y, 1, 1);
} else if (Utils.isNumber(prober.top) && Utils.isNumber(prober.left) && Utils.isNumber(prober.w) && Utils.isNumber(prober.h)) {
imageData = ctx.getImageData(prober.top, prober.left, prober.w, prober.h);
}
// Show the coo grid back again
this.aladin.showCooGrid();
return imageData;
};
View.prototype.unselectObjects = function() {
if (this.manualSelection) {
return;
@@ -1970,7 +2076,7 @@ export let View = (function () {
ALEvent.POSITION_CHANGED.dispatchedTo(this.aladin.aladinDiv, this.viewCenter);
this.requestRedraw();
this.redraw();
var self = this;
setTimeout(function () { self.refreshProgressiveCats(); }, 1000);

View File

@@ -140,13 +140,10 @@ export class ServiceQueryBox extends Box {
let [ra, dec] = self.aladin.pix2world(c.x, c.y);
let radius = self.aladin.angularDist(c.x, c.y, c.x + c.r, c.y);
//var hlon = this.lon/15.0;
//var strlon = Numbers.toSexagesimal(hlon, this.prec+1, false);
let coo = new Coo(ra, dec, 7);
let [lon, lat] = coo.format('d2');
let fov = new Angle(radius, 1).degrees();
//selectorBtn.update({tooltip: {content: 'center: ' + ra.toFixed(2) + ', ' + dec.toFixed(2) + '<br\>radius: ' + radius.toFixed(2), position: {direction: 'left'}}})
self.form.set('ra', lon)
self.form.set('dec', lat)
self.form.set('rad', fov)

View File

@@ -0,0 +1,93 @@
// Copyright 2013 - UDS/CNRS
// The Aladin Lite program is distributed under the terms
// of the GNU General Public License version 3.
//
// This file is part of Aladin Lite.
//
// Aladin Lite is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 3 of the License.
//
// Aladin Lite is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// The GNU General Public License is available in COPYING file
// along with Aladin Lite.
//
/******************************************************************************
* Aladin Lite project
*
* File gui/Stack/Menu.js
*
*
* Author: Matthieu Baumann [CDS, matthieu.baumann@astro.unistra.fr]
*
*****************************************************************************/
import { ActionButton } from "../Widgets/ActionButton.js";
import targetIcon from '../../../../assets/icons/color-picker.svg';
import { ALEvent } from "../../events/ALEvent.js";
import { View } from "../../View.js";
export class ColorPicker extends ActionButton {
// Constructor
constructor(aladin) {
let self;
super({
icon: {
url: targetIcon,
monochrome: true,
},
classList: ['aladin-colorPicker-control'],
size: 'medium',
tooltip: {
content: 'A color picker tool',
position: { direction: 'top right' },
},
action(o) {
if (self.mode !== View.TOOL_COLOR_PICKER) {
aladin.fire('colorpicker');
} else {
aladin.fire('default');
}
}
})
self = this;
this.aladin = aladin;
this.mode = aladin.view.mode;
this.addListeners()
}
updateStatus() {
if (this.mode === View.TOOL_COLOR_PICKER) {
if (this.aladin.statusBar) {
this.aladin.statusBar.appendMessage({
id: 'colorpicker',
message: 'Color picker mode, click on a pixel to copy it',
type: 'info'
})
}
} else {
if (this.aladin.statusBar) {
this.aladin.statusBar.removeMessage('colorpicker')
}
}
this.update({toggled: this.mode === View.TOOL_COLOR_PICKER})
}
addListeners() {
ALEvent.MODE.listenedBy(this.aladin.aladinDiv, e => {
let mode = e.detail.mode;
this.mode = mode;
this.updateStatus();
});
}
}

View File

@@ -33,14 +33,6 @@ import { ALEvent } from "../../events/ALEvent.js";
import waveOnIconUrl from '../../../../assets/icons/wave-on.svg';
import waveOffIconUrl from '../../../../assets/icons/wave-off.svg';
/*
options = {
action: (connector) => {
}
tooltip
}
*/
export class SAMPActionButton extends ActionButton {
// Constructor
constructor(options, aladin) {

View File

@@ -61,8 +61,7 @@ export class SettingsCtxMenu extends ContextMenu {
name: 'reticleColor',
change(e) {
let hex = e.target.value;
let reticle = aladin.getReticle();
reticle.update({color: hex})
aladin.setDefaultColor(hex)
}
});

View File

@@ -150,7 +150,7 @@ export class Location extends DOMElement {
}
let [lon, lat] = lonlat;
self.field.el.blur()
//self.field.el.blur()
self.update({
lon, lat,
frame: aladin.view.cooFrame,
@@ -224,7 +224,7 @@ export class Location extends DOMElement {
self.field.removeClass('aladin-not-valid');
self.field.removeClass('aladin-valid');
self.field.element().style.color = options.isViewCenter ? aladin.getReticle().getColor() : 'white';
self.field.element().style.color = options.isViewCenter ? 'var(--aladin-color)' : 'white';
//self.field.el.blur()
};

View File

@@ -41,6 +41,20 @@ export class Tooltip extends DOMElement {
let el = document.createElement('span');
el.classList.add('aladin-tooltip');
// Set the anchor to the element on which
// the tooltip is set
if (!options.position) {
options.position = {
direction: 'right',
}
}
options.position.anchor = target;
if (!options.delayShowUpTime) {
options.delayShowUpTime = 500;
}
let targetParent = target.parentNode;
// Insert it into the DOM tree
@@ -60,19 +74,6 @@ export class Tooltip extends DOMElement {
wrapperEl.appendChild(el);
}
// Set the anchor to the element on which
// the tooltip is set
if (!options.position) {
options.position = {
direction: 'right',
}
}
options.position.anchor = target;
if (!options.delayShowUpTime) {
options.delayShowUpTime = 500;
}
super(wrapperEl, options)
this.element().classList.add('aladin-dark-theme')
@@ -185,6 +186,8 @@ export class Tooltip extends DOMElement {
return;
}
let targetEl = target.element()
if (options.global) {
let statusBar = options.aladin && options.aladin.statusBar;
if (!statusBar) {
@@ -192,7 +195,7 @@ export class Tooltip extends DOMElement {
}
// handle global tooltip div display
Utils.on(target.el, 'mouseover', (e) => {
Utils.on(targetEl, 'mouseover', (e) => {
statusBar.removeMessage('tooltip')
statusBar.appendMessage({
id: 'tooltip',
@@ -201,13 +204,14 @@ export class Tooltip extends DOMElement {
type: 'tooltip'
})
});
Utils.on(target.el, 'mouseout', (e) => {
Utils.on(targetEl, 'mouseout', (e) => {
statusBar.removeMessage('tooltip')
});
return;
}
target.tooltip = new Tooltip(options, target.element())
target.tooltip = new Tooltip(options, targetEl)
}
}
}

View File

@@ -33,7 +33,6 @@ import { samp } from "../libs/samp";
import A from "../A";
export class SAMPConnector {
static _createTag = (function() {
var count = 0;
return function() {