Bug: Execution interrupted while drawing

Posted on
  • First, I should mention that I'm pretty new to JS, so forgive me if this is a rookie mistake. I'm trying to make a custom clock for my bangle.js 2, and it involves inverting the colors of portions of the screen. I wrote a function that iterates over the pixels in a given circle and inverts them:

    // swaps background and foreground colors inside any given circle
    function invertCircle(x, y, rad) {
      rad = Math.round(rad);
      sw = g.getWidth() - 1;
      bg = g.getBgColor();
      fg = g.getColor();
      
      // iterate over every onscreen pixel within the radius
      let iEnd = Math.min(g.getHeight() - 1, y + rad);
      for (let i = Math.max(0, y - rad); i <= iEnd; i ++) {
        
        let chord = Math.round(Math.sqrt(Math.pow(rad, 2) - Math.pow(Math.abs(i - y), 2)));
        let jEnd = Math.min(sw, x + chord);
        for (let j = Math.max(0, x - chord); j <= jEnd; j ++) {
          
          // invert pixel color
          if (g.getPixel(j, i) == bg) {
            g.setPixel(j, i, fg);
          } else {
            g.setPixel(j, i, bg);
          }
        }
      }
    }
    
    // draw test image
    g.clear();
    g.setFont("Vector", 16);
    g.drawLine(0, g.getHeight() / 2, g.getWidth(), g.getHeight() / 2);
    g.drawString("Test\nImage", g.getWidth() / 2, g.getHeight() / 2 - 15);
    
    invertCircle(g.getWidth() / 2, g.getHeight() / 2, 30);
    

    When inverting larger areas, execution gets halted a few hundred milliseconds into the drawing process.

    Execution Interrupted
    at line 1 col 319
    ...(j,i)==bg){g.setPixel(j,i,fg);}else{g.setPixel(j,i,bg);}}}
                                      ^
    in function "invertCircle" called from line 1 col 53
    invertCircle(g.getWidth() / 2, g.getHeight() / 2, 30);
                                                        ^
    >echo(1)
    =undefined
    >
    

    I'm guessing that this has something to do with hogging the CPU for too long, but I'm not really sure. Any advice on how to avoid halting execution and / or make this more efficient would be appreciated.

  • Yes, I think likely what's happening is the IDE uploads the code and then doesn't get a response after a second or so, so it issues a Ctrl-C to break out of it.

    You could do:

    setTimeout(function() {
      invertCircle(g.getWidth() / 2, g.getHeight() / 2, 30);
    }, 1000);
    

    to run the command after a delay so the IDE can upload the code fine and see a response, but then the code happens after.

    BUT: iterating over every pixel is always going to be really slow I'm afraid - it's probably not something you want to be doing if there's any way to avoid it. What I'd suggest is actually making two images - one with what you want to draw and one with the circle, and XORing the actual binary data of the image together.

    ga = Graphics.createArrayBuffer(176,176,1,{msb:true});
    gb = Graphics.createArrayBuffer(176,176,1,{msb:true});
    var ba = new Uint32Array(ga.buffer);
    var bb = new Uint32Array(gb.buffer);
    
    ga.setFont("Vector:80").drawString("Hello");
    gb.fillCircle(40,40,40);
    
    // define this once so JIT only runs once
    function xor(a,i) { "jit"
      return a ^ bb[i];
    }
    
    function update() {
      var t = getTime();
      E.mapInPlace(ba, ba, xor);
      g.drawImage(ga); // draws with current FG and BG colors
      print("took",getTime()-t,"sec");
    }
    update();
    

    There are other ways to do it (eg compiled code) but this way a relatively straightforward.

    It still takes ~0.4sec to do the entire screen, but if you only wanted part of the screen done then it'd be a bit faster.

    I was considering adding different draw styles (add/xor/or/etc) to the Graphics lib which would really help with this, but I'm afraid that isn't implemented yet.

  • I was trying to implement something similar for a stop watch app except mine was a rectangle, not a circle, so I was able to use g.setClipRect.

    Maybe there could also be a g.setClipCircle?

  • That feels like it's quite a rare thing to want to do - I could imagine maybe having a 'mask' image where you set the areas you want to write to though?

    ...but personally I'd have thought that extending drawImages would be better - so we could allow each image to have a 'blend mode', much like you might blend layers in photoshop/Gimp.

    In the case above, you could have an XOR mode, but you could also do AND/OR or even just using an image to set the transparency, which would do what you want too

  • Yeah, I was using setClipRect as a mask essentially and layering the draws to create the animation.

    
    const SCREEN_WIDTH = g.getWidth();
    const SCREEN_HEIGHT = g.getHeight();
    const BOX_HEIGHT = 50;
    const NUMBER_SIZE = 30;
    const ANIMATION_DURATION = 3000;
    
    const BOX_DIMENSIONS = {
      left: 0,
      top: SCREEN_HEIGHT / 2 - BOX_HEIGHT / 2,
      width: SCREEN_WIDTH,
      height: BOX_HEIGHT
    };
    
    let numberValue = 0;
    let startTime = 0;
    let animationInterval;
    
    function setRect(method) {
      g[method](BOX_DIMENSIONS.left, BOX_DIMENSIONS.top, BOX_DIMENSIONS.width, BOX_DIMENSIONS.top + BOX_DIMENSIONS.height);
    }
    
    function drawNumber(yPosition, color) {
      g.setColor(color);
      g.setFont("Vector", NUMBER_SIZE);
      g.drawString(numberValue.toString(), SCREEN_WIDTH / 2 - g.stringWidth(numberValue.toString()) / 2, yPosition);
    }
    
    function animateNumber() {
      const now = Date.now();
      const progress = Math.min(1, (now - startTime) / ANIMATION_DURATION);
      g.clear();
    
      const currentY = (SCREEN_HEIGHT - NUMBER_SIZE) * progress;
    
      drawNumber(currentY, g.theme.fg);
    
      setRect('fillRect');
      setRect('setClipRect');
    
      drawNumber(currentY, g.theme.bg);
    
      g.setClipRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
      
      g.flip();
    
      if (progress < 1) {
        animationInterval = setTimeout(animateNumber, 1000 / 60);
      } else {
        numberValue = Math.floor(Math.random() * 100);
        startTime = Date.now();
        animationInterval = setTimeout(animateNumber, 500);
      }
    }
    
    startTime = Date.now();
    animateNumber();
    
    

    But it sounds like extending drawImages like you said could be way more versatile.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Bug: Execution interrupted while drawing

Posted by Avatar for Frickolas @Frickolas

Actions