Get stroke color of svg drawing

Hi, I'm trying to animate a line drawing. This article helped me to do it: https://forum.processing.org/one/topic/make-a-dot-follow-a-svg-path.html But - I have an SVG with strokes in different colors. How can I read it so the slowly painted image looks like the sourcefile in the end? (The draw order or z-index is also interesting. ) I could not find any explanation how to get and use the stroke-color with geomerative anywhere. Best regards Jens

Answers

  • Can you share your sketch code and a link to a sample SVG file?

    SVG files vary a lot -- the kind you are trying to animate makes a big difference.

    Is this specifically a geomerative library question?

  • I create the SVG in inkscape. This is not necessarily a geomerative library question. I just found the code mentioned above that uses it. Perhaps it is possible to do this without the library(?)

    Here is an svg i made for this test:

    jensdreske.de/stuff/draw_svg_test/data/apple.svg

    Here is the code:

    import geomerative.*;
    import processing.opengl.*;
    //import processing.xml.*;
    
    // global variables
    RShape grp;
    String svgFile;
    ArrayList ve;
    int nve = 1;
    int colo;
    
    // global params
    float pl = 4.0; // maximum segments length
    
    
    void setup() {
    // Initialize the sketch
    size(400, 400);
    frameRate(24);
    ve= new ArrayList();
    
    
    
    // Choice of colors
    background(255);
    fill(255, 102, 0);
    stroke(100);
    strokeWeight(1);
    
    // VERY IMPORTANT: Allways initialize the library in the setup
    RG.init(this);
    
    svgFile = "apple.svg";
    //svgFile = "image.svg";
    grp = RG.loadShape(svgFile);
    //grp = RG.centerIn(grp, g);
    // Set the origin to draw in the middle of the sketch
    //translate(width/2, 3*height/4);
    // Enable smoothing
    smooth();
    
    RG.setPolygonizer(RG.UNIFORMLENGTH);
    RG.setPolygonizerLength(pl);
    
    exVert(grp);
    println("tot points: " + ve.size());
    println(grp.getTopLeft().x);
    //grp.draw();
    }
    
    void draw() {
    
    if (nve < ve.size()) {
    //strokeWeight(map(mouseY, 0, height, 1, 10));
    if (((Point) ve.get(nve)).z != -10.0) {
    if (mousePressed) {
      grp.draw();
    //((Point) ve.get(nve-1)).x += random(-5, +5);
    //((Point) ve.get(nve-1)).y += random(-5, +5);
    //line(((Point) ve.get(nve)).x, ((Point) ve.get(nve)).y, ((Point) ve.get(nve)).x+random(-10, +10), ((Point) ve.get(nve)).y+random(-10, +10));
    }
    line(((Point) ve.get(nve-1)).x, ((Point) ve.get(nve-1)).y, ((Point) ve.get(nve)).x, ((Point) ve.get(nve)).y);
    }
    nve++;
    }
    else { // restart drawing
    delay(3000);
    background(255);
    nve = 1;
    }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // recursively find RShape children and "flattens", by putting vertices inside an array
    //
    void exVert(RShape s) {
    RShape[] ch; // children
    int n, i, j;
    RPoint[][] pa;
    
    n = s.countChildren();
    if (n > 0) {
    ch = s.children;
    for (i = 0; i < n; i++) {
    exVert(ch[i]);
    }
    }
    else { // no children -> work on vertex
    pa = s.getPointsInPaths();
    n = pa.length;
    for (i=0; i<n; i++) {
    for (j=0; j<pa[i].length; j++) {
    //ellipse(pa[i][j].x, pa[i][j].y, 2,2);
    if (j==0)
    ve.add(new Point(pa[i][j].x, pa[i][j].y, -10.0));
    else
    ve.add(new Point(pa[i][j].x, pa[i][j].y, 0.0));
    }
    }
    //println("#paths: " + pa.length);
    }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Class for a 3D point
    //
    class Point { 
    float x, y, z; 
    Point(float x, float y, float z) { 
    this.x = x; 
    this.y = y;
    this.z = z;
    } 
    
    void set(float x, float y, float z) { 
    this.x = x; 
    this.y = y;
    this.z = z;
    }
    }
    
  • Well, it looks like geomerative element does return that stroke color information. Have you tried using it?

    https://github.com/rikrd/geomerative/blob/master/src/geomerative/RSVG.java#L430

        // Get the stroke for the geometrical element
        if(element.hasAttribute("stroke")){
          geomElem.setStroke(element.getString("stroke"));
        }
    
  • No, I didn't try yet. Sorry, my programming skills are too poor. I just tried to read the "style" string in the svg like this:

    PShape s;
    XML xml;
    
    void setup() {
      size(400 , 400);
      s = loadShape("apple.svg");
      xml = loadXML("apple.svg");
    
      XML[] children = xml.getChildren("g/path");
    
      for (int i = 0; i < children.length; i++) {
        String id = children[i].getString("id");
        String style = children[i].getString("style");
        println(i + " " + id + " "+ style);
      }
    }
    
    
    void draw() {
      background(255);
      smooth();
      s.enableStyle();
      shape(s, 10, 10);
    } 
    

    This returns strings like this: 10 path4298 opacity:1;fill:none;fill-opacity:1;stroke:#000000;stroke-width:1.41732287;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1

    (I don't know how to get only the "stroke"-attribute however.)

    I have no idea how to apply your suggestion using geomerative. I guess I could get the stroke-color somehow when the sketch loops through the children of Rshape (void exvert, line 76 code below) and store the colors somewhere, but I have no idea how to access this attribute? Could you give me a hint or some pseudocode how to do it? best regards Jens

    import geomerative.*;
    import processing.opengl.*;
    //import processing.xml.*;
    
    // global variables
    RShape grp;
    String svgFile;
    ArrayList ve;
    int nve = 1;
    int colo;
    
    // global params
    float pl = 4.0; // maximum segments length
    
    
    void setup() {
      // Initialize the sketch
      size(400, 400);
      frameRate(24);
      ve= new ArrayList();
    
    
    
      // Choice of colors
      background(255);
      fill(255, 102, 0);
      stroke(100);
      strokeWeight(1);
    
      // VERY IMPORTANT: Allways initialize the library in the setup
      RG.init(this);
    
      svgFile = "apple.svg";
    
      grp = RG.loadShape(svgFile);
    
      // Enable smoothing
      smooth();
    
      RG.setPolygonizer(RG.UNIFORMLENGTH);
      RG.setPolygonizerLength(pl);
    
      exVert(grp);
      println("tot points: " + ve.size());
      println(grp.getTopLeft().x);
      //grp.draw();
      println(ve);
    }
    
    void draw() {
    
      if (nve < ve.size()) {
        if (((Point) ve.get(nve)).z != -10.0) {
          line(((Point) ve.get(nve-1)).x, ((Point) ve.get(nve-1)).y, ((Point) ve.get(nve)).x, ((Point) ve.get(nve)).y);
        }
    
        nve++;
      } else { // restart drawing
        delay(3000);
        background(255);
        nve = 1;
      }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // recursively find RShape children and "flattens", by putting vertices inside an array
    //
    void exVert(RShape s) {
      RShape[] ch; // children
      int n, i, j;
      RPoint[][] pa;
    
      n = s.countChildren();
      if (n > 0) {
        ch = s.children;
        for (i = 0; i < n; i++) {
          exVert(ch[i]);
        }
      } else { // no children -> work on vertex
        pa = s.getPointsInPaths();
        n = pa.length;
        for (i=0; i<n; i++) {
          for (j=0; j<pa[i].length; j++) {
            //ellipse(pa[i][j].x, pa[i][j].y, 2,2);
            if (j==0)
              ve.add(new Point(pa[i][j].x, pa[i][j].y, -10.0));
            else
              ve.add(new Point(pa[i][j].x, pa[i][j].y, 0.0));
          }
        }
      }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Class for a 3D point
    //
    class Point { 
      float x, y, z; 
      Point(float x, float y, float z) { 
        this.x = x; 
        this.y = y;
        this.z = z;
      } 
    
      void set(float x, float y, float z) { 
        this.x = x; 
        this.y = y;
        this.z = z;
      }
    }
    
  • Yeah! I got it :-)

      ch[i].name); //  id of path 
    
      ch[i].getStyle().strokeColor; // get strokeColor 
    
      ch[i].getStyle().strokeWeight; // get strokeWeight of path
    

    Here is the complete sketch. It loads a .svg draws the stroke-paths in their original color and width, adjustable speed. Hit a key to start saving the pencil animation as a targa sequence with transparent background. strokes are drawn as little curve-segments, not ideal but the strokes look better. (the first and last few vertices are invisible though) suggestions welcome. best regards Jens

    import geomerative.*;
    import processing.opengl.*;
    //import processing.xml.*;
    
    // preview is 50%, output is 1920*1080, .tga transparent background
    // press a key to start saving frames
    // drawing order depends on appearance in .svg (look at xml-editor in inkscape to sort) 
    
    // global variables
    String file = "apple";
    int speed = 10;
    float pensize = 5;
    //int[] pencolor = {255, 0, 0, 210};
    float scale = 4;  // scale xy-pos of vertices
    float scaleStroke = 15;
    int offset = 100;
    
    
    String svgFile = file+".svg";
    
    RShape grp;
    
    ArrayList ve;
    int nve = 2;
    int pngframe = 1;
    //int colo;
    boolean render = false;
    
    // global params
    float pl = 4.0; // maximum segments length
    
    PGraphics alphaG;
    
    void setup() {
      // Initialize the sketch
      size(960, 540, P3D);
    
      // create an extra pgraphics object for rendering on a transparent background 
      alphaG = createGraphics(1920, 1080, P3D);
    
      frameRate(30);
    
      // Enable smoothing
      smooth(8);
    
      ve= new ArrayList();
    
    
    
      // Choice of colors
      background(255);
    
      // VERY IMPORTANT: Allways initialize the library in the setup
      RG.init(this);
    
    
      grp = RG.loadShape(file+".svg");
    
      RG.setPolygonizer(RG.UNIFORMLENGTH);
      RG.setPolygonizerLength(pl);
    
      exVert(grp, 0, 5);
      /*
      println("tot points: " + ve.size());
       println(grp.getTopLeft().x);
       //grp.draw();
       println(ve);
       */
      background(255);
    
      alphaG.beginDraw();
      alphaG.background(255, 0);
      alphaG.endDraw();
    }
    
    
    
    void draw() {
    
      // draw into the pgraphics object
      alphaG.beginDraw();
      //alphaG.stroke(pencolor[0], pencolor[1], pencolor[2], pencolor[3]);
      //alphaG.strokeWeight(pensize);
    
      alphaG.noFill();
    
    
    
      for (int i = 0; i < speed; i = i+1) { 
        if (nve < ve.size()-2) {  
    
          alphaG.stroke(((Point) ve.get(nve)).clr);
    
          alphaG.strokeWeight(((Point) ve.get(nve)).strk * scaleStroke);
    
          alphaG.beginShape();
    
    
          if (((Point) ve.get(nve)).z == -20.0) {  
            alphaG.endShape();
            nve = nve+3;
          } 
          alphaG.curveVertex(
            ((Point) ve.get(nve-2)).x, ((Point) ve.get(nve-2)).y
            );
          alphaG.curveVertex(
            ((Point) ve.get(nve-1)).x, ((Point) ve.get(nve-1)).y
            );
          alphaG.curveVertex(
            ((Point) ve.get(nve)).x, ((Point) ve.get(nve)).y
            );
          alphaG.curveVertex(
            ((Point) ve.get(nve+1)).x, ((Point) ve.get(nve+1)).y
            );
    
    
    
          alphaG.endShape();
    
    
    
          nve = nve+1;
        } else { // don't restart drawing
          exit();
        }
      }
    
      alphaG.endDraw();
    
      // draw the second renderer into the window, so we can see something 
      background(255);
      image(alphaG, 0, 0, 960, 540);
    
      framesaver();
    }
    
    
    //---------------------------
    
    void keyPressed() {
    
      nve = 2;
      pngframe = 1;
      render = true;
    
      alphaG.beginDraw();
      alphaG.clear();
      alphaG.background(255, 0);
      alphaG.endDraw();
    
      background(255);
    }
    
    //----------------------
    
    
    void framesaver() {
      if (render == true) {
        String counter = nf(pngframe, 8);
        alphaG.save(file+"/"+file+"_"+counter+".tga"); 
        println(file+"/"+file+"_"+counter+".tga saved.");
        pngframe++;
      }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // recursively find RShape children and "flattens", by putting vertices inside an array
    // pass the strokeColor from paths to vertices
    //
    void exVert(RShape s, color c, float stk) {
      RShape[] ch; // children
      int n, i, j;
      RPoint[][] pa;
    
    
    
      n = s.countChildren();
      if (n > 0) {
        ch = s.children;
        for (i = 0; i < n; i++) {
          println("id: "+ch[i].name); // print id of path 
    
          c = ch[i].getStyle().strokeColor; // get strokeColor of path
          printColors(c); 
    
    
          stk = ch[i].getStyle().strokeWeight; // get strokeWeight of path
          println("strokeWeight: " + stk);
    
          exVert(ch[i], c, stk);
        }
      } else { // no children -> work on vertex
        pa = s.getPointsInPaths();
    
        n = pa.length;
        for (i=0; i<n; i++) {
          for (j=0; j<pa[i].length; j++) {
            if (j==0) {
              ve.add(new Point(pa[i][j].x, pa[i][j].y, -10.0, c, stk));  // z = -10 :beginn of a path
            } else {
              if (j==pa[i].length-1) {
                ve.add(new Point(pa[i][j].x, pa[i][j].y, -20.0, c, stk)); // z = -20 :end of a path
              } else {         
                ve.add(new Point(pa[i][j].x, pa[i][j].y, 0.0, c, stk));
              }
            }
          }
        }
      }
    }
    
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Class for a 3D point
    //
    class Point { 
      float x, y, z; 
      color clr;
      float strk;
      Point(float x, float y, float z, color clr, float strk) { 
        this.x = x*scale+offset; 
        this.y = y*scale+offset;
        this.z = z;
        this.clr = clr;
        this.strk = strk;
      } 
    
      void set(float x, float y, float z) { 
        this.x = x; 
        this.y = y;
        this.z = z;
      }
    }
    
    
    
    //------------------
    
    void printColors (color rgb) {
    
      float red = rgb >> 16 & 0xFF;  // Very fast to calculate
      float green = rgb >> 8 & 0xFF; 
      float blue = rgb & 0xFF;
      float alpha = alpha(rgb);  // slower to calculate
      println("rgba: " + red +" "+ green +" "+ blue +" "+ alpha );
    }
    
  • @royal_orchestra -- congratulations on solving the problem!

    Thanks also for sharing your solution. It looks like the key SVG color information for each child shape was in:

    • RShape s.children[i].getStyle().strokeColor
    • RShape s.children[i].getStyle().strokeWeight
  • Thanks, I'm happy it works. SInce the code above has some limitations here is an update with working opacity handling that produces nice lines without breaks and some other improvements. In case someone is interested in pencil animation.

    What's missing now is a scribble effect that converts filled shapes to a vector- stroke that looks like drawn by hand. It seems the geomerative library is the way to go but the learning curve is hard...

    import geomerative.*;
    import processing.opengl.*;
    
    // preview is 50%, output is 1920*1080, .tga transparent background
    // press a key to start saving frames
    // drawing order depends on appearance in .svg (look at xml-editor in inkscape to rearrange) 
    
    // global variables
    String file = "apple";   //.svg file
    int speed = 10;                 //  vertices per frame
    
    float scale = 4;              // scale xy-pos of vertices
    float scaleStroke = 6;
    
    boolean curves = false;          // slow curves or fast polylines
    
    int offsetx = 100;
    int offsety = 100;
    
    int blending = MULTIPLY;         // DARKEST, MULTIPLY, BLEND , ...
    
    boolean ignoreAlpha = false;      
    int alphaOver = 255;                // override value if ignoreAlpha is true 
    float alphaExp = 1;                // scale transparency exponentially, only if ignoreAlpha is false
    
    boolean blackWhite = false;      // convert colors to grayscale
    
    RShape grp;
    
    ArrayList ve;
    int nve = 0;
    int pngframe = 1;
    
    boolean render = false;           // hit a key to save frames
    
    
    float pl = 4.0; // maximum segments length
    
    PGraphics alphaG;
    
    void setup() {
      // Initialize the sketch
      size(960, 540, P3D);
    
      // create an extra pgraphics object for rendering on a transparent background 
      alphaG = createGraphics(1920, 1080, P3D);
    
    
      frameRate(30);
    
      // Enable smoothing
      smooth(8);
    
      // frameRate(60);
      ve= new ArrayList();
    
    
      // VERY IMPORTANT: Allways initialize the library in the setup
      RG.init(this);
    
    
      grp = RG.loadShape(file+".svg");
    
      RG.setPolygonizer(RG.UNIFORMLENGTH);
      RG.setPolygonizerLength(pl);
    
      exVert(grp, 0, 5);
      /*
      println("tot points: " + ve.size());
       println(grp.getTopLeft().x);
       //grp.draw();
       println(ve);
       */
    
      alphaG.beginDraw();
      alphaG.blendMode(blending); // MULTIPLY for filthy pen , BLEND for opaque colors (def)
      alphaG.background(255, 0); // black transparent background leads to dark borders
      alphaG.endDraw();
    }
    
    
    
    void draw() {
    
      // draw into the pgraphics object
      alphaG.beginDraw();
    
      alphaG.noFill();
      alphaG.background(255, 0);
      alphaG.strokeJoin(ROUND); // ROUND,BEVEL, MITER(DEF) ???
    
    
      alphaG.beginShape();
    
    
      for (int p = 0; p < nve; p= p+1) {
    
        if (((Point) ve.get(p)).z == -10.0) {  
          alphaG.endShape();
          alphaG.beginShape();
        }
    
        if (curves == false) {
          alphaG.vertex(
            ((Point) ve.get(p)).x, ((Point) ve.get(p)).y
            );
        } else {
          alphaG.curveVertex(
            ((Point) ve.get(p)).x, ((Point) ve.get(p)).y
            );
        }
    
        if (blackWhite == true ) {
          alphaG.stroke(grayScale(((Point) ve.get(p)).clr));
        } else {
          alphaG.stroke(((Point) ve.get(p)).clr);
        }
    
        alphaG.strokeWeight(((Point) ve.get(p)).strk * scaleStroke);
      }
    
      if (nve < ve.size()) {
        nve = min(ve.size(), nve + speed);
      } else {
        exit();
      }
    
      alphaG.endShape();
    
      alphaG.endDraw();
    
    
    
      // draw the second renderer into the window, so we can see something 
      background(255);
      image(alphaG, 0, 0, 960, 540);
    
      framesaver();
    }
    
    
    //---------------------------
    
    void keyPressed() {
    
      nve = 2;
      pngframe = 1;
      render = true;
    
      alphaG.beginDraw();
      alphaG.clear();
      alphaG.background(255, 0); // black transparent background produces dark outlines (?) 
      alphaG.endDraw();
    
      background(255);
    }
    
    //----------------------
    
    
    void framesaver() {
      if (render == true) {
        String counter = nf(pngframe, 8);
        alphaG.save(file+"/"+file+"_"+counter+".tga"); 
        println(file+"/"+file+"_"+counter+".tga saved.");
        pngframe++;
      }
    }
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // recursively find RShape children and "flattens", by putting vertices inside an array
    // pass the strokeColor from paths to vertices
    //
    void exVert(RShape s, color c, float stk) {
      RShape[] ch; // children
      int n, i, j;
      RPoint[][] pa;
      int alp;
    
    
    
      n = s.countChildren();
      if (n > 0) {
        ch = s.children;
        for (i = 0; i < n; i++) {
          println("id: "+ch[i].name); // print id of path 
    
          c = ch[i].getStyle().strokeColor; // get strokeColor of path
    
          alp = ch[i].getStyle().strokeAlpha; // get opacity of path
    
          if (ignoreAlpha == false ) {
            c = color(c, scaleAlpha(alp, alphaExp));
          } else {
            c = color(c, alphaOver);
          }
    
          printColors(c);
    
          stk = ch[i].getStyle().strokeWeight; // get strokeWeight of path
          println("strokeWeight: " + stk);
    
          exVert(ch[i], c, stk);
        }
      } else { // no children -> work on vertex
        pa = s.getPointsInPaths();
    
        n = pa.length;
        for (i=0; i<n; i++) {
          for (j=0; j<pa[i].length; j++) {
            if (j==0) {
              ve.add(new Point(pa[i][j].x, pa[i][j].y, -10.0, c, stk));  // z = -10 :beginn of a path
            } else {
              if (j==pa[i].length-1) {
                ve.add(new Point(pa[i][j].x, pa[i][j].y, -20.0, c, stk)); // z = -20 :end of a path
              } else {         
                ve.add(new Point(pa[i][j].x, pa[i][j].y, 0.0, c, stk));
              }
            }
          }
        }
      }
    }
    
    
    // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    // Class for a 3D point
    // + color and strokeWeight
    class Point { 
      float x, y, z; 
      color clr;
      float strk;
      Point(float x, float y, float z, color clr, float strk) { 
        this.x = x*scale+offsetx; 
        this.y = y*scale+offsety;
        this.z = z;
        this.clr = clr;
        this.strk = strk;
      }
    }
    
    
    
    //------------------
    
    void printColors (color rgb) {
    
      float red = rgb >> 16 & 0xFF;  
      float green = rgb >> 8 & 0xFF; 
      float blue = rgb & 0xFF;
      float alpha = rgb >> 24 & 0xFF;
      ;
      println("rgba: " + red +" "+ green +" "+ blue +" " + alpha);
    }
    
    //------------------
    
    color grayScale (color rgb) {
      float red = rgb >> 16 & 0xFF;  
      float green = rgb >> 8 & 0xFF; 
      float blue = rgb & 0xFF;
      float alpha = rgb >> 24 & 0xFF; 
      float grayValue = (0.2989 * red + 0.5870* green + 0.1140* blue); // weighted b/w conversion
      color gray = color(grayValue, alpha);
      return gray;
    }
    
    //---------------
    int scaleAlpha (float a, float exp) {
      int scaled = (int)(pow((a/255), exp)*255);
      return scaled;
    }
    
  • a scribble effect that converts filled shapes to a vector-stroke that looks like drawn by hand

    Sounds interesting! Might be worth a separate question thread. I've got some ideas about how to tackle that -- and somebody might have done it already.

  • Shorter way to extract alpha: Instead of rgb >> 24 & 0xff, go w/ rgb >>> 24. *-:)

Sign In or Register to comment.