KonvaJS transform rotation around center on SVG - javascript

I create text on canvas using KonvaJS. And I can freeze the text using Transformer. While drawing, the origin remains in the center as I wish there is no problem here.
However, when I want to use the data with svg here, x andy come true without freezing. But when I do ice cream it freezes outside the center. Here
transform={{
rotation: 90,
originX: x + width / 2,
originY: y + height / 2,
}}
It works when I freeze using it. Because x and y values are not changing. However, while drawing on KonvJs, if I freeze, the values of xtation andy change together with the value of ratation.
The code between the shortened example.
import React, { Fragment, useEffect, useRef } from 'react'
import { Text, Transformer } from 'react-konva'
import { useStore } from './store'
export default function ({ id, text }) {
const { regions, selected, setRegions, setSelected } = useStore()
const { x, y, value, color, rotation, fontSize } = text
const TextRef = useRef()
const TransformRef = useRef()
const onDragEnd = ({ target }) => {
setRegions(
regions.map((item) => {
if (item.id === id) {
item.text = {
...item.text,
x: target.x(),
y: target.y(),
width: target.width(),
height: target.height(),
}
}
return item
})
)
}
const onTransformEnd = () => {
const node = TextRef.current
const width = node.width()
const height = node.height()
const x = node.x()
const y = node.y()
const rotation = node.rotation()
const originX = x + width / 1.3
const originY = y + height / 1.3
setRegions(
regions.map((item) => {
if (item.id === id) {
item.text = {
...item.text,
x, // <= it changes when this is rotation
y, // <= it changes when this is rotation
rotation,
originX,
originY,
}
}
return item
})
)
}
const isSelected = id === selected
useEffect(() => {
if (isSelected) {
TransformRef.current.setNode(TextRef.current)
TransformRef.current.getLayer().batchDraw()
}
}, [isSelected])
return (
<Fragment>
<Text
draggable
name="region"
x={x}
y={y}
text={value}
fill={color}
ref={TextRef}
rotation={rotation}
fontSize={fontSize}
onDragEnd={onDragEnd}
onTransformEnd={onTransformEnd}
/>
{isSelected && (
<Transformer
ref={TransformRef}
/>
)}
</Fragment>
)
}
It will be enough to work in the section I made by giving offset in the convention. The important example is that I can show with svg on the client side. React Native Svg
<G
transform={{
rotation: rotation,
originX: x + width / 2,
originY: y + height / 2,
}}>
<Text
x={x}
y={y}
fill={'#fff'}
fontSize={fontSize}
textAnchor="start"
>
{value}
</Text>
</G>

You may need to calculate the center of the shape, to position it in SVG correctly.
function getCenter(shape) {
return {
x:
shape.x +
(shape.width / 2) * Math.cos(shape.rotation) +
(shape.height / 2) * Math.sin(-shape.rotation),
y:
shape.y +
(shape.height / 2) * Math.cos(shape.rotation) +
(shape.width / 2) * Math.sin(shape.rotation)
};
}
function Show({ text, x, y, rotation, width, height }) {
const center = getCenter({
x,
y,
width,
height,
rotation: (rotation / 180) * Math.PI
});
return (
<svg width={window.innerWidth} height={window.innerHeight / 2}>
<text
y={height / 1.3}
fill="#000"
fontSize={50}
alignment-baseline="bottom"
transform={`translate(${x}, ${y}) rotate(${rotation})`}
>
{text}
</text>
{/* Origin Circl */}
<circle cx={center.x} cy={center.y} r={10} fill="red" />
</svg>
);
}
Demo: https://codesandbox.io/s/konva-and-svg-demo-jx7sj?file=/src/Playground.js:1704-2583

Related

infinite loop scrolling using JavaScript doesn't work correctly

I simply want to create infinitely moving object with javascript.
I integrate it with react just for exploration but the idea is to make it infinite
I have an issue that the result eventually is not quiet what I wanted to.
The issue is it goes correctly infinite to the left direction but not to the right, when moving to the right, it only moves 2 iteration, and then no more objects appearing
scroll --> is smooth scrolling factor
Here is the link to the code playground:
https://codesandbox.io/s/infinite-loop-scrolling-c2iq0c
here is my code:
import { useRef, useEffect } from "react";
import Physics from "./Physics";
import { useStateValue } from "./StateProvider";
export function App() {
return (
<>
<Boxes />
<Physics />
</>
);
}
const totalbox = 16;
export const Boxes = () => {
return (
<>
{Array(totalbox)
.fill(totalbox)
.map((num, i) => (
<BoxDetail key={i} keyindex={i} />
))}
</>
);
};
const BoxDetail = ({ keyindex }) => {
const [{ pos: scroll }] = useStateValue();
const boxRef = useRef();
//infinite
// total box * ( width + margin )
const margin = 50;
const width = 50;
const WHOLEWIDTH = totalbox * (margin + width);
function calcPos(_scroll, _position) {
const temp =
((_scroll + _position + margin + width + WHOLEWIDTH) % WHOLEWIDTH) - margin - width;
return temp;
}
useEffect(() => {
const boxPos = 100 * keyindex;
boxRef.current.style.left = `${calcPos(scroll, boxPos)}px`;
}, [scroll, calcPos]);
return (
<>
<div
ref={boxRef}
style={{
border: '1px solid red',
position: 'absolute',
color: 'blue',
margin: '0px',
width: '50px',
height: '50px',
}}
>
box {keyindex}
</div>
</>
);
};
the picture below shows -> going to right direction
the picture below shows -> going to left direction
Any idea where the problem is ?
The problem is in your method of wrapping around
You are using % WHOLEWIDTH, which is perfectly appropriate, for situations of scrolling one way. This is because, for example, if WHOLEWIDTH is 1500, then 1700 % WHOLEWIDTH is 200, and 30050 % WHOLEWIDTH is 50.
The problem is when you scroll in the other direction. -300 % WHOLEWIDTH will not automatically become 1200. You have a good mitigation, which is to add WHOLEWIDTH, before doing the % WHOLEWIDTH.
However that only deals with numbers in the range -1500 to 0. If the number goes more negative than -1500, the result will be negative, which is not what you want.
Solution
The simplest solution is to add not just one WHOLEWIDTH, but many. For example, try changing this line:
const temp =
((_scroll + _position + margin + width + WHOLEWIDTH) % WHOLEWIDTH) - margin - width;
to this:
const temp =
((_scroll + _position + margin + width + 10000*WHOLEWIDTH) % WHOLEWIDTH) - margin - width;
That will allow you to scroll the boxes to wrap round 10000 times in the negative direction and still have them all display.

How to get the correct pixel color on image [React Native]

I am working on an app which the app is intented to capture/load an image, then get the correct color from the touched coordinate on that image / camera view. (Similar to iOS's Swatches app)
The libraries I am using is react-native-camera, react-native-get-pixel-color and react-native-draggable .
There is some logic issue I'm stucked on currently....and I have researched from online and still lost in the jungle
Ratio of every devices is not same so I could not have hardcoded on the ratio, based on the document stated that the ratio is only available for android but not for iOS (default will be 4:3 for android if not defined any). So I'm thinking is it better to not to set it ? Rely on pictureSize better ? Or can without both ? Any advices is welcome...
The part which I'm quite confusing now is on the calculation part, I wanted to get the correct part of the image's color, despite different ratio , device's screen size and image size:
Live view - always get from the center
Still view - based on draggable and get the coordinates
renderCamView = () => {
if (this.state.isEnabled) {
return (
<Image
source={{ uri: this.state.liveImg.data.uri }}
style={styles.imgPreview}
/>
);
} else {
return (
<RNCamera
ref={(ref) => {
this.camera = ref;
console.log('isref');
}}
onCameraReady={()=> this.getCamData()}
style={{
flex: 1,
justifyContent: 'flex-start',
alignItems: 'center',
backgroundColor: colors.black
}}
type={RNCamera.Constants.Type.back}
flashMode={this.getFlashMode()}
exposure={this.state.camera.exposure}
whiteBalance={this.state.camera.whiteBalance}
//ratio={'1:1'}
// pictureSize={this.state.pictureSize}
captureAudio={false}
androidCameraPermissionOptions={{
title: 'Permission to use camera',
message: 'We need your permission to use your camera',
buttonPositive: 'Ok',
buttonNegative: 'Cancel',
}}
/>
);
}
};
renderCenterView = () => {
if(this.state.isEnabled){
const { liveImg, mainView } = this.state;
return <Draggable
x={(this.state.mainView.width/2)-20} y={(this.state.mainView.height/2)-20}
isCircle
onShortPressRelease={this.captureColor}
onDragRelease={this.onDragEvt}
minX={0}
minY={0}
maxX={this.state.mainView.width}
maxY={this.state.mainView.height}
>
<View
style={{ width:50, height:50,borderWidth:15, borderRadius: 50, borderColor:'#d1c1b6', opacity: 0.8 }}></View>
</Draggable>
}else{
return <TouchableOpacity
onPress={this.captureColor}
style={[styles.middlePoint, { top:(this.state.mainView.height/2)-20, left:(this.state.mainView.width/2)-20 }]}/>
}
}
// Logic on Draggable, to find the coordinates on image view
onDragEvt = (event, gestureState, bounds) => {
let sourceWidth = this.state.liveImg.width;
let sourceHeight = this.state.liveImg.height;
// Find view dimensions
let width = this.state.mainView.width;
let height = this.state.mainView.height;
// Assuming image has been scaled to the smaller dimension to fit, calculate the scale
let wScale = width / sourceWidth;
let hScale = height / sourceHeight;
let scale = Math.min(wScale, hScale);
// Find the offset in the direction the image doesn't fill the screen
// For ImageViews that wrap the content, both offsets will be 0 so this is redundant
let offsetX = 0;
let offsetY = 0;
if (wScale <= hScale) {
offsetY = height / 2 - (scale * sourceHeight) / 2;
} else {
offsetX = width / 2 - (scale * sourceWidth) / 2;
}
// Convert event coordinates to image coordinates
let sourceX = (gestureState.moveX - offsetX) / scale;
let sourceY = (gestureState.moveY - offsetY) / scale;
if (sourceX < 0) {
sourceX = 0;
} else if (sourceX > sourceWidth) {
sourceX = sourceWidth - 5;
}
if (sourceY < 0) {
sourceY = 0;
} else if (sourceY > sourceHeight) {
sourceY = sourceHeight - 5;
}
this.setState({ dragView: { x: sourceX, y: sourceY } });
};
getPixelByPercentage(percentage, widthOrHeight) {
return (percentage / 100) * widthOrHeight;
}
getPixel(imageData, x, y) {
try {
GetPixelColor.setImage(isIOS ? imageData.uri : imageData.base64).catch(
(err) => {
// Handle errors
console.error(err);
},
);
GetPixelColor.pickColorAt(x, y)
.then((color) => {
// from react-native-get-pixel-color
// 'color' return as HEX
})
.catch((err) => {
// Handle errors
console.error(err);
});
} catch (e) {}
}
captureColor = async () => {
if (this.state.isEnabled) {
// live view
const { dragView, liveImg } = this.state;
if (Object.keys(dragView).length !== 0) {
let sourceX = dragView.x;
let sourceY = dragView.y;
this.getPixel(liveImg.data, sourceX, sourceY);
} else {
let getPiX = this.getPixelByPercentage(50, liveImg.width);
let getPiY = this.getPixelByPercentage(50, liveImg.height);
this.getPixel(liveImg.data, getPiX, getPiY);
}
} else {
if (this.camera) {
const data = await this.camera.takePictureAsync(CAM_OPTIONS);
await Image.getSize(data.uri, (width, height) => {
console.log('W: ', width);
console.log('H: ', height);
this.setState({
capImage: {
data: data,
imageBase64: data.base64,
width: width,
height: height,
},
});
}).then((res) => {
const { capImage } = this.state;
let getPiX = this.getPixelByPercentage(50, capImage.width);
let getPiY = this.getPixelByPercentage(50, capImage.height);
this.getPixel(capImage.data, getPiX, getPiY);
});
}
}
};
NOTES:
1. renderCamView() - render either Camera or Image
2. renderCenterView() - render the center item, either is draggable in 'still' view or fixed touchable button in 'live' view to always get the center point
3. onDragEvt() - the Draggable's event to keep track the draggable item's movement
4. this.state.mainView - the state that holds width and height of the root <View /> of this screen
5. captureColor() - onPress event to get the pixel color
6. this.state.isEnabled - define it is still view / live view
Live view - (Camera view)
Still view - (Capture and display as image / image select from album)
*Sorry for my bad English, if there is any confusing part please let me know

Simple problem svg `<image>` problem: repeating and not filling the space

I'm trying to have the image fill the bounds of the lime box.
It works when zoom === 1, positionDelta === [0,0]:
But behaving in a way that I don't expect when zoom === 0.688, positionDelta === [0,19]:
It should look like this (aside from the green stroke):
import computeFilters from './computeFilters';
import { useState } from 'react';
import imageMap from './imageMap';
export default function SvgImage({state,value,bounds,div, imageUrlMap}){
const [dataUrl, setDataUrl] = useState(null);
let {width, height, x, y} = bounds;
let cropId = value.value.cropId;
let crop = state.cropsById[ cropId ];
let imageId = crop.imageId || state.imageSets[crop.imageSetId].figureImageId;
let imageSet = state.imageSets[ crop.imageSetId ];
let angle = imageSet.rotation;
let imageRecord = state.imageUploadsById[ imageId ];
let url = imageRecord.url;
let adjustments = imageRecord.adjustments;
let imgStyle = {height:crop.height * width, width, }
let clipId = 'clip-'+div.id;
console.log(value);
let href = imageUrlMap[value._id]
let zoom = (value.imageAdjustments && value.imageAdjustments.zoom) || 1;
let [deltaX,deltaY] = (
value.imageAdjustments && value.imageAdjustments.positionDelta
) || [0,0];
let imageX = x + deltaX;
let imageY = y + deltaY;
let imageWidth = width * zoom;
let imageHeight = height * zoom;
console.log(JSON.stringify({imageX,imageY,imageWidth,imageHeight,zoom,deltaX,deltaY}))
return(
<>
<rect stroke="red" strokeWidth={1} x={x} y={y} width={width} height={height} fill={"none"}/>
<rect stroke="lime" strokeWidth={3}
x={imageX} y={imageY}
width={imageWidth} height={imageHeight} fill={"none"}/>
<image
xlinkHref={href}
width={imageWidth}
height={imageHeight}
x={imageX}
y={imageY}
preserveAspectRatio="none"
/>
</>
)
}
And if I set the following:
xlinkHref={href}
width={width}
height={height}
x={imageX}
y={imageY}
preserveAspectRatio="none"
/>
I get this:
I can try to clip it, but I don't understand why it won't just scale the way I tell it to and it feels the need to rudely insert more than I'm asking for into the view uninvited.
The console log for the three images are:
{"imageX":52,"imageY":95,"imageWidth":309.59999999999997,"imageHeight":87.37599999999999,"zoom":0.688,"deltaX":0,"deltaY":19}
{"imageX":52,"imageY":203,"imageWidth":450,"imageHeight":38,"zoom":1,"deltaX":0,"deltaY":0}
{"imageX":52,"imageY":241,"imageWidth":450,"imageHeight":51,"zoom":1,"deltaX":0,"deltaY":0}

How do I draw a line to point derived from angle?

I'm writing a component (React) which draws a fancy box around a bit of text with SVG. The component receives the width and height of the dom element and draws a polygon derived from those values. Simplified example follows:
import React from 'react';
const Box = ({ dimensions }) => {
const { width, height } = dimensions;
const mod = 10;
const tlX = 0;
const tlY = 0;
const trX = tlX + width;
const trY = tlY;
const brX = trX;
const brY = trY + height;
const blX = 0;
const blY = tlY + height;
return (
<picture>
<svg height={height + 50} width={width + 200}>
<polygon
points={`${tlX},${tlY} ${trX},${trY} ${brX},${brY} ${blX},${blY}`}
style={{ fill: 'black', fillOpacity: '0.5' }}
/>
</svg>
</picture>
);
};
In this stripped down example the result is a rectangle with straight corners based on the width and height of the supplied dom element. In reality these values are given some random modifiers to create more of a trapezoid, as in fig A.
Illustration of desired result
In the fig B you can see my problem with this method. When drawing a longer or shorter box, the figure looks squished. What I want is for the box to behave like in fig C, in that it will draw the horizontal lines at a given angle until it has reached a certain width.
From what I can intuit this should be possible with some math savvy, but I am unable to quite figuring it out on my own.
Thanks in advance for any input, and please let me know if I'm being unclear on anything.
Edit:
A "trapezoid" shape is apparently not what I'm looking for. My apologies. I just want a sort of janky rectangle. I was asked to show the code I've been using in more detail. As you will see I am basically just taking the values from the last example and messing them up a bit by adding or subtracting semi-randomly.
import React from 'react';
import PropTypes from 'prop-types';
function getRandomArbitrary(min, max) {
return Math.random() * (max - min) + min;
}
const point = (core, edge) => getRandomArbitrary(core - edge, core + edge);
const Box = ({ dimensions }) => {
const { width, height } = dimensions;
const mod = 10;
const tlX = point(25, mod);
const tlY = point(40, mod);
const trX = point(width + 55, mod);
const trY = point(25, mod);
const brX = point(width + 25, mod);
const brY = point(height - 25, mod);
const blX = point(5, mod);
const blY = point(height - 40, mod);
return (
<picture>
<svg height={height + 50} width={width + 200}>
<polygon
points={`${tlX},${tlY} ${trX},${trY} ${brX},${brY} ${blX},${blY}`}
style={{ fill: 'black', fillOpacity: '0.5' }}
/>
</svg>
</picture>
);
};
Box.propTypes = {
dimensions: PropTypes.shape({
width: PropTypes.number,
height: PropTypes.number,
}).isRequired,
};
export default Box;
In order to calculate the y for the top right point I'm imagining a circle with the center in the top left point. The radius of the circle is tlX - trX, The angle is -5 degs but you can change it to what you need. In order to calculate the value for the y you can do
const trY = tlY - (tlX - trX)*Math.sin(a1)
To calculate the y for the bottom right point I'm doing the same only this time the angle is 5 degs, the center in the bottom left point and the radius of the circle is blX - brX
const brY = blY - (blX - brX)*Math.sin(a2)
The important part of the demo is this:
//calculating the points for the polygon
const tlX = BB.x-10;
const tlY = BB.y-5;
const trX = tlX + 20 + BB.width;
//const trY = tlY - 10;
const trY = tlY - (tlX - trX)*Math.sin(a1)
const brX = trX - 5;
const blX = tlX + 5;
const blY = tlY + 10 + BB.height;
//const brY = trY + 30+ BB.height;
const brY = blY - (blX - brX)*Math.sin(a2)
Next comes a demo where I'm using plain javascript. Please change the length of the text to see if this is what you need.
let bb = txt.getBBox();
let m = 10;
// the blue rect
updateSVGelmt({x:bb.x-m,y:bb.y-m,width:bb.width+2*m,height:bb.height+2*m},theRect)
// the bounding box of the blue rect
let BB = theRect.getBBox();
//the angles for the polygon
let a1 = -5*Math.PI/180;
let a2 = -a1;
//calculating the points for the polygon
const tlX = BB.x-10;
const tlY = BB.y-5;
const trX = tlX + 20 + BB.width;
//const trY = tlY - 10;
const trY = tlY - (tlX - trX)*Math.sin(a1)
const brX = trX - 5;
const blX = tlX + 5;
const blY = tlY + 10 + BB.height;
//const brY = trY + 30+ BB.height;
const brY = blY - (blX - brX)*Math.sin(a2)
let points = `${tlX},${tlY} ${trX},${trY} ${brX},${brY} ${blX},${blY}`;
poly.setAttributeNS(null, "points", points)
let polybox = poly.getBBox();
svg.setAttributeNS(null, "viewBox", `${polybox.x-2} ${polybox.y-2} ${polybox.width+4} ${polybox.height+4}`)
svg.setAttributeNS(null, "width",3*(polybox.width+4))
function updateSVGelmt(o,elmt) {
for (let name in o) {
if (o.hasOwnProperty(name)) {
elmt.setAttributeNS(null, name, o[name]);
}
}
}
svg{border:1px solid}
<svg id="svg">
<polygon id="poly"
points=""
style="stroke: black; fill:none"
/>
<rect id="theRect" fill="#d9d9ff" />
<text id="txt" text-anchor="middle">element</text>
</svg>

JavaScript - React native wait for callback to execute and use its variables

this is my function where am calculating image's height and width, but I had to use react native's image. getSizebut this has a callback which a has a delay somehow and the return brock seems to execute before this image. getSize finish.
renderNode (node, index, siblings, parent, defaultRenderer) {
let finalwidth ;
let finalheight;
if (node.name === 'img') {
const a = node.attribs;
Image.getSize(a.src, (width, height) => {
let screenSize = Dimensions.get('window');
let hRatio = screenSize.width / width;
let vRatio = screenSize.height / height;
let ratio = Math.max(hRatio, vRatio);
let rat = hRatio / vRatio;
finalwidth = parseInt(width * ratio);
finalheight = parseInt(height * ratio);
alert(finalwidth + 'x' + finalheight) // they have values here
})
return (
<Image
key={index}
source={{uri: a.src}}
resizeMode={'stretch'}
style={{
width: finalwidth, //finalwidth is null here
height:finalheight, //finalheight is null here
}}
/>
);
}
}
Actually, I want to access the value of final width and final height in my return brock
use setState() and set those values into state so that the component is rerendered when the data is updated
constructor(props){
super(props);
this.state={finalwidth : null,
finalheight: null
}
}
renderNode (node, index, siblings, parent, defaultRenderer) {
if (node.name === 'img') {
const a = node.attribs;
Image.getSize(a.src, (width, height) => {
let screenSize = Dimensions.get('window');
let hRatio = screenSize.width / width;
let vRatio = screenSize.height / height;
let ratio = Math.max(hRatio, vRatio);
let rat = hRatio / vRatio;
finalwidth = parseInt(width * ratio);
finalheight = parseInt(height * ratio);
this.setState({finalwidth, finalheight})
alert(finalwidth + 'x' + finalheight) // they have values here
})
return (
<Image
key={index}
source={{uri: a.src}}
resizeMode={'stretch'}
style={{
width: this.state.finalwidth, //finalwidth from state
height: this.state.finalheight, //finalheight from state
}}
/>
);
}
}

Categories

Resources