I'm trying to create the Mandelbrot set and other Julia Sets in JavaScript for a school project. I've looked over my code and it looks like it follows the formula correctly, but my Mandelbrot set is not being drawn correctly. Any tips on what's wrong?
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="canvas" width="600" height="600" style="background-color: black;"></canvas>
<script>
//center the canvas at the coordinate plane
var canvas = document.getElementById("canvas");
const width = canvas.width;
const height = canvas.height;
const scalingFactor = width / 4;
var graphics = canvas.getContext("2d");
function mandelbrotDraw(iterations) {
//multiply by 4 in order to get the scaling right
for (var i = 0; i <= width; i++) {
for (var j = 0; j <= height; j++) {
var x = 0;
var y = 0;
var re = (i - width / 2) / scalingFactor;
var im = (j - height / 2) / scalingFactor;
var k = 0;
while (x * x + y * y <= 4 && k < iterations) {
x = x * x - y * y + re;
y = 2 * x * y + im;
k++;
}
if (k < iterations) {
graphics.fillStyle = "black";
graphics.fillRect(i, j, 1, 1);
} else {
graphics.fillStyle = "white";
graphics.fillRect(i, j, 1, 1);
}
}
}
}
mandelbrotDraw(25);
</script>
</body>
</html>
Related
This question already has an answer here:
Unable to update HTML5 canvas pixel color using putImageData in JavaScript
(1 answer)
Closed last year.
I am trying to draw a gray gradient (or a few of them actually) to the canvas. To do this I am using the getImageData() and putImageData() methods of canvas. data is an Uint8ClampedArray with size 1000 * 1000 * 4. In the for loops the rgb color elements of data are correctly set to be x%255, as shown by printing the data array to console.
However the result of code differs from what is expected. A completely white cnvas is shown while 4 gray gradients are expected.
Minimal working example:
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id = "canvas" width = "1000" height = "1000"></canvas>
<script>
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
draw();
function draw(){
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data;
for(let y = 0; y < canvas.height; y++){
for(let x = 0; x < canvas.width; x++){
for(let color = 0; color < 3; color++){
let idx = (y * canvas.width + x) * 4;
data[idx + color] = x % 255;
}
}
}
ctx.putImageData(imageData, 0, 0);
}
</script>
</body>
</html>
You're not setting the alpha channel.
for (let y = 0; y < canvas.height; y++) {
for (let x = 0; x < canvas.width; x++) {
let idx = (y * canvas.width + x) * 4;
for (let color = 0; color < 3; color++) {
data[idx + color] = x % 255;
}
data[idx + 3] = 255; // alpha
}
}
So I've been reading this post which I found is the closest to what I want. Basically, I want two different text elements (two different words) moving around the page randomly and also bouncing off each other when they come in contact.
I have tried to edit the code so that it's black and have only one text element. However how can I also include a second text element (a different word) moving randomly as well?
Also how can I change it to Roboto Mono font?
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).slice(0);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
for (var i = 0; i < 1; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50,
height: 20,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
context.beginPath();
context.fillStyle = color;
context.font = "40px 'Roboto Mono'";
context.textBaseline = "hanging";
context.fillText("motion", x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>
Multiple text boxes is easy :
Change the initialize boxes part to include more than one.
// Initialize random boxes:
var boxes = [];
var boxCount = 2
for (var i = 0; i < boxCount; i++) {
Second your random color is flawed , try this :
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
I don't think that roboto is available in the canvas element. (I could be wrong)
monospace is available though.
Edit
If you want different words you could use another array for them:
var words = ['motion','designer']
for (var i = 0; i < words.length; i++) {
as you create boxes use the words array.
You can't hard code the size of the box if you want have the words to collide. Height should be at least the font height 40px and if you want to get the width of the text box there is a context helping function :
box.width = context.measureText(words[i]).width;
See the snippet for usage :
// Return random RGB color string:
function randomColor() {
var hex = Math.floor(Math.random() * 0x1000000).toString(16);
return "#" + ("000000" + hex).substr(hex.length);
}
// Poor man's box physics update for time step dt:
function doPhysics(boxes, width, height, dt) {
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Update positions:
box.x += box.dx * dt;
box.y += box.dy * dt;
// Handle boundary collisions:
if (box.x < 0) {
box.x = 0;
box.dx = -box.dx;
} else if (box.x + box.width > width) {
box.x = width - box.width;
box.dx = -box.dx;
}
if (box.y < 0) {
box.y = 0;
box.dy = -box.dy;
} else if (box.y + box.height > height) {
box.y = height - box.height;
box.dy = -box.dy;
}
}
// Handle box collisions:
for (let i = 0; i < boxes.length; i++) {
for (let j = i + 1; j < boxes.length; j++) {
var box1 = boxes[i];
var box2 = boxes[j];
var dx = Math.abs(box1.x - box2.x);
var dy = Math.abs(box1.y - box2.y);
// Check for overlap:
if (2 * dx < (box1.width + box2.width ) &&
2 * dy < (box1.height + box2.height)) {
// Swap dx if moving towards each other:
if ((box1.x > box2.x) == (box1.dx < box2.dx)) {
var swap = box1.dx;
box1.dx = box2.dx;
box2.dx = swap;
}
// Swap dy if moving towards each other:
if ((box1.y > box2.y) == (box1.dy < box2.dy)) {
var swap = box1.dy;
box1.dy = box2.dy;
box2.dy = swap;
}
}
}
}
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
// Initialize random boxes:
var boxes = [];
var words = ["designer","motion","hi"]
for (var i = 0; i < words.length; i++) {
var box = {
x: Math.floor(Math.random() * canvas.width),
y: Math.floor(Math.random() * canvas.height),
width: 50, // Will be dynamic
height: 42,
dx: (Math.random() - 0.5) * 0.3,
dy: (Math.random() - 0.5) * 0.3
};
boxes.push(box);
}
// Initialize random color and set up interval:
var color = randomColor();
setInterval(function() {
color = randomColor();
}, 450);
// Update physics at fixed rate:
var last = performance.now();
setInterval(function(time) {
var now = performance.now();
doPhysics(boxes, canvas.width, canvas.height, now - last);
last = now;
}, 50);
// Draw animation frames at optimal frame rate:
function draw(now) {
context.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < boxes.length; i++) {
var box = boxes[i];
// Interpolate position:
var x = box.x + box.dx * (now - last);
var y = box.y + box.dy * (now - last);
box.width = context.measureText(words[i]).width;
context.beginPath();
context.fillStyle = color;
context.font = "normal 40px monospace";
context.textBaseline = "hanging";
context.fillText(words[i], x, y);
context.closePath();
}
requestAnimationFrame(draw);
}
requestAnimationFrame(draw);
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
<!DOCTYPE html>
<html>
<body>
<head>
<link rel="stylesheet" href="test.css">
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
</head>
<canvas id="canvas"></canvas>
<script src="test.js"></script>
</body>
</html>
I'm using JsIso (found it on github) to (hopefully) make a fun little browser game. I modified the hardcoded values for a height map, into a variable and function to generate terrain randomly. What I would like to do, but can't picture in my head at all, is to have a given tile no more or less than 2 levels different than the tile next to it, getting rid of towers and pits.
This is my current code:
var tileHeightMap = generateGround(10, 10); //Simple usage
function generateGround(height, width)
{
var ground = [];
for (var y = 0 ; y < height; y++)
{
ground[y] = [];
for (var x = 0; x < width; x++)
{
ground[y][x] = tile();
}
}
return ground;
function tile()
{
return (Math.random() * 5 | 0);
}
}
It looks like it would be best to modify the tile function, perhaps passing it the value of the previous tile, and not the generate ground function. If more info is needed, let me know!
You can use a two-dimensional Value Noise.
It basically works like this:
Octave #1: Create a number of random points (8, for example) that are evenly spaced in x direction and interpolate between them (if you choose linear interpolation, it could look like this):
Octave #2: Do the same thing as in #1, but double the amount of points. The amplitude should be the half of the amplitude in #1. Now interpolate again and add the values from both octaves together.
Octave #3: Do the same thing as in #2, but with the double amount of points and an amplitude that is the half of the amplitude in #2.
Continue these steps as long as you want.
This creates a one-dimensional Value Noise. The following code generates a 2d Value Noise and draws the generated map to the canvas:
function generateHeightMap (width, height, min, max) {
const heightMap = [], // 2d array containing the heights of the tiles
octaves = 4, // 4 octaves
startFrequencyX = 2,
startFrequencyY = 2;
// linear interpolation function, could also be cubic, trigonometric, ...
const interpolate = (a, b, t) => (b - a) * t + a;
let currentFrequencyX = startFrequencyX, // specifies how many points should be generated in this octave
currentFrequencyY = startFrequencyY,
currentAlpha = 1, // the amplitude
octave = 0,
x = 0,
y = 0;
// fill the height map with zeros
for (x = 0 ; x < width; x += 1) {
heightMap[x] = [];
for (y = 0; y < height; y += 1) {
heightMap[x][y] = 0;
}
}
// main loop
for (octave = 0; octave < octaves; octave += 1) {
if (octave > 0) {
currentFrequencyX *= 2; // double the amount of point
currentFrequencyY *= 2;
currentAlpha /= 2; // take the half of the amplitude
}
// create random points
const discretePoints = [];
for (x = 0; x < currentFrequencyX + 1; x += 1) {
discretePoints[x] = [];
for (y = 0; y < currentFrequencyY + 1; y += 1) {
// create a new random value between 0 and amplitude
discretePoints[x][y] = Math.random() * currentAlpha;
}
}
// now interpolate and add to the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
const currentX = x / width * currentFrequencyX,
currentY = y / height * currentFrequencyY,
indexX = Math.floor(currentX),
indexY = Math.floor(currentY),
// interpolate between the 4 neighboring discrete points (2d interpolation)
w0 = interpolate(discretePoints[indexX][indexY], discretePoints[indexX + 1][indexY], currentX - indexX),
w1 = interpolate(discretePoints[indexX][indexY + 1], discretePoints[indexX + 1][indexY + 1], currentX - indexX);
// add the value to the height map
heightMap[x][y] += interpolate(w0, w1, currentY - indexY);
}
}
}
// normalize the height map
let currentMin = 2; // the highest possible value at the moment
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] < currentMin) {
currentMin = heightMap[x][y];
}
}
}
// currentMin now contains the smallest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] -= currentMin;
}
}
// now, the minimum value is guaranteed to be 0
let currentMax = 0;
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (heightMap[x][y] > currentMax) {
currentMax = heightMap[x][y];
}
}
}
// currentMax now contains the highest value in the height map
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] /= currentMax;
}
}
// the values are now in a range from 0 to 1, modify them so that they are between min and max
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
heightMap[x][y] = heightMap[x][y] * (max - min) + min;
}
}
return heightMap;
}
const map = generateHeightMap(40, 40, 0, 2); // map size 40x40, min=0, max=2
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
for (let x = 0; x < 40; x += 1) {
for (let y = 0; y < 40; y += 1) {
const height = map[x][y];
ctx.fillStyle = 'rgb(' + height * 127 + ', 127, 127)';
// draw the tile (tile size 5x5)
ctx.fillRect(x * 5, y * 5, 5, 5);
}
}
<canvas width="200" height="200"></canvas>
Note that the values in this height map can reach from -2 to 2. To change that, change the method that is used to create the random values.
Edit:
I made a mistake there, the version before the edit reached from -1 to 1. I modified it so that you can easily specify the minimum and maximum value.
First, I normalize the height map so that the values really reach from 0 to 1. Then, I modify all values so that they are between the specified min and max value.
Also, I changed how the heights are displayed. Instead of land and water, it now displays a smooth noise. The more red a point contains, the higher it is.
By the way, this algorithm is widely used in Procedural Content Generation for games.
If you want further explanation, just ask!
What I've been trying to do is to assign pixels colours based on their coordinates. I've done something but I'm quite new to this and I'm not sure why it doesn't work.
var canvas = document.getElementById("plane");
var context = canvas.getContext("2d");
// parameters of the plance - should be editable
var width = canvas.width;
var height = canvas.heigth;
var x;
var y;
var color;
var imagedata = context.createImageData(width, height);
function createImage(offset)
{
for (x = 0; x < width; x += 1)
{
for (y = 0; y < height; y += 1)
{
if (x < width / 3)
color = {r: 256, g: 0, b: 0};
else if (x < width / 2)
color = {r: 0, g: 256, b: 0};
else if (x < width)
color = {r: 0, g: 0, b: 256};
var pixelindex = (y * width + x) * 4;
imagedata.data[pixelindex] = color.r;
imagedata.data[pixelindex + 1] = color.g;
imagedata.data[pixelindex + 2] = color.b;
imagedata.data[pixelindex + 3] = 255;
}
}
}
context.putImageData(imagedata, 0, 0);
I created the HTML separately.
<html>
<head>
<meta charset="utf-8">
<title>
Something
</title>
<script type="text/javascript" src="something.js"></script>
</head>
<body>
<canvas id="plane" width="640" height="480"></canvas>
</body>
</html>
Thank you for your help.
All you did wrong is a typo in var height = canvas.heigth;, see "heigth", should be "height" and you forgot to call createImage(0);. That the image is not split evenly in three parts is because your math of width / 3, width / 2 and width just is not an even split, mathematically.
Corrected version if needed:
var canvas = document.getElementById("plane");
var context = canvas.getContext("2d");
// parameters of the plance - should be editable
var width = canvas.width;
var height = canvas.height;
var x;
var y;
var color;
var imagedata = context.createImageData(width, height);
function createImage(offset) {
for (x = 0; x < width; x += 1) {
for (y = 0; y < height; y += 1) {
if (x < width / 3) {
color = {r: 256, g: 0, b: 0};
} else if (x < 2* width / 3) {
color = {r: 0, g: 256, b: 0};
} else if (x < width) {
color = {r: 0, g: 0, b: 256};
}
var pixelindex = (y * width + x) * 4;
imagedata.data[pixelindex] = color.r;
imagedata.data[pixelindex + 1] = color.g;
imagedata.data[pixelindex + 2] = color.b;
imagedata.data[pixelindex + 3] = 255;
}
}
}
createImage(0)
context.putImageData(imagedata, 0, 0);
<html>
<head>
<meta charset="utf-8">
<title>
Something
</title>
</head>
<body>
<canvas id="plane" width="640" height="480"></canvas>
</body>
</html>
On the side, you could also use fillRect or other canvas features (green grid as example):
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
for (let w = 0; w < canvas.width; w++) {
for (let h = 0; h < canvas.height; h++) {
if ((w + h) % 2 === 0)
ctx.fillRect(w, h, 1, 1);
}
}
<html>
<body>
<canvas id="canvas" width="180" height="120">
your browswer does not support canvas
</canvas>
</body>
</html>
I have this code of a sliding puzzle game.
When I use exactly 500x500 pixel image everything works fine but when I insert a large image, only the left upper corner of the image is visible and only this part is devided into 16 puzzle parts. How do I fit the whole image on the canvas?
<!DOCTYPE html>
<html><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<style>
.picture {
border: 1px solid black;
}
</style>
</head>
<body>
<div id="title">
<h2>Sliding Puzzle</h2>
</div>
<div id="slider">
<form>
<label>Easy</label>
<input type="range" id="scale" value="4" min="3" max="5" step="1">
<label>Hard</label>
</form>
<br>
</div>
<div id="main" class="main">
<canvas id="puzzle" width="500px" height="500px"></canvas>
</div>
<script>
var context = document.getElementById('puzzle').getContext('2d');
var img = new Image();
img.src = 'http://www.antilimit.com/nature/i/37.jpg';
img.addEventListener('load', drawTiles, false);
var boardSize = document.getElementById('puzzle').width;
var tileCount = 4
var tileSize = boardSize / tileCount;
var clickLoc = new Object;
clickLoc.x = 0;
clickLoc.y = 0;
var emptyLoc = new Object;
emptyLoc.x = 0;
emptyLoc.y = 0;
var solved = false;
var boardParts;
setBoard();
document.getElementById('scale').onchange = function() {
tileCount = this.value;
tileSize = boardSize / tileCount;
setBoard();
drawTiles();
};
document.getElementById('puzzle').onclick = function(e) {
clickLoc.x = Math.floor((e.pageX - this.offsetLeft) / tileSize);
clickLoc.y = Math.floor((e.pageY - this.offsetTop) / tileSize);
if (distance(clickLoc.x, clickLoc.y, emptyLoc.x, emptyLoc.y) == 1) {
slideTile(emptyLoc, clickLoc);
drawTiles();
}
if (solved) {
setTimeout(function() {alert("You solved it!");}, 500);
}
};
function setBoard() {
boardParts = new Array(tileCount);
for (var i = 0; i < tileCount; ++i) {
boardParts[i] = new Array(tileCount);
for (var j = 0; j < tileCount; ++j) {
boardParts[i][j] = new Object;
boardParts[i][j].x = (tileCount - 1) - i;
boardParts[i][j].y = (tileCount - 1) - j;
}
}
emptyLoc.x = boardParts[tileCount - 1][tileCount - 1].x;
emptyLoc.y = boardParts[tileCount - 1][tileCount - 1].y;
solved = false;
}
function drawTiles() {
context.clearRect ( 0 , 0 , boardSize , boardSize );
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if(i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize, y * tileSize, tileSize, tileSize,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
function distance(x1, y1, x2, y2) {
return Math.abs(x1 - x2) + Math.abs(y1 - y2);
}
function slideTile(toLoc, fromLoc) {
if (!solved) {
boardParts[toLoc.x][toLoc.y].x = boardParts[fromLoc.x][fromLoc.y].x;
boardParts[toLoc.x][toLoc.y].y = boardParts[fromLoc.x][fromLoc.y].y;
boardParts[fromLoc.x][fromLoc.y].x = tileCount - 1;
boardParts[fromLoc.x][fromLoc.y].y = tileCount - 1;
toLoc.x = fromLoc.x;
toLoc.y = fromLoc.y;
checkSolved();
}
}
function checkSolved() {
var flag = true;
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
if (boardParts[i][j].x != i || boardParts[i][j].y != j) {
flag = false;
}
}
}
solved = flag;
}
</script>
</body></html>
The image you are using in your example is 1000px by 1000px, so dividing this into sixteen tiles requires each the image to be split into sixteen sections where each section will be 250px by 250px. Currently you are using sixteen sections each of which is 125px by 125px and so your are only copying the top left quarter of the image.
Take a rectangular image of width W and height H, then starting at the top left corner you can take a square image of imgSize = W if W<=H or of imgSize = H if H
You find the scaling factor imgScale = imgSize/500
In your example imgSize = 1000 and so imgScale = 2 so you want to copy sections twice the width and height of the tiles
Change the drawTiles function to
function drawTiles() {
var imgSize=Math.min(img.width,img.height);
var imgScale=imgSize/500;
context.clearRect ( 0 , 0 , boardSize , boardSize );
for (var i = 0; i < tileCount; ++i) {
for (var j = 0; j < tileCount; ++j) {
var x = boardParts[i][j].x;
var y = boardParts[i][j].y;
if(i != emptyLoc.x || j != emptyLoc.y || solved == true) {
context.drawImage(img, x * tileSize*imgScale, y * tileSize*imgScale, tileSize*imgScale, tileSize*imgScale,
i * tileSize, j * tileSize, tileSize, tileSize);
}
}
}
}
Here is a jsfiddle