Rendering imageData to new canvas - paperjs

I'm following a tutorial by George Francis in the tutorial after some initial examples he shows how to use image data to create random layouts.
I'm trying to work out how to get the image data from a canvas created using paper.js, as I need to get the rgb values from each individual pixel on the canvas
Link to codepen
Unknowns:
Do I need to use the rasterize() method on the shape I've created?
Currently I am attempting the following:
// create a white rectangle the size of the view (not sure I need this but doing it so that there are both white and black pixels)
const bg = new paper.Path.Rectangle({
position: [0,0],
size: view.viewSize.multiply(2),
fillColor: 'white'
})
// create a black rectangle smaller than the view size
const shape = new paper.Path.RegularPolygon({
radius: view.viewSize.width * 0.4,
fillColor: 'black',
strokeColor: 'black',
sides: 4,
position: view.center
})
// So far so good shapes render as expected. Next put the shapes in a group
const group = new paper.Group([bg,shape])
// rasterise the group (thinking it needs to be rasterized to get the pixel data, but again , not sure?)
group.rasterize()
// iterate over each pixel on the canvas and get the image data
for(let x = 0; x < width; x++){
for(let y = 0; y < height; y++){
const { data } = view.context.getImageData(x,y,1,1)
console.log(data)
}
}
Expecting: To get an array of buffers where if the pixel is white it would give me
Uint8ClampedArray(4) [0, 0, 0, 0, buffer: ArrayBuffer(4),
byteLength: 4, byteOffset: 0, length: 4]
0: 255
1: 255
2: 255
//(not sure if the fourth index represents (rgb'a')?
3: 255
buffer:
ArrayBuffer(4)
byteLength: 4
byteOffset: 0
length: 4
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: TypedArray
and if the pixel is black I should get
Uint8ClampedArray(4) [0, 0, 0, 0, buffer: ArrayBuffer(4),
byteLength: 4, byteOffset: 0, length: 4]
0: 0
1: 0
2: 0
3: 0
buffer:
ArrayBuffer(4)
byteLength: 4
byteOffset: 0
length: 4
Symbol(Symbol.toStringTag): (...)
[[Prototype]]: TypedArray
i.e either 255,255,255 (white) or 0,0,0(black)
Instead, all the values are 0,0,0?

I think that your issue was that at the time where you are getting the image data, your scene is not yet drawn to the canvas.
In order to make sure it's drawn, you just need to call view.update().
Here's a simple sketch demonstrating how it could be used.
Note that you don't need to rasterize your scene if you are using the Canvas API directly to manipulate the image data. But you could also rasterize it and take advantage of Paper.js helper methods like raster.getPixel().
// Draw a white background (you effectively need it otherwise your default
// pixels will be black).
new Path.Rectangle({
rectangle: view.bounds,
fillColor: 'white'
});
// Draw a black rectangle covering most of the canvas.
new Path.Rectangle({
rectangle: view.bounds.scale(0.9),
fillColor: 'black'
});
// Make sure that the scene is drawn into the canvas.
view.update();
// Get the canvas image data.
const { width, height } = view.element;
const imageData = view.context.getImageData(0, 0, width, height);
// Loop over each pixel and store all the different colors to check that this works.
const colors = new Set();
const length = imageData.data.length;
for (let i = 0; i < length; i += 4) {
const [r, g, b, a] = imageData.data.slice(i, i + 4);
const color = JSON.stringify({ r, g, b, a });
colors.add(color);
}
console.log('colors', [...colors]);

Related

Select points in line series

I'd like to use a modifier key with the left mouse button that will select the data inside the rectangle, rather than the zoom to that data. Is this possible? I cannot find a suitable API for it. Bonus points if there's a way to select data that falls inside a polygon (like a lasso tool).
Here's one example of completely custom ChartXY interactions.
Key points:
Default rectangle fit & zoom interactions are disabled.
Line series data is cached to a variable which can be used for custom statistics.
RectangleSeries is used for visualizing drag area on chart.
UI elements are used for displaying statistics of selected area.
ChartXY.onSeriesBackgroundMouseDrag event is used for hooking custom actions to user interactions.
Below you'll find a code snippet where dragging with left mouse button creates a rectangular area which shows highlighted X area and solved Y data range within.
Releasing the mouse button results in the full selected data points array being solved (length is logged to console).
const {
Point,
ColorRGBA,
SolidFill,
RadialGradientFill,
SolidLine,
translatePoint,
lightningChart,
UIOrigins,
UIElementBuilders,
UILayoutBuilders,
emptyFill
} = lcjs;
const { createProgressiveTraceGenerator } = xydata;
const chart = lightningChart()
.ChartXY()
// Disable default chart interactions with left mouse button.
.setMouseInteractionRectangleFit(false)
.setMouseInteractionRectangleZoom(false)
.setTitleFillStyle(emptyFill)
const axisX = chart.getDefaultAxisX()
const axisY = chart.getDefaultAxisY()
const lineSeries = chart.addLineSeries({
dataPattern: {
pattern: 'ProgressiveX',
},
})
// Generate test data set.
let dataSet
createProgressiveTraceGenerator()
.setNumberOfPoints(10 * 1000)
.generate()
.toPromise()
.then((data) => {
// Cache data set for analytics logic + add static data to series.
dataSet = data
lineSeries.add(data)
})
// Rectangle Series is used to display data selection area.
const rectangleSeries = chart.addRectangleSeries()
const rectangle = rectangleSeries
.add({ x1: 0, y1: 0, x2: 0, y2: 0 })
.setFillStyle(
new RadialGradientFill({
stops: [
{ offset: 0, color: ColorRGBA(255, 255, 255, 30) },
{ offset: 1, color: ColorRGBA(255, 255, 255, 60) },
],
}),
)
.setStrokeStyle(
new SolidLine({
thickness: 2,
fillStyle: new SolidFill({ color: ColorRGBA(255, 255, 255, 255) }),
}),
)
.dispose()
// UI elements are used to display information about the selected data points.
const uiInformationLayout = chart.addUIElement(UILayoutBuilders.Column, { x: axisX, y: axisY }).dispose()
const uiLabel0 = uiInformationLayout.addElement(UIElementBuilders.TextBox)
const uiLabel1 = uiInformationLayout.addElement(UIElementBuilders.TextBox)
// Add events for custom interactions.
chart.onSeriesBackgroundMouseDrag((_, event, button, startLocation) => {
// If not left mouse button, don't do anything.
if (button !== 0) return
// Translate start location and current location to axis coordinates.
const startLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
lineSeries.scale,
)
const curLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(event.clientX, event.clientY),
chart.engine.scale,
lineSeries.scale,
)
// Place Rectangle figure between start location and current location.
rectangle.restore().setDimensions({
x1: startLocationAxis.x,
y1: startLocationAxis.y,
x2: curLocationAxis.x,
y2: curLocationAxis.y,
})
// * Gather analytics from actively selected data *
const xStart = Math.min(startLocationAxis.x, curLocationAxis.x)
const xEnd = Math.max(startLocationAxis.x, curLocationAxis.x)
// Selected Y range has to be solved from data set.
// NOTE: For top solve performance, results should be cached and only changes from previous selection area should be checked.
const { yMin, yMax } = solveDataRangeY(xStart, xEnd)
// Set UI labels text.
uiLabel0.setText(`X: [${xStart.toFixed(0)}, ${xEnd.toFixed(0)}]`)
uiLabel1.setText(`Y: [${yMin.toFixed(1)}, ${yMax.toFixed(1)}]`)
// Place UI layout above Rectangle.
uiInformationLayout
.restore()
.setOrigin(UIOrigins.LeftBottom)
.setPosition({ x: xStart, y: Math.max(startLocationAxis.y, curLocationAxis.y) })
})
chart.onSeriesBackgroundMouseDragStop((_, event, button, startLocation) => {
// If not left mouse button, don't do anything.
if (button !== 0) return
// Translate start location and current location to axis coordinates.
const startLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(startLocation.x, startLocation.y),
chart.engine.scale,
lineSeries.scale,
)
const curLocationAxis = translatePoint(
chart.engine.clientLocation2Engine(event.clientX, event.clientY),
chart.engine.scale,
lineSeries.scale,
)
// Print selected data points to console.
const xStart = Math.max(0, Math.floor(Math.min(startLocationAxis.x, curLocationAxis.x)))
const xEnd = Math.min(dataSet.length - 1, Math.ceil(Math.max(startLocationAxis.x, curLocationAxis.x)))
const selectedDataPoints = dataSet.slice(xStart, xEnd)
console.log(`Selected ${selectedDataPoints.length} data points.`)
// Hide visuals.
rectangle.dispose()
uiInformationLayout.dispose()
})
// Logic for solving Y data range between supplied X range from active data set.
const solveDataRangeY = (xStart, xEnd) => {
// Reduce Y data min and max values within specified X range from data set.
// Note, this can be very heavy for large data sets - repeative calls should be avoided as much as possible for best performance.
let yMin = Number.MAX_SAFE_INTEGER
let yMax = -Number.MAX_SAFE_INTEGER
xStart = Math.max(0, Math.floor(xStart))
xEnd = Math.min(dataSet.length - 1, Math.ceil(xEnd))
for (let iX = xStart; iX < xEnd; iX += 1) {
const y = dataSet[iX].y
yMin = y < yMin ? y : yMin
yMax = y > yMax ? y : yMax
}
return { yMin, yMax }
}
<script src="https://unpkg.com/#arction/xydata#1.4.0/dist/xydata.iife.js"></script>
<script src="https://unpkg.com/#arction/lcjs#3.0.0/dist/lcjs.iife.js"></script>
There's many different directions to go with this kind of custom interactions, and while we can't cover every single one with an example, most of the logic should stay the same.

Qt (QML) Dashed Circle

Is there any way to draw half dashed circle in QML? I drawn half circle in this way
var Circle = getContext("2d");
Circle.save();
var CircleGradient =
Circle.createLinearGradient(parent.width/4,parent.height,parent.width/4,0);
CircleGradient.addColorStop(0, firstGradientPoint);
CircleGradient.addColorStop(1, secondGradientPoint);
Circle.clearRect(0, 0, parent.width, parent.height);
Circle.beginPath();
Circle.lineCap = "round";
Circle.lineWidth = 10;
Circle.strokeStyle = CircleGradient
Circle.arc(parent.width/2, parent.height/2, canvas.radius - (Circle.lineWidth / 2), Math.PI/2, canvas.Value);
Circle.stroke();
Circle.restore();
Result
But how can I make it dashed like this.
I need
I know that this question is very outdated, but it might help someone. You can use Qt Quick Shapes (since Qt 5.10) to render what you want. It's not copy-paste code, but more of an approach:
Shape {
ShapePath {
id: shapePath
strokeColor: "black"
strokeStyle: ShapePath.DashLine
dashPattern: [6, 8]
fillColor: "transparent"
PathArc {
x: 0
y: radiusX + radiusY
radiusX: 100
radiusY: 100
useLargeArc: true
}
}
}
PathArc documentation has pretty much everything you need. Here are some more Shape Examples.
I know QML little bit but never coded.
But you can solve your problem by logic.
Here is the logic- Code below is pseudo, will not work but will give you an idea.
Draw the small arcs in loop with spaces in between.
//DECLARE YOUR ANGLES START AND END
startAngle = 0.0;
endAngle = pi/20;// 10 ARCS AND 10 SPACES
while (q++ < 10){
Circle.arc(parent.width/2, parent.height/2, canvas.radius - (Circle.lineWidth / 2), startAngle, endAngle, canvas.Value)
//LEAVE SPACE AND CREATE NEW START AND END ANGLE.
startAngle = endAngle + endAngle;
endAngle = startAngle + endAngle;
}

Three.js - Rotation of a cylinder that represents a vector

I am using three.js to create a simple 3d vector environment. I am using lines to represent all 3 vector compontens x, y, z and a line for the final vector representation. Problem is that setting the width of a line is not working in Windows. The workaround that I try to implement is placing a cylinder onto the line (see red object in image below).
That is my current result:
As you see I am not able to rotate the cylinder to the correct position.
I faced the problem that the rotation center of the cylinder is in the middle of the object, so I moved the rotation point to the beginning of the cylinder. But still, rotation is not working correctly. I guess, the rotations around the axis influence each other.
Here is the code:
// VEKTOR
var vektor = {};
vektor._x = 2;
vektor._y = 1.5;
vektor._z = 1;
vektor._length = Math.sqrt(vektor._x*vektor._x + vektor._y*vektor._y + vektor._z*vektor._z);
// CYLINDER
var cyl_material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
// cylinder which is our line that represents the vector
var cyl_width = 0.025; // default line width
var cyl_height = vektor._length;
// THREE.CylinderGeometry(radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded)
var cylGeometry = new THREE.CylinderGeometry(cyl_width, cyl_width, cyl_height, 20, 1, false);
// translate the cylinder geometry so that the desired point within the geometry is now at the origin
// https://stackoverflow.com/questions/12746011/three-js-how-do-i-rotate-a-cylinder-around-a-specific-point
cylGeometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, cyl_height/2, 0 ) );
var cylinder = new THREE.Mesh(cylGeometry, cyl_material);
updateCylinder();
scene.add( cylinder );
And the function updateCylinder trys to do the rotation.
function updateCylinder() {
// ... stuff, then:
cylinder.rotation.x = Math.atan2(vektor._z,vektor._y);
cylinder.rotation.y = 0.5*Math.PI+Math.atan2(vektor._x,vektor._z);
cylinder.rotation.z = Math.atan2(vektor._x,vektor._y);
}
Here is the current demo: http://www.matheretter.de/3d/vektoren/komponenten/
What am i doing wrong with the rotation? How to implement it so that the cylinder is following the vector line?
Thanks for your help.
If you want to transform a cylinder so that one end is at the origin and the other end points toward a specific point, here is the pattern you can follow:
First, transform your geometry so one end of the cylinder is at the origin, and the other end (the top) is on the positive z-axis.
var geometry = new THREE.CylinderGeometry( 0, 1, length, 8, 1, true );
geometry.applyMatrix( new THREE.Matrix4().makeTranslation( 0, length / 2, 0 ) );
geometry.applyMatrix( new THREE.Matrix4().makeRotationX( Math.PI / 2 ) );
Then create your mesh, and call the lookAt() method:
var mesh = new THREE.Mesh( geometry, material );
mesh.lookAt( point );
three.js r.67

Canvas and unit/normal gradients

I am trying to use unit/normal vector based gradients in html5 canvas element and transform them afterwards for the desired results. However, I seem to figure troubles which might be because of my lack of math. I am trying to create a simple linear gradient going from 0,0 to 1,0 (i.e. a simple unit gradient going from left to right). Afterwards, I transform the canvas for scaling, rotating and moving the gradient. However, when for example giving a rotation value of 45DEG, the actual gradient gets painted wrong. The right bottom corner has way to much black that is, the gradient seems to be not "big" enough. Here's my code:
var rect = {x: 0, y: 0, w: 500, h: 500};
var rotation = 45 * Math.PI/180;
var sx = 1;
var sy = 1;
var tx = 0;
var ty = 0;
var radial = false;
// Create unit vector 0,0 1,1
var grd = radial ? ctx.createRadialGradient(0, 0, 0, 0, 0, 0.5) : ctx.createLinearGradient(0, 0, 1, 0);
grd.addColorStop(0, 'black');
grd.addColorStop(0.1, 'lime');
grd.addColorStop(0.9, 'yellow');
grd.addColorStop(1, 'black');
// Add our rectangle path before transforming
ctx.beginPath();
ctx.moveTo(rect.x, rect.y);
ctx.lineTo(rect.x + rect.w, rect.y);
ctx.lineTo(rect.x + rect.w, rect.y + rect.h);
ctx.lineTo(rect.x, rect.y + rect.h);
ctx.closePath();
// Rotate and scale unit gradient
ctx.rotate(rotation);
ctx.scale(sx * rect.w, sy * rect.h);
ctx.fillStyle = grd;
// Fill gradient
ctx.fill();
And here's the fiddle to try it out:
http://jsfiddle.net/4GsCE/1/
Curious enough, changing the unit linear gradient vector to a factor of about 1.41 makes the gradient look right:
ctx.createLinearGradient(0, 0, 1.41, 0)
Which can be seen in this fiddle:
http://jsfiddle.net/4GsCE/2/
But I couldn't figure how to calculate that factor?
Since you want to use normalized gradients, you have to decide how to normalize. Here you choose to center the gradient, and to have its (x,y) in the [-0.5, 0.5 ] range.
First issue is that the linear gradient is not centered, it's in the [0, 1.0] range.
Normalize them the same way :
var linGrd = ctx.createLinearGradient(-0.5, 0, 0.5, 0);
Second issue is that you must translate to the center of your figure, then scale, then draw in a normalized way.
Meaning you must use same coordinate system as your gradients.
Since you were both drawing a shape having (w,h) as size AND using a scale of (w,h), you were drawing a ( ww, hh ) sized rect.
Correct draw code is this one :
function drawRect(rect, fill) {
ctx.save();
// translate to the center of the rect (the new (0,0) )
ctx.translate(rect.x + rect.w / 2, rect.y + rect.h / 2);
// Rotate
ctx.rotate(rotation);
// scale to the size of the rect
ctx.scale(rect.w, rect.h);
// ...
ctx.fillStyle = fill;
// draw 'normalized' rect
ctx.fillRect(-0.5, -0.5, 1, 1);
ctx.restore();
}
Notice that by default the radialGradient will end at a distance of 0.5, meaning, if you are filling a rect, that it will fill the corners with the last color of the gradient. Maybe you want the corners to end the gradient.
In that case, you want to have the gradient to reach its value at a distance of :
sqrt ( 0.5*0.5 + 0.5*0.5 ) = 0.7 ( pythagore in the normalized circle)
So you'll define your normalized gradient like :
var fullRadGrd = ctx.createRadialGradient(0, 0, 0, 0, 0, 0.7) ;
http://jsfiddle.net/gamealchemist/4GsCE/4/

How to draw graph from a list of points?

In this answer to my recent question, there is some code that draws a graph, but I can't manage to edit it into something that accepts any list of points as a parameter.
I'd like the Drawing method to accept these parameters:
List of Vector2, Point or VertexPositionColor, I can work with whichever.
Offset for the whole graph
These optional requirements would be appreciated:
Color that may override VertexPositionColor's color and apply to all points.
Size of the graph, so it can be shrunk or expanded, either as Vector2 as multiplier, or Point as target size. Maybe even combine this with offset in Rectangle.
And if it's possible, I'd like to have it all in a class, so graphs can be used separately from each other, each with its own Effect.world matrix, etc.
Here is that code (by Niko Drašković):
Matrix worldMatrix;
Matrix viewMatrix;
Matrix projectionMatrix;
BasicEffect basicEffect;
VertexPositionColor[] pointList;
short[] lineListIndices;
protected override void Initialize()
{
int n = 300;
//GeneratePoints generates a random graph, implementation irrelevant
pointList = new VertexPositionColor[n];
for (int i = 0; i < n; i++)
pointList[i] = new VertexPositionColor() { Position = new Vector3(i, (float)(Math.Sin((i / 15.0)) * height / 2.0 + height / 2.0 + minY), 0), Color = Color.Blue };
//links the points into a list
lineListIndices = new short[(n * 2) - 2];
for (int i = 0; i < n - 1; i++)
{
lineListIndices[i * 2] = (short)(i);
lineListIndices[(i * 2) + 1] = (short)(i + 1);
}
worldMatrix = Matrix.Identity;
viewMatrix = Matrix.CreateLookAt(new Vector3(0.0f, 0.0f, 1.0f), Vector3.Zero, Vector3.Up);
projectionMatrix = Matrix.CreateOrthographicOffCenter(0, (float)GraphicsDevice.Viewport.Width, (float)GraphicsDevice.Viewport.Height, 0, 1.0f, 1000.0f);
basicEffect = new BasicEffect(graphics.GraphicsDevice);
basicEffect.World = worldMatrix;
basicEffect.View = viewMatrix;
basicEffect.Projection = projectionMatrix;
basicEffect.VertexColorEnabled = true; //important for color
base.Initialize();
}
And the drawing method:
foreach (EffectPass pass in basicEffect.CurrentTechnique.Passes)
{
pass.Apply();
GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionColor>(
PrimitiveType.LineList,
pointList,
0,
pointList.Length,
lineListIndices,
0,
pointList.Length - 1
);
}
The Graph class that does the requested can be found here.About 200 lines of code seemed too much to paste here.
The Graph is drawn by passing a list of floats (optionally with colors) to its Draw(..) method.
Graph properties are:
Vector2 Position - the bottom left corner of the graph
Point Size - the width (.X) and height (.Y) of the graph. Horizontally, values will be distributed to exactly fit the width. Vertically, all values will be scaled with Size.Y / MaxValue.
float MaxValue - the value which will be at the top of the graph. All off the chart values (greater than MaxValue) will be set to this value.
GraphType Type - with possible values GraphType.Line and GraphType.Fill, determines if the graph will be drawn line only, or bottom filled.
The graph is drawn with a line list / triangle strip.

Resources