Unexpected border pattern in a grid with rectangles in p5.js - javascript

I am trying to generate a square grid like pattern in p5js that covers as much of the browser window as possible.I am using p5js in instance mode as I using this with react and I am using chrome in Win10.
Here is my code:-
var size = 15;
var height = window.innerHeight;
var width = window.innerWidth;
Sketch = (p) => {
p.setup = () => {
p.createCanvas(width,height)
p.frameRate(60);
p.noLoop();
}
p.draw = () => {
p.background(250);
p.stroke(0);
p.noFill();
for(let i =0;i*size +size <width;i++) {
for(let j=0;j*size +size<height;j++) {
p.rect(i*size,j*size,size,size);
}
}
}
p.mouseDragged = (e) => {
p.stroke(0);
let x = Math.floor(e.clientY/size);
let y = Math.floor(e.clientX/size);
p.fill(220);
p.rect(y*size,x*size,size,size);
}
}
I call p.noLoop() so it doesnt refreshes everytime and I also have a button that calls p.redraw() to change everything to default. Here is the grid and behaviour I get:
The borders of grids are of varying sizes, first they decrease then increase then decrease and so on. Also, the area around which I drag my mouse has even more weird borders(This gets resolved when I click somewhere else so is this a GPU Aliasing rendering issue?). How do I create grid with same borders throughout my screen?
Edit: When I render even a single box, it has issues. The left and upper border are fine. However the right and down borders have an extra pixel of grayish borders which seems to be the problem. How do I fix this?
Also, How does strokeWeight and rect work in p5js? If I do strokeWeight(10) and rect(3,2,50,50), does that create a 50 by 50 rectangle with 10 pixels borders all around or the borders are included in the rectangle size?

Related

FabricJS, change control button image when hover

I am using a custom rotation icon that shows when I select objects, defined as I create the canvas.
function renderRotationIcon(ctx, left, top, styleOverride, fabricObject) {
//#ts-ignore
let size = this.cornerSize
ctx.save()
ctx.translate(left, top)
//#ts-ignore
ctx.rotate(fabric.util.degreesToRadians(fabricObject.angle))
ctx.drawImage(img, -size / 2, -size / 2, size, size)
ctx.restore()
}
However, I would like to change the image img when the user hovers the rotation icon.
I tried this when defining img
img.onmouseover = () => {
img.src = "LibraryHoverTrans.png"
}
As expected, this didn't work, as I suspect that something more specific to fabricJS is needed. I tried searching for a related solution.

Transparent HTML5 canvas todataurl only rendering transparent background on mobile devices

EDITED, see end of question.
In my application I have two canvas elements. One shows layered, transparent pngs, the other one gets an image from a file input and masks it. The chosen image is transparent where it is not masked. This image is then converted to a dataUrl, transformed to fit into the first canvas and added as the top layer of the first canvas.
Everything works as expected on desktop browsers: Chrome OSX, Safari OSX. I only add it in on load, so I made sure no race conditions can occur.
On Android Chrome and Safari iOS the canvas converted todataURL is rendered transparent. If I add a non-transparent image to the second canvas, the rendered image will show even on mobile devices.
To check I added the supposedly transparent canvas to the body. It shows correctly on desktop, but is transparent on mobile Browsers. Here the simplified JS. I am using fabric.js for convenience, but the problem is the same without the lib. I even once added a background color. Then only the color will show. Any ideas why todataurl on mobile browsers renders only transparent pixels?
<body>
<canvas id="canv"></canvas>
<script src="fabric.js"></script>
<script>
// main canvas
var c = new fabric.Canvas('canv');
c.setWidth(200);
c.setHeight(200);
var i = document.createElement('img');
i.src = 'dummy.jpg';
// i.src = 'dummy1.png';
i.onload = function(e) {
//document.body.appendChild(i);
scale = 1; // resizes the image
var ci = new fabric.Image(i);
ci.set({
left: 0,
top: 0,
scaleX: scale,
scaleY: scale,
originX: 'left',
originY: 'top'
}).setCoords();
// temporary canvas, will be converted to dataurl, contains transformed image
var tmpCanvas = new fabric.Canvas();
tmpCanvas.setWidth(100);
tmpCanvas.setHeight(100);
ci.scaleToWidth(100);
tmpCanvas.add(ci);
tmpCanvas.renderAll();
// create image from temporary canvas
var customImage = new fabric.Image.fromURL(tmpCanvas.toDataURL({ format: 'png' }), function (cImg) {
// add it to original canvas
c.clear();
c.add(cImg);
c.renderAll();
data = c.toDataURL({ format: 'png' });
// resized image
var newc = new fabric.StaticCanvas().setWidth(300).setHeight(300);
var newImg = new fabric.Image.fromURL(data, function (c1Img) {
newc.add(c1Img);
newc.renderAll();
// append to body to check if canvas is rendered correctly
document.body.appendChild(newc.lowerCanvasEl);
});
});
}
</script>
EDIT: I solved the problem, but could not find the problem on the Javascript side.
The problem was that I copied a temporary canvas onto another canvas. The scale and position of the added canvas was computed by finding the bounding box of non transparent pixels in a png, which was generated exactly for this purpose. A mask in short.
The bounding box was calculated in another temporary canvas at the start of the app (based on this answer). Although all sizes of the mask and its canvas were set correctly and the canvas was never added to the DOM, when loaded on a small screen the results of the bounding box differed from from the full screen results. After much testing i found this was true on Desktop too.
Because I already spent so much time on the problem, I decided to try to calculate the bounds in PHP and put it into a data attribute. Which worked great!
For those interested in the PHP solution:
function get_bounding_box($imgPath) {
$img = imagecreatefrompng($imgPath);
$w = imagesx($img);
$h = imagesy($img);
$bounds = [
'left' => $w,
'right' => 0,
'top' => $h,
'bottom' => 0
];
// get alpha of every pixel, if it is not fully transparent, write it to bounds
for ($yPos = 0; $yPos < $h; $yPos++) {
for ($xPos = 0; $xPos < $w; $xPos++) {
// Check, ob Pixel nicht vollständig transparent ist
$rgb = imagecolorat($img, $xPos, $yPos);
if (imagecolorsforindex($img, $rgb)['alpha'] < 127) {
if ($xPos < $bounds['left']) {
$bounds['left'] = $xPos;
}
if ($xPos > $bounds['right']) {
$bounds['right'] = $xPos;
}
if ($yPos < $bounds['top']) {
$bounds['top'] = $yPos;
}
if ($yPos > $bounds['bottom']) {
$bounds['bottom'] = $yPos;
}
}
}
}
return $bounds;
}
The problem was that I copied a temporary canvas onto another canvas. The scale and position of the added canvas was computed by finding the bounding box of non transparent pixels in a png, which was generated exactly for this purpose. A mask in short.
The bounding box was calculated in another temporary canvas at the start of the app (based on this answer). Although all sizes of the mask and its canvas were set correctly and the canvas was never added to the DOM, when loaded on a small screen the results of the bounding box differed from from the full screen results. After much testing i found this was true on Desktop too.
Because I already spent so much time on the problem, I decided to try to calculate the bounds in PHP and put it into a data attribute. Which worked great!
For those interested in the PHP solution:
function get_bounding_box($imgPath) {
$img = imagecreatefrompng($imgPath);
$w = imagesx($img);
$h = imagesy($img);
$bounds = [
'left' => $w,
'right' => 0,
'top' => $h,
'bottom' => 0
];
// get alpha of every pixel, if it is not fully transparent, write it to bounds
for ($yPos = 0; $yPos < $h; $yPos++) {
for ($xPos = 0; $xPos < $w; $xPos++) {
// Check, ob Pixel nicht vollständig transparent ist
$rgb = imagecolorat($img, $xPos, $yPos);
if (imagecolorsforindex($img, $rgb)['alpha'] < 127) {
if ($xPos < $bounds['left']) {
$bounds['left'] = $xPos;
}
if ($xPos > $bounds['right']) {
$bounds['right'] = $xPos;
}
if ($yPos < $bounds['top']) {
$bounds['top'] = $yPos;
}
if ($yPos > $bounds['bottom']) {
$bounds['bottom'] = $yPos;
}
}
}
}
return $bounds;
}

Shouldn't this code make the whole picture black?

In p5.js, I am trying to process each pixel of an image for a personal project so I thought I would start out slow and just try to make each pixel black. For some reason the screen is just staying white and I have no idea why the pixels aren't being updated. Here's the code:
var Canvas;
var srcImg;
var defaultImg = "http://i.imgur.com/ARg0OOy.jpg";
function preload() {
srcImg = loadImage(defaultImg);
}
function setup () {
createCanvas(srcImg.width,srcImg.height);
noLoop();
}
function draw() {
srcImg.loadPixels();
for (var x = 0; x < srcImg.width; x++) {
for (var y = 0; y < srcImg.height; y++) {
var loc = x + y*srcImg.width;
srcImg.pixels[loc] = color(224,29,29);
}
}
console.log(loc);
console.log(srcImg.width);
console.log(srcImg.height);
console.log(srcImg.width * srcImg.height);
srcImg.updatePixels();
//image(srcImg, 0,0,srcImg.width, srcImg.height);
}
Also, if I uncomment the last line, I see the original picture and it is cut off at the top (and it hasn't turned every pixel black). You can see for yourself here. Any thoughts on why this is happening?
Edit: I tried even doing one row of pixels to be a vibrant red color and the reason I'm getting a white screen is because no matter what color I set the pixels to, they become white... Also, when I tried making the whole row this red color, it stopped at about 1/4 the way through as shown here (and is still white). I don't know why this is happening.
You're making all of the pixels black, but then you're drawing srcImage on top of those black pixels. So all you see is srcImage.
Try commenting out the image(srcImg, 0,0,srcImg.width, srcImg.height); line to see the black pixels.

Paper.js Subraster Selecting Wrong Area

I'm working in a Paper.js project where we're essentially doing image editing. There is one large Raster. I'm attempting to use the getSubRaster method to copy a section of the image (raster) that the user can then move around.
After the raster to edit is loaded, selectArea is called to register these listeners:
var selectArea = function() {
if(paper.project != null) {
var startDragPoint;
paper.project.layers[0].on('mousedown', function(event) { // TODO should be layer 0 in long run? // Capture start of drag selection
if(event.event.ctrlKey && event.event.altKey) {
startDragPoint = new paper.Point(event.point.x + imageWidth/2, (event.point.y + imageHeight/2));
//topLeftPointOfSelectionRectangleCanvasCoordinates = new paper.Point(event.point.x, event.point.y);
}
});
paper.project.layers[0].on('mouseup', function(event) { // TODO should be layer 0 in long run? // Capture end of drag selection
if(event.event.ctrlKey && event.event.altKey) {
var endDragPoint = new paper.Point(event.point.x + imageWidth/2, event.point.y + imageHeight/2);
// Don't know which corner user started dragging from, aggregate the data we have into the leftmost and topmost points for constructing a rectangle
var leftmostX;
if(startDragPoint.x < endDragPoint.x) {
leftmostX = startDragPoint.x;
} else {
leftmostX = endDragPoint.x;
}
var width = Math.abs(startDragPoint.x - endDragPoint.x);
var topmostY;
if(startDragPoint.y < endDragPoint.y) {
topmostY = startDragPoint.y;
} else {
topmostY = endDragPoint.y;
}
var height = Math.abs(startDragPoint.y - endDragPoint.y);
var boundingRectangle = new paper.Rectangle(leftmostX, topmostY, width, height);
console.log(boundingRectangle);
console.log(paper.view.center);
var selectedArea = raster.getSubRaster(boundingRectangle);
var selectedAreaAsDataUrl = selectedArea.toDataURL();
var subImage = new Image(width, height);
subImage.src = selectedAreaAsDataUrl;
subImage.onload = function(event) {
var subRaster = new paper.Raster(subImage);
// Make movable
subRaster.onMouseEnter = movableEvents.showSelected;
subRaster.onMouseDrag = movableEvents.dragItem;
subRaster.onMouseLeave = movableEvents.hideSelected;
};
}
});
}
};
The methods are triggered at the right time and the selection box seems to be the right size. It does indeed render a new raster for me that I can move around, but the contents of the raster are not what I selected. They are close to what I selected but not what I selected. Selecting different areas does not seem to yield different results. The content of the generated subraster always seems to be down and to the right of the actual selection.
Note that as I build the points for the bounding selection rectangle I do some translations. This is because of differences in coordinate systems. The coordinate system where I've drawn the rectangle selection has (0,0) in the center of the image and x increases rightward and y increases downward. But for getSubRaster, we are required to provide the pixel coordinates, per the documentation, which start at (0,0) at the top left of the image and increase going rightward and downward. Consequently, as I build the points, I translate the points to the raster/pixel coordinates by adding imageWidth/2 and imageHeight/2`.
So why does this code select the wrong area? Thanks in advance.
EDIT:
Unfortunately I can't share the image I'm working with because it is sensitive company data. But here is some metadata:
Image Width: 4250 pixels
Image Height: 5500 pixels
Canvas Width: 591 pixels
Canvas Height: 766 pixels
My canvas size varies by the size of the browser window, but those are the parameters I've been testing in. I don't think the canvas dimensions are particularly relevant because I'm doing everything in terms of image pixels. When I capture the event.point.x and event.point.y to the best of my knowledge these are image scaled coordinates, but from a different origin - the center rather than the top left. Unfortunately I can't find any documentation on this. Observe how the coordinates work in this sketch.
I've also been working on a sketch to illustrate the problem of this question. To use it, hold Ctrl + Alt and drag a box on the image. This should trigger some logging data and attempt to get a subraster, but I get an operation insecure error, which I think is because of security settings in the image request header. Using the base 64 string instead of the URL doesn't give the security error, but doesn't do anything. Using that string in the sketch produces a super long URL I can't paste here. But to get that you can download the image (or any image) and convert it here, and put that as the img.src.
The problem is that the mouse events all return points relative to 0, 0 of the canvas. And getSubRaster expects the coordinates to be relative to the 0, 0 of the raster item it is extracting from.
The adjustment needs to be eventpoint - raster.bounds.topLeft. It doesn't really have anything to do with the image width or height. You want to adjust the event points so they are relative to 0, 0 of the raster, and 0, 0 is raster.bounds.topLeft.
When you adjust the event points by 1/2 the image size that causes event points to be offset incorrectly. For the Mona Lisa example, the raster size (image size) is w: 320, h: 491; divided by two they are w: 160, h: 245.5. But bounds.topLeft of the image (when I ran my sketch) was x: 252.5, y: 155.5.
Here's a sketch that shows it working. I've added a little red square highlighting the selected area just to make it easier to compare when it's done. I also didn't include the toDataURL logic as that creates the security issues you mentioned.
Here you go: Sketch
Here's code I put into an HTML file; I noticed that the sketch I put together links to a previous version of the code that doesn't completely work.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Rasters</title>
<script src="./vendor/jquery-2.1.3.js"></script>
<script src="./vendor/paper-0.9.25.js"></script>
</head>
<body>
<main>
<h3>Raster Bug</h3>
<div>
<canvas id="canvas"></canvas>
</div>
<div id="position">
</div>
</main>
<script>
// initialization code
$(function() {
// setup paper
$("#canvas").attr({width: 600, height: 600});
var canvas = document.getElementById("canvas");
paper.setup(canvas);
// show a border to make range of canvas clear
var border = new paper.Path.Rectangle({
rectangle: {point: [0, 0], size: paper.view.size},
strokeColor: 'black',
strokeWidth: 2
});
var tool = new paper.Tool();
// setup mouse position tracking
tool.on('mousemove', function(e) {
$("#position").text("mouse: " + e.point);
});
// load the image from a dataURL to avoid CORS issues
var raster = new paper.Raster(dataURL);
raster.position = paper.view.center;
var lt = raster.bounds.topLeft;
var startDrag, endDrag;
console.log('rb', raster.bounds);
console.log('lt', lt);
// setup mouse handling
tool.on('mousedown', function(e) {
startDrag = new paper.Point(e.point);
console.log('sd', startDrag);
});
tool.on('mousedrag', function(e) {
var show = new paper.Path.Rectangle({
from: startDrag,
to: e.point,
strokeColor: 'red',
strokeWidth: 1
});
show.removeOn({
drag: true,
up: true
});
});
tool.on('mouseup', function(e) {
endDrag = new paper.Point(e.point);
console.log('ed', endDrag);
var bounds = new paper.Rectangle({
from: startDrag.subtract(lt),
to: endDrag.subtract(lt)
});
console.log('bounds', bounds);
var sub = raster.getSubRaster(bounds);
sub.bringToFront();
var subData = sub.toDataURL();
sub.remove();
var subRaster = new paper.Raster(subData);
subRaster.position = paper.view.center;
});
});
var dataURL = ; // insert data or real URL here
</script>
</body>
</html>

jQuery and Canvas loading behaviour

After being a long time lurker, this is my first post here! I've been RTFMing and searching everywhere for an answer to this question to no avail. I will try to be as informative as I can, hope you could help me.
This code is for my personal webpage.
I am trying to implement some sort of a modern click-map using HTML5 and jQuery.
In the website you would see the main image and a hidden canvas with the same size at the same coordinates with this picture drawn into it.
When the mouse hovers the main picture, it read the mouse pixel data (array of r,g,b,alpha) from the image drawn onto the canvas. When it sees the pixel color is black (in my case I only check the RED value, which in a black pixel would be 0) it knows the activate the relevant button.
(Originally, I got the idea from this article)
The reason I chose this method, is for the page to be responsive and dynamically change to fit different monitors and mobile devices. To achieve this, I call the DrawCanvas function every time the screen is re-sized, to redraw the canvas with the new dimensions.
Generally, this works OK. The thing is ,there seems to be an inconsistent behavior in Chrome and IE(9). When I initially open the page, I sometimes get no pixel data (0,0,0,0), until i re-size the browser. At first I figured there's some loading issues that are making this happen so I tried to hack it with setTimeout, it still doesn't work. I also tried to trigger the re-size event and call the drawCanvas function at document.ready, still didn't work.
What's bothering me is most, are the inconsistencies. Sometimes it works, sometimes is doesn't. Generally, it is more stable in chrome than in IE(9).
Here is the deprecated code:
<script type="text/javascript">
$(document).ready(function(){setTimeout(function() {
// Get main image object
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
// Create a hidden canvas the same size as the main image and append it to main div
var canvas = document.createElement('canvas');
canvas.height = mapWrapper.clientHeight;
canvas.width = mapWrapper.clientWidth;
canvas.fillStyle = 'rgb(255,255,255)';
canvas.style.display = 'none';
canvas.id = 'hiddencvs';
$('#map_wrapper').append(canvas);
// Draw the buttons image into the canvas
drawCanvas(null);
$("#map_wrapper").mousemove(function(e){
var canvas = document.getElementById('hiddencvs');
var context = canvas.getContext('2d');
var pos = findPos(this);
var x = e.pageX - pos.x;
var y = e.pageY - pos.y;
// Get pixel information array (red, green, blue, alpha)
var pixel = context.getImageData(x,y,1,1).data;
var red = pixel[0];
var main_img = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
if (red == 0)
{
...
}
else {
...
}
});
},3000);}); // End DOM Ready
function drawCanvas(e)
{
// Get context of hidden convas and set size according to main image
var cvs = document.getElementById('hiddencvs');
var ctx = cvs.getContext('2d');
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
cvs.width = mapWrapper.clientWidth;
cvs.height = mapWrapper.clientHeight;
// Create img element for buttons image
var img = document.createElement("img");
img.src = "img/main-page-buttons.png";
// Draw buttons image inside hidden canvas, strech it to canvas size
ctx.drawImage(img, 0, 0,cvs.width,cvs.height);
}
$(window).resize(function(e){
drawCanvas(e);
}
);
function findPos(obj)
{
...
}
</script>
I'd appreciate any help!
Thanks!
Ron.
You don't wait for the image to be loaded so, depending on the cache, you may draw an image or not in the canvas.
You should do this :
$(function(){
var img = document.createElement("img");
img.onload = function() {
var mapWrapper = document.getElementById('map_wrapper').getElementsByTagName('img').item(0);
...
// your whole code here !
...
}
img.src = "img/main-page-buttons.png";
});

Categories

Resources