Draw ASCII art version of image on canvas using canvas-sketch (Javascript) - javascript

The idea was for the code to draw an ASCII art version of an image and my initial code was drawing the glyphs on the background and not the profile of the person.
profile of Pastor Gospel
I played around a bit and found out that the profile wasn't being drawn because the resolution of that part is low so I included these 2 lines of code: if (v < 40) return "."; if (v < 50) return "/";.
And it looks pretty decent but the thing is I can't see the eyes, nose and whatnots because it's not clear. So my question is how can I made them visible?
This is my code:
const canvasSketch = require("canvas-sketch");
const random = require("canvas-sketch-util/random");
const settings = {
dimensions: [1080, 1080],
};
let manager, image;
let text = "C";
let fontSize = 1200;
let fontFamily = "serif";
const typeCanvas = document.createElement("canvas");
const typeContext = typeCanvas.getContext("2d");
const sketch = ({ context, width, height }) => {
const cell = 20;
const cols = Math.floor(width / cell);
const rows = Math.floor(width / cell);
const numCells = cols * rows;
typeCanvas.width = cols;
typeCanvas.height = rows;
return ({ context, width, height }) => {
typeContext.fillStyle = "black";
typeContext.fillRect(0, 0, cols, rows);
typeContext.save();
typeContext.drawImage(image, 0, 0, cols, rows);
typeContext.restore();
const typeData = typeContext.getImageData(0, 0, cols, rows).data;
context.fillStyle = "black";
context.fillRect(0, 0, width, height);
for (let i = 0; i < numCells; i++) {
const col = i % cols;
const row = Math.floor(i / cols);
const x = col * cell + random.range(-cell, cell) * 0.5;
const y = row * cell + random.range(-cell, cell) * 0.5;
const r = typeData[i * 4 + 0];
const g = typeData[i * 4 + 1];
const b = typeData[i * 4 + 2];
const a = typeData[i * 4 + 3];
const glyph = getGlyph(r);
context.font = `${cell * 2}px ${fontFamily}`;
if (Math.random() < 0.1) context.font = `${cell * 6}px ${fontFamily}`;
context.fillStyle = `rgb(${r}, ${g}, ${b})`;
//context.fillStyle = "black";
context.save();
context.translate(x, y);
context.translate(cell * 0.5, cell * 0.5);
//context.fillRect(0, 0, cell, cell);
//context.fillStyle = "white";
context.fillText(glyph, 0, 0);
context.restore();
}
context.drawImage(typeCanvas, 0, 0);
};
};
const getGlyph = (v) => {
if (v < 40) return ".";
if (v < 50) return "/";
if (v < 100) return ".";
if (v < 150) return "-";
if (v < 200) return "+";
const glyphs = "_= /".split("");
return random.pick(glyphs);
};
const loadMeSomeImage = (url) => {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = () => reject();
img.src = url;
});
};
const start = async () => {
const url = "./headshot-pstG.jpeg";
image = await loadMeSomeImage(url);
typeCanvas.width = image.width;
typeCanvas.height = image.height;
manager = await canvasSketch(sketch, settings);
};
start();

Related

Algorithm for adding black bars to an image

I am trying to write a function that adds black bars to an image such as in the example below:
before and after
I have the following code. I believe the algorithm is correct, however, it only works for certain images and certain values. For example, it does work when the "cuts" variable is equal to 7 and the resolution of the uploaded image is 1920x1080, however it doesn't for any other values other than 7, 1 or 2. Why?
Also, if you think of a better algorithm please share it with me.
rectangleButton.addEventListener("click", () => {
if(loadCounter === 4) {
var cuts = parseInt(text.value);
text.value = null;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var divisions = (2*cuts)+1;
var lines = canvas.height;
var columns = canvas.width;
console.log(canvas.height);
console.log(canvas.width);
console.log(divisions);
var k = 1;
var scannedRectangleImage = ctx.getImageData(0, 0, canvas.width, canvas.height);
var scannedRectangleImageData = scannedRectangleImage.data;
for(k = 1; k < 2*cuts; k = k+2) {
for(var i = k*lines/divisions; i < (k+1)*lines/divisions; i++)
{
for(var j = 0; j < columns; j++)
{
scannedRectangleImageData[i*4*columns+4*j] = 0;
scannedRectangleImageData[i*4*columns+4*j+1] = 0;
scannedRectangleImageData[i*4*columns+4*j+2] = 0;
}
}
console.log(k + " < " + 2*cuts);
}
scannedRectangleImage.data = scannedRectangleImageData;
ctx.putImageData(scannedRectangleImage, 0, 0); }
else
{
alert("Image upload is not ready");
}
})
This is how I would do it
function drawImage(image) {
const canvas = document.getElementById('canvas');
canvas.setAttribute('width', image.width);
canvas.setAttribute('height', image.height);
const ctx = canvas.getContext('2d');
ctx.drawImage(image, 0, 0);
const stripeCount = document.getElementById('stripeCount').value;
const sectionCount = 2 * stripeCount + 1;
const stripeHeight = Math.ceil(image.height / sectionCount);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let i = 0; i < stripeCount; i++) {
drawStripe(2 * i + 1, stripeHeight, imageData.data, canvas.width);
}
ctx.putImageData(imageData, 0, 0);
}
function drawStripe(stripeIdx, stripeHeight, imageData, imageWidth) {
const lineStart = stripeIdx * stripeHeight;
const dataStart = lineStart * imageWidth * 4;
const dataLength = stripeHeight * imageWidth * 4;
for (let idx = dataStart, l = dataStart + dataLength; idx < l; idx += 4) {
imageData[idx] = 0;
imageData[idx + 1] = 0;
imageData[idx + 2] = 0;
}
}

Problems integrating P5 blob maker with React-P5 wrapper and as a graphics layer

I am trying to integrate this P5 Blob Maker into one of my React components. It's rendered as a graphics layer on top of an image loaded from a file reader. The P5 canvas size is set to the same size as any image loads. I had to substitute TWO_PI with Math.PI*2, HALF_PI with Math.PI/2, and all of the other math operators from P5 like random(), cos() and sin() with a Math. in front. I think the things listed above should not be causing problems, however, my blob shape is not very blobby and remains stuck at the corner of the canvas. I also added a transparency to the shape. I see it kind of working as the graphics layer is cleared when the blob is being re-built, but then it becomes opaque again.
Please help me review my code:
import React from 'react';
import Sketch from 'react-p5';
import ColorSelector from './ColorSelector';
import FileInput from '../FileInput';
var b;
var img;
var pg;
var imgWidth;
var imgHeight;
var angleSlider;
let distanceSlider;
var blobPoints = [];
let numPoints = 6; // try different values for different shaped blobs
let baseRadius = 100;
let radiusRandomness = 0.2; // amount of random variation in the blob radius
let cpOffsetAngle;
let cpdist;
var transparency;
export default function P5Mold() {
const [color, setColor] = React.useState(['#ff0000']);
const [image, setImage] = React.useState(null);
const setup = (p5, canvasParentRef) => {
p5.createCanvas(400, 400).parent(canvasParentRef);
pg = p5.createGraphics(600, 600);
img = p5.loadImage(image, img => {
p5.image(img, 0, 0);
});
angleSlider = p5.createSlider(0, 2.4, 2, 0.05);
angleSlider.position(200, 250);
angleSlider.changed(buildBlob);
distanceSlider = p5.createSlider(10, 150, 50, 5);
distanceSlider.position(200, 300);
distanceSlider.changed(buildBlob);
buildBlob();
}
const draw = p5 => {
if (image) {
imgWidth = img.width;
imgHeight = img.height;
if (imgWidth > 0 && imgHeight > 0) {
p5.resizeCanvas(imgWidth, imgHeight);
}
p5.image(img, 0, 0);
transparency = p5.color(color);
transparency.setAlpha(10);
pg.fill(transparency);
pg.stroke(transparency);
pg.beginShape();
pg.vertex();
for (b = 1; b < blobPoints.length; b++) {
let bp = blobPoints[b];
let pp = blobPoints[b - 1];
pg.bezierVertex(pp.cp[1].x, pp.cp[1].y, bp.cp[0].x, bp.cp[0].y, bp.x, bp.y);
let lastp = blobPoints[blobPoints.length - 1];
let firstp = blobPoints[0]
pg.bezierVertex(lastp.cp[1].x, lastp.cp[1].y, firstp.cp[0].x, firstp.cp[0].y, firstp.x, firstp.y);
pg.endShape();
}
p5.image(pg, 0, 0, imgWidth, imgHeight);
} else {
return null
}
}
function buildBlob(p5) {
pg.clear();
blobPoints = [];
cpOffsetAngle = angleSlider.value();
cpdist = distanceSlider.value();
for (let p = 0; p < numPoints; p++) {
let a = p * (Math.PI * 2) / numPoints;
let r = baseRadius + Math.random(-radiusRandomness * baseRadius, radiusRandomness * baseRadius);
let bp = {
x: Math.cos(a) * r,
y: Math.sin(a) * r,
angle: a,
cp: []
};
blobPoints.push(bp);
}
for (let b = 0; b < blobPoints.length; b++) {
let thisp = blobPoints[b];
let randomangle = Math.random(-cpOffsetAngle, cpOffsetAngle);
let cp1angle = thisp.angle - ((Math.PI / 2) + randomangle);
let cp2angle = thisp.angle + ((Math.PI / 2) - randomangle);
let cp1 = {
x: thisp.x + (Math.cos(cp1angle) * cpdist),
y: thisp.y + (Math.sin(cp1angle) * cpdist)
};
let cp2 = {
x: thisp.x + (Math.cos(cp2angle) * cpdist),
y: thisp.y + (Math.sin(cp2angle) * cpdist)
};
thisp.cp = [cp1, cp2];
}
}
if (image) {
return (
<div>
{image && (
<div>
<Sketch setup={setup} draw={draw} />
</div>
)}
<ColorSelector selectColor={color => setColor(color)} />
</div>
)
} else {
return (
<FileInput selectImage={setImage} />
);
}
}
Math.random() works differently than the p5.js random(min, max) function. The built-in JavaScript Math.random() function does not take any parameters and always returns a number between 0 and 1. In order to use Math.random() in place of the p5.js random(min, max) function you would want to use an expression like this:
min + Math.random() * (max - min)
However, in every case where you've switched from using p5.js functions and variables to using the built in JavaScript alternatives you could have just used the p5 object (i.e. p5.sin(), p5.PI, p5.random() etc).

Runtime differences between two machines in TF.js

I was working on a small scale project for Fashion MNIST. I have used the below code. I first tried executing the code on my primary machine and received an unchanging loss of ~2, after which I tried running the same code on my secondary machine and I could see that my loss and accuracy metrics were performing just the way they should have.
Here is my index.html
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#latest"></script>
<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs-vis"></script>
</head>
<body>
<h1>Fashion Classifier!</h1>
<canvas id="canvas" width="280" height="280" style="position:absolute;top:100;left:100;border:8px solid;"></canvas>
<img id="canvasimg" style="position:absolute;top:10%;left:52%;width=280;height=280;display:none;">
<input type="button" value="classify" id="sb" size="48" style="position:absolute;top:400;left:100;">
<input type="button" value="clear" id="cb" size="23" style="position:absolute;top:400;left:180;">
<script src="fashion-data.js" type="module"></script>
<script src="fashion-script_exercise.js" type="module"></script>
</body>
</html>
A JS code to get the data
const IMAGE_SIZE = 784;
const NUM_CLASSES = 10;
const NUM_DATASET_ELEMENTS = 70000;
const TRAIN_TEST_RATIO = 1 / 7;
const NUM_TRAIN_ELEMENTS = Math.floor(TRAIN_TEST_RATIO * NUM_DATASET_ELEMENTS);
const NUM_TEST_ELEMENTS = NUM_DATASET_ELEMENTS - NUM_TRAIN_ELEMENTS;
const MNIST_IMAGES_SPRITE_PATH =
'https://storage.googleapis.com/learnjs-data/model-builder/fashion_mnist_images.png';
const MNIST_LABELS_PATH =
'https://storage.googleapis.com/learnjs-data/model-builder/fashion_mnist_labels_uint8';
export class FMnistData {
constructor() {
this.shuffledTrainIndex = 0;
this.shuffledTestIndex = 0;
}
async load() {
// Make a request for the MNIST sprited image.
const img = new Image();
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const imgRequest = new Promise((resolve, reject) => {
img.crossOrigin = '';
img.onload = () => {
img.width = img.naturalWidth;
img.height = img.naturalHeight;
const datasetBytesBuffer =
new ArrayBuffer(NUM_DATASET_ELEMENTS * IMAGE_SIZE * 4);
const chunkSize = 5000;
canvas.width = img.width;
canvas.height = chunkSize;
for (let i = 0; i < NUM_DATASET_ELEMENTS / chunkSize; i++) {
const datasetBytesView = new Float32Array(
datasetBytesBuffer, i * IMAGE_SIZE * chunkSize * 4,
IMAGE_SIZE * chunkSize);
ctx.drawImage(
img, 0, i * chunkSize, img.width, chunkSize, 0, 0, img.width,
chunkSize);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let j = 0; j < imageData.data.length / 4; j++) {
// All channels hold an equal value since the image is grayscale, so
// just read the red channel.
datasetBytesView[j] = imageData.data[j * 4] / 255;
}
}
this.datasetImages = new Float32Array(datasetBytesBuffer);
resolve();
};
img.src = MNIST_IMAGES_SPRITE_PATH;
});
const labelsRequest = fetch(MNIST_LABELS_PATH);
const [imgResponse, labelsResponse] =
await Promise.all([imgRequest, labelsRequest]);
this.datasetLabels = new Uint8Array(await labelsResponse.arrayBuffer());
this.trainIndices = tf.util.createShuffledIndices(NUM_TRAIN_ELEMENTS);
this.testIndices = tf.util.createShuffledIndices(NUM_TEST_ELEMENTS);
this.trainImages =
this.datasetImages.slice(0, IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.testImages = this.datasetImages.slice(IMAGE_SIZE * NUM_TRAIN_ELEMENTS);
this.trainLabels =
this.datasetLabels.slice(0, NUM_CLASSES * NUM_TRAIN_ELEMENTS);
this.testLabels =
this.datasetLabels.slice(NUM_CLASSES * NUM_TRAIN_ELEMENTS);
}
nextTrainBatch(batchSize) {
return this.nextBatch(
batchSize, [this.trainImages, this.trainLabels], () => {
this.shuffledTrainIndex =
(this.shuffledTrainIndex + 1) % this.trainIndices.length;
return this.trainIndices[this.shuffledTrainIndex];
});
}
nextTestBatch(batchSize) {
return this.nextBatch(batchSize, [this.testImages, this.testLabels], () => {
this.shuffledTestIndex =
(this.shuffledTestIndex + 1) % this.testIndices.length;
return this.testIndices[this.shuffledTestIndex];
});
}
nextBatch(batchSize, data, index) {
const batchImagesArray = new Float32Array(batchSize * IMAGE_SIZE);
const batchLabelsArray = new Uint8Array(batchSize * NUM_CLASSES);
for (let i = 0; i < batchSize; i++) {
const idx = index();
const image =
data[0].slice(idx * IMAGE_SIZE, idx * IMAGE_SIZE + IMAGE_SIZE);
batchImagesArray.set(image, i * IMAGE_SIZE);
const label =
data[1].slice(idx * NUM_CLASSES, idx * NUM_CLASSES + NUM_CLASSES);
batchLabelsArray.set(label, i * NUM_CLASSES);
}
const xs = tf.tensor2d(batchImagesArray, [batchSize, IMAGE_SIZE]);
const labels = tf.tensor2d(batchLabelsArray, [batchSize, NUM_CLASSES]);
return {xs, labels};
}
}
The implementation JS file
import {FMnistData} from './fashion-data.js';
var canvas, ctx, saveButton, clearButton;
var pos = {x:0, y:0};
var rawImage;
var model;
function getModel() {
model = tf.sequential();
model.add(tf.layers.conv2d({inputShape: [28, 28, 1], kernelSize: 3, filters: 8, activation: 'relu'}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.conv2d({filters: 16, kernelSize: 3, activation: 'relu'}));
model.add(tf.layers.maxPooling2d({poolSize: [2, 2]}));
model.add(tf.layers.flatten());
model.add(tf.layers.dense({units: 128, activation: 'relu'}));
model.add(tf.layers.dense({units: 10, activation: 'softmax'}));
model.compile({optimizer: tf.train.adam(), loss: 'categoricalCrossentropy', metrics: ['accuracy']});
return model;
}
async function train(model, data) {
const metrics = ['loss', 'val_loss', 'acc', 'val_acc'];
const container = { name: 'Model Training', styles: { height: '1000px' } };
const fitCallbacks = tfvis.show.fitCallbacks(container, metrics);
const BATCH_SIZE = 512;
const TRAIN_DATA_SIZE = 6000;
const TEST_DATA_SIZE = 1000;
const [trainXs, trainYs] = tf.tidy(() => {
const d = data.nextTrainBatch(TRAIN_DATA_SIZE);
return [
d.xs.reshape([TRAIN_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
const [testXs, testYs] = tf.tidy(() => {
const d = data.nextTestBatch(TEST_DATA_SIZE);
return [
d.xs.reshape([TEST_DATA_SIZE, 28, 28, 1]),
d.labels
];
});
return model.fit(trainXs, trainYs, {
batchSize: BATCH_SIZE,
validationData: [testXs, testYs],
epochs: 10,
shuffle: true,
callbacks: fitCallbacks
});
}
function setPosition(e){
pos.x = e.clientX-100;
pos.y = e.clientY-100;
}
function draw(e) {
if(e.buttons!=1) return;
ctx.beginPath();
ctx.lineWidth = 24;
ctx.lineCap = 'round';
ctx.strokeStyle = 'white';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
rawImage.src = canvas.toDataURL('image/png');
}
function erase() {
ctx.fillStyle = "black";
ctx.fillRect(0,0,280,280);
}
function save() {
var raw = tf.browser.fromPixels(rawImage,1);
var resized = tf.image.resizeBilinear(raw, [28,28]);
var tensor = resized.expandDims(0);
var prediction = model.predict(tensor);
var pIndex = tf.argMax(prediction, 1).dataSync();
var classNames = ["T-shirt/top", "Trouser", "Pullover",
"Dress", "Coat", "Sandal", "Shirt",
"Sneaker", "Bag", "Ankle boot"];
alert(classNames[pIndex]);
}
function init() {
canvas = document.getElementById('canvas');
rawImage = document.getElementById('canvasimg');
ctx = canvas.getContext("2d");
ctx.fillStyle = "black";
ctx.fillRect(0,0,280,280);
canvas.addEventListener("mousemove", draw);
canvas.addEventListener("mousedown", setPosition);
canvas.addEventListener("mouseenter", setPosition);
saveButton = document.getElementById('sb');
saveButton.addEventListener("click", save);
clearButton = document.getElementById('cb');
clearButton.addEventListener("click", erase);
}
async function run() {
const data = new FMnistData();
await data.load();
const model = getModel();
tfvis.show.modelSummary({name: 'Model Architecture'}, model);
await train(model, data);
await model.save('downloads://my_model');
init();
alert("Training is done, try classifying your drawings!");
}
document.addEventListener('DOMContentLoaded', run);
I used the same version of Chrome and the Chrome server extension to run the code. What could possibly be the problem? Note: I have also checked the console logs and receive no errors there too.
I can see in your code no initialization of the kernel weights, so depending on the default implementation on different machines, you might have the weight initialized to 0, which makes it very difficult for the optimizer to initiate its convergence.
Try in the implementation JS file section, in the get_model function, in the layers definition, to add an option kernelInitializer: 'glorotUniform' to see if any improvement.

Issues with multiple p5.image

I'm playing around with creating some optical art with p5 and I'm running into issues with the image function. This is mt sketch.js file:
import Tile from "./Tile.js";
new p5(p5 => {
const rows = 14;
const columns = 14;
const dimension = 40
const height = rows * dimension;
const width = columns * dimension;
const framerate = 1;
const tiles = [];
p5.setup = () => {
p5.createCanvas(width, height);
p5.frameRate(framerate);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < columns; c++) {
tiles.push(new Tile(p5, c * dimension, r * dimension, dimension, r, c));
}
}
};
p5.draw = () => {
p5.background(200);
tiles.forEach((tile) => {
console.log(tile);
p5.image(tile.update(), tile.x, tile.y);
});
};
});
And this is Tile.js:
export default class Tile {
constructor(p5, x, y, dimension, row, column) {
this.p5 = p5;
this.x = x;
this.y = y;
this.dimension = dimension;
this.row = row;
this.column = column;
this.onFirst = true;
this.on = p5.color(255, 184, 0);
this.off = p5.color(26, 17, 16);
this.diameter = Math.sqrt(Math.pow(dimension, 2) * 2)
this.pg = this.p5.createGraphics(dimension, dimension)
this.pg.noStroke();
}
update() {
if (this.diameter < 0) {
this.diameter = Math.sqrt(Math.pow(this.dimension, 2) * 2);
this.onFirst = !this.onFirst
}
else {
this.diameter -= 1;
}
return this.draw();
}
draw() {
this.pg.fill(this.onFirst ? this.off : this.on);
this.pg.rect(this.x, this.y, this.dimension, this.dimension);
this.pg.fill(this.onFirst ? this.on : this.off);
this.pg.circle(this.x + this.dimension / 2, this.y + this.dimension / 2, this.diameter);
return this.pg;
}
}
While I starting out I just had the one image and that was displayed on the top left as I wanted... subsequent images are not though, artefacts appear in odd places though (as you can see here: https://jsfiddle.net/annoyingmouse/Lbs9v8f4/). I've tried all sorts of things like exporting the image as a Base64 image and using loadImage but I think I might've been barking up the wrong tree.
Any help would be greatly appreciated :-)
Note, the method Tile.draw draws to an image (.pg) with a size of (.dimension, .dimension) rather than the canvas.
So the the origin has to be (0, 0) rather than (.x, .y), because the final image (.pg) is placed on the canvas at (.x, .y).
Change the code as follows, to solve the issue:
draw() {
this.pg.fill(this.onFirst ? this.off : this.on);
// this.pg.rect(this.x, this.y, this.dimension, this.dimension);
this.pg.rect(0, 0, this.dimension, this.dimension);
this.pg.fill(this.onFirst ? this.on : this.off);
// this.pg.circle(this.x + this.dimension / 2, this.y + this.dimension / 2, this.diameter);
this.pg.circle(this.dimension / 2, this.dimension / 2, this.diameter);
return this.pg;
}
See the example:
new p5(p5 => {
const rows = 14;
const columns = 14;
const dimension = 40
const height = rows * dimension;
const width = columns * dimension;
const framerate = 60;
const tiles = [];
p5.setup = () => {
p5.createCanvas(width, height);
p5.frameRate(framerate);
for (let r = 0; r < rows; r++) {
for (let c = 0; c < columns; c++) {
tiles.push(new Tile(p5, c * dimension, r * dimension, dimension, r, c));
}
}
};
p5.draw = () => {
p5.background(200);
tiles.forEach((tile) => {
console.log(tile);
p5.image(tile.update(), tile.x, tile.y);
});
};
});
class Tile {
constructor(p5, x, y, dimension, row, column) {
this.p5 = p5;
this.x = x;
this.y = y;
this.dimension = dimension;
this.row = row;
this.column = column;
this.onFirst = true;
this.on = p5.color(255, 184, 0);
this.off = p5.color(26, 17, 16);
this.diameter = Math.sqrt(Math.pow(dimension, 2) * 2)
this.pg = this.p5.createGraphics(dimension, dimension)
this.pg.noStroke();
}
update() {
if (this.diameter < 0) {
this.diameter = Math.sqrt(Math.pow(this.dimension, 2) * 2);
this.onFirst = !this.onFirst
}
else {
this.diameter -= 1;
}
return this.draw();
}
draw() {
this.pg.fill(this.onFirst ? this.off : this.on);
this.pg.rect(0, 0, this.dimension, this.dimension);
this.pg.fill(this.onFirst ? this.on : this.off);
this.pg.circle(this.dimension / 2, this.dimension / 2, this.diameter);
return this.pg;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>

Adding falling objects with different colors

Here is the JavaScript for a catcher game I’m making. Some of the code was given, which is why I quite fully don’t understand how to do certain things. Right now, I’m have different faller objects that are basically red rectangles that vary in height and width. What I’m trying to do is make it so that the faller objects randomize between red and blue (blue showing up less) but I’m extremely confused as how to do so. I tried making it so that the colors added to game.fillstyle were randomized prior, but that doesn’t seem to be working. Any help or advice is greatly appreciated–doesn’t have to be an answer. I’m just looking to figure this out.
Also, if I should put all of the code in please let me know.
Here is the JSfiddle : https://jsfiddle.net/ianlizzo/4dLr48v0/7/#&togetherjs=irGLk3uxOE
(() => {
let canvas = document.getElementById("game");
let game = canvas.getContext("2d");
let lastTimestamp = 0;
const FRAME_RATE = 60;
const FRAME_DURATION = 1000 / FRAME_RATE;
let fallers = [];
let score = 0;
let colourValues = ["red", "blue"]
colourValues = {
red: "#ff0000",
blue: "#0000ff"
};
let colour = colourValues[Math.floor(Math.random()*colourValues.length)];
//ignore
//let scoreCount = document.getElementById("scoreCount”);
let showScore = function(){
scoreCount.innerHTML = "Your score is " + score;
};
let addScore = function(pointValue){
score += pointValue;
showScore();
};
let fallerIn = function(inside){
inside.captured = true;
addScore(inside.pointValue);
};
const DEFAULT_DESCENT = 0.0001; // This is per millisecond.
let Faller = function (x, y, width, height, dx = 0, dy = 0, ax = 0, ay = DEFAULT_DESCENT) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.captured = false;
this.pointValue = 5;
this.colour;
// Velocity.
this.dx = dx;
this.dy = dy;
// Acceleration.
this.ax = ax;
this.ay = ay;
};
Faller.prototype.draw = function () {
game.fillStyle = colour;
game.fillRect(this.x, this.y, this.width, this.height);
};
Faller.prototype.move = function (millisecondsElapsed) {
this.x += this.dx * millisecondsElapsed;
this.y += this.dy * millisecondsElapsed;
this.dx += this.ax * millisecondsElapsed;
this.dy += this.ay * millisecondsElapsed;
};
const DEFAULT_PLAYER_WIDTH = 65;
const DEFAULT_PLAYER_HEIGHT = 45;
const DEFAULT_PLAYER_Y = canvas.height - DEFAULT_PLAYER_HEIGHT;
let Player = function (x, y = DEFAULT_PLAYER_Y, width = DEFAULT_PLAYER_WIDTH, height = DEFAULT_PLAYER_HEIGHT) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
};
Player.prototype.draw = function () {
let grd = game.createLinearGradient(0, 200, 200, 0);
grd.addColorStop(0, "black");
grd.addColorStop(0.5, "red");
grd.addColorStop(1, "white");
game.fillStyle = grd;
game.fillRect(this.x, this.y, this.width, this.height);
game.fill();
};
let player = new Player(canvas.width / 2);
let draw = (millisecondsElapsed) => {
game.clearRect(0, 0, canvas.width, canvas.height);
fallers.forEach((faller) => {
faller.draw();
faller.move(millisecondsElapsed);
if (!(faller.captured)&&
faller.y + faller.height > canvas.height &&
faller.x + faller.width < player.x + player.width &&
faller.x > player.x){
fallerIn(faller);
}
});
player.draw();
fallers = fallers.filter((faller) => {
return faller.y < canvas.height;
});
};
const MIN_WIDTH = 10;
const WIDTH_RANGE = 20;
const MIN_HEIGHT = 10;
const HEIGHT_RANGE = 20;
const MILLISECONDS_BETWEEN_FALLERS = 750;
let fallerGenerator;
let startFallerGenerator = () => {
fallerGenerator = setInterval(() => {
let fallerWidth = Math.floor(Math.random() * WIDTH_RANGE) + MIN_WIDTH;
fallers.push(new Faller(
Math.floor(Math.random() * (canvas.width - fallerWidth)), 0,
fallerWidth, Math.floor(Math.random() * HEIGHT_RANGE) + MIN_HEIGHT
));
}, MILLISECONDS_BETWEEN_FALLERS);
};
let stopFallerGenerator = () => clearInterval(fallerGenerator);
let setPlayerPositionBasedOnMouse = (event) => {
player.x = event.clientX / document.body.clientWidth * canvas.width;
};
document.body.addEventListener("mouseenter", setPlayerPositionBasedOnMouse);
document.body.addEventListener("mousemove", setPlayerPositionBasedOnMouse);
let running = false;
let nextFrame = (timestamp) => {
if (!lastTimestamp) {
lastTimestamp = timestamp;
}
if (timestamp - lastTimestamp < FRAME_DURATION) {
if (running) {
window.requestAnimationFrame(nextFrame);
}
return;
}
draw(timestamp - lastTimestamp);
lastTimestamp = timestamp;
if (running) {
window.requestAnimationFrame(nextFrame);
}
};
document.getElementById("start-button").addEventListener("click", () => {
running = true;
lastTimestamp = 0;
startFallerGenerator();
window.requestAnimationFrame(nextFrame);
});
document.getElementById("stop-button").addEventListener("click", () => {
stopFallerGenerator();
running = false;
});
})();
let colourValues = ["red", "blue"]
/* colourValues.length will be undefined for object.
colourValues = {
red: "#ff0000",
blue: "#0000ff"
};*/
let colour = colourValues[Math.floor(Math.random()*colourValues.length)];
See this fiddle
Random color generator should generate red for 75% times.
Faller.prototype.randomColour = function() {
return colourValues[Math.floor(Math.random() * colourValues.length * 0.75)];
};
Faller should use its own color to fill
Faller.prototype.draw = function() {
game.fillStyle = this.colour;
game.fillRect(this.x, this.y, this.width, this.height);
};
which was assigned in Faller constructor.
this.colour = this.randomColour();
I couldn't figure out how to set ES6 in jsFiddle. So it is done in ES5
let colourValues = ["red", "blue", "red", "red"];
game.fillStyle = colourValues[Math.floor(Math.random()*colourValues.length)];
plnkr http://plnkr.co/edit/uY5hm8Pkaoklfr6Tikrd?p=preview

Categories

Resources