How do you achieve text scrolling inside a canvas - scroll

I have the code below which renders a text on canvas, that refracts to the 3D object, I want to make the text scroll with the mouse wheel either using translate but cannot seem to make it bind, is there a way to achieve that?
I have done word wrap with lineheight and make the font text not blurry on HD Monitors on the different functions but cannot seem to achieve the scrolling only, any help would be much appreciated thank you.
function printAtWordWrap(context, text, x, y, lineHeight, fitWidth) {
fitWidth = fitWidth || 0;
if (fitWidth <= 0) {
context.fillText(text, x, y);
return;
}
var words = text.split(' ');
var currentLine = 0;
var idx = 1;
while (words.length > 0 && idx <= words.length) {
var str = words.slice(0, idx).join(' ');
var w = context.measureText(str).width;
if (w > fitWidth) {
if (idx == 1) {
idx = 2;
}
context.fillText(words.slice(0, idx - 1).join(' '), x, y + (lineHeight * currentLine));
currentLine++;
words = words.splice(idx - 1);
idx = 1;
} else {
idx++;
}
}
if (idx > 0)
context.fillText(words.join(' '), x, y + (lineHeight * currentLine));
}
var PIXEL_RATIO = (function() {
var ctx = document.createElement("canvas").getContext("2d"),
dpr = window.devicePixelRatio || 1,
bsr = ctx.webkitBackingStorePixelRatio ||
ctx.mozBackingStorePixelRatio ||
ctx.msBackingStorePixelRatio ||
ctx.oBackingStorePixelRatio ||
ctx.backingStorePixelRatio || 1;
return dpr / bsr;
})();
let can;
function createHiDPICanvas(w, h, ratio) {
if (!ratio) {
ratio = PIXEL_RATIO;
}
can = document.createElement('canvas');
can.width = w * ratio;
can.height = h * ratio;
can.style.width = w + "px";
can.style.height = h + "px";
can.getContext("2d").setTransform(ratio, 0, 0, ratio, 0, 0);
return can;
}
//Create canvas with a custom resolution.
if (window.devicePixelRatio > 1) {
var myCanvas = createHiDPICanvas(window.innerWidth, window.innerHeight, 4);
} else {
var myCanvas = createHiDPICanvas(window.innerWidth, window.innerHeight, 2);
}
var ctx = myCanvas.getContext('2d', {
alpha: false,
antialias: false
});
let x = window.innerWidth / 2;
var y = window.innerHeight / 2;
ctx.translate(0, 200);
const font = new FontFace("MyCanvasFont", "url(https://use.typekit.net/af/2759ad/00000000000000007735a2d2/30/l?primer=7cdcb44be4a7db8877ffa5c0007b8dd865b3bbc383831fe2ea177f62257a9191&fvd=n3&v=3)");
document.fonts.add(font);
ctx.font = '300 150px "MyCanvasFont"';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
var txt = 'CONNECTING BRANDS TO HUMANS THROUGH CULTURE, DESIGN AND TECHNOLOGY';
printAtWordWrap(ctx, txt, x, y, 120, window.innerWidth / 2);

The main point is: You need know how the first line to show. You can use a param to control this.
function printAtWordWrap(context, text, x, y, lineHeight, fitWidth, line)
control what line can show:
if(currentLine >= line){
//remove line value of currentline
context.fillText(words.slice(0, idx - 1).join(' '), x, y + (lineHeight * (currentLine - line) )); }
Note that ai remove line value of currentLin to up in canvas position.
Look my project: https://codepen.io/Luis4raujo/pen/xxRrmoE
If you can up lines instead hide, you need remove if statement (line 24)
If this answer help you, vote or check as correnct!

Related

Providing canvas2d image tint for Spritefonts

I'm doing Spritefonts and currently implemented tint for it on WebGL!
But on canvas2d i tried to do it via ctx.globalCompositeOperation but it shows following
As you see, Black pixels are also filled...
Here is my code...
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
ctx.save();
if (q) ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
ctx.globalCompositeOperation = "source-in";
ctx.fillStyle = "green";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
}
When trying with "darken" mode instead, It fills correctly but also it fills background (Which i don't want this...)
I also tried with ctx.getImageData() and ctx.putImageData() but letters not shown
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
if (q) {
ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
f = ctx.getImageData(x + (spacing || 0) + (i * size), y, size, size);
for (var i = 0; i < f.data.length; i += 4) {
f.data[i + 0] = 100;
f.data[i + 1] = 100;
f.data[i + 2] = 255;
f.data[i + 3] = 255;
}
ctx.putImageData(f, x + (spacing || 0) + (i * size), y, 0, 0, size, size);
}
}
The image i'm using is from here
Fixed by using "lighten" mode for black pixels with filling background, Then applied "darken" mode instead of "source-in" and all done!
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
ctx.save();
ctx.globalCompositeOperation = "lighten";
ctx.fillStyle = ctx.canvas.style.backgroundColor;
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
if (q) ctx.drawImage(fonts[0].src, q.x, q.y, q.w, q.h, x + (spacing || 0) + (i * size), y, size, size);
ctx.globalCompositeOperation = "darken";
ctx.fillStyle = "green";
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.restore();
}
This is better way i found:
Create canvas with dimensions that complies with spritefont image dimensions
Save context state in the created canvas
Set fillStyle of the created canvas context with spritefont text color (Tint)
Set globalAlpha of created canvas context to opacity
Fill created canvas background with spritefont text color (Tint)
Apply "destination-atop" composite mode in created canvas context
Reset globalAlpha of created canvas context to 1 (Default)
Draw spritefont image onto created canvas
Restore context state in created canvas
Then, Let default canvas context (Not created one) draw characters from spritefont image, So we let it draw part of canvas we created (Note that spritefont image fills all created canvas)
Done!
var size = 32;
var x = 200;
var y = 200;
var spacing = 0;
var opacity = 0.8;
var color = "green";
for (var i = 0; i < txt.length; i++) {
var q = fonts[0].info[txt[i]];
var c = document.createElement("canvas").getContext("2d");
c.canvas.width = fonts[0].src.width;
c.canvas.height = fonts[0].src.height;
c.save();
c.fillStyle = color;
c.globalAlpha = opacity || 0.8;
c.fillRect(0, 0, c.canvas.width, c.canvas.height);
c.globalCompositeOperation = "destination-atop";
c.globalAlpha = 1;
c.drawImage(fonts[0].src, 0, 0);
c.restore();
if (q) ctx.drawImage(c.canvas, q.x, q.y, q.w, q.h, x + (i * (size + spacing)), y, size, size);
}

How could I move randomly this triangle? p5.js

Since few days I try to animate my triangle, I want to move it randomly on my canvas. All of my tests were a failure so if you have some tips I am open to it!
I wish my triangle move on x and y axis randomly like a free electron and in the future I would like to have other triangles that move randomly and when they touch each other they bounce but it's another step!
My code:
let x = 50;
let y = 200;
let y1 = 100;
let y2 = 200
let x1 = 100;
let x2= 150;
let speed = 5;
let startColor;
let endColor;
let amt = 0;
function setup() {
startColor = color("hsl(172, 100%, 50%)");
endColor = color("hsl(335, 100%, 50%)");
createCanvas(windowWidth, 800);
frameRate(45);
}
function draw() {
colorMode(RGB);
background(252, 238, 10);
shape(); // Appel de la function shape
bounce();// appel de la fonction bounce
}
function bounce() {
x = x + speed;
x1 = x1 + speed;
x2 = x2 + speed;
y = y + speed
y1 = y1 + speed
y2 = y2 + speed
if (x2 > windowWidth || x < 0) {
speed = speed * -1;
}
}
function shape() {
if (amt >= 1) {
amt = 0;
let tmpColor = startColor;
startColor = endColor;
endColor = tmpColor;
}
amt += 0.01;
let colorTransition = lerpColor(startColor, endColor, amt);
noStroke();
fill(colorTransition);
triangle(x, y, x1, y1, x2, y2);
}
First you have a code working to make your triangle always move in the same direction. What you could do to make it random is to change the speed you are using:
For now each call to bounce moves the triangle by speed pixels. So if in draw() before calling shape() you add the following, triangle will begin to randomly move by a small amount:
speed = map(random(), 0, 1, -5, 5);
There are tons of different ways to do it, here we have making use of processing's random() to generate a number between 0 and 1 and map() to get a value between -5 and 5.
Now the issue is that you have only one type of speed and you apply to both axis x and y. What you want is probably to have speedX and speedY with two different values applied to both component of your position.
Once you try to do that you'll realize that having two variables for speedX and speedY is not very convenient and that you'd rather have one variable for your position with two component x and y and same for your speed. This way you'll be able to do position = position + speed. This requires that you refactor your code to use a more object oriented paradigm. To learn how to do that, one of the best resources online is the "Nature of Code" playlist by The coding train youtube channel.
I work every day and i followed all your advices and now this is what i have product, thanks for all !!
let triangle1;
let triangle2;
let triangle3;
let triangle4;
let triangle5;
let speedX;
let speedY;
let startColor;
let endColor;
let amt = 0;
function setup() {
startColor = color("hsl(172, 100%, 50%)");
endColor = color("hsl(335, 100%, 50%)");
createCanvas(windowWidth, 800);
//creer notre triangle
triangle1 = new Triangles(200, 100, 0, 4);
triangle2 = new Triangles(100, 50, 2, 0);
triangle3 = new Triangles(50, 200, -1, 4);
triangle4 = new Triangles(250, 400, 4, 4);
triangle5 = new Triangles(150, 500, 0, 2);
}
function draw() {
colorMode(RGB);
background(252, 238, 10);
triangle1.show();
triangle1.move();
triangle2.show();
triangle2.move();
triangle3.show();
triangle3.move();
triangle4.show();
triangle4.move();
triangle5.show();
triangle5.move();
}
class Triangles {
//configuration de l'objet
constructor(triX, triY, speedX, speedY){
this.x = triX;
this.y = triY;
this.speedX = speedX;
this.speedY = speedY;
}
show(){
if (amt >= 1) {
amt = 0;
let tmpColor = startColor;
startColor = endColor;
endColor = tmpColor;
}
amt += 0.01;
let colorTransition = lerpColor(startColor, endColor, amt);
noStroke();
fill(colorTransition);
noStroke();
triangle(this.x, this.y, this.x + 25, this.y + 40, this.x -25, this.y + 40);
}
move(){
this.x += this.speedX;
this.y += this.speedY;
if(this.x > width || this.x < 0){
this.speedX *= -1;
}
if(this.y > height || this.y < 0){
this.speedY = this.speedY * -1;
}
}
}

How to make a crescent moon shape in HTML canvas

I need to make the following shape in HTML5 canvas. I have tried using cubic bezier arcs and also clipping two circles.
How can I make this shape?
Here's my work in progress, just cant get it right
https://codepen.io/matt3224/pen/oeXbdg?editors=1010
var canvas = document.getElementById("canvas1");
var ctx1 = canvas.getContext("2d");
ctx1.lineWidth = 2;
ctx1.beginPath();
ctx1.bezierCurveTo(4, 42, 0, 0, 42, 4);
ctx1.moveTo(4, 42);
ctx1.bezierCurveTo(4, 42, 0, 84, 42, 84);
ctx1.stroke();
var canvas = document.getElementById("canvas2");
var ctx2 = canvas.getContext("2d");
ctx2.lineWidth = 2;
ctx2.beginPath();
ctx2.arc(55, 75, 50, 0, Math.PI * 2, true);
ctx2.moveTo(165, 75);
ctx2.arc(75, 75, 50, 0, Math.PI * 2, true);
ctx2.fill();
Circle circle boolean operation.
Incase anyone is interested in a programmatic solution the example below finds the intercept points of the two circles and uses those points to calculate the start and end angles for the outer and inner circle.
This is a little more flexible than a masking solution as it give you a path.
Snippet shows circle, move mouse over circle to see crescent solution. Not the stroke that would not be available if using a masking solution.
const PI2 = Math.PI * 2;
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 400;
const mouse = {x : 0, y : 0, button : false}
function mouseEvents(e){
const m = mouse;
const bounds = canvas.getBoundingClientRect();
m.x = e.pageX - bounds.left - scrollX;
m.y = e.pageY - bounds.top - scrollY;
m.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : m.button;
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
// generic circle circle intercept function. Returns undefined if
// no intercept.
// Circle 1 is center x1,y1 and radius r1
// Circle 2 is center x2,y2 and radius r2
// If points found returns {x1,y1,x2,y2} as two points.
function circleCircleIntercept(x1,y1,r1,x2,y2,r2){
var x = x2 - x1;
var y = y2 - y1;
var dist = Math.sqrt(x * x + y * y);
if(dist > r1 + r2 || dist < Math.abs(r1-r2)){
return; // no intercept return undefined
}
var a = (dist * dist - r1 * r1 + r2 *r2) / ( 2 * dist);
var b = Math.sqrt(r2 * r2 - a * a);
a /= dist;
x *= a;
y *= a;
var mx = x2 - x;
var my = y2 - y;
dist = b / Math.sqrt(x * x + y * y);
x *= dist;
y *= dist;
return {
x1 : mx-y,
y1 : my+x,
x2 : mx+y,
y2 : my-x,
};
}
// draws a crescent from two circles if possible
// If not then just draws the first circle
function drawCrescent(x1,y1,r1,x2,y2,r2){
// The circle circle intercept finds points
// but finding the angle of the points does not consider
// the rotation direction and you end up having to do a lot of
// checking (if statments) to determin the correct way to draw each circle
// the following normalises the direction the circle are from each other
// thus making the logic a lot easier
var dist = Math.hypot(x2-x1,y2-y1);
var ang = Math.atan2(y2-y1,x2-x1);
var intercepts = circleCircleIntercept(x1,y1,r1,x1 + dist,y1,r2);
if(intercepts === undefined){
ctx.beginPath();
ctx.arc(x1, y1, r1, 0, PI2);
if(dist < r1){
ctx.moveTo(x2 + r2, y2);
ctx.arc(x2, y2, r2, 0, PI2, true);
}
ctx.fill();
ctx.stroke();
return;
}
// get the start end angles for outer then inner circles
const p = intercepts;
var startA1 = Math.atan2(p.y1 - y1, p.x1 - x1) + ang;
var endA1 = Math.atan2(p.y2 - y1, p.x2 - x1) + ang;
var startA2 = Math.atan2(p.y1 - y1, p.x1 - (x1 + dist)) + ang;
var endA2 = Math.atan2(p.y2 - y1, p.x2 - (x1 + dist)) + ang;
ctx.beginPath();
if(endA1 < startA1){
ctx.arc(x1, y1, r1, startA1, endA1);
ctx.arc(x2, y2, r2, endA2, startA2, true);
}else{
ctx.arc(x2, y2, r2, endA2, startA2);
ctx.arc(x1, y1, r1, startA1, endA1,true);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
const outerRadius = 100;
const innerRadius = 80;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var globalTime;
ctx.font = "32px arial";
ctx.textAlign = "center";
ctx.lineJoin = "round";
ctx.lineWidth = 8;
ctx.strokeStyle = "#999";
// main update function
function mainLoop(timer){
globalTime = timer;
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "black";
ctx.fillRect(0,0,w,h);
ctx.fillStyle = "white";
ctx.fillText("Move mouse over circle",cw,40);
drawCrescent(cw, ch-40, outerRadius, mouse.x, mouse.y, innerRadius);
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
Solved it using globalCompositeOperation
https://codepen.io/matt3224/pen/oeXbdg?editors=1010

How to rotate image inside HTML5 canvas without drawing it?

I'm generating image programmatically inside canvas.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// here I have some code in loop setting individual pixels
// ...
//
// save image to variable
var dataURL = canvas.toDataURL();
How can I rotate created image by 90 degrees?
EDIT:
This is not duplicate because I don't draw image, it is never visible. I only want to generate it, rotate it and save to variable.
EDIT2:
I'm trying to rotate it with this code:
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
But it doesn't work
EDIT3:
This is more complex example of my code:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
canvas.setPixel = function (x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, 1, 1);
}
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
case 1:
var color = '#ffff00',
type = 'w'
break
case 3:
var rgb = (256 - parseInt(pixels[i][j]) - minus.grass).toString(16),
color = '#00' + rgb + '00',
type = 'g'
break
case 4:
var rgb = (256 - parseInt(pixels[i][j]) - minus.hills).toString(16),
color = '#' + rgb + rgb + '00',
type = 'h'
break
case 5:
var rgb = (parseInt(pixels[i][j]) + minus.mountains).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
case 6:
var rgb = (parseInt(pixels[i][j]) + minus.snow).toString(16),
color = '#' + rgb + rgb + rgb,
type = 'm'
break
}
if (i % fieldSize == 0 && j % fieldSize == 0) {
if (notSet(fields[y])) {
fields[y] = []
}
fields[y][x] = type
x++
}
canvas.setPixel(i, j, color)
}
if (i % fieldSize == 0) {
x = 0
y++
}
}
ctx.translate(canvas.width / 2, canvas.height / 2)
ctx.rotate(90 * Math.PI / 180)
var token = {
type: 'save',
map: canvas.toDataURL('image/png')
}
ws.send(JSON.stringify(token))
To rotate image by 90 degrees I had to put
ctx.translate(0, canvas.height)
ctx.rotate(270 * Math.PI / 180)
before
for (var i in data) {
for (var j in data[i]) {
switch (data[i][j]) {
// ... drawing pixels
}
}
}

Compose an image with floating point layers in webgl

I have trying to render an image in the browser which is built like this:
A bunch of rectangles are each filled with a radial gradient (ideally Gaussian, but can be approximated with a few stopping points
Each rectangle is rotated and translated before being deposited on a drawing area
The image is flattened by summing all the intensities of the rectangles (and cropping to the drawing area's dimensions )
The intensity is rescaled so that the highest intensity is 255 and the lowest 0 (ideally I can apply some sort of gamma correction too)
Finally an image is drawn where the color of each pixel is taken from a palette of 256 colors.
The reason I cannot do this easily with a canvas object is that I need to be working in floating points or I'll lose precision. I do not know in advance what the maximum intensity and minimum intensity will be, so I cannot merely draw transparent rectangles and hope for the best.
Is there a way to do this in webgl? If so, how would I go about it?
You can use the regular canvas to perform this task :
1) check min/max of your rects, so you can build a mapping function double -> [0-255] out of that range.
2) draw the rects in 'lighter' mode == add the component values.
3) you might have a saturation when several rects overlaps : if so, double the mapping range and go to 2).
Now if you don't have saturation just adjust the range to use the full [0-255] range of the canvas, and you're done.
Since this algorithm makes use of getImageData, it might not reach 60 fps on all browsers/devices. But more than 10fps on desktop/Chrome seems perfectly possible.
Hopefully the code below will clarify my description :
//noprotect
// boilerplate
var cv = document.getElementById('cv');
var ctx = cv.getContext('2d');
// rectangle collection
var rectCount = 30;
var rects = buildRandRects(rectCount);
iterateToMax();
// --------------------------------------------
function iterateToMax() {
var limit = 10; // loop protection
// initialize min/max mapping based on rects min/max
updateMapping(rects);
//
while (true) {
// draw the scene using current mapping
drawScene();
// get the max int value from the canvas
var max = getMax();
if (max == 255) {
// saturation ?? double the min-max interval
globalMax = globalMin + 2 * (globalMax - globalMin);
} else {
// no sauration ? Just adjust the min-max interval
globalMax = globalMin + (max / 255) * (globalMax - globalMin);
drawScene();
return;
}
limit--;
if (limit <= 0) return;
}
}
// --------------------------------------------
// --------------------------------------------
// Oriented rectangle Class.
function Rect(x, y, w, h, rotation, min, max) {
this.min = min;
this.max = max;
this.draw = function () {
ctx.save();
ctx.fillStyle = createRadialGradient(min, max);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.scale(w, h);
ctx.fillRect(-1, -1, 2, 2);
ctx.restore();
};
var that = this;
function createRadialGradient(min, max) {
var gd = ctx.createRadialGradient(0, 0, 0, 0, 0, 1);
var start = map(that.min);
var end = map(that.max);
gd.addColorStop(0, 'rgb(' + start + ',' + start + ',' + start + ')');
gd.addColorStop(1, 'rgb(' + end + ',' + end + ',' + end + ')');
return gd;
}
}
// Mapping : float value -> 0-255 value
var globalMin = 0;
var globalMax = 0;
function map(value) {
return 0 | (255 * (value - globalMin) / (globalMax - globalMin));
}
// create initial mapping
function updateMapping(rects) {
globalMin = rects[0].min;
globalMax = rects[0].max;
for (var i = 1; i < rects.length; i++) {
var thisRect = rects[i];
if (thisRect.min < globalMin) globalMin = thisRect.min;
if (thisRect.max > globalMax) globalMax = thisRect.max;
}
}
// Random rect collection
function buildRandRects(rectCount) {
var rects = [];
for (var i = 0; i < rectCount; i++) {
var thisMin = Math.random() * 1000;
var newRect = new Rect(Math.random() * 400, Math.random() * 400, 10 + Math.random() * 50, 10 + Math.random() * 50, Math.random() * 2 * Math.PI, thisMin, thisMin + Math.random() * 1000);
rects.push(newRect);
}
return rects;
}
// draw all rects in 'lighter' mode (=sum values)
function drawScene() {
ctx.save();
ctx.globalCompositeOperation = 'source-over';
ctx.clearRect(0, 0, cv.width, cv.height);
ctx.globalCompositeOperation = 'lighter';
for (var i = 0; i < rectCount; i++) {
var thisRect = rects[i];
thisRect.draw();
}
ctx.restore();
}
// get maximum value for r for this canvas
// ( == max r, g, b value for a gray-only drawing. )
function getMax() {
var data = ctx.getImageData(0, 0, cv.width, cv.height).data;
var max = 0;
for (var i = 0; i < data.length; i += 4) {
if (data[i] > max) max = data[i];
if (max == 255) return 255;
}
return max;
}
<canvas id='cv' width = 400 height = 400></canvas>

Resources