Loading...
Logo
Processing Forum


I've written a simple sketch for drawing up and down (black and white) triangles up on a grid, mutating some one by changing its color (black ones may become white, white ones may become black or coloured). I draw each triangle using the triangle/6 primitive and the problem is visible on the default renderer as on the pdf one (after exporting).

As you can see from the image, with a black background beneath, when four white triangles are near one another (the middle one was a black triangle) you can spot the margin between them even tough I used noStroke() and just a fill.

I suspect that it is due to some numerical imprecision even tough the coordinates I'm giving in the grid are float representing integer numbers.

Any clue on how to improve the sketch? (I am attaching a copy of it)
http://www.mediafire.com/?rptt5h2ctrgg2et

Replies(9)

I did lotsa hack & slash to your code. Even to Grid2D class!
I've changed PVector to a customized IntVector I did. Which just has 2 int fields -> x, y.
This way, all coordinates are assured to be always whole values.
I can't tell much whether it made any differences. But check it out: 

Pattini.pde
Copy code
    /** 
     * Pattini (v2.23)
     * by  Arranger (2013/Jul)
     * mod GoToLoop
     * 
     * http://forum.processing.org/topic/rendering-imperfections-drawing-lines
     * http://www.mediafire.com/?rptt5h2ctrgg2et
     */
    
    import processing.pdf.*;
    
    final static color BLACK = 0, WHITE = -1;
    final static color GOLD = #FFD300, ORCHID = #EE2B7A;
    
    final static int TRIANGS = 25, SMOOTHNESS = 2 << 4;
    
    final Grid2D grid = new Grid2D(
    new PVector(-100, -30), TRIANGS, TRIANGS, 40, 30);
    
    final color[][] blackTriangles = new color[TRIANGS][TRIANGS];
    final color[][] whiteTriangles = new color[TRIANGS][TRIANGS];
    
    //final static String GFX = JAVA2D;
    final static String GFX = P2D;
    
    PGraphics pg;
    boolean toDrawGrid;
    
    void setup() {
      size(800, 600, GFX);
      noLoop();
    
      mousePressed();
    
      pg = createGraphics(width, height, GFX);
    
      pg.beginDraw();
      pg.background(BLACK);
      pg.smooth(SMOOTHNESS);
      pg.endDraw();
    }
    
    void draw() {
      drawTriangles(pg, grid, blackTriangles, whiteTriangles);
      if (toDrawGrid)  grid.display(pg, GOLD);
      image(pg, 0, 0);
    }
    
    void mousePressed() {
      redraw();
    
      if (mouseButton == CENTER) {
        toDrawGrid = !toDrawGrid;
        return;
      }
    
      initTriangleColor(blackTriangles, BLACK);
      initTriangleColor(whiteTriangles, WHITE);
    
      mutateTriangleColor(blackTriangles, WHITE, 25);
    
      mutateTriangleColor(whiteTriangles, BLACK, 25);
      mutateTriangleColor(whiteTriangles, GOLD, 30);
      mutateTriangleColor(whiteTriangles, ORCHID, 30);
    }
    
    void keyPressed() {
      if (keyCode == 'S')
        selectOutput("Select a file to write to:", "exportToPDF");
    
      else {
        mouseButton = LEFT;
        mousePressed();
      }
    }
    
    void exportToPDF(File selection) {
      if (selection == null) {
        println("Window was closed or the user hit cancel.");
        return;
      }
    
      String path = selection.getAbsolutePath();
      if (!path.toLowerCase().endsWith(".pdf"))  path += ".pdf";
    
      final PGraphics pdf = createGraphics(width, height, PDF, path);
    
      pdf.beginDraw();
      pdf.background(WHITE);
      pdf.smooth(SMOOTHNESS);
      pdf.endDraw();
    
      drawTriangles(pdf, grid, blackTriangles, whiteTriangles);
      if (toDrawGrid)  grid.display(pdf, GOLD);
    
      pdf.dispose();
    }
    


Functions.pde:

Copy code
    void initTriangleColor(color[][] triangles, color colour) {
      final int rows = triangles.length;
      final int cols = triangles[0].length;
    
      for (int i = 0; i != rows; ++i)  for (int j = 0; j != cols; ++j)
        triangles[i][j] = colour;
    }
    
    void mutateTriangleColor(color[][] triangles, color colour, int prob) {
      final int rows = triangles.length;
      final int cols = triangles[0].length;
    
      for (int i = 0; i != rows; ++i)   for (int j = 0; j != cols; ++j)
        if (random(prob) < 1)  triangles[i][j] = colour;
    }
    
    void drawTriangles(
    PGraphics layer, 
    Grid2D matrix, 
    color[][] darkTriangles, 
    color[][] lightTriangles)
    {
      final IntVector[][] points = matrix.points;
      //final PVector[][] points = matrix.getPoints();
    
      layer.beginDraw();
      layer.noStroke();
    
      for (int i = 0; i != TRIANGS; ++i)  for (int j = 0; j != TRIANGS; ++j) {
        if (j+1 >= TRIANGS | i-1 <= 0 | j-1 <= 0)  continue;
    
        int firstX, firstY, secondX, secondY, thirdX, thirdY;
        //float firstX, firstY, secondX, secondY, thirdX, thirdY;
    
        if ((i & 1) == 0) {
          /* drawing black ones */
          firstX = points[i][j].x;
          firstY = points[i][j].y;
          secondX = points[i][j + 1].x;
          secondY = points[i][j + 1].y;
          thirdX = (firstX + secondX) / 2;
          thirdY = points[i - 1][0].y;
    
          layer.fill(darkTriangles[i][j]);
          layer.triangle(firstX, firstY, secondX, secondY, thirdX, thirdY);
    
          /* drawing white ones */
          firstX = (points[i - 1][j].x + points[i - 1][j + 1].x) / 2;
          firstY = points[i - 1][0].y;
          secondX = (points[i - 1][j + 1].x + points[i - 1][j + 2].x) / 2;
          secondY = points[i - 1][0].y;
          thirdX = points[i][j + 1].x;
          thirdY = points[i][j + 1].y;
    
          layer.fill(lightTriangles[i][j]);
          layer.triangle(firstX, firstY, secondX, secondY, thirdX, thirdY);
        }
    
        else {
          /* drawing black ones */
          firstX = (points[i][j].x + points[i][j + 1].x) / 2;
          firstY = points[i][0].y;
          secondX = (points[i][j + 1].x + points[i][j + 2].x) / 2;
          secondY = points[i][0].y;
          thirdX = points[i - 1][j + 1].x;
          thirdY = points[i - 1][j + 1].y;
    
          layer.fill(darkTriangles[i][j]);
          layer.triangle(firstX, firstY, secondX, secondY, thirdX, thirdY);
    
          /* drawing white ones */
          firstX = points[i - 1][j].x;
          firstY = points[i - 1][j].y;
          secondX = points[i - 1][j + 1].x;
          secondY = points[i - 1][j + 1].y;
          thirdX = (firstX + secondX) / 2;
          thirdY = points[i][0].y;
    
          layer.fill(lightTriangles[i][j]);
          layer.triangle(firstX, firstY, secondX, secondY, thirdX, thirdY);
        }
      }
    
      layer.endDraw();
    }
    


Grid2D.pde:

Copy code
    /***************************************************************************
     *   Copyright (C) 2013 by Antonio Vergari                                 *
     *   arranger1044@aim.com                                                  *
     *                                                                         *
     *   Department of Computer Science                                        *
     *   University of Bari, Italy                                             *
     *                                                                         *
     *   This program is free software; you can redistribute it and/or modify  *
     *   it under the terms of the GNU General License as published by  *
     *   the Free Software Foundation; either version 2 of the License, or     *
     *   (at your option) any later version.                                   *
     *                                                                         *
     *   This program is distributed in the hope that it will be useful,       *
     *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
     *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
     *   GNU General License for more details.                          *
     *                                                                         *
     *   You should have received a copy of the GNU General License     *
     *   along with this program; if not, write to the                         *
     *   Free Software Foundation, Inc.,                                       *
     *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
     ***************************************************************************/
    
    // Hacked by GoToLoop (2013/Jul)
    
    class Grid2D {
      final PVector origin;
      IntVector[][] points;
    
      int cellWidth, cellHeight;
      int colPoints, rowPoints;
    
      /* Constructors **********************************************************/
    
      Grid2D(PVector orig, int cols, int rows, int cellW) {
        this(orig, cols, rows, cellW, cellW);
      }
    
      Grid2D(PVector orig, int cols, int rows, int cellW, int cellH) {
        origin = orig;
    
        cellWidth  = cellW;
        cellHeight = cellH;
    
        colPoints = cols + 1;
        rowPoints = rows + 1;
    
        initGridPoints();
      }
    
      /* Accessors **************************************************************/
    
      PVector[][] getPoints() {
        final PVector[][] p = new PVector[rowPoints][colPoints];
    
        for (int i = 0; i != rowPoints; ++i)  for (int j = 0; j != colPoints; ++j)
          p[i][j] = points[i][j].get();
    
        return p;
      }
    
      void setCellHeight(int newHeight) {
        stretchGridPoints(0, newHeight - cellHeight);
        cellHeight = newHeight;
      }
    
      void setCellWidth(int newWidth) {
        stretchGridPoints(newWidth - cellWidth, 0);
        cellWidth = newWidth;
      }
    
      void setRows(int rows) {
        rowPoints = rows + 1;
        initGridPoints();
      }
    
      void setColumns(int cols) {
        colPoints = cols + 1;
        initGridPoints();
      }
    
      /* Modifying the grid *****************************************************/
    
      void initGridPoints() {
        points = new IntVector[rowPoints][colPoints];
        final int xStart = (int) origin.x, yStart = (int) origin.y;
    
        for (int i = 0; i != rowPoints; ++i) {
          final int yPos = yStart + i*cellHeight;
    
          for (int j = 0; j != colPoints; ++j) {
            final int xPos = xStart + j*cellWidth;
            points[i][j] = new IntVector(xPos, yPos);
          }
        }
      }
    
      void stretchGridPoints(float xOffset, float yOffset) {
        for (int i = 0; i != rowPoints; ++i)  for (int j = 0; j != colPoints; ++j) {
          final IntVector point = points[i][j];
          point.x += j * xOffset;
          point.y += i * yOffset;
        }
      }
    
      /* Drawing ***************************************************************/
    
      void display(PGraphics pg, color c) {
        pg.stroke(c);
        display(pg);
      }
    
      void display(PGraphics pg) {
        pg.beginDraw();
    
        /* Drawing row lines */
        for (int i = 0; i != rowPoints; ++i) {
          final IntVector startingPoint = points[i][0];
          final IntVector endingPoint = points[i][colPoints - 1];
    
          pg.line(startingPoint.x, startingPoint.y, endingPoint.x, endingPoint.y);
        }
    
        /* Drawing col lines */
        for (int j = 0; j != colPoints; ++j) {
          final IntVector startingPoint = points[0][j];
          final IntVector endingPoint = points[rowPoints - 1][j];
    
          pg.line(startingPoint.x, startingPoint.y, endingPoint.x, endingPoint.y);
        }
    
        pg.endDraw();
      }
    }
    


IntVector.pde:

Copy code
    class IntVector {
      int x, y;
    
      IntVector(float xx, float yy) {
        set((int) xx, (int) yy);
      }
    
      IntVector(PVector p) {
        set(p);
      }
    
      void set(int xx, int yy) {
        x = xx;
        y = yy;
      }
    
      void set(PVector p) {
        x = (int) p.x;
        y = (int) p.y;
      }
    
      PVector get() {
        return new PVector(x, y);
      }
    }
    


Thanks for your reply GoToLoop and for your mods, so in the end it was an issue related to floats.

Among your other modifications what hits my eyes is the use of the keyword 'final' (mainly inside loops) is it for preventing the vars to be reassigned some value?
Any other modification you wanna pointing it out to me?
I use keyword final almost anywhere I can! 
For field variables, to make them constants.
And for local variables, just to flag to some1 reading the source code
that it's not gonna change its value till the end of its scope.
Glad you've liked it! 

Now some explanation:
In a division operation in Java, not in JavaScript, when both of its operands are whole values,
its resulting division is truncated to an int data-type.
By truncating, I mean anything after a decimal point is removed!

Pixel coordinates are always whole values. If a floating value is used, it ends up rounded internally.
I dunno which strategy is used, a regular round or a raw truncation.

Sometimes, those auto round operations/truncations may have some unexpected results for drawing!
I still have not got it quite right, in your code it is missing the line of code mutating the black triangles into white ones (potentially generating the problem):
Copy code
  1. mutateTriangleColor(blackTriangles, WHITE, 25);

if I add it then the problem appears to persist in the pdf export while not appearing in the default render.
Is this a question about renders (P2D vs PDF) or the issue gets solved by using the trick of drawing the graphics context as an image?

EDIT: the image posted refers to a bug in your code, you forgot to refactor pg.noStroke() into layer.noStroke(). However the issue persists as show in this other image.
Newer version available -> v2.05.

While perusing my version, I've found out this buggy pg.noStroke() line by myself.
But it doesn't make any difference for screen drawing, since pg is the "real" PGraphics object anyways!

I've presumed that when pdf is passed as argument, the lack of noStroke() would show up. But no diff. either!
Even in your original downloaded code, WHITE triangles have some kinda BLACK stroked border there!

On screen, P2D is the only engine where WHITE triangles got no discernible stroked borders! Dunno why! 

I've chosen P2D b/c it renders faster in this particular program.
Although in general, JAVA2D is faster than P2D! 

Newer version -> v2.10.

Found a palliative fix for PDF engine:
within exportToPDF(), change -> pdf.background(BLACK); to -> pdf.background(WHITE);

Although BLACK triangles has some WHITE stroked borders now instead! 

I believe there are still some gaps between triangle drawings.
The reminiscent stroked borders are actually the background() color which wasn't properly drawn upon.

So, your drawTriangles() function still needs some more adjustments! 
Eh, eh, this is the exact behaviour I'm talking about in the first post : D
So in the end it was not a numeric approximation issue but a rendering engine one. I wonder how P2D handles the matter compared to PDF and the default one.
Perhaps if you adjust the triangle rendering function to make them a little "fatter", those gaps would be filled!
Yeah, there are many hacks to "cover" those imperfections, like drawing a whole big white triangle on top of those four small ones for example, but it will "ruin" the (simple set of) procedural rules I used.

Has anyone got any clue about the renderer behaviour?