【Wenzy】用创意编程打造动态画板

1,329 阅读4分钟
原文链接: mp.weixin.qq.com

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

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

查看图片

查看图片

查看图片


制作会弹跳的图形动画

查看图片

查看图片

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

查看图片

查看图片

以上的演示都是使用Processing 和 openFrameWorks 完成的,它们的原理并不复杂。在以后的 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;
}
}
  • 实例3只能记录连续的绘制过程。若想区分不同的笔画,可在 mouseReleased 事件激活时,用坐标(0,0)来表示间隔

  • 按 C 键重绘

实例5-旋转画板


    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;
}
}
  • 函数 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

我们下期再见