mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2026-01-24 10:24:51 -08:00
Compare commits
2 Commits
hips_3d
...
feat-color
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b275ac70ec | ||
|
|
4bc8c81ad4 |
8
assets/icons/color-picker.svg
Normal file
8
assets/icons/color-picker.svg
Normal 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 |
@@ -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
123
examples/al-read-pixel.html
Normal 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>
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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>(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
110
src/js/Aladin.js
110
src/js/Aladin.js
@@ -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) {
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
168
src/js/FiniteStateMachine/LineSelect.js
Normal file
168
src/js/FiniteStateMachine/LineSelect.js
Normal 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 = [];
|
||||
};
|
||||
}
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -38,7 +38,6 @@ export class RectSelect extends FSM {
|
||||
const {callback} = params;
|
||||
|
||||
this.callback = callback;
|
||||
view.setMode(View.SELECT)
|
||||
}
|
||||
|
||||
let mousedown = (params) => {
|
||||
|
||||
102
src/js/HiPS.js
102
src/js/HiPS.js
@@ -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) {
|
||||
|
||||
@@ -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})
|
||||
}
|
||||
|
||||
|
||||
182
src/js/View.js
182
src/js/View.js
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
93
src/js/gui/Button/ColorPicker.js
Normal file
93
src/js/gui/Button/ColorPicker.js
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -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()
|
||||
};
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,6 @@ import { samp } from "../libs/samp";
|
||||
import A from "../A";
|
||||
|
||||
export class SAMPConnector {
|
||||
|
||||
static _createTag = (function() {
|
||||
var count = 0;
|
||||
return function() {
|
||||
|
||||
Reference in New Issue
Block a user