How to have scalable image with viewport in p5js? - p5.js

I'm making a website for my design studio and I would like to set image sizes with the p5js language.
Here, I made a function that allows each mouse click to display an image. The problem is that once the image is displayed, it appears at the indicated dimensions, adapt to the screen size but the ratio is not good. The image is either too wide or too extended.
so what do you need to do to display an image with the right original width/height ratio
All your feedback is appreciated.
Thanks in advance !
let works = []
function preload() {
for (let i = 0; i < 6; i++) {
works[i] = loadImage("img/work" + i + ".png")
}
}
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function draw() {
cursor(CROSS);
}
var counter = 0
function mouseClicked() {
imageMode(CENTER);
counter++
image(works[counter%6], mouseX, mouseY, windowWidth/2, windowHeight/2);
}

The image function can take 3, 5, 7, or 9 parameters, and in each case it has different behavior:
image(img, x, y) - Draw the entire image with it's original size at the specified location
image(img, x, y, width, height) - Draw the entire image, scaled to the specified width and height without preserving aspect ratio, at the specified position.
image(img, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) - Draw a specific part of the image, scaled to the specified width and height without preserving aspect ratio, at the specified position.
The function of the dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight parameters is explained by this diagram:
In order to draw an image scaled relative to the window size constrained to the aspect ratio of the window, you will need to determine how best to crop your images.
let works = []
function preload() {
works[0] = loadImage("https://www.paulwheeler.us/files/no_idea_why.jpg");
}
function setup() {
canvas = createCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function windowResized() {
resizeCanvas(windowWidth, windowHeight);
canvas.position(0, 0);
canvas.style('z-index', '1');
}
function draw() {
cursor(CROSS);
}
var counter = 0
function mouseClicked() {
imageMode(CENTER);
counter++
let img = works[counter % works.length];
let windowAspect = width / height;
let imageAspect = img.width / img.height;
let w, h;
// This code naively crops the bottom or right edge of the image as necessary. Obviously there are other ways to limit the image size.
if (windowAspect >= imageAspect) {
// Our window is wider than our image, we need to constrain the height of the image
w = img.width;
h = w / windowAspect;
} else {
// Our window is narrower than or image, we need to constrain the width of the image
h = img.height;
w = h * windowAspect;
}
image(img, mouseX, mouseY, windowWidth / 2, windowHeight / 2, 0, 0, w, h);
}
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.3.1/p5.min.js"></script>
</head>
<body>
</body>
</html>

Related

Not able to copy paste a section of image in P5.js

I have been working on a project in which I have to select an area of image and then use CTRL+C and CTRL+V to copy and paste the selected area. I have tried creating a buffer using createGraphics() but did not work. I have also tried getting pixels one by one and creating image but that is not working either. Can somebody help? Below is my code.
if (this.isCtrlPressed == true && p5.key == 'v') {
let img = p5.createImage(this.dw, this.dh);
p5.draw = () => {
img.loadPixels();
for (let i = 0; i < parseInt(this.sw); i++) {
for (let j = 0; j < parseInt(this.sh); j++) {
img.set(i, j, p5.get(i, j));
}
}
img.updatePixels();
p5.stroke(255);
p5.image(img, 10, 10);
p5.noFill();
p5.rect(p5.mouseX, p5.mouseY, 10, 10);
return;
}
You're not too far off.
Bare in mind that you can also call get() with width, height arguments, not just the first two (x, y): this will make it easy to copy a section of the image.
The p5.Graphics you get using createGraphics() is handy indeed.
Remember to store the selection rectangle properties (x, y, width, height) to copy the image in the first place.
Here's a very rough sketch based on the CreateImage p5.js example:
/*
* #name Create Image
* #description The createImage() function provides a fresh buffer of pixels to
* play with. This example creates an image gradient.
*/
let img; // Declare variable 'img'.
// copy/paste selection rectangle
let selection = {x:0, y:0, w:0, h:0};
// p5.Image from main image using selection
let clipboard;
// where to paste the clipboard p5.Image
let pasteBuffer;
function setup() {
createCanvas(720, 400);
img = createImage(230, 230);
img.loadPixels();
for (let x = 0; x < img.width; x++) {
for (let y = 0; y < img.height; y++) {
let a = map(y, 0, img.height, 255, 0);
img.set(x, y, [0, 153, 204, a]);
}
}
img.updatePixels();
// selection drawing style
noFill();
stroke(255);
// setup buffer to paste into
pasteBuffer = createGraphics(width, height);
}
function draw() {
background(0);
// render original image
image(img, 0, 0);
// render pasted graphics
image(pasteBuffer, 0, 0);
// render selection rectangle
rect(selection.x, selection.y, selection.w, selection.h);
}
function mousePressed(){
// store selection start
selection.x = mouseX;
selection.y = mouseY;
}
function mouseDragged(){
// update selection dimension as the difference between the current mouse coordinates and the previous ones (selection x, y)
selection.w = mouseX - selection.x;
selection.h = mouseY - selection.y;
}
function keyPressed(keyEvent){
if (key == 'c') {
console.log("copy image", selection);
// use get() to "clone" a subsection of the main image
clipboard = img.get(selection.x, selection.y, selection.w, selection.h);
}
if (key == 'v') {
console.log("paste image");
if(clipboard){
// simply render the clipboard image
pasteBuffer.image(clipboard, mouseX, mouseY);
}
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.4.2/p5.min.js"></script>
Note the above isn't perfect (e.g. you need to use abs() to handle making selections from right to left, not just left to right), but hopefully it illustrates the usage of a selection rectangle, copying pixels and "pasting"/rendering them into a buffer.
Speaking of copying pixels, an alternative option could be using a p5.Image (using createImage() instead of createGraphics()) and copy() (instead of buffer.image()) (and you'd use the selection rectangle properties to know from where to where to paste pixels).

How can I show only a part of an image in Processing?

I want to compare two images (same size) fr a presentation with a shifting line. On the left side of this line the one image is should be displayed while on the right side the other picture should stay visible.
This is what I tried (bitmap and ch are the images)
PImage bitmap;
PImage ch;
int framerate = 1000;
void setup() {
size(502, 316);
bitmap = loadImage("bitmap_zentriert.jpg"); // Load an image into the program
ch = loadImage("Karte_schweiz_zentriert.jpg"); // Load an image into the program
frameRate(40); //framerate
}
void draw() {
background(255);
image(ch, 10, 10); // the one image in the back
image(bitmap, 10, 10, bitmap.width, bitmap.height, 10, 10, mouseX, bitmap.height); //show part of the second image in front
rect(mouseX, 10, 1, bitmap.height-1); //make line
}
But the image "bitmap" is the whole image distorted.
How can I do that?
I'd recommend using a PGraphics buffer, which is essentially "Another sketch" that also acts as an Image for drawing purposes, and most definitely not looping at "a thousand frames per second". Only draw something when you have something new to draw, using the redraw function in combination with mouse move events:
PImage img1, img2;
PGraphics imagebuffer;
void setup() {
size(502, 316);
imagebuffer = createGraphics(width, height);
img1 = loadImage("first-image.jpg");
img2 = loadImage("second-image.jpg");
noLoop();
}
void mouseMoved() {
redraw();
}
void draw() {
image(img1, 0, 0);
if (mouseX>0) {
imagebuffer = createGraphics(mouseX, height);
imagebuffer.beginDraw();
imagebuffer.image(img2, 0, 0);
imagebuffer.endDraw();
image(imagebuffer, 0, 0);
}
}
In our setup we load the image and turn off looping because we'll be redrawing based on redraw, and then in response to mouse move events, we generate a new buffer that is only as wide as the current x-coordinate of the mouse, draw our image, which gets cropped "for free" because the buffer is only limited width, and then we draw that buffer as if it were an image on top of the image we already have.
There are many ways to do it, one thing I suggest is to create a 3rd image with the same width and height, then you load the two images pixels and insert in your 3rd image part of image1 pixels and then second part from image2, I wrote this code to test it out, works fine:
PImage img1, img2, img3;
void setup() {
size(500, 355);
img1 = loadImage("a1.png"); // Load an image into the program
img2 = loadImage("a2.png"); // Load an image into the program
img3 = createImage(width, height, RGB); //create your third image with same width and height
img1.loadPixels(); // Load the image pixels so you can access the array pixels[]
img2.loadPixels();
frameRate(40); // frame rate
}
void draw() {
background(255);
// Copy first half from first image
for(int i = 0; i < mouseX; i++)
{
for (int j = 0; j < height ; j++) {
img3.pixels[j*width+i] = img1.pixels[j*width+i];
}
}
// Copy second half from second image
for(int i = mouseX; i < width; i++)
{
for (int j = 0; j < height ; j++) {
img3.pixels[j*width+i] = img2.pixels[j*width+i];
}
}
// Update the third image pixels
img3.updatePixels();
// Simply draw that image
image(img3, 0, 0); // The one image in the back
// Draw the separation line
rect(mouseX, 0, 0, height); // Make line
}
Result :

HTML5 Canvas rotate trouble

I have a canvas element on which I am drawing a number of images and overlaying them with text. Unfortunately the problem requires that some of these images and corresponding text be rotated. Added to this the problem that there must be a corresponding background color on some of the images (images are simple outlines of desks for a floorplan)
Here is the function I have built to handle adding a single desk to the plan. The problem I am having is that when I use the rotate neither the text nor the background colors show up, while the appear correctly if I do not rotate the image, except that they are not rotated and the background fillRect() is oriented 90 degrees off.
function redrawDesk(desk, ctx, color) {
var rotate = desk.rotation == 90 || desk.rotation == 270;
if (rotate) {
ctx.save();
ctx.rotate(Math.PI / 2);
ctx.clearRect(desk.left, desk.top, desk.width, desk.height);
ctx.restore()
}
var img = $("#desk_" + desk.rowID)[0];
ctx.drawImage(img, desk.left, desk.top, desk.height, desk.width);
var x = desk.left;
var y = desk.top;
var h = desk.height;
var w = desk.width;
if (rotate) {
//ctx.save()
ctx.rotate(Math.PI / 2);
var tmp=x;
x=y;
y=tmp;
tmp=h;
h=w;
w=tmp;
}
ctx.textAlign = "center";
ctx.fillText(desk.deskID, x + w / 2,y + h/ 2);
if (color) {
ctx.fillStyle = color;
ctx.fillRect(x, y, w, h);
}
//ctx.restore();
if (rotate) {
ctx.rotate(Math.PI / -2);
}
}
Thank you
The main problem is that you are defining the desk and text as absolute coordinates.
Define objects in there local coordinate system. Eg the desk has a height and width but not a position. Its draw relative to its self (around 0,0)
const desk = {
w : 10, h : 10,
color : "blue",
draw() {
ctx.fillStyle = this.color;
ctx.fillRect(-this.w / 2, -this.h / 2, this.w, this.h);
}
};
You can then position the desk into the world coordinate system (the canvas) by defining where its center will be.
function drawObj(obj, x, y) { // what to draw and where
ctx.setTransform(1,0,0,1,x,y); // Same as ctx.translate if 2D API is in default context
// The means you do not have to use ctx.save and
// ctx.restore in this function
obj.draw(); // draw desk
}
For a full transform its much the same
function drawObj(obj, x, y, scale, rotate) { // rotate is in radians
ctx.setTransform(scale, 0, 0, scale, x, y);
ctx.rotate(rotate);
obj.draw();
}
To add text you can add it as an object to the desk and draw it to its own local coordinate system
desk.name = {
text : "Desk",
color : "black",
font : "bold " + 20 + "px Calibri",
draw() {
ctx.font = this.font;
ctx.textAlign = "center";
ctx.fillStyle = this.color;
ctx.fillText(this.text, 0,0);
}
};
You can now draw the desk and name using the draw object function
drawObj(desk,200, 200, 1, Math.PI / 2); // Draw at 200,200 rotated 90deg CW
drawObj(desk.name, 200, 200, 1, Math.PI / 2); // draw the text rotated same and centered over desk
// Or if the text should be above and not rotated
drawObj(desk.name, 200, 200 - 30, 1, 0);
As the above functions use setTransform you may need to restore the transform. There are two ways to do this.
ctx.resetTransform(); // Check browser support for this call
ctx.setTransform(1,0,0,1,0,0); // same as above just does it manaly
In my code I tested to see if there is a rotation needed. If so I set a translate on the canvas to give me a new start point: ctx.translate(x, y); This allowed me to simplify my location settings for placing text and the background colors, which means they are showing up correctly. Here is the changed code to compare with the original:
if (rotate) {
ctx.save();
tmp = h;
h = w;
w = tmp;
ctx.translate(x, y);
}
if (color) {
ctx.fillStyle = color;
ctx.fillRect(0, 0, w, h);
}
ctx.font = "bold " + w / 2 + "px Calibri";
ctx.textAlign = "center";
ctx.fillStyle = "#000";
var c=ctx.canvas;
ctx.rotate(Math.PI / -2);
ctx.fillText(desk.deskID, 0-h/2, w/2); //x + w / 2, y + h / 2);
ctx.restore();

Cannot get context.rotate to show image properly

I need to rotate the image 180 degrees, but when I add in the rotate code, the image becomes a square.
var moulding_matte_canvas_height = [],
canvas = document.createElement('canvas'),
ctx = canvas.getContext("2d"),
width = 5.171363636363637,
opening_i = 0,
rotate = 180 * Math.PI / 180,
i = 2;
moulding_matte_canvas_height[0] = 225;
canvas.width = 285;
canvas.height = 335;
img = new Image();
img.src = 'http://www.asa.tframes.org:1881/system/components/compimg/80ac89fad42cf14561b641df241bf406/pattern';
function moulding_get_segment_width(img_width, opening_i)
{
return 175;
}
//
$(img).on("load", function() {
ctx.save();
ctx.translate(width, moulding_matte_canvas_height[opening_i]);
//ctx.rotate(rotate);
//ctx.rotate(Math.PI);
ctx.drawImage(img, 0, 0, moulding_get_segment_width(img.width, i), width);
ctx.restore();
//
});
$("#mattes").append(canvas); /*appending the canvas*/
http://fiddle.jshell.net/onpa628e/1/
(Whitespace is intentional due to other elements that will be drawn and the red next to the image is just for clarity)
Without rotate:
With rotate:
Expected:
Your image looks like a square, because it's only showing a tiny portion on the canvas. This happens because translate is only moving ~ 5px (your "width" variable) on the x axis.
Here's your example modified with a higher x value.

flickering twin images in memory game

I have a problem of flickering twin images in https://github.com/dzenanr/educ_memory_game.
In the Board class of view/board.dart, the following code in the _imageBox method creates and loads images:
var imagePath = 'images/${cell.image}';
ImageElement image = new Element.tag('img');
image.src = imagePath;
image.onLoad.listen((event) {
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
});
A click on a board cell shows an image for a second. When the same 2 images are discovered, those twin images stay displayed , but they flicker every second (a timer refresh interval).
In order to solve the flickering problem, I create those images in a constructor of the Board class:
for (var cell in memory.cells) {
ImageElement image = new Element.tag('img');
image.src = 'images/${cell.image}';
imageMap[cell.image] = image;
}
Then, I get an image from the map. However neither of the following two works:
ImageElement image = imageMap[cell.image];
image.onLoad.listen((event) {
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
});
or
ImageElement image = imageMap[cell.image];
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
Changing the src attribute of the image imply a network access and is not good, you have to used the cached images.
To display the image correctly, just change the _imageBox function a little bit.
void _imageBox(Cell cell) {
var x = cell.column * boxSize;
var y = cell.row * boxSize;
context.beginPath();
// Moved at the beginning otherwise it is drawn above the image.
context.rect(x, y, boxSize, boxSize);
context.fill();
context.stroke();
context.closePath();
if (cell.hidden ) {
context.fillStyle = HIDDEN_CELL_COLOR_CODE;
var centerX = cell.column * boxSize + boxSize / 2;
var centerY = cell.row * boxSize + boxSize / 2;
var radius = 4;
context.arc(centerX, centerY, radius, 0, 2 * PI, false);
} else {
ImageElement image = imageMap[cell.image]; // if decomment, comment the above 3 lines
context.drawImageToRect(image, new Rect(x, y, boxSize, boxSize));
}
}

Resources