创意画板

用创意编程工具打造动态画板

如果你已经厌倦了用传统方式创作图形作品。不妨换个思路,用创意编程给自己制作一些有趣的绘画工具

比如,你可以用它绘制流动的星空

制作会弹跳的图形动画

也可以写一个旋转画板,制作有趣的图案

以上的演示都是使用Processing 和 openFrameWork 完成的,它们的原理并不复杂。在以后的 Creative Drawing 系列,会用代码给大家演示,如何在Processing 中实现这些绘图效果。

Creative Drawing

数字和图形,是最让人着迷的组合。抽象的数字一旦和图形发生化学反应,会焕发出新的光彩。

此系列默认大家已经有一定的 Processing 基础,不讲具体的技术细节,通篇围绕“画”来做文章。你可以跟随一个个小实例,由简至繁地深入。

###关于画板你要了解的“元知识”

  • 如何存储笔刷的轨迹
  • 如何记录绘制的过程
  • 如何构建笔刷

掌握这些元知识,你就能自如地创作画板了。这篇文章的实例里,就涉及了前两点。

从后面开始,会展示大段的源码,最终你可以实现上例里的旋转画板。如果你是没有编程基础的读者,但又希望体验一番,那可以先下载Processing,然后复制实例7的代码,在程序中点击运行按钮即可。

实例1-最简单的绘图程序

void setup(){
  size(500,500);
  background(0);
}

void draw(){
}

void mouseDragged(){
  stroke(255);
  line(pmouseX,pmouseY,mouseX,mouseY);
}
  • 利用拖动事件实现绘制效果。轨迹此时画在了background上,数据并没有以坐标的形式存储下来。

实例2-记录绘制轨迹

PVector []pos;

void setup(){
  size(500,500);  
  pos = new PVector[0];
}

void draw(){
  background(0);
  stroke(255);
  for(int i = 0;i < pos.length - 1;i++){
    line(pos[i].x,pos[i].y,pos[i + 1].x,pos[i + 1].y);
  }
}

void mouseDragged(){
  pos = (PVector [])append(pos,new PVector(mouseX,mouseY));  //每新增一个位置坐标,添加到pos列表后
}
  • 利用 PVector 数组保存系列的坐标。在 draw 函数中,通过循环绘制所有线条

实例3-记录绘制过程

PVector []pos;
float []pTime;
boolean drawOnOff;
float pressTime;
int brushNum;

void setup(){
  size(500,500);
  background(0);
  pos = new PVector[0];
  pTime = new float[0];
  drawOnOff = false;
  brushNum = 0;
}

void draw(){
  if(drawOnOff){
    if(millis()-pressTime > pTime[brushNum+1]-pTime[0]){
      line(pos[brushNum].x + 20,pos[brushNum].y,pos[brushNum+1].x + 20,pos[brushNum+1].y);
      brushNum++;
    }
    if(brushNum+1 == pos.length){  //终止绘制的判断
      drawOnOff = false;
    }
  }
}

void mouseDragged(){
  stroke(255);
  line(pmouseX,pmouseY,mouseX,mouseY);
  pos = (PVector [])append(pos,new PVector(mouseX,mouseY));  //每新增一个位置坐标,添加到pos列表后
  pTime = append(pTime,millis());
}

void keyPressed(){
  if(keyCode == 'A'){
    pressTime = millis();
    drawOnOff = true;   
  }
}
  • 用 pTime 数组储存绘制时间。通过循环,判断与第一个坐标点的时间差,来确定绘制的时间
  • 按 A 键播放

实例4-记录绘制过程(可停顿,可清除重绘)

PVector []pos;
float []pTime;
boolean drawOnOff;
float pressTime;
int brushNum;

void setup(){
  size(500,500);
  background(0);
  pos = new PVector[0];
  pTime = new float[0];
  drawOnOff = false;
  brushNum = 0;
}

void draw(){
  if(drawOnOff){
    if(millis() - pressTime > pTime[brushNum+1] - pTime[0]){
      line(pos[brushNum].x + 20,pos[brushNum].y,pos[brushNum+1].x + 20,pos[brushNum+1].y);     
      brushNum++;
      if(pos[brushNum+1].x == 0 && pos[brushNum+1].y == 0){
        brushNum+=2;
      }
    }
    if(brushNum+1 >= pos.length-1){  //终止绘制的判断
      drawOnOff = false;
    }
  }
}

void mouseDragged(){
  stroke(255);
  line(pmouseX,pmouseY,mouseX,mouseY);
  pos = (PVector [])append(pos,new PVector(mouseX,mouseY));  //每新增一个位置坐标,添加到pos列表后
  pTime = append(pTime,millis());
}

void mouseReleased(){
  pos = (PVector [])append(pos,new PVector(0,0)); 
  pTime = append(pTime,millis()); 
}

void keyPressed(){
  if(keyCode == 'A'){
    pressTime = millis();
    drawOnOff = true;   
  }
  if(keyCode == 'C'){
    background(0);
    pos = new PVector[0];
    pTime = new float[0];
    brushNum = 0;
  }
}
  • 实例3只能记录连续的绘制过程。若想区分不同的笔画,可在 mouseReleased 事件激活时,用坐标(0,0)来表示间隔
  • 按 C 键重绘

实例5-旋转画板

PVector []pos;
PVector [][]newPos;
float []pTime;
boolean drawOnOff;
float pressTime;
int brushNum,num;

void setup(){
  size(500,500);
  background(0);
  num = 6;   //设置旋转的画笔数量
  pos = new PVector[0];
  newPos = new PVector[num][0];
  pTime = new float[0];
  drawOnOff = false;
  brushNum = 0;
}

void draw(){
  if(drawOnOff){
    if(millis() - pressTime > pTime[brushNum+1] - pTime[0]){
      for(int i = 0;i < num - 1;i++){
        line(newPos[i][brushNum].x,newPos[i][brushNum].y,newPos[i][brushNum+1].x,newPos[i][brushNum+1].y);          
      }
      brushNum++;
      if(newPos[0][brushNum+1].x == 0 && newPos[0][brushNum+1].y == 0){
        brushNum+=2;
      }
    }
    if(brushNum+1 >= newPos[0].length-1){  //终止绘制的判断
      drawOnOff = false;
    }
  }
}

void mouseDragged(){
  stroke(255);
  line(pmouseX,pmouseY,mouseX,mouseY);
  pos = (PVector [])append(pos,new PVector(mouseX,mouseY));  //每新增一个位置坐标,添加到pos列表后
  for(int i = 0;i < num - 1;i++){
     newPos[i] = (PVector [])append(newPos[i],Trans(pos[pos.length-1],2*PI/num*(i+1)));
  }
  pTime = append(pTime,millis());
}

void mouseReleased(){
  pos = (PVector [])append(pos,new PVector(0,0)); 
  for(int i = 0;i < num;i++){
     newPos[i] = (PVector [])append(newPos[i],new PVector(0,0)); 
  }
  pTime = append(pTime,millis()); 
}

void keyPressed(){
  if(keyCode == 'A' && pos.length!=0){
    pressTime = millis();
    drawOnOff = true;
    pos = new PVector[0];
  }
  if(keyCode == 'C'){
    background(0);
    pos = new PVector[0];
    newPos = new PVector[num][0];
    pTime = new float[0];
    brushNum = 0;
    drawOnOff = false;
  }
}

PVector Trans(PVector a,float angle){
  PVector center = new PVector(width/2,height/2);
  float l = PVector.dist(a,center);
  float angle1 = atan2(a.y - center.y,a.x - center.x);
  float angle2 = angle1 + angle;
  float x = center.x + l*cos(angle2);
  float y = center.y + l*sin(angle2); 
  PVector newPos = new PVector(x,y); 
  return newPos;
}
  • 函数 Trans 计算旋转后的点坐标

实例6-连线规则

PVector []pos;  
float brushInterval,connectInterval;

void setup(){
  size(650,650);
  background(255);
  pos = new PVector[0];
  brushInterval = 2;   // 录入坐标点的距离
  connectInterval = 200;    //坐标点连线的最大距离
}

void draw(){
 
}

void mouseDragged(){
  if(pos.length == 0 || dist(mouseX,mouseY,pos[pos.length-1].x,pos[pos.length-1].y) > brushInterval){
    pos = (PVector [])append(pos,new PVector(mouseX,mouseY));
  }
 
  for(int i = 0;i < pos.length;i++){
     float r = dist(pos[i].x,pos[i].y,pos[pos.length-1].x,pos[pos.length-1].y);
     if(r < connectInterval){
        stroke(0,map(r,0,connectInterval,10,5));
        line(pos[i].x,pos[i].y,pos[pos.length-1].x,pos[pos.length-1].y);
     }
  }
}

void keyPressed(){
  if(keyCode == 'S'){
    saveFrame(frameCount + ".png");
  }
}
  • 通过判断坐标点之间的距离,决定连线与否。从而产生粘稠的,层次丰富的网状笔刷
  • 按‘S’ 键保存

实例7 - 网状旋转画板

PVector []pos;
PVector [][]newPos;
float []pTime;
boolean drawOnOff;
float pressTime;
int brushNum,num;
float interval,cInterval;

void setup(){
  size(500,500);
  background(255);
  num = 10;
  pos = new PVector[0];
  newPos = new PVector[num][0];
  pTime = new float[0];
  drawOnOff = false;
  brushNum = 0;
  interval = 5;
  cInterval = 100;
}

void draw(){
  if(drawOnOff){
    if(millis() - pressTime > pTime[brushNum+1] - pTime[0]){
      for(int i = 0;i < num-1;i++){
        for(int j = 0;j < brushNum;j++){        
           float r = dist(newPos[i][j].x,newPos[i][j].y,newPos[i][brushNum].x,newPos[i][brushNum].y);
           if(r < cInterval){
              stroke(0,map(r,0,cInterval,8,2));
              line(newPos[i][j].x,newPos[i][j].y,newPos[i][brushNum].x,newPos[i][brushNum].y);
           }        
        }          
      }
      brushNum++;
      if(newPos[0][brushNum+1].x == 0 && newPos[0][brushNum+1].y == 0){
        brushNum+=2;
      }
    }
    if(brushNum+1 >= newPos[0].length-1){  //终止绘制的判断
      drawOnOff = false;
    }
  }
}

void mouseDragged(){
  if(pos.length == 0 || dist(mouseX,mouseY,pos[pos.length-1].x,pos[pos.length-1].y) > interval){
    pos = (PVector [])append(pos,new PVector(mouseX,mouseY));
  }
  println(pos.length);
  for(int i = 0;i < pos.length;i++){
     float r = dist(pos[i].x,pos[i].y,pos[pos.length-1].x,pos[pos.length-1].y);
     if(r < cInterval){
        stroke(0,map(r,0,cInterval,8,2));
        line(pos[i].x,pos[i].y,pos[pos.length-1].x,pos[pos.length-1].y);
     }
  }
  
  for(int i = 0;i < num;i++){
     newPos[i] = (PVector [])append(newPos[i],Trans(pos[pos.length-1],2*PI/num*(i+1)));
  }
  pTime = append(pTime,millis());
}

void mouseReleased(){
  pos = (PVector [])append(pos,new PVector(0,0)); 
  for(int i = 0;i < num;i++){
     newPos[i] = (PVector [])append(newPos[i],new PVector(0,0)); 
  }
  pTime = append(pTime,millis()); 
}

void keyPressed(){
  if(keyCode == 'A' && pos.length!=0){
    pressTime = millis();
    drawOnOff = true;
    pos = new PVector[0];
  }
  if(keyCode == 'C'){
    background(255);
    pos = new PVector[0];
    newPos = new PVector[num][0];
    pTime = new float[0];
    brushNum = 0;
    drawOnOff = false;
  }
}

PVector Trans(PVector a,float angle){
  PVector center = new PVector(width/2,height/2);
  float l = PVector.dist(a,center);
  float angle1 = atan2(a.y - center.y,a.x - center.x);
  float angle2 = angle1 + angle;
  float x = center.x + l*cos(angle2);
  float y = center.y + l*sin(angle2); 
  PVector newPos = new PVector(x,y); 
  return newPos;
}
  • 按 A 键播放,C 键重绘
  • 将连线规则附加到旋转画板中,大功告成~~

END

这节就到这里,我们下期再见