Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Program crashes when calling ctx.stroke() even when wrapped in try #388

Closed
kwhitefoot opened this issue Mar 11, 2022 · 7 comments
Closed

Comments

@kwhitefoot
Copy link

kwhitefoot commented Mar 11, 2022

I'm using pixie.Context to experiment with some semi-random drawing.
I build strokeStyle and path and then execute ctx.stroke. The program crashes even if I wrap the ctx.stroke call in a try.
I daresay that the strokeStyle is the culprit but it should surely not kill the program.

the snippet of code looks like this:

        echo "GStroke"
        echo "StrokeStyle: ", ctx.strokeStyle.repr
        try:
          ctx.stroke()
        except:
          echo getCurrentExceptionMsg()
          raise

And this is the result:

GStroke
StrokeStyle: ref 0x7fb0d3461590 --> [kind = AngularGradientPaint,
blendMode = NormalBlend,
opacity = 0.2196078449487686,
color = [r = 0.2156862765550613,
g = 0.0,
b = 0.007843137718737125,
a = 0.2196078449487686],
image = ref 0x7fb0d345a050 --> [width = 1,
height = 1,
data = 0x7fb0d345a080@[[r = 0,
g = 0,
b = 0,
a = 0]]],
imageMat = [arr = [1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0]],
gradientHandlePositions = 0x7fb0d345e5d0@[[arr = [0.0, 0.0]], [arr = [0.0, 0.0]], [arr = [0.0, 0.0]]],
gradientStops = 0x7fb0d345e450@[[color = [r = 0.0,
g = 0.0,
b = 0.0,
a = 0.0],
position = 0.0]]]
Killed
Error: execution of an external program failed: ...

I can try to create a stand alone example if necessary.

Edit: I've hardcoded the paint kind to SolidPaint so presumably it is not the strokeStyle that is the problem after all, perhaps it is the path.

@treeform
Copy link
Owner

If you can give us a stand alone example we can run that would be great.

When I run this, nothing crashes:

import pixie

let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))

let ctx = newContext(image)

echo "GStroke"
echo "StrokeStyle: ", ctx.strokeStyle.repr
try:
  ctx.stroke()
except:
  echo getCurrentExceptionMsg()
  raise

image.writeFile("crashStroke.png")

Please provide a better example and we will fix it.

@treeform
Copy link
Owner

Here is even a better example that replicates your stroke style, still no crash:

import pixie

let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))

let ctx = newContext(image)

ctx.beginPath()
ctx.moveTo(75, 50)
ctx.lineTo(100, 75)
ctx.lineTo(100, 25)

ctx.lineWidth = 10

ctx.strokeStyle = Paint(
  kind: AngularGradientPaint,
  blendMode: NormalBlend,
  opacity: 0.2196078449487686,
  color: color(0.2156862765550613, 0.0, 0.007843137718737125, 0.2196078449487686),
  image: newImage(1, 1),
  imageMat: mat3(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0),
  gradientHandlePositions: @[vec2(0.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 0.0)],
  gradientStops: @[ColorStop(color: color(0.0, 0.0, 0.0, 0.0), position: 0.0)]
)

echo "GStroke"
echo "StrokeStyle: ", ctx.strokeStyle.repr
try:
  ctx.stroke()
except:
  echo getCurrentExceptionMsg()
  raise

image.writeFile("crashStroke.png")```

@kwhitefoot
Copy link
Author

I added code to my program to make it write the nim statements that were being executed. So here is a relatively short program that illustrates the problem. It never returns from ctx.StrokePolygon. I'm not sure exactly why it fails on that instead of at ctx.Stroke now but it's probably because I commented out some code in a vain attempt to isolate the problem.

import pixie

let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))

let ctx = newContext(image)
ctx.strokeStyle = "#FF5C00"
ctx.translate(100, 100)

echo "StrokeStyle: ", ctx.strokeStyle.repr

ctx.lineWidth = 10

let
  start = vec2(25, 25)
  stop = vec2(175, 175)

echo "ctx lw: ", ctx.lineWidth
ctx.strokeSegment(segment(start, stop))

ctx.restore()
ctx.clearRect(0.0, 0.0, 0.0, 0.0)
ctx.clearRect(0.0, 0.0, 0.0, 0.0)
ctx.rotate(0.0)
ctx.save()
#ctx.fillStyle = paint
ctx.strokeRoundedRect(rect(vec2(0.0, 0.0), vec2(0.0, 0.0)), 0.0, 0.0, 0.0, 0.0)
ctx.strokeRect(rect(vec2(0.0, 0.0), vec2(0.0, 0.0)))
ctx.setLineDash(@[0.0.float32])
ctx.resetTransform()
ctx.strokeRoundedRect(rect(vec2(0.0, 0.0), vec2(0.0, 0.0)), 0.0, 0.0, 0.0, 0.0)
ctx.rotate(0.0)
ctx.globalAlpha = 0.0
ctx.clearRect(0.0, 0.0, 0.0, 0.0)
#ctx.strokeStyle = paint
ctx.roundedRect(rect(vec2(0.0, 0.0), vec2(0.0, 0.0)), 0.0,0.0,0.0,0.0)
ctx.strokeRoundedRect(rect(vec2(0.0, 0.0), vec2(0.0, 0.0)), 0.0, 0.0, 0.0, 0.0)
#ctx.strokeStyle = paint
ctx.clearRect(0.0, 0.0, 0.0, 0.0)
ctx.rotate(0.0)
#ctx.strokeStyle = paint
#ctx.fillStyle = paint
#ctx.fillStyle = paint
ctx.lineCap = RoundCap
echo "poly with 3 sides, size=50, in centre"
ctx.strokePolygon(vec2(100.0, 100.0), 50.0, 3)
echo "poly with 3 sides, size=10, in centre"
ctx.strokePolygon(vec2(100.0, 100.0), 10.0, 3)
echo "poly with 3 sides, size=10"
ctx.strokePolygon(vec2(0.0, 0.0), 10.0, 3)
echo "poly with 3 sides, size=0"
ctx.strokePolygon(vec2(0.0, 0.0), 0.0, 3)
echo "poly with 0 sides, size=10"
ctx.strokePolygon(vec2(0.0, 0.0), 10.0, 0)
echo "poly with 0 sides, size=0"
ctx.strokePolygon(vec2(0.0, 0.0), 0.0, 0)

image.writeFile("line.png")

It is the operating system that is killing the program. It reached 99% CPU, 69.6% memory before htop and everything else stopped responding.
I thought for a moment that it was the fact that the strokePolygon call has zero length sides so I added the calls with non-zero size, sides, and centre, all fail.
I'll try to pare down the number of statements but I thought I should post it as is while I try because you might see straight away what is just mysterious to me.

@treeform
Copy link
Owner

Thanks! I see the bug now.

That was hardly a simple example here is the same crash, but with all not needed lines removed:

import pixie

let image = newImage(200, 200)
let ctx = newContext(image)
ctx.setLineDash(@[0.0.float32])
ctx.strokePolygon(vec2(100.0, 100.0), 50.0, 3)

The problem is setLineDash to 0, it causes an infinite loop in the dash generating code. It probably should raise an exception or simply draw nothing...

treeform added a commit that referenced this issue Mar 12, 2022
@treeform
Copy link
Owner

Fixed, now it will throw an exception when this happens. Should be in the next release, you can get it from source or just don't set line dash to 0.

@kwhitefoot
Copy link
Author

Got it.

import pixie

let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))

let ctx = newContext(image)

ctx.setLineDash(@[0.0.float32])    <---- This is the offending statement.  
ctx.strokePolygon(vec2(100.0, 100.0), 50.0, 3)

image.writeFile("line.png")

if the argument tosetLineDash contains only zeroes then strokePolygon never completes and uses all available memory.

I think it must be in the pixie/paths.nim strokePaths proc that the infinite loops occurs:


    for i in 1 ..< shape.len:
      let
        pos = shape[i]
        prevPos = shape[i - 1]

      if dashes.len > 0:
        var distance = dist(prevPos, pos)
        let dir = dir(pos, prevPos)
        var currPos = prevPos
        block dashLoop:
          while true:
            for i, d in dashes:
              if i mod 2 == 0:
                let d = min(distance, d)
                shapeStroke.add(makeRect(currPos, currPos + dir * d))
              currPos += dir * d
              distance -= d
              if distance <= 0:  <--- when the dashes are all zeroes this condition will never be true (I think)
                break dashLoop
      else:
        shapeStroke.add(makeRect(prevPos, pos))

I can easily check for this condition in my code but perhaps pixie should too.
And lastly, thanks for creating pixie!

@kwhitefoot
Copy link
Author

It probably doesn't matter but it seems to me that your fix is stricter than necessary.
Throwing an exception if any dash is zero is efficient but at the moment a lineDash array that sums to greater than zero is legal even if some of it's constituent values are zero or even negative. At least this works at the moment.

import pixie

let image = newImage(200, 200)
image.fill(rgba(255, 255, 255, 255))

let ctx = newContext(image)
ctx.strokeStyle = "#FF5C00"

ctx.lineWidth = 10

ctx.setLineDash(@[-2.0.float32, 10.0.float32, 0.0.float32, 0.0.float32])
ctx.strokePolygon(vec2(100.0, 100.0), 70.0, 3)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants