joshmarinacci / node-pureimage

Pure JS implementation of the HTML Canvas 2D drawing API

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Re-stroking paths creates unexpected fills

FossPrime opened this issue · comments

Expected Behaviour

In this code:

const ctx = canvas.getContext('2d');
ctx.beginPath() // Only needed in pureimage :/

// First sub-path
ctx.lineWidth = 26;
ctx.strokeStyle = 'rgba(255,165,0,0.5)'
ctx.moveTo(20, 20);
ctx.lineTo(160, 20);
ctx.stroke();

// Second sub-path
ctx.lineWidth = 14;
ctx.strokeStyle = 'rgba(0,255,0,0.5)'
ctx.moveTo(20, 80);
ctx.lineTo(220, 80);
ctx.stroke();

// Third sub-path
ctx.lineWidth = 4;
ctx.strokeStyle = 'rgba(255,192,203,0.5)'
ctx.moveTo(20, 140);
ctx.lineTo(280, 140);
ctx.stroke();
  1. Lines should be parallel
    https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/stroke#re-stroking_paths
    image

Actual Behaviour

  1. lines are mostly parallel, with lots of diagonal fills

Screenshot from 2021-12-17 03-33-13

Steps To Reproduce

  1. Minimal: https://stackblitz.com/edit/koa-starter-yrc77r?file=index.js
  2. Workaround https://stackblitz.com/edit/koa-starter-zvulkw?file=index.js
  3. Where I had to workaround this bug: https://stackblitz.com/edit/feathersjs-7kqlyt?file=server.js

Any Relevant Code Snippets

Full koa server for testing:

import Koa from 'koa'
import { make, encodePNGToStream } from 'pureimage'
import { createWriteStream } from 'fs'
import logger from 'koa-logger'
import Router from '@koa/router'
import serve from 'koa-static'

const filepath = '/tmp/output.png'

function stroker(canvas) {
  const ctx = canvas.getContext('2d')
  ctx.beginPath()

  // First sub-path
  ctx.lineWidth = 26
  ctx.strokeStyle = 'rgba(255,165,0,0.5)'
  ctx.moveTo(20, 20)
  ctx.lineTo(160, 20)
  ctx.stroke()

  // Second sub-path
  ctx.lineWidth = 14
  ctx.strokeStyle = 'rgba(0,255,0,0.5)'
  ctx.moveTo(20, 80)
  ctx.lineTo(220, 80)
  ctx.stroke()

  // Third sub-path
  ctx.lineWidth = 4
  ctx.strokeStyle = 'rgba(255,192,203,0.5)'
  ctx.moveTo(20, 140)
  ctx.lineTo(280, 140)
  ctx.stroke()
}

const webCanvas = (ctx) => {
  ctx.body = `
    <h3>Expected Behavior:</h3>
    <canvas id="canvas"></canvas>
    <h3>Actual Behavior:</h3>
    <img src="output.png" />
    <script>
    ${stroker.toString()}
    stroker(document.getElementById('canvas'))
    </script>
  `
}

const app = new Koa()

// middlewares
app.use(logger())
const router = Router()
router.get('/', webCanvas)
app.use(serve('/tmp'))
app.use(router.routes())

// Run canvas code on server
try {
  const canvas = make(300, 150)
  stroker(canvas)

  await encodePNGToStream(canvas, createWriteStream(filepath))

  console.log('done writing to ', filepath)

  app.listen(3030)
} catch (err) {
  // When a request is aborted - err is an AbortError
  console.error(err)
}

My current understanding:

  • if you preface each set of lines with beginPath() then this won't happen. No re-stroking happens so it looks fine.
  • If you don't preface the later sets of lines with beginPath() then the later calls should re-stroke, but without the extra lines.

The beginPath function resets the path object, to prevent re-stroking. This is the correct behavior. When beginPath is not called the stroke function will convert the path to flat lines, and then to a new polygon representing the stroked path of the lines. This is where the bug lies. It doesn't work right if there are multiple sub-paths. This is the function path_to_stroked_path in context.js.

This would happen even without the missing beginPath. You can simply try to draw two lines as separate sub-paths and this will happen. Stroking doesn't work with multiple subpaths. That's the root bug.

The fix is to divide the path into distinct sub-paths, then handle them separately, then combine the results.

I just pushed back a fix. Can you see if the latest on MAIN fixes it? If so I'll push out a release.

That's a whole lot better! I think I have to change the blendmode on the browser version to screen and it should look exactly like what pure image produces.

image
https://stackblitz.com/edit/koa-starter-firdac?file=package.json,index.js

I've pushed the changes back and released a patch update on NPM. I'm going to mark this bug fixed. The remaining issues you see I think are due to color blending bugs (perhaps linear vs non-linear?).