I am writing an Ionic 3 application to take small pictures.
I am using the Camera Preview plugin to do this:
Then I get the coordinates of the green box using this:
<div id="mask" style="width:100%;height:100px;border:10px solid rgb(125, 255, 0);" *ngIf="!picture"> </div>
var box = document.getElementById("mask");
var rect = box.getBoundingClientRect();
console.log("MASK: "+rect.left+" "+rect.top+" "+rect.width+" "+rect.height)
Then I crop the image using the coordinates I got from the box to get this:
I am using the following code to crop the image:
generateFromImage(img, x, y, w, h, quality: number = 1, callback) {
var canvas: any = document.createElement("canvas");
var image = new Image();
image.src = img;
image.onload = () => {
canvas.width = w;
canvas.height = h;
var ctx = canvas.getContext("2d");
ctx.drawImage(image, x+this.offsetX, y+this.offsetY, w*this.scaleX, h*this.scaleY, 0, 0, w, h);
var dataUrl = canvas.toDataURL('image/jpeg', quality);
callback(dataUrl)
}
}
Where x,y,w,h are the coordinates I got from box.getBoundingClientRect().
As you can see, I had to introduce an offsetX, offsetY, scaleX, scaleY to adjust the coordinates because it was not working.
drawImage() parameters are sx,sy,sw,sh (source box -> coordinates from the original image) and x,y,w,h (destination -> coordinate of the destination image). I do not understand why it is off.
After some trial and error, I found that the following configuration works for my iPhone 8:
offsetX=125
offsetY=48
scaleX=1.7
scaleY=1.4
The Y coordinate difference I suspect that has something to do with the application toolbar. I have no idea why x is off and why using the same w and h on both source and destination does not keep the aspect ratio.
I did not test on another devices but it will most certainly fail because the offsets and scale factors will be different.
Why is it happening? Why do I need to fix the coordinates?
Thanks for your help!
Your main problem is probably that you don't take the CSS scaling into account.
The coordinates we use in drawImage are relative to the image's natural size (i.e, the one of the media, not of the <img>). This means that you can't come from the rendered transform matrix (getBoundingClientRect) to the media's one directly.
You need to first determine the scale that has been applied by CSS, so you can transform your screen space coordinates to the media's space.
var img_bbox = img.getBoundingClientRect();
// the ratio by which our image has been scaled by CSS
var scale_x = img_bbox.width / img.naturalWidth;
var scale_y = img_bbox.height / img.naturalHeight;
// ...then
ctx.drawImage(img,
rect.left / scale_x,
rect.top / scale_y,
rect.width/ scale_x,
rect.height / scale_y,
0,
0,
rect.width,
rect.height
);
Now, you will also have to take into account the difference between the position of your rectangle and the one of your <img>, so e.g x will actually be (rect_bbox.left - img_bbox.left) / scale_x.
One thing you didn't cleared up though, is if you wanted to crop inside, or outside the border, i.e should the border itself be part of the cropped area.
Here is an example which does take the border area too.
onload = crop;
function crop() {
// get the rendered bounding box of our elements
var r_bbox = rect.getBoundingClientRect();
var img_bbox = img.getBoundingClientRect();
// the ratio by which our image has been scaled by CSS
var scale_x = img_bbox.width / img.naturalWidth;
var scale_y = img_bbox.height / img.naturalHeight;
// our output coords
var output = {
x: r_bbox.left - img_bbox.left,
y: r_bbox.top - img_bbox.top,
w: r_bbox.width,
h: r_bbox.height
};
var ctx = canvas.getContext('2d');
canvas.width = output.w;
canvas.height = output.h;
ctx.drawImage(img,
// source
// we need to scale all coords by the CSS scaling
output.x / scale_x,
output.y / scale_y,
output.w / scale_x,
output.h / scale_y,
// destination, to rendered space, no scaling
0,
0,
output.w,
output.h
);
}
img{
width: 200px;
height: 200px;
}
#rect{
position:absolute;
left: 42px;
top: 50px;
width: 100px;
height: 50px;
z-index: 2;
border: 5px solid rgba(0,255,0,.5);
}
*{
vertical-align: top;
}
<img id="img" src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg">
<div id="rect"></div>
<canvas id="canvas"></canvas>
And one which takes only what is inside the border-area
onload = crop;
function crop() {
// get the rendered bounding box of our elements
var r_bbox = rect.getBoundingClientRect();
var img_bbox = img.getBoundingClientRect();
// the ratio by which our image has been scaled by CSS
var scale_x = img_bbox.width / img.naturalWidth;
var scale_y = img_bbox.height / img.naturalHeight;
// our output coords
var output = {
// rect.clientLeft is the size of the left border
// so add it to 'x'
x: r_bbox.left - img_bbox.left + rect.clientLeft,
// same as for 'x'
y: r_bbox.top - img_bbox.top + rect.clientTop,
// size of padding box
w: rect.clientWidth,
h: rect.clientHeight
};
var ctx = canvas.getContext('2d');
canvas.width = output.w;
canvas.height = output.h;
ctx.drawImage(img,
output.x / scale_x,
output.y / scale_y,
output.w / scale_x,
output.h / scale_y,
0,
0,
output.w,
output.h
);
}
/* scale our image through CSS */
img{
width: 200px;
height: 200px;
}
#rect{
position:absolute;
left: 42px;
top: 50px;
width: 100px;
height: 50px;
z-index: 2;
border: 5px solid rgba(255,0,0,.5);
background: rgba(0,255,0,.5);
}
*{
vertical-align: top;
}
<img id="img" src="https://upload.wikimedia.org/wikipedia/commons/5/55/John_William_Waterhouse_A_Mermaid.jpg">
<div id="rect"></div>
<canvas id="canvas"></canvas>
Related
It looks to me like labels are meant to be anchored to objects in the map. In this case, I want to display some text on top of the map all of time.
I want it to be in the webgl itself, not an html element on top of it.
There are probably a million different ways to better accomplish your REAL objective, if you'd only state what that is, but anyway, if you're really determined to do what your question states...
Make an image with your text.
Base64 Encode that image.
Initialize the map with preserveDrawingBuffer: true (note, performance will suffer)
Use the following code. Replace the long base64 string with YOUR image. Replace the token with YOUR token.
// TO MAKE THE MAP APPEAR YOU MUST
// ADD YOUR ACCESS TOKEN FROM
// https://account.mapbox.com
mapboxgl.accessToken = 'REPLACEME';
const map = new mapboxgl.Map({
container: 'map', // container ID
// Choose from Mapbox's core styles, or make your own style with Mapbox Studio
style: 'mapbox://styles/mapbox/streets-v11', // style URL
center: [-74.5, 40], // starting position [lng, lat]
zoom: 9, // starting zoom
projection: 'globe', // display the map as a 3D globe,
preserveDrawingBuffer: true
});
map.on('render', () => {
var img, tex, vloc, tloc, vertexBuff, texBuff;
var cvs3d = map.getCanvas();
var ctx3d = cvs3d.getContext("experimental-webgl");
var uLoc;
ctx3d.pixelStorei(ctx3d.UNPACK_FLIP_Y_WEBGL, true);
// create shaders
var vertexShaderSrc = `
attribute vec2 aVertex;
attribute vec2 aUV;
varying vec2 vTex;
uniform vec2 pos;
void main(void) {
gl_Position = vec4(aVertex + pos, 0.0, 1.0);
vTex = aUV;
}`;
var fragmentShaderSrc = `
precision highp float;
varying vec2 vTex;
uniform sampler2D sampler0;
void main(void){
gl_FragColor = texture2D(sampler0, vTex);
}`;
var vertShaderObj = ctx3d.createShader(ctx3d.VERTEX_SHADER);
var fragShaderObj = ctx3d.createShader(ctx3d.FRAGMENT_SHADER);
ctx3d.shaderSource(vertShaderObj, vertexShaderSrc);
ctx3d.shaderSource(fragShaderObj, fragmentShaderSrc);
ctx3d.compileShader(vertShaderObj);
ctx3d.compileShader(fragShaderObj);
var progObj = ctx3d.createProgram();
ctx3d.attachShader(progObj, vertShaderObj);
ctx3d.attachShader(progObj, fragShaderObj);
ctx3d.linkProgram(progObj);
ctx3d.useProgram(progObj);
ctx3d.viewport(0, 0, 64, 64);
vertexBuff = ctx3d.createBuffer();
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([-1, 1, -1, -1, 1, -1, 1, 1]), ctx3d.STATIC_DRAW);
texBuff = ctx3d.createBuffer();
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
ctx3d.bufferData(ctx3d.ARRAY_BUFFER, new Float32Array([0, 1, 0, 0, 1, 0, 1, 1]), ctx3d.STATIC_DRAW);
vloc = ctx3d.getAttribLocation(progObj, 'aVertex');
tloc = ctx3d.getAttribLocation(progObj, 'aUV');
uLoc = ctx3d.getUniformLocation(progObj, 'pos');
var drawImage = function(imgobj, x, y, w, h) {
tex = ctx3d.createTexture();
ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MIN_FILTER, ctx3d.NEAREST);
ctx3d.texParameteri(ctx3d.TEXTURE_2D, ctx3d.TEXTURE_MAG_FILTER, ctx3d.NEAREST);
ctx3d.texImage2D(ctx3d.TEXTURE_2D, 0, ctx3d.RGBA, ctx3d.RGBA, ctx3d.UNSIGNED_BYTE, imgobj);
ctx3d.enableVertexAttribArray(vloc);
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, vertexBuff);
ctx3d.vertexAttribPointer(vloc, 2, ctx3d.FLOAT, false, 0, 0);
ctx3d.enableVertexAttribArray(tloc);
ctx3d.bindBuffer(ctx3d.ARRAY_BUFFER, texBuff);
ctx3d.bindTexture(ctx3d.TEXTURE_2D, tex);
ctx3d.vertexAttribPointer(tloc, 2, ctx3d.FLOAT, false, 0, 0);
ctx3d.drawArrays(ctx3d.TRIANGLE_FAN, 0, 4);
};
img = new Image();
img.src = 'data:image/png;base64,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';
img.onload = function() {
drawImage(this, 0, 0, 64, 64);
var img2 = new Image();
img2.src = 'data:image/png;base64,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';
img2.onload = function() {
drawImage(img2, 64-16, 64-16, 16, 16); // draw in bottom right corner
}
};
});
Fiddle (you have to change map token in order to see it in action): https://jsfiddle.net/rygj51ca/1/
The above code will render an image of a smiley on the mapbox canvas, bottom left corner, every time the map renders.
If you want text in "the top right corner", then you don't want it in the map at all, you want it over the map.
In that case, the best way is just to use a different HTML element, styled however you like. Along these lines:
<div id="map" />
<div id="text-overlay" style="position: absolute; right: 0; top: 0;>Here's my text</div>
I am moving some code from Leaflet into Mapbox GL JS. I have a 2d animated canvas which is rendered correctly when viewed top-down (pitch 0), rotated (bearing whatever), but is skewed when the pitch isn't zero. I have spent the past few days trying to correct it without much success.
My code needs to be able to convert between screen dimensions and map coordinates. E.g. for a given pixel, what lat-lon results (with map.project([lon, lat]) ). Or for a lat-lon, what pixel does it correspond to (with map.unproject([x,y]) ). When the canvas is rendered with pitch 0, the latitudes are equally spaced (*enough) across the pixels and everything works as desired. The conversions occur correctly.
When pitch is > 0, the coordinate conversions appear to work as expected as well. However, I think Mapbox GL JS takes the canvas, and turns it into a WebGL texture. This texture is then somehow scaled/rotated which turns the previous rectangle canvas into a trapezoid.
I need to be able to include this trapezoid scaling or account for this distortion in the canvas calculations.
Rough Sample
The snippet below is an attempt at replicating what is going on with a far simpler codebase. It's based on https://docs.mapbox.com/mapbox-gl-js/example/canvas-source/. You'll see the circles moving around, turning into ovals at different points and stretching--they should remain circles. This is partly caused by the map.unproject() calls using the screen dimensions, which are used to determine the map bounds. In 2d space, this works well because the screen bounds are identical to the camera view. But I think in 3d space, the screen dimensions are distorted by the camera view, and so the relation is lost between screen--camera--map which causes the skew.
My goal is to either correct the skew, or somehow use the camera properties to create the bounds (and project/unproject), OR there's another way to solve this. Essentially I want what is on the canvas at particular coordinates to stay there and not moved when pitched.
Thank you!
const PITCH = 45; // Change to 0 to see uniform shapes
// Based on https://docs.mapbox.com/mapbox-gl-js/example/canvas-source/
mapboxgl.accessToken = 'pk.eyJ1IjoiYnVzaGZpcmVpbyIsImEiOiJjazU3NGwzNGEwM2QxM2VwcWJ0djk1amFyIn0.DP3giqn33iswMWzlclr9jg';
var map = new mapboxgl.Map({
container: 'map',
zoom: 5,
minZoom: 4,
center: [95.899147, 18.088694],
pitch: PITCH,
style: 'mapbox://styles/mapbox/streets-v11'
});
//Animation from https://javascript.tutorials24x7.com/blog/how-to-draw-animated-circles-in-html5-canvas
var canvas = document.getElementById('canvasID');
var ctx = canvas.getContext('2d');
var circles = [];
var radius = 20;
function Circle(x, y, dx, dy, radius, color) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.radius = radius;
this.draw = function () {
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2, false);
ctx.strokeStyle = color;
ctx.stroke();
};
this.update = function () {
if (this.x + this.radius > 400 || this.x - this.radius < 0) {
this.dx = -this.dx;
}
if (this.y + this.radius > 400 || this.y - this.radius < 0) {
this.dy = -this.dy;
}
this.x += this.dx;
this.y += this.dy;
this.draw();
};
}
for (var i = 0; i < 5; i++) {
var color =
'#' +
(0x1000000 + Math.random() * 0xffffff).toString(16).substr(1, 6);
var x = Math.random() * (400 - radius * 2) + radius;
var y = Math.random() * (400 - radius * 2) + radius;
var dx = (Math.random() - 0.5) * 2;
var dy = (Math.random() - 0.5) * 2;
circles.push(new Circle(x, y, dx, dy, radius, color));
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, 400, 400);
for (var r = 0; r < 5; r++) {
circles[r].update();
}
}
animate();
let size = this.map.getContainer();
const nw = map.unproject([0, 0]);
const ne = map.unproject([size.clientWidth, 0]);
const se = map.unproject([size.clientWidth, size.clientHeight]);
const sw = map.unproject([0, size.clientHeight]);
map.on('load', function () {
map.addSource('canvas-source', {
type: 'canvas',
canvas: 'canvasID',
coordinates: [
[nw.lng, nw.lat],
[ne.lng, ne.lat],
[se.lng, se.lat],
[sw.lng, sw.lat],
],
animate: true
});
map.addLayer({
id: 'canvas-layer',
type: 'raster',
source: 'canvas-source'
});
});
body { margin: 0; padding: 0; }
#map { position: absolute; top: 0; bottom: 0; width: 100%; }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Canvas Demo</title>
<meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
<link href="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.css" rel="stylesheet">
<script src="https://api.mapbox.com/mapbox-gl-js/v2.3.1/mapbox-gl.js"></script>
</head>
<body>
<canvas id="canvasID" width="400" height="400">Canvas not supported</canvas>
<div id="map"></div>
</body>
</html>
Im trying to draw a gantt figure by the function below, but seems like the official documentations and examples didn't give the way to add border radius to those kind of rect, is there anyone know how to deal with it ?
function renderItem(params, api) {
var categoryIndex = api.value(0);
var start = api.coord([api.value(1), categoryIndex]);
var end = api.coord([api.value(2), categoryIndex]);
var height = api.size([0, 1])[1] * 0.6;
var rectShape = echarts.graphic.clipRectByRect({
x: start[0],
y: start[1] - height / 2,
width: end[0] - start[0],
height: height
}, {
x: params.coordSys.x,
y: params.coordSys.y,
width: params.coordSys.width,
height: params.coordSys.height
});
return rectShape && {
type: 'rect',
transition: ['shape'],
shape: rectShape,
style: api.style()
};
They do provide an approach for custom shape to add border radius, it can be find in the documentation. it is shape{ r: 10}
I am using the two classes two flip vertically and horizontally. Whichever one I do first works fine. However, If for example I flip vertically it works fine, but if I then try to flip horizontally it doesn't flip. Any ideas why this is?
.flippedhorizontal {
-moz-transform: scaleX(-1);
-o-transform: scaleX(-1);
-webkit-transform: scaleX(-1);
transform: scaleX(-1);
filter: FlipH;
-ms-filter: "FlipH";
}
.flippedvertical {
-moz-transform: scaleY(-1);
-o-transform: scaleY(-1);
-webkit-transform: scaleY(-1);
transform: scaleY(-1);
filter: FlipV;
-ms-filter: "FlipV";
}
Check your dev toolbar to see which one of the declarations is used, the others will be shown crossed out. By adding the second class, you are asking it to either keep or replace the current declaration. In this case, the default behavior seems to be keeping the original value.
I have omitted prefixes for brevity, here is what I think you want:
// START WITH HORIZONTAL
transform: scaleX(-1);
// ADD VERTICAL
transform: scaleX(-1) scaleY(-1);
and the reverse:
// START WITH VERTICAL
transform: scaleY(-1);
// ADD HORIZONTAL
transform: scaleY(-1) scaleX(-1);
// putting X first ^here would be same in this case... *I think...
But to make it easy, lets put in the implicit transforms:
// START WITH IDENTITY TRANSFORM
transform: scaleX(1) scaleY(1)
// ADD HORIZONTAL
transform: scaleX(-1) scaleY(1);
// ADD VERTICAL
transform: scaleX(-1) scaleY(-1);
To solve this, you will need to control the transform itself, rather than the classes. This is relatively simple:
var el, scaleX, scaleY;
function setTransform (element, scaleXArg, scaleYArg) {
var scaleString = ("scaleX(" + scaleXArg + ") scaleY(" + scaleYArg + ")");
// now attach that variable to each prefixed style
element.style.webkitTransform = scaleString;
element.style.MozTransform = scaleString;
element.style.msTransform = scaleString;
element.style.OTransform = scaleString;
element.style.transform = scaleString;
}
el = document.getElementById("elementYouWantToUse");
scaleX = "1";
scaleY = "1";
// call setTransform to initialize
setTransform(el, scaleX, scaleY);
// to finish you can choose to call setTransform with new arguments:
setTransform(el, -1, 1);
setTransform(el, 1, -1);
setTransform(el, -1, -1);
setTransform(el, 1, 1);
The caveat is if you use the scaleX and scaleY variables, you can get away with only testing and changing one value in your event handler, rather than both as required when calling directly with values. Here is the logic for the simple test (replace scaleX with scaleY as needed):
if ( scaleX === "1" ) {
scaleX = "-1";
setTransform(el, scaleX, scaleY);
} else {
scaleX = "1";
setTransform(el, scaleX, scaleY);
}
An element can not have 2 different values for the same property. This is already in Thisiate answer, and it is absolutely correct.
In your case, transform can not have at the same time a scaleX and a scaleY, even if they come from 2 different classes.
The easiest way to solve your case is to create a new style for the combined classes (when the element has at the same time the 2 classes)
function flipX() {
$('#test').toggleClass("flipX");
}
function flipY() {
$('#test').toggleClass("flipY");
}
#test {
border: solid 1px blue;
font-size: 80px;
display: inline-block;
transition: all 1s;
}
.flipX {
transform: scaleX(-1);
}
.flipY {
transform: scaleY(-1);
}
.flipX.flipY {
transform: scale(-1, -1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" onclick="flipX()">flip X</input>
<input type="checkbox" onclick="flipY()">flip Y</input>
<div id="test">TEST</div>
Need a boolean, not some function bound to rect.on('mouseup')
lots of thanks
How to hit-test a rotated rect in KineticJS
A demo Fiddle: http://jsfiddle.net/m1erickson/wTYac/
The method:
Reverse-rotate the mouseXY coordinate by the amount that the rect has been rotated.
Hit-test the un-rotated mouseXY against an unrotated version of the rect.
Details:
First, get current info about the rect
// get current info about this rect
var cx=this.getX();
var cy=this.getY();
var offX=this.getOffsetX();
var offY=this.getOffsetY();
var w=this.getWidth();
var h=this.getHeight();
Then calculate the current angle of the mouseXY versus the rect's point-of-rotation.
Note: the assumed point-of-rotation is the rect's centerpoint.
// calc the xy's angle versus the rect's centerpoint
var dx=x-cx;
var dy=y-cy;
var rotatedRadians=Math.atan2(dy,dx);
Then "unrotate" the mouseXY by the amount of the rect's rotation:
// un-rotate the x,y by the amount of the rect's rotation
var lengthToXY=Math.sqrt(dx*dx+dy*dy);
var unrotatedRadians=rotatedRadians-this.getRotation();
var unX=cx+lengthToXY*Math.cos(unrotatedRadians);
var unY=cy+lengthToXY*Math.sin(unrotatedRadians);
Finally, hit-test the unrotated mouseXY versus the rect's rotation point:
// unX/unY are now in unrotated space
// so just hittest against the unrotated rect
var xx=this.getX()-offX;
var yy=this.getY()-offY;
var hit=(unX>=xx && unX<=xx+w && unY>=yy && unY<=yy+h);
return(hit);
The code below adds a boolean rect.hit property to a Kinetic.Rect
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/wTYac/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Prototype</title>
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.5.5.min.js"></script>
<style>
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:300px;
height:300px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 300,
height: 300
});
var layer = new Kinetic.Layer();
stage.add(layer);
enableStageClick();
function enableStageClick(){
$(stage.getContent()).on('click', function (event) {
var pos=stage.getMousePosition();
var mouseX=parseInt(pos.x);
var mouseY=parseInt(pos.y);
var hit=rect1.hit(mouseX,mouseY)?"Clicked in rect":"Clicked outside rect";
$("#hit").text(hit);
});
}
var rect1=makeRect(150,150,100,50,"skyblue","lightgray",3);
function makeRect(x,y,w,h,fill,stroke,linewidth){
var rect = new Kinetic.Rect({
x: x,
y: y,
width: w,
height: h,
offset:[w/2,h/2], // rotate from rect centerpoint
fill: fill,
stroke: stroke,
strokeWidth: linewidth,
draggable:true
});
// return true if x,y is in this rotated rect
rect.hit=function(x,y){
// get current info about this rect
var cx=this.getX();
var cy=this.getY();
var offX=this.getOffsetX();
var offY=this.getOffsetY();
var w=this.getWidth();
var h=this.getHeight();
// un-rotate this x,y
var dx=x-cx;
var dy=y-cy;
var rotatedRadians=Math.atan2(dy,dx);
// un-rotate the x,y by the amount of the rect's rotation
var lengthToXY=Math.sqrt(dx*dx+dy*dy);
var unrotatedRadians=rotatedRadians-this.getRotation();
var unX=cx+lengthToXY*Math.cos(unrotatedRadians);
var unY=cy+lengthToXY*Math.sin(unrotatedRadians);
// unX/unY are now in unrotated space
// so just hittest against the unrotated rect
var xx=this.getX()-offX;
var yy=this.getY()-offY;
var hit=(unX>=xx && unX<=xx+w && unY>=yy && unY<=yy+h);
return(hit);
};
layer.add(rect);
layer.draw();
return(rect);
}
$("#rotateBtn").click(function(){
rect1.rotate(30*Math.PI/180);
layer.draw();
});
}); // end $(function(){});
</script>
</head>
<body>
<p>Click to hit-test the rectangle</p>
<button id="rotateBtn">rotate</button>
<span id="hit"></span>
<div id="container"></div>
</body>
</html>