javascriptprocessingprocessing.js

Weird visual bug when using my pixel art program


As the title states, a strange visual bug keeps happening with my pixel art program. When I scale down using pushMatrix(), scale() and popMatrix(). I can't really describe the bug, so I got a screenshot of it (the bug is the strange white lines in between pixels).

Visual bug

Code:

<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <title>Pixel Art Drawer & Importer</title>
</head>

<body>
  <canvas id="mycanvas"></canvas>

  <input type="text" id="color1" value="100" maxlength="3">
  <input type="text" id="color2" value="100" maxlength="3">
  <input type="text" id="color3" value="100" maxlength="3">
  <p>You can import the pixel art textures into a game or program with this <a href="https://www.khanacademy.org/computer-programming/example-pixel-art/5355812004872192">code</a>.</p>
  <script src="https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js"></script>
  <script>
    var sketchProc = function(processingInstance) {
      with(processingInstance) {
        size(400, 400);
        frameRate(30);
        // HOW TO USE
        // Pick a grid size with the grid variable below
        // Change the color variable below to whatever
        // There is no eraser, becaue I couldn't figure out how to make it work.

        var gridSize = 16;

        var pixels = [];
        var alreadyExists;
        var color1 = document.getElementById('color1');
        var color2 = document.getElementById('color2');
        var color3 = document.getElementById('color3');

        background(255, 255, 255);
        for (var i = 0; i < gridSize; i++) {
          stroke(0, 0, 0);
          line(400 / gridSize * i, 0, 400 / gridSize * i, 400);
          line(0, 400 / gridSize * i, 400, 400 / gridSize * i);
        }

        var update = function() {
          background(255, 255, 255);
          for (var i = 0; i < gridSize; i++) {
            stroke(0, 0, 0);
            line(400 / gridSize * i, 0, 400 / gridSize * i, 400);
            line(0, 400 / gridSize * i, 400, 400 / gridSize * i);
          }
          for (var i = 0; i < pixels.length; i++) {
            noStroke();
            pushMatrix();
            scale(0.5);
            fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
            rect(pixels[i][0] * (400 / gridSize), pixels[i][1] * (400 / gridSize), 400 / gridSize, 400 / gridSize);
            popMatrix();
          }
          document.getElementById("output").innerHTML = pixels;
        };

        var mousePressed = mouseDragged = function() {
          alreadyExists = false;
          closestPixel = [ceil(mouseX / (400 / gridSize)) - 1, ceil(mouseY / (400 / gridSize)) - 1, [color1.value, color2.value, color3.value]];

          for (var i = 0; i < pixels.length; i++) {
            if (samePixels(pixels[i], closestPixel)) {
              alreadyExists = true;
              pixels[i][2] = [color1.value, color2.value, color3.value];

              break;
            }
          }

          if (!alreadyExists) {
            pixels.push(closestPixel);
          }
          update();
        };

        var samePixels = function(p1, p2) {
          var samePosition = p1[0] === p2[0] && p1[1] === p2[1];

          if (!samePosition) {
            return false;
          }

          return true;
        };

      }
    };

    var canvas = document.getElementById("mycanvas");
    // Pass the function sketchProc (defined in myCode.js) to Processing's constructor.
    var processingInstance = new Processing(canvas, sketchProc);
  </script>
  <p id="output" style="color:red">This is where the output will appear.</p>
</body>

</html>

Solution

  • Looks like a case of the program doing what you tell it to do, not you want it to do :)

    The pushMatrix() / scale() / popMatrix() part you're using applies to how each "pixel" block is rendered (including the stroke which is what you're noticing), but doesn't affect any of the mouse cursor logic.

    One option would be to simply half the rect() x, y values and size:

    e.g.

    for (var i = 0; i < pixels.length; i++) {
                noStroke();
                // pushMatrix();
                // scale(0.5);
                fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
                rect(pixels[i][0] * (400 / gridSize) / 2, pixels[i][1] * (400 / gridSize) / 2, 400 / gridSize / 2, 400 / gridSize / 2);
                // popMatrix();
              }
    

    Note the above is just the relevant for loop in update(): hopefully the idea is clearly illustrated.

    You can also take this scale into account throughout the code, including in the mouse interation (which can be constrained):

    <!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <title>Pixel Art Drawer & Importer</title>
    </head>
    
    <body>
      <canvas id="mycanvas"></canvas>
    
      <input type="text" id="color1" value="100" maxlength="3">
      <input type="text" id="color2" value="100" maxlength="3">
      <input type="text" id="color3" value="100" maxlength="3">
      <p>You can import the pixel art textures into a game or program with this <a href="https://www.khanacademy.org/computer-programming/example-pixel-art/5355812004872192">code</a>.</p>
      <script src="https://cdn.jsdelivr.net/processing.js/1.4.8/processing.min.js"></script>
      <script>
        var sketchProc = function(processingInstance) {
          with(processingInstance) {
            size(400, 400);
            var scaleFactor = 0.5;
            var gridWidth   = width * scaleFactor;
            var gridHeight  = height * scaleFactor;
            frameRate(30);
            // HOW TO USE
            // Pick a grid size with the grid variable below
            // Change the color variable below to whatever
            // There is no eraser, becaue I couldn't figure out how to make it work.
    
            var gridSize = 16;
    
            var pixels = [];
            var alreadyExists;
            var color1 = document.getElementById('color1');
            var color2 = document.getElementById('color2');
            var color3 = document.getElementById('color3');
    
            var update = function() {
              background(255, 255, 255);
              for (var i = 0; i < gridSize; i++) {
                stroke(0, 0, 0);
                line(gridWidth / gridSize * i, 0, gridWidth / gridSize * i, gridWidth);
                line(0, gridHeight / gridSize * i, gridHeight, gridHeight / gridSize * i);
              }
              for (var i = 0; i < pixels.length; i++) {
                noStroke();
                // pushMatrix();
                // scale(0.5);
                fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
                rect(pixels[i][0] * (gridWidth / gridSize), 
                     pixels[i][1] * (gridHeight / gridSize) , 
                     gridWidth / gridSize , gridHeight / gridSize);
                // popMatrix();
              }
              document.getElementById("output").innerHTML = pixels;
            };
    
            var mousePressed = mouseDragged = function() {
              mouseX = constrain(mouseX, 0, gridWidth);
              mouseY = constrain(mouseY, 0, gridHeight);
              alreadyExists = false;
              closestPixel = [ceil(mouseX / (gridWidth / gridSize)) - 1, 
                              ceil(mouseY / (gridHeight / gridSize)) - 1, 
                              [color1.value, color2.value, color3.value]];
    
              for (var i = 0; i < pixels.length; i++) {
                if (samePixels(pixels[i], closestPixel)) {
                  alreadyExists = true;
                  pixels[i][2] = [color1.value, color2.value, color3.value];
    
                  break;
                }
              }
    
              if (!alreadyExists) {
                pixels.push(closestPixel);
              }
              update();
            };
    
            var samePixels = function(p1, p2) {
              var samePosition = p1[0] === p2[0] && p1[1] === p2[1];
    
              if (!samePosition) {
                return false;
              }
    
              return true;
            };
    
          }
        };
    
        var canvas = document.getElementById("mycanvas");
        // Pass the function sketchProc (defined in myCode.js) to Processing's constructor.
        var processingInstance = new Processing(canvas, sketchProc);
      </script>
      <p id="output" style="color:red">This is where the output will appear.</p>
    </body>
    
    </html>