I'm working on image processing using JavaScript and I would like to you know if there was any generic formula to determine the x-neighbors of a pixel.
I know that for a 3*3 square there is 8 neighbors that can be determine using a specific x and y pixel.
(x-1,y-1) , (x-1,y) , (x-1,y+1),
(x,y-1) , (x,y) , (x,y+1),
(x+1,y-1) , (x+1,y) , (x+1,y+1).
The problem is that I'm working with 5*5 squares,7*7 squares and 9*9 squares and I was wondering if there was any way to have all the x-neighbors of a pixel from those three squares without having to write the location manually in my program.
Thanks
var size = 5;
var d = Math.floor(size / 2);
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
// Do something with (x + dx, y + dy)
}
}
}
If you're doing this is a lot (i.e. for every pixel in an image), it might be worth first creating a flat array of value first:
var size = 5;
var d = Math.floor(size / 2);
var dPixels = [];
for (var dx = -d; dx <= d; dx++) {
for (var dy = -d; dy <= d; dy++) {
if (dx || dy) {
dPixels.push(dx, dy);
}
}
}
// Then for each pixel (x, y)
for (var i = 0; i < dPixels.length; i += 2){
// Do something with x + dPixels[i], y + dPixels[i + 1]
}
Related
I am trying to make my own 3D renderer in JavaScript using raycasting, but despite checking over the math and the code countless times, it still does not seem to be working. I've tried everything I possibly could to get this thing to work and it won't, so I'm hoping someone else can figure it out.
My code runs an Update method every frame, increasing the yaw (Camera.Rot.Yaw) by 0.1 radians every iteration, but it ends up looking weird and unrealistic, and I can't figure out why. Sorry if it's confusing and long, I can't really think of a way to make a minimal reproducible example of this.
This is the Update method:
Update(Canvas, Ctx, Map, Camera) {
var id = Ctx.getImageData(0, 0, Canvas.width, Canvas.height);
var Pixels = id.data;
//Distance of projection plane from camera
//It should be behind I think
var PlaneDist = 64;
//Divides the second slopes by this so each ray goes a shorter
//distance each iteration, effectively increasing quality
var Quality = 160;
//The midpoint of the projection plane for each coordinate
var MidX =
Camera.Pos.X +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.cos(Camera.Rot.Yaw);
var MidY = Camera.Pos.Y + PlaneDist * Math.sin(Camera.Rot.Pitch);
var MidZ =
Camera.Pos.Z +
PlaneDist * Math.cos(Camera.Rot.Pitch) * Math.sin(Camera.Rot.Yaw);
//Slopes to get to other points on the projection plane
var SlopeX =
Math.sin(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.cos(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
var SlopeY = -Math.cos(Camera.Rot.Pitch);
var SlopeZ =
Math.cos(Camera.Rot.Yaw) +
(Canvas.height / Canvas.width) *
Math.sin(Camera.Rot.Yaw) *
Math.sin(Camera.Rot.Pitch);
//Loops for every point on the projection plane
for (let i = 0; i < Canvas.height; i++) {
for (let j = 0; j < Canvas.width; j++) {
let NewX = Camera.Pos.X;
let NewY = Camera.Pos.Y;
let NewZ = Camera.Pos.Z;
//Slopes for the actual ray to follow, just the distance between
//the plane point and the camera divided by quality
let SlopeX2 = (Camera.Pos.X-(MidX - SlopeX * (j - Canvas.width / 2)))/ Quality;
let SlopeY2 = (Camera.Pos.Y-(MidY - SlopeY * (i - Canvas.height / 2))) / Quality;
let SlopeZ2 = (Camera.Pos.Z-(MidZ - SlopeZ * (j - Canvas.width / 2)))/ Quality;
//Ray's current map position, divides the map into a 16x32x16
//list of blocks (map initialization shown elsewhere)
let MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
//Iterates until ray either hits a block with max opacity, or
//hits the boundary of the map
while (
MapPos[3] !== 255 &&
NewX + SlopeX2 < 256 &&
NewY + SlopeY2 < 512 &&
NewZ + SlopeZ2 < 256 &&
NewX + SlopeX2 >= 0 &&
NewY + SlopeY2 >= 0 &&
NewZ + SlopeZ2 >= 0
) {
//Advances ray's current position according to slopes
NewX += SlopeX2;
NewY += SlopeY2;
NewZ += SlopeZ2;
MapPos =
Map.MData[0][Math.floor(NewX / 16) + 2][Math.floor(NewY / 16)][
Math.floor(NewZ / 16)
];
}
//Sets pixel on screen to the color of the block the ray hit
//or just white (opacity 0) if it hit the boundary
Pixels[(i * id.width + j) * 4] = MapPos[0];
Pixels[(i * id.width + j) * 4 + 1] = MapPos[1];
Pixels[(i * id.width + j) * 4 + 2] = MapPos[2];
Pixels[(i * id.width + j) * 4 + 3] = MapPos[3];
}
}
//Displays the final image
Ctx.putImageData(id, 0, 0);
}
The map initialization (CreateChunk) looks like this:
constructor() {
this.MData = [];
}
CreateChunk(X, Y) {
let Chunk = [X, Y];
for (let x = 0; x < 16; x++) {
let Plane = [];
for (let y = 0; y < 32; y++) {
let Row = [];
for (let z = 0; z < 16; z++) {
//Colors are just to help tell which pixels are at what coordinates
if (y < 8) Row.push([x * 15, y * 7, z * 15, 255]);
else Row.push([0, 0, 0, 0]);
}
Plane.push(Row);
}
Chunk.push(Plane);
}
this.MData.push(Chunk);
}
I'm hoping it's just some coding mistake I've made, but despite my countless checks it may be the trigonometry that's wrong.
I have this canvas that you can draw on, but since I'm going to use a physics library on the whole thing at some point, I'd like the drawing to be a bit less detailed.
I was thinking about something along the lines of reading the mouse position at a certain interval and then just drawing a straight line to the new mouse position. I've somehow tried this with setInterval and setTimeout, but it never does anything close to what I'd like to see. Does anyone have any suggestions how I could do this?
Thank you!
Here is a javascript implementation of Douglas-Peucker path simplifying.
http://mourner.github.io/simplify-js/
(See full code for simplify.js below.)
You might also consider using curves instead of lines to reduce points (path smoothing)
Smoothing paths is an often considered task:
Douglas Alan Schepers (from w3c):
http://schepers.cc/getting-to-the-point
The Spiro library (as used in Inkscape, etc):
http://www.levien.com/spiro/
Ken Fyrstenberg Nilsen (frequent contributor at SO):
http://www.codeproject.com/Tips/562175/Draw-Smooth-Lines-on-HTML5-Canvas
Paper.js has methods to both smooth and also simplify paths:
http://paperjs.org/tutorials/paths/smoothing-simplifying-flattening/
Code for Simplify.js (BSD license at http://mourner.github.io/simplify-js/):
(function (global, undefined) {
// to suit your point format, run search/replace for '.x' and '.y';
// to switch to 3D, uncomment the lines in the next 2 functions
// (configurability would draw significant performance overhead)
function getSquareDistance(p1, p2) { // square distance between 2 points
var dx = p1.x - p2.x,
// dz = p1.z - p2.z,
dy = p1.y - p2.y;
return dx * dx +
// dz * dz +
dy * dy;
}
function getSquareSegmentDistance(p, p1, p2) { // square distance from a point to a segment
var x = p1.x,
y = p1.y,
// z = p1.z,
dx = p2.x - x,
dy = p2.y - y,
// dz = p2.z - z,
t;
if (dx !== 0 || dy !== 0) {
t = ((p.x - x) * dx +
// (p.z - z) * dz +
(p.y - y) * dy) /
(dx * dx +
// dz * dz +
dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
// z = p2.z;
} else if (t > 0) {
x += dx * t;
y += dy * t;
// z += dz * t;
}
}
dx = p.x - x;
dy = p.y - y;
// dz = p.z - z;
return dx * dx +
// dz * dz +
dy * dy;
}
// the rest of the code doesn't care for the point format
// basic distance-based simplification
function simplifyRadialDistance(points, sqTolerance) {
var i,
len = points.length,
point,
prevPoint = points[0],
newPoints = [prevPoint];
for (i = 1; i < len; i++) {
point = points[i];
if (getSquareDistance(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) {
newPoints.push(point);
}
return newPoints;
}
// simplification using optimized Douglas-Peucker algorithm with recursion elimination
function simplifyDouglasPeucker(points, sqTolerance) {
var len = points.length,
MarkerArray = (typeof Uint8Array !== undefined + '')
? Uint8Array
: Array,
markers = new MarkerArray(len),
first = 0,
last = len - 1,
i,
maxSqDist,
sqDist,
index,
firstStack = [],
lastStack = [],
newPoints = [];
markers[first] = markers[last] = 1;
while (last) {
maxSqDist = 0;
for (i = first + 1; i < last; i++) {
sqDist = getSquareSegmentDistance(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
markers[index] = 1;
firstStack.push(first);
lastStack.push(index);
firstStack.push(index);
lastStack.push(last);
}
first = firstStack.pop();
last = lastStack.pop();
}
for (i = 0; i < len; i++) {
if (markers[i]) {
newPoints.push(points[i]);
}
}
return newPoints;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDistance(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
};
// export either as a Node.js module, AMD module or a global browser variable
if (typeof exports === 'object') {
module.exports = simplify;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return simplify;
});
} else {
global.simplify = simplify;
}
}(this));
I need to generate and store the coordinates of each point of a filled circle of say, radius 10 in Javascript.
It seems like the best way to do this would be to use the midpoint circle algorithm, but I'm not sure how to adapt it to find every point in the circle. The coordinates are going to be stored as objects in an array.
Could someone help me with the implementation?
Personally I think it would probably be faster in this case to test all pixels in the bounding box for their distance to the center. If <= r then the point is in the circle and should be pushed onto your array.
function distance(p1, p2)
{
dx = p2.x - p1.x; dx *= dx;
dy = p2.y - p1.y; dy *= dy;
return Math.sqrt( dx + dy );
}
function getPoints(x, y, r)
{
var ret = [];
for (var j=x-r; j<=x+r; j++)
for (var k=y-r; k<=y+r; k++)
if (distance({x:j,y:k},{x:x,y:y}) <= r) ret.push({x:j,y:k});
return ret;
}
You loop through all the possible points and you run the Point-In-Circle check on them.
Something like the following would suffice...
var result = [];
var d = 10;
var r = d / 2;
var rr = r*r;
for(var y=0; y<d; y++)
for(var x=0; x<d; x++)
if((x-r)*(x-r)+(y-r)*(y-r) < rr)
result.push({"x": x, "y": y});
Modifying the above algorithm to handle other (more complex) shapes/path/polygons would be difficult. For a more generic solution you could use HTML5 CANVAS. You create a canvas, get the 2d context draw all of your shapes/paths/polygons in solid black then iterate through the pixel data and find the pixels with an alpha channel greater than 0 (or 127 if you want to alleviate false positives from anti-aliasing).
var r = 5; // radius of bounding circle
//
// set up a canvas element
//
var canvas = document.createElement("canvas");
canvas.width = r*2;
canvas.height = r*2;
canvas.style.width = (r*2) + "px";
canvas.style.height = (r*2) + "px";
var ctx = canvas.getContext("2d");
ctx.fillStyle = "#000";
//
// draw your shapes/paths/polys here
//
ctx.beginPath();
ctx.arc(r, r, r, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
//
// process the pixel data
//
var imageData = ctx.getImageData(0,0,(r*2),(r*2));
var data = imageData.data;
var result = [];
var str = "";
for(var y = 0; y<(r*2); y++) {
for(var x = 0; x<(r*2); x++) {
var pixelOffset = (y * (r*2) + x) * 4;
if(data[pixelOffset+3] > 127) {
result.push({x: x, y: y});
str += "(" + x + ", " + y + ") "; // debug
}
}
}
//
// debug/test output
//
document.body.innerHTML += str;
document.body.appendChild(canvas);
alert(result.length);
I did an experiment today to see what I can do with <div>s. So I made a simple Paint-like program, which you can draw with <div>s.
$(window).mousemove(function(e){
if(!mousedown){
return false;
}
var x = e.clientX,
y = e.clientY;
drawDot(x,y,ele);
lastX = x;
lastY = y;
});
This is part of the code. It works, but there are gaps between dots. So I created a function called fillDot which will draw a line from point A (last point) to point B (current point).
drawDot(x,y,ele);
fillDot(lastX,lastY,x,y,ele);
function fillDot(lx,ly,x,y,canvas){
var rise = y - ly,
run = x - lx,
slope = rise / run,
yInt = y - (slope * x);
if( lx < x ){
for(var i = lx; i < x ; i+=.5){
var y = slope * i + yInt;
drawDot(i,y,canvas);
}
}else if( x < lx ){
for(var i = x; i < lx ; i++){
var y = slope * i + yInt;
drawDot(i,y,canvas);
}
}
}
It works fine only when I am drawing horizontal-ish lines. When I draw from top to bottom or bottom to top, there will still be gaps. I found something called Bresenham's line algorithm which can do the same thing, but I don't know how to use it...
Any idea how to fill all points in between?
ps: I know there is <canvas>, but I am testing what I can do with <div>.
Nevermind, I translated the Bresenham's line algorithm into JavaScript and it works perfectly now!
function fillDot(x0, y0, x1, y1){
var dx = Math.abs(x1-x0);
var dy = Math.abs(y1-y0);
var sx = (x0 < x1) ? 1 : -1;
var sy = (y0 < y1) ? 1 : -1;
var err = dx-dy;
while(true){
drawDot(x0,y0);
if ((x0==x1) && (y0==y1)) break;
var e2 = 2*err;
if (e2>-dy){
err -= dy;
x0 += sx;
}
if (e2 < dx){
err += dx;
y0 += sy;
}
}
}
I'm currently writing a little drawing application that needs to access pixel data for its smudge and blur tools and bumped into a nasty issue with HTML5 Canvas API in Firefox. Apparently it does not implement getImageData quite as defined in the spec. The spec specifically says "... Pixels outside the canvas must be returned as transparent black. ...".
This doesn't happen in FF (tested in FF 3.6 and 4 beta 9). Instead it will give an error such as this: An invalid or illegal string was specified" code: "12
Note that this appears to work in Chrome just fine.
I guess this means I will have to implement some extra code to work around this limitation. I managed to bypass the issue using the following code:
getImageDataAround: function(p, r) {
p = this._toAbsolute(p);
r = this._toAbsolute(r);
p = p.sub(r);
var d = r * 2;
var width = d;
var height = d;
// XXX: FF hack
if(navigator.userAgent.indexOf('Firefox') != -1) {
if(p.x < 0) {
width += p.x;
p.x = 0;
}
if(p.y < 0) {
height += p.y;
p.y = 0;
}
var x2 = p.x + width;
if(x2 >= this.width) {
width = d - (x2 - this.width);
}
var y2 = p.y + height;
if(y2 >= this.height) {
height = d - (y2 - this.height);
}
if((width != d) || (height != d)) {
// XXX: not ideal but at least this won't give any
// errors
return this.ctx.createImageData(d, d);
}
}
return this.ctx.getImageData(p.x, p.y, width, height);
},
This isn't cool since I return bunch of empty pixels to the caller. It would be way nicer to return results just like in the spec.
Just to clarify the code is a part of a Context API that wraps real context and provides some extra functionality (relative coords etc.). That probably explains where things like this.width etc. come from.
It's the XXX part that's troublesome. I simply need some way to return ImageData that's up to spec. Any ideas on how to do this are welcome. :)
Perhaps you could create a canvas of size d by d and draw the appropriate portion of the original canvas on to it? Sadly you can't draw the original canvas directly because you run into the same sort of bounds-checking code, so you have to figure out the overlap.
You should consider sniffing for Gecko rather than Firefox.
By the way, this is Mozilla bug 392751.
I ended up using following snippet to work around the issue. Hopefully someone finds it useful...
var getImageDataAround = function(ctx, p, r) {
// ctx: HTML5 Canvas 2D context
// p: {x: 23, y: 37}
// r: radius in px
// FF fails with fractional values
p.x = Math.round(p.x);
p.y = Math.round(p.y);
r = parseInt(r);
p.x -= r;
p.y -= r;
var d = r * 2;
var width = d;
var height = d;
// FF fails at bounds
if(navigator.userAgent.indexOf('Gecko') != -1) {
var xOffset = 0;
var yOffset = 0;
if(p.x < 0) {
xOffset = -p.x;
width += p.x;
p.x = 0;
}
if(p.y < 0) {
yOffset = -p.y;
height += p.y;
p.y = 0;
}
var x2 = p.x + width;
if(x2 >= ctx.canvas.width) {
width = d - (x2 - ctx.canvas.width);
}
var y2 = p.y + height;
if(y2 >= ctx.canvas.height) {
height = d - (y2 - ctx.canvas.height);
}
if((width != d) || (height != d)) {
var data = ctx.createImageData(d, d);
if(xOffset >= d || yOffset >= d ||
width < 1 || height < 1) {
// totally outside of bounds
return data;
}
var originalData = ctx.getImageData(p.x, p.y,
width, height);
var pos = 4 * (xOffset + d * yOffset);
var dataLen = 4 * d * (yOffset + height);
for(var originalPos = 0, x = xOffset;
pos < dataLen;
pos += 4, originalPos += 4, x++) {
if(x == d) {
x = xOffset;
pos += xOffset * 4;
}
if(xOffset <= x && x < width + xOffset) {
data.data[pos] = originalData.data[originalPos];
data.data[pos + 1] = originalData.data[originalPos + 1];
data.data[pos + 2] = originalData.data[originalPos + 2];
data.data[pos + 3] = originalData.data[originalPos + 3];
}
else {
originalPos -= 4;
}
}
return data;
}
}
return ctx.getImageData(p.x, p.y, width, height);
}