Place objects on a defined shape - css

I'm looking for a way, to allow a drag and drop, only on what's the area of an image.
So, if the image is the following:
I will have the option to add another layer on top, with some text, but that new layer, can't go anywhere, but inside the shape of that image.
So, after some research, I still wonder how can I achieve something like that?
I know I can use map and area to map existing elements on an image, but how can I add new elements, that fit only to that map? Any ideas?

To illustration your solution, let's assume you want to drop a raindrop in your cloud and be sure all raindrop pixels are fully inside the cloud...
Then the answer to your question requires asking this question:
Are all opaque raindrop pixels fully inside the cloud?
To answer this question, you must compare every pixel on the raindrop with every pixel underneath.
If the raindrop pixel is transparent, then ignore this pixel because this part of the raindrop is transparent anyway.
If the raindrop pixel is opaque and the pixel underneath is transparent, then this raindrop pixel is not contained in the cloud.
If both the raindrop pixel & underneath pixel are opaque, then this raindrop pixel is contained in the cloud.
You can get the required transparency information about the raindrop and cloud by drawing their images on a canvas and then requesting getImageData. 'getImageData' returns the red, green, blue & alpha information about every pixel on the canvas. To answer the question, we just need the alpha information.
Here's annotated code and a Demo:
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var offsetX,offsetY;
// load the cloud and raindrop images
var cloudmap,rainmap;
var rain=new Image();
rain.crossOrigin='anonymous';
rain.onload=start;
rain.src='https://dl.dropboxusercontent.com/u/139992952/raindrop1.png';
var cloud=new Image();
cloud.crossOrigin='anonymous';
cloud.onload=start;
cloud.src="https://dl.dropboxusercontent.com/u/139992952/cloud.png";
var cloud1=new Image();
cloud1.crossOrigin='anonymous';
cloud1.onload=start;
cloud1.src="https://dl.dropboxusercontent.com/u/139992952/multple/cloud1.png";
var imageCount=3;
function start(){
if(--imageCount>0){return;}
// resize the canvas to the size of the cloud
// and draw the cloud on the canvas
cw=canvas.width=cloud.width;
ch=canvas.height=cloud.height;
draw();
// create a transparency map of the cloud
cloudmap={
width:cloud.width,
height:cloud.height,
map:transparencyMap(cloud),
};
// create a transparency map of the raindrop
rainmap={
width:rain.width,
height:rain.height,
map:transparencyMap(rain),
}
// listen for mousemove events
$("#canvas").mousemove(function(e){handleMouseMove(e);});
// listen for window scroll events
calcCanvasOffset();
$(window).scroll(function(){ calcCanvasOffset(); });
}
function transparencyMap(img){
// create a temp canvas sized to the img size
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
c.width=img.width;
c.height=img.height;
// draw the img on the canvas
cctx.drawImage(img,0,0);
// get the pixel data for every pixel on the canvas
var data=cctx.getImageData(0,0,c.width,c.height).data;
// create an array that reports the status
// of every pixel on the canvas
// (status: true if opaque, false if transparent)
var map=[];
for(var i=0;i<data.length;i+=4){
map.push(data[i+3]>250);
}
return(map);
}
function draw(mouseX,mouseY,isContained){
// draw the cloud
ctx.clearRect(0,0,cw,ch);
if(isContained){
// draw the blue cloud indicating the raindrop is not fully contained
ctx.drawImage(cloud,0,0);
}else{
// draw the yellow cloud indicating the raindrop is fully contained
ctx.drawImage(cloud1,0,0);
}
// if the mouse position was supplied
if(mouseX){
ctx.drawImage(rain,mouseX-rain.width/2,mouseY-rain.height/2);
}
}
function AcontainsB(ax,ay,amap,bx,by,bmap){
// set a flag indicating of the raindrop is fully contained in the cloud
var isContained=true;
// calc the relative position of the raindrop vs cloud in the canvas
var deltaX=bx-ax;
var deltaY=by-ay;
// test every pixel of B against A
// if B is opaque and a is not opaque then B is not contained by A
var y=0;
while(isContained && y<bmap.height){
var x=0;
while(isContained && x<bmap.width){
// calc the map array indexes for the cloud(A) & raindrop(B)
var mapIndexA=(y+deltaY)*amap.width+(x+deltaX);
var mapIndexB=y*bmap.width+x;
// if the raindrop is opaque at this pixel
if(bmap.map[mapIndexB]){
// ...and if this pixel is off canvas
if(mapIndexA<0 || mapIndexA>=amap.map.length){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
// ...or if the pixel under the raindrop is transparent
}else if(!amap.map[mapIndexA]){
// ...then the raindrop is not in the cloud at this pixel
isContained=false;
}
}
x++;
}
y++;
}
return(isContained);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the current mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// calc the top-left corner of the raindrop image
var rainX=parseInt(mouseX-rain.width/2);
var rainY=parseInt(mouseY-rain.height/2);
// ask if the raindrop is fully contained in the cloud
var isContained=AcontainsB(0,0,cloudmap,rainX,rainY,rainmap);
// redraw the cloud & raindrop
draw(mouseX,mouseY,isContained);
}
// recalc the canvas offsetX & offsetY
function calcCanvasOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Use the mouse to drag the raindrop over the canvas<br>The cloud turns blue if the rain is fully inside the cloud</h4>
<canvas id="canvas" width=600 height=500></canvas>
You can use this "transparency mapping" to test anything you can draw into a canvas including images and text. Note that text is drawn with context.fillText.
If you're dropping a text element external to the canvas (maybe using jqueryUI or native "draggable"), you will have to:
Fetch the x,y position of the drop.
Fetch the text content of the dropped element.
Create a temporary canvas containing the text. Do it like this...
function textToCanvas(text,fontsize,fontface){
var c=document.createElement('canvas');
var cctx=c.getContext('2d');
cctx.font=fontsize+'px '+fontface;
var textWidth=cctx.measureText(text).width;
c.width=textWidth;
c.height=fontsize+4;
cctx.font=fontsize+'px '+fontface;
cctx.textBaseline='top';
cctx.fillText(text,0,0);
return(c);
}
Use the temporary canvas just like an image to create a transparency map. This is possible because the canvas will accept another canvas as its image source.
Good luck with your project.
[ Additional questions from comments ]
Additional Question:
"How will this react when you have a raindrop placed somewhere in the
cloud (saved it's position), and then you try to add something that's
on top of it?" Said another way: "How can I test if 2 objects are
overlapping?"
Answer: You can test if 2 objects overlap by using transparency maps again. Create another test (AintersectsB) which tests if any pixel in A is opaque while the associated pixel in B is also opaque. You can start with the AcontainsB and modify it to create the AintersectsB test.
Additional Question:
"How can I save and later restore dropped object positions?"
Answer: Since canvas doesn't remember what it draws, you must remember for it. This is usually done by creating a javascript object for each dropped item and saving all these objects in an array. That way if you need to save the positions on a server you can use JSON.stringify to turn the array of objects into a string and send that string to the server to be saved in a database (or file). To recreate your work, the server pulls the string from the database and sends it to the browser. The browser uses JSON.parse to turn the string back into an array of javascript objects. Then you can redraw the scene exactly as it was using the information in the objects.

Related

Is there a way to prevent a Shape for being transformed?

That is the thing, i'm creating a shape that covers all canvas to act like a white background, i have a transformer and im listening to canvas clicks, inside it im adding objects into transformer nodes, but i don't like to transform the white shape that im using as a background.
Canvas white Shape
Just don't add transformer into it. I see several solutions:
Set listening = false on that background shape. In that case, it will not trigger any mouse/touch/pointer events
Or set a special name for it in just ignore in click callback
const background = new Konva.Rect({
fill: 'white',
width: stage.width(),
height: stage.height(),
name: 'nonSelectable'
});
stage.on('click', (e) => {
// ignore such shape
if (e.target.hasName('nonSelectable')) {
return;
}
// else attach transformer
});

How can I learn pixels band values from image in screen of Earth Engine?

I want to learn pixels band values, for example when I clik on mNDWI image in screen of Earth Engine, I need learning values of red, green and blue
var geometry=ee.Geometry.Polygon([[38.877002459052335,40.75574968156597],
[41.206104021552335,41.17882292442983],
[40.645801287177335,41.59918091806734],
[40.052539568427335,41.84517989453356],
[39.569141130927335,41.886088143011904],
[38.800098162177335,41.48405920501165],
[38.877002459052335,40.75574968156597],
]);
var s2SR = ee.ImageCollection('COPERNICUS/S2_SR')
//filter start and end date
.filter(ee.Filter.calendarRange(2020,2020,'year'))
.filter(ee.Filter.calendarRange(8,8,'month'))
//filter according to drawn boundary
.filterBounds(geometry)
.filterMetadata('CLOUD_COVERAGE_ASSESSMENT', 'less_than',10);
//Map.addLayer(s2SR, {bands:['B4', 'B3', 'B2'], min:0, max:8000}, 's2SR');
// adding mNDWI function
var addMNDWI = function(image) {
var mndwi = ee.Image(image).normalizedDifference(['B3', 'B11']).rename('MNDWI');
return ee.Image(image).addBands(mndwi);
};
var mndwı=s2SR
.map(addMNDWI);
Map.addLayer(mndwı.first(), { min:245, max:5000}, 'mndwı');
It is simple to view the values for any displayed image. First, click on the “Inspector” tab in the top right pane of the Earth Engine Code Editor.
Then, click wherever you want on the map. The Inspector tab will display:
The coordinates of the location you clicked.
The values of every band of every image under that point. (When there are many, as a chart.)
The details of the image (or feature), including properties.

How to get the coordinates of the point that I click?

I am playing with <canvas> tag and just encountered a problem:
https://jsfiddle.net/awguo/6yLqa5hz/
I want to get the coordinates of a point when I click on a canvas.
I searched for a while and found some functions, but as a 300x300 canvas, the point of its right-bottom point is (300,150). Shouldn't it be 300,300 (because the img is 300x300 and the canvas is 100% on it)?
Why?
What should I do to get the 300x300?
You must adjust the event.clientX and event.clientY coordinates returned by your event handlers by the offset of the canvas element vs the window. To do that you can use canvas.getBoundingClientRect to get the left & top canvas offsets. Be sure to listen for resize & scroll events. When those events happen you must re-fetch the canvas's current offset.
// Fetch offsetX & offsetY variables that contain the
// canvas offset versus the window
// Re-fetch when the window is resized or scrolled
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
Here's how to use the offsets to calculate correct mouse coordinates in an event handler:
function handleMouseDown(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// calculate the mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// your stuff
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX,
y: evt.clientY
};
}
Is enough to work. You image is 350px big, not 300px.

Removing X-axis tiling of google maps

In a given instance of google maps...
Is there a way to turn off the other instances of the maps which tile to the right and left of the initial central map?
I've seen ways to restrict the pan ability of the map ... but it uses lat + lng to determine when to re-center the map ... and thus the usage falls apart at different zoom levels.
For example: https://google-developers.appspot.com/maps/documentation/javascript/examples/map-simple
If on zooms all the way out, you can see how the map of the globe is tiled along the x-axis.
I would like a single instance of the map.
You could add an observer to the zoom_changed event, and set the minZoom if more than 90 degrees of map is shown. The second statement recursively reduces the zoom to an acceptable limit before the limit is known.
var zoomObserver = function () {
width = Math.abs(map.getBounds().getNorthEast().lat() -
map.getBounds().getSouthWest().lat());
if (width > 90){
var opt = { minZoom: map.getZoom()};
map.setOptions(opt);
}
if (width > 179){
map.setZoom(map.getZoom() + 1);
}
};
google.maps.event.addListener(map,'zoom_changed', zoomObserver );
This simple solution works for most use cases, doesn't take into account doubling the width of the map through resizing the browser window, so you may need to add the observer to another event in that case.

Flex scrolling more than 10.000 pixels

The Flex Canvas container is limited to 10,000x10,000 pixels. Yet, I've seen Flex apps that are scrolling way more than 10,000 pixels. Any idea how this can be done?
The content I want to scroll is in pieces already, but I need to add those pieces to something that can scroll vertically more than 10,000 pixels.
Depending on what you actually want to display you may be able to split your content into tiles. This is how Google Maps works, every time the map is moved the program determines which tiles are visible on the screen and loads them in. Any markers or overlays that are on the map are notified that the map has moved and determine where their new location is. If their location is off the screen they can be removed from the canvas. For example, the width of all the tiles at zoom level 20 on Google Maps is (256 pixels * 2^20) which equals 268,435,456 total pixels.
Essentially you just need to create a special Sprite that keeps track of the actual x,y location it is supposed to be positioned at. Any time the container moves you simply iterate through all of the child objects and determine where to put them.
function onCanvasScroll() {
//determine the top left coordinates of the canvas
//you will figure out how to do this depending on how the scrolling window
//is implemented
var canvas_scroll_x;
var canvas_scroll_y;
//create a bounding box for the canvas
var view_bounds = new Rectangle(canvas_scroll_x, canvas_scroll_y, canvas.width, canvas.height);
for (child in canvas) {
var x = child.actual_x - view_bounds.x;
var y = child.actual_y - view_bounds.y;
var childBounds = new Rectangle(x, y, child.width, child.height);
//determine if the component is visible on screen
if (view_bounds.intersects(child_bounds)) {
child.visible = true;
child.x = x;
child.y = y;
}
else {
child.visible = false;
}
}
}
So if you have a canvas that is positioned at (100, 20000), a sprite that is positioned at (300, 20100), and a window that is (640,408), you would place it at (200, 100) and it would be visible on the screen.
Instead of just setting visible to true or false a better approach would be to remove the items from the canvas entirely when they are not within the bounds of the view.

Resources