pts icon indicating copy to clipboard operation
pts copied to clipboard

Canvas size issue when html attribute and css style width & height differs.

Open cdaein opened this issue 2 years ago • 4 comments

Hi, I'm trying to use pts.js with canvas-sketch, which provides its own canvas and context.

When I use it with the CanvasForm object only, there is no problem.

But when I add CanvasSpace supplying the existing canvas, there is a canvas sizing issue.

I think this happens when <canvas> width & height html attribute is different from width & height css style value. canvas-sketch automatically resizes the canvas css w&h according to the current browser window size.

I haven't found a way to update pts.js code accordingly. If anyone has a suggestion, I'd appreciate it.

Here is my sample code to reproduce the problem:

const canvasSketch = require("canvas-sketch");
const { CanvasSpace, CanvasForm, Pt } = require("pts");

const sketch = ({ canvas, context: ctx }) => {
  const space = new CanvasSpace(canvas); // adding space object causes the issue
  const form = new CanvasForm(ctx);

  return {
    render({ width, height, playhead }) {
      ctx.fillStyle = "#aaa";
      ctx.fillRect(0, 0, width, height);

      const center = new Pt([width / 2, height / 2]);
      const diam = Math.sin(playhead * Math.PI * 2) * 100 + 200;
      form.strokeOnly("#ff0", 12).point(center, diam, "circle");
    },
  };
};

const settings = {
  dimensions: [1400, 1400], // try a big value to reproduce the problem
  animate: true,
  duration: 4,
};

canvasSketch(sketch, settings);

cdaein avatar Jun 10 '22 03:06 cdaein

Hi @cdaein - yes, the css styles and width/height attributes differ to support different pixel density screens automatically. Eg, if you're using a 2x retina screen, the canvas' actual size will be double of the width & height.

I'm not familiar with canvas-sketch, but here are a couple suggestions --

  1. If you already have the <canvas> set up via canvas-sketch, you may not need to create another CanvasSpace. You can pass the rendering context directly to CanvasForm, eg new CanvasForm( ctx )

  2. If you are looking for a way to generate high-quality prints, you can try node-pts-canvas. Note that this is experimental and may not work 100%.

Hope this helps!

williamngan avatar Jun 11 '22 08:06 williamngan

Thank you for the response, @williamngan ! As you suggested, I am just using CanvasForm although I wish I can use convenience methods and properties that come with CanvasSpace. If only there is a way to modify my canvas to do that...

Also when using new CanvasForm(ctx), it seems that the gradient is not supported? I was recreating one of your demos - CanvasForm.gradient, and the gradient does not work properly.

I get an error:

Uncaught TypeError: Cannot read properties of undefined (reading 'ctx')
    at get ctx [as ctx] (dist.js:3146:26)
    at dist.js:3209:33
    at dist.js:5283:13
    at dist.js:5288:3

Sample code below. This time, I am using pts-starter-kit and do not have any other dependency.

import { CanvasForm, Pt, Bound, Create, Circle } from "pts";

// prepare canvas
const canvas = document.createElement("canvas");
canvas.width = 600;
canvas.height = 600;
document.body.appendChild(canvas);
const ctx = canvas.getContext("2d");

const form = new CanvasForm(ctx);

// recreate CanvasSpace properties for demonstration
const center = new Pt(canvas.width / 2, canvas.height / 2);
const size = new Pt(canvas.width, canvas.height);
const innerBound = new Bound(new Pt(), size);

// simulate mouse pointer
const pointer = new Pt(100, 100);

let scale = center.$subtract(pointer).divide(center).abs();
let bound = new Bound(new Pt(), size.$add(0, size.y * scale.y));
let cells = Create.gridCells(bound, 21, 30);
let offy = (bound.height - innerBound.height) / 2;
let cy = 1 - Math.abs(center.y - pointer.y) / center.y;

let radial = form.gradient([
  [0.2, `rgba(${70 * cy}, 0, ${255 * cy})`],
  [0.6, `rgba(${205 * cy}, 0, ${30 * cy})`],
  [0.95, `rgba(${255 * cy}, ${220 * cy}, 0)`],
]);

// radial gradient seems to have an issue with 'ctx'
form
  .fill(
    radial(
      Circle.fromCenter(pointer, center.y / 2),
      Circle.fromCenter(pointer, size.y * 1.5)
    )
  )
  .rect(innerBound);

for (let i = 0, len = cells.length; i < len; i++) {
  let grad = form.gradient(["rgba(255,255,255,1)", "rgba(255,255,255,0)"]);
  form
    .fillOnly(i % 2 === 0 ? grad(cells[i]) : "rgba(0,0,0,0)")
    .rect(cells[i].subtract(0, offy));
}

cdaein avatar Jun 11 '22 15:06 cdaein

Thanks for the report! I will take a look soon.

This is a common enough use case, so maybe we should create a CustomCanvasSpace or something similar for this type of use case.

williamngan avatar Jun 14 '22 08:06 williamngan

Thank you for looking into this. the gradient issue is now fixed with 0.10.12 as mentioned in the other issue.

cdaein avatar Jun 14 '22 14:06 cdaein