How to check orientation based on css `transform: rotate(270deg)` in Cypress? - rotation

I need to check the orientation of a div with Cypress.
This is the test:
cy
.get('data-test="vertical"')
.should('have.css', 'transform', 'translate(-100%, 0) rotate(270deg)')
The div behaviour is standard in a scenario, or it's rotated with css transform: translate(-100%, 0) rotate(270deg); in the other scenario.
I need a way to check the orientation, but transform is difficult to check, because for example in Cypress test what happens is:
when I expect transform: rotateX(180deg) I actually receive transform: matrix3d(1, 0, 0, 0, 0, -1, 1.22465e-16, 0, 0, -1.22465e-16, -1, 0, 0, 0, 0, 1).
I need to find a smart way to detect the orientation. Suggestions? Tips?

Issue is solved like this, with the creation of a command:
const getTransformRotationAngle = (cssTransformMatrix, absoluteValue) => {
const cssTransformMatrixIndexes = cssTransformMatrix
.split('(')[1]
.split(')')[0]
.split(',')
const cssTransformScale = Math.sqrt(
cssTransformMatrixIndexes[0] * cssTransformMatrixIndexes[0] +
cssTransformMatrixIndexes[1] * cssTransformMatrixIndexes[1]
)
const cssTransformSin = cssTransformMatrixIndexes[1] / cssTransformScale
const cssTransformAngle = Math.round(
Math.asin(cssTransformSin) * (180 / Math.PI)
)
return absoluteValue ? Math.abs(cssTransformAngle) : cssTransformAngle
}
Cypress.Commands.add('getTransformRotationAngle', getTransformRotationAngle)
and its use in the test:
cy
.get('data-test="vertical"')
.invoke('css', 'transform')
.then(cssTransform => {
cy.getTransformRotationAngle(cssTransform, true).should(
'eq',
90 || 270
)
})

Related

P5.js: how to programatically calculate BURN blendMode between 2 colors?

I am doing some blendMode(BURN) to paint some shapes. And other shapes I need to painted directly in the resulting color of the previous shapes, so I need to generate the blended resulting color my self.
I am looking for something like:
let blendedColor = blendColorsBurn(color1, color2);
There's no built in function for this. In 2d mode p5.js leverages the canvas's globalCompositeOperation property, which when set to color-burn performs the following color blending operation:
Divides the inverted bottom layer by the top layer, and then inverts the result.
This sounds simple enough, but I wanted to verify it means what it sounds like it means, so I decided to try a quick test implementation. Press the shift and control keys to see the built in BURN blendMode vs my calculations respectively.
let existingContent;
let newContent;
let manualBurn;
let scaleFactor = 1;
function preload() {
existingContent = loadImage("https://www.paulwheeler.us/files/existing-content.png");
newContent = loadImage("https://www.paulwheeler.us/files/new-content.png");
}
function setup() {
createCanvas(windowWidth, windowHeight);
noLoop();
scaleFactor = height / newContent.height;
manualBurn = createImage(newContent.width, newContent.height);
// Divides the inverted bottom layer by the top layer, and then inverts the result.
for (let x = 0; x < newContent.width; x++) {
for (let y = 0; y < newContent.height; y++) {
const c1 = existingContent.get(x, y);
const c2 = newContent.get(x, y);
if (alpha(c2) > 0) {
let a = alpha(c1) / 255;
// Inverted bottom layer
let [inv_r, inv_g, inv_b, inv_a] = [
// Subtracting from alpha instead of 1 is to deal with pre-multiplied alpha
a - red(c1) / 255,
a - green(c1) / 255,
a - blue(c1) / 255,
1 - alpha(c1) / 255,
];
// divided by the top layer
let [div_r, div_g, div_b, div_a] = [
inv_r / (red(c2) / 255),
inv_g / (green(c2) / 255),
inv_b / (blue(c2) / 255),
inv_a / (alpha(c2) / 255),
];
// inverted
let out = [255 * (1 - div_r), 255 * (1 - div_g), 255 * (1 - div_b), max(alpha(c2), 255 * (1 - div_a))];
manualBurn.set(x, y, out);
} else {
manualBurn.set(x, y, c1);
}
}
}
manualBurn.updatePixels();
}
function keyPressed() {
redraw();
}
function keyReleased() {
redraw();
}
function draw() {
clear();
scale(scaleFactor);
if (keyIsDown(CONTROL)) {
blendMode(BLEND);
image(manualBurn, 0, 0);
} else {
image(
existingContent,
0,
0
);
if (keyIsDown(SHIFT)) {
blendMode(BURN);
image(newContent, 0, 0);
}
}
}
html, body {
margin: 0;
padding: 0;
}
body {
background-image: url("https://www.paulwheeler.us/files/checkerboard.jpeg");
background-repeat: repeat;
background-size: 200px;
}
canvas {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.0/p5.js"></script>
There were definitely some gotchas:
It may be obvious but this math has to be done with values from 0 to 1, not 0 to 255.
colorMode(RGB, 1, 1, 1, 1) does not work because the red(), green(), and blue() function round to integers 🤦‍♂️
When inverting a color, you need to start from the alpha value rather than 1, because of premultiplied alpha.
It's not obvious from the description, but it would appear that the burn images alpha takes precedence.

Three.js, unexpected position shift when scaling object

I'm trying to create a zoom box, so far I managed to translate the cursor positions from locale to world coordinates and create a box object around the cursor with the right uvs.
Here is the fiddle of my attempt : https://jsfiddle.net/2ynfedvk/2/
Without scaling the box is perfectly centered around the cursor, but if you toggle the scaling checkbox to set the scale zoomMesh.scale.set(1.5, 1.5, 1), the box position shift the further you move the cursor from the scene center.
Am I messing any CSS like "transform origin" for three.js to center the scale around the object, is this the right approach the get this kind of zoom effect ?
I'm new to three.js and 3d in general, so thanks for any help.
When you scale your mesh with 1.5, it means that apply transform matrix that scales values of vertices.
The issue comes from changing of vertices. Vertices are in local space of the mesh. And when you set the left-top vertex of the square, for example, to [10, 10, 0] and then apply .scale.set(1.5, 1.5, 1) to the mesh, then the coordinate of vertex became [15, 15, 0]. The same to all the other 3 vertices. And that's why the center of the square does not match at 1.5 times from the center of the picture to mouse pointer.
So, an option is not to scale a mesh, but change the size of the square.
I changed your fiddle a bit, so maybe it will be more explanatory:
const
[width, height] = [500, 300],
canvas = document.querySelector('canvas'),
scaleCheckBox = document.querySelector('input')
;
console.log(scaleCheckBox)
canvas.width = width;
canvas.height = height;
const
scene = new THREE.Scene(),
renderer = new THREE.WebGLRenderer({canvas}),
camDistance = 5,
camFov = (2 * Math.atan( height / ( 2 * camDistance ) ) * ( 180 / Math.PI )),
camera = new THREE.PerspectiveCamera(camFov, width/height, 0.1, 1000 )
;
camera.position.z = camDistance;
const
texture = new THREE.TextureLoader().load( "https://picsum.photos/500/300" ),
imageMaterial = new THREE.MeshBasicMaterial( { map: texture , side : 0 } )
;
texture.minFilter = THREE.LinearFilter;
texture.magFilter = THREE.LinearFilter;
texture.format = THREE.RGBFormat;
const
planeGeometry = new THREE.PlaneGeometry( width, height ),
planeMesh = new THREE.Mesh( planeGeometry, imageMaterial )
;
const
zoomGeometry = new THREE.BufferGeometry(),
zoomMaterial = new THREE.MeshBasicMaterial( { map: texture , side : 0 } ),
zoomMesh = new THREE.Mesh( zoomGeometry, zoomMaterial )
;
zoomMaterial.color.set(0xff0000);
zoomGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([
0, 0, 0,
0, 0, 0,
0, 0, 0,
0, 0, 0
]), 3));
zoomGeometry.setIndex([
0, 1, 2,
2, 1, 3
]);
scene.add( planeMesh );
scene.add( zoomMesh );
var zoom = 1.;
function setZoomBox(e){
const
size = 50 * zoom,
x = e.clientX - (size/2),
y = -(e.clientY - height) - (size/2),
coords = [
x,
y,
x + size,
y + size
]
;
const [x1, y1, x2, y2] = [
coords[0] - (width/2),
coords[1] - (height/2),
coords[2] - (width/2),
coords[3] - (height/2)
];
zoomGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array([
x1, y1, 0,
x2, y1, 0,
x1, y2, 0,
x2, y2, 0
]), 3));
const [u1, v1, u2, v2] = [
coords[0]/width,
coords[1]/height,
coords[2]/width,
coords[3]/height
]
zoomGeometry.setAttribute('uv',
new THREE.BufferAttribute(new Float32Array([
u1, v1,
u2, v1,
u1, v2,
u2, v2,
u1, v1,
u1, v2
]), 2));
}
function setScale(e){
//zoomMesh.scale.set(...(scaleCheckBox.checked ? [1.5, 1.5, 1] : [1, 1, 1]));
zoom = scaleCheckBox.checked ? 1.5 : 1 ;
}
function render(){
renderer.render(scene, camera);
requestAnimationFrame(render);
}
render();
canvas.addEventListener('mousemove', setZoomBox);
scaleCheckBox.addEventListener('change', setScale);
html, body {
margin: 0;
height: 100%;
}
body{
background: #333;
color: #FFF;
font: bold 16px arial;
}
canvas{
}
<script src="https://threejs.org/build/three.min.js"></script>
<canvas></canvas>
<div>Toggle scale <input type="checkbox" /></div>
thanks for the answer, not quite what I was looking for (not only resize the square but also zoom in the image), but you pointed me in the right direction.
Like you said the positions coordinate are shifting with the scale, so I have to recalculate the new position relative to the scale.
Added these new lines, with new scale and offset variables :
if(scaleCheckBox.checked){
const offset = scale - 1;
zoomMesh.position.set(
-(x1 * offset) - (size*scale)/2) -(size/2),
-((y1 * offset) + (size*scale)/2) -(size/2)),
0
);
}
Here is the working fiddle : https://jsfiddle.net/dc9f5v0m/
It's a bit messy, with a lot of recalculation (Especially to center the cursor around the square), but it gets the job done and the zoom effect can be achieved with any shape not only a square.
Thanks again for your help.

How to rotate a sprite around a fixed point so it follows cursor

I'm developing a small minigolf game, where the user can shoot moving the cursor around to set an angle, and the force applied will be the length of an arrow (less force when the cursor is closer to the ball). You can check exactly how it works here: https://imgur.com/a/AQ1pi
I have figured out how to rotate the arrow sprite to follow the cursor but I don't know yet how to make it move around the ball, right now it's just rotating in its anchor, in this case the head of the arrow.
I'm using Panda.js (a Pixi.js based framework) to develop the game, but its API is similar to the native Canvas functions. I don't need an exact implementation (that's why I'm not posting any code), but I would like to get some ideas about how to rotate the sprite around a point in a given radius. In this case, the point would be the center of the ball, and the radius will be the ball radius. Thanks!
You set the point of rotation with ctx.translate or ctx.setTransform then apply the rotation with ctx.rotate(ang); Then draw the image offset so that the point of rotation is at (0,0). Ie if you want the point of rotation to be at image coordinates (100,50) then render at ctx.drawImage(image,-100,-50);
To get the angle from a point to the mouse use Math.atan2
requestAnimationFrame(update);
// draws rotated image at x,y.
// cx, cy is the image coords you want it to rotate around
function drawSprite(image, x, y, cx, cy, rotate) {
ctx.setTransform(1, 0, 0, 1, x, y);
ctx.rotate(rotate);
ctx.drawImage(image, -cx, -cy);
ctx.setTransform(1, 0, 0, 1, 0, 0); // restore defaults
}
// function gets the direction from point to mouse and draws an image
// rotated to point at the mouse
function rotateAroundPoint(x, y, mouse) {
const dx = mouse.x - x;
const dy = mouse.y - y;
const dir = Math.atan2(dy, dx);
drawSprite(arrow, x, y, 144, 64, dir);
}
// Main animation loop.
function update(timer) {
globalTime = timer;
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0, 0, w, h);
strokeCircle(150, 75, 10);
rotateAroundPoint(150, 75, mouse);
requestAnimationFrame(update);
}
//=====================================================
// All the rest is unrelated to the answer.
const ctx = canvas.getContext("2d");
const mouse = { x: 0, y: 0, button: false };
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
function mouseEvents(e) {
mouse.bounds = canvas.getBoundingClientRect();
mouse.x = e.pageX - mouse.bounds.left - scrollX;
mouse.y = e.pageY - mouse.bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
const CImage = (w = 128, h = w) => (c = document.createElement("canvas"), c.width = w, c.height = h, c);
const CImageCtx = (w = 128, h = w) => (c = CImage(w, h), c.ctx = c.getContext("2d"), c);
const drawPath = (ctx, p) => {var i = 0;while (i < p.length) {ctx.lineTo(p[i++], p[i++])}};
const strokeCircle = (l,y=ctx,r=ctx,c=ctx) =>{if(l.p1){c=y; r=leng(l);y=l.p1.y;l=l.p1.x }else if(l.x){c=r;r=y;y=l.y;l=l.x}c.beginPath(); c.arc(l,y,r,0,Math.PI*2); c.stroke()};
const aW = 10;
const aH = 20;
const ind = 5;
const arrow = CImageCtx();
arrow.ctx.beginPath();
drawPath(arrow.ctx, [
ind, 64 - aW,
128 - ind - aH, 64 - aW,
128 - ind - aH, 64 - aH,
128 - ind, 64,
128 - ind - aH, 64 + aH,
128 - ind - aH, 64 + aW,
ind, 64 + aW,
]);
arrow.ctx.fillStyle = "red";
arrow.ctx.fill();
ctx.strokeStyle = "black";
ctx.lineWidth = 2;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
canvas {
border: 2px solid black;
}
<canvas id="canvas"></canvas>

Rotate openlayers fill pattern

I want to rotate line pattern fill inside openlayers feature. The rotation works well when testing outside openlayers, but when try to use it as feature fill color, rotation doesn't work.
p = ctx.createPattern(canvasPattern, 'repeat');
//the following code has no effect on p in openlayers, while it works independent on canvas
ctx.fillStyle = p;
ctx.translate(cw / 2, ch / 2);
ctx.rotate(0.5 * Math.PI / 4); // the most important!
ctx.fillRect(-cw, -ch, cw * 2, ch * 2);
ctx.setTransform(1, 0, 0, 1, 0, 0);
featurePoly.setStyle(new ol.style.Style({
fill: new ol.style.Fill({
color: p
})
}));
https://jsfiddle.net/nt6ae1Ld/23/

Why would TextOut() be using a different coordinate system than AlphaBlend()?

I'm trying to write a text overlay function that generates a semitransparent background with text on it in the top right hand corner of the viewport. I wrote a test MFC application project with mostly default settings (I don't remember exactly, but AFAIK, none of the settings should cause the problems I'm seeing).
Here is the code:
void DrawSemitransparentRect(CDC& destDC, CRect rect, float percentGrayBackground, COLORREF overlayColour, float overlayPercentOpaque)
{
rect.NormalizeRect();
CDC temp_dc; // Temp dc for semitransparent text background
temp_dc.CreateCompatibleDC(&destDC);
CBitmap layer; // Layer for semitransparent text background
layer.CreateCompatibleBitmap(&destDC, 1, 1);
CBitmap* pOldBitmap = temp_dc.SelectObject(&layer);
BLENDFUNCTION blendFunction = { AC_SRC_OVER, 0, 0, 0 };
auto DrawSemitransparentRectHelper = [&](COLORREF colour, float transparency)
{
temp_dc.SetPixel(0, 0, colour);
blendFunction.SourceConstantAlpha = BYTE(transparency * 255 / 100);
// Draw semitransparent background
VERIFY(destDC.AlphaBlend(rect.left, rect.top, rect.Width(), rect.Height()
, &temp_dc, 0, 0, 1, 1, blendFunction));
};
// Lighten up the area to make more opaque without changing overlay colour.
DrawSemitransparentRectHelper(RGB(255, 255, 255), percentGrayBackground);
// Draw overlay colour
DrawSemitransparentRectHelper(overlayColour, overlayPercentOpaque);
temp_dc.SelectObject(pOldBitmap);
}
void DrawOverlayText(CDC & dc, CFont &windowFont, CRect const& windowRectDP, CString const& overlayText, CRect* pBoundingRectDP)
{
static bool debug = true;
int savedDC = dc.SaveDC();
::SetMapMode(dc.GetSafeHdc(), MM_TWIPS);
// Reset the window and viewport origins to (0, 0).
CPoint windowOrg, viewportOrg;
::SetWindowOrgEx(dc.GetSafeHdc(), 0, 0, &windowOrg);
::SetViewportOrgEx(dc.GetSafeHdc(), 0, 0, &viewportOrg);
LOGFONT logFont;// = { 12 * 10, 0, 0, 0, 100, 0, 0, 0, 0, 0, 0, 0, 255, _T("Times New Roman") };
windowFont.GetLogFont(&logFont);
logFont.lfHeight = 12 * 10; // 12 point font? Why isn't this *20? TWIPS are 20ths of a point.
// Font for the overlay text
CFont font;
font.CreatePointFontIndirect(&logFont, &dc);
CFont* pOldFont = dc.SelectObject(&font);
// window rect in Logical Points
CRect windowRect(windowRectDP);
dc.DPtoLP(windowRect);
// Get text extent in Logical Points
CRect textRect;
dc.DrawText(overlayText, textRect, DT_CALCRECT);
// inflation rectangle to add pixels around text
CRect inflate(8, 0, 8, 4);
dc.DPtoLP(&inflate);
// Create the bounding rect on the right hand of the view, making it a few pixels wider.
CRect boundingRect(textRect);
if (!debug)
{
boundingRect.InflateRect(inflate);
}
boundingRect.NormalizeRect();
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
CRect boundingRectDP(boundingRect);
if (pBoundingRectDP || !debug)
{
// Get the bounding rect in device coordinates
dc.LPtoDP(boundingRectDP);
*pBoundingRectDP = boundingRectDP;
}
if (!debug)
{
// round the bottom corners of the text box by clipping it
CRgn clip;
boundingRectDP.NormalizeRect();
clip.CreateRoundRectRgn(
boundingRectDP.left + 1 // +1 needed to make rounding coner match more closely to bottom right coner
, boundingRectDP.top - boundingRectDP.Height() // Getting rid of top rounded corners
, boundingRectDP.right
, boundingRectDP.bottom + 1
, 16, 16 // rounding corner may have to be more dynamic for different DPI screens
);
::SelectClipRgn(dc.GetSafeHdc(), (HRGN)clip.GetSafeHandle());
clip.DeleteObject();
}
// Calculatte centre position of text
CPoint centrePos(
boundingRect.left + (boundingRect.Width() - textRect.Width()) / 2 + 1
, boundingRect.top + (boundingRect.Height() - textRect.Height()) / 2 + 1);
if (debug)
{
// in debug mode, output text and then put semitransparent bounding rect over it.
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetBkColor(RGB(255, 0, 0));
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
}
else
{
// 2 pixel offset in Logical Points
CPoint textShadowOffset(2, 2);
dc.DPtoLP(&textShadowOffset);
// in !debug mode, output semitransparent bounding rect and then put text over it.
DrawSemitransparentRect(dc, boundingRect, 60, RGB(0, .25 * 255, .75 * 255), 40);
dc.SetBkMode(debug ? OPAQUE : TRANSPARENT);
dc.SetTextColor(RGB(0, 0, 0));
dc.TextOut(centrePos.x, centrePos.y, overlayText);
dc.SetTextColor(RGB(255, 255, 255));
dc.TextOut(centrePos.x - textShadowOffset.x, centrePos.y - textShadowOffset.y, overlayText);
}
// Restore DC's state
dc.SelectObject(pOldFont);
dc.RestoreDC(savedDC);
}
// OnPaint() function for CView derived class.
void COverlayOnCViewView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, &dc);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(dc, windowFont, windowRect, m_overlayText, nullptr);
}
Now, this works perfectly well in the default project, where I get the following:
But when I put it into another preexisting project, I get this:
You can see that the text is actually positioned above the translucent rectangle.
If I move the rectangle down the height of the text box, by changing
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), 0);
to
boundingRect += CPoint(windowRect.Width() - boundingRect.Width(), textRect.Height());
I get:
It's like the text function is specifying the bottom left corner rather than the top left corner for placement.
I wrote the free functions so that it should work with any DC, even if that DC has had its coordinate system manipulated, but perhaps I've forgotten to reset something?
The default project is using MFC 14.0.24212.0, but the project I tried to import this code into is using MFC 12.0.21005.1. Could that be an issue? I'm not sure how to change the default project to use the earlier version of MFC to test that.
Edit
Note that in the default project, I could have put the code into the OnDraw() function like this:
void COverlayOnCViewView::OnDraw(CDC* pDC)
{
COverlayOnCViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: add draw code for native data here
CString m_overlayText = _T("abcdefg ABCDEFG");
CFont windowFont;
LOGFONT logFont = { -12, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, 0, 0, CLEARTYPE_QUALITY, 0, _T("Segoe UI") };
windowFont.CreatePointFontIndirect(&logFont, pDC);
CRect windowRect;
GetClientRect(windowRect);
DrawOverlayText(*pDC, windowFont, windowRect, m_overlayText, nullptr);
}
The only reason why I didn't was because the application I'm putting this into doesn't have one and I wanted to mimic that project as closely as possible. If you create a default application to test this, remember either to put the ON_WM_PAINT() macro in the MESSAGE MAP or use the OnDraw() function shown instead. They both seem to have the same results in the default project.

Resources