查看原文
其他

用创意编程打造动态画板

2015-11-12 Wenzy InsLab

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


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



https://v.qq.com/txp/iframe/player.html?vid=b0172gz439i&width=500&height=375&auto=0

制作会弹跳的图形动画


https://v.qq.com/txp/iframe/player.html?vid=u0172r5mbu3&width=500&height=375&auto=0


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


https://v.qq.com/txp/iframe/player.html?vid=h0172j7oayd&width=500&height=375&auto=0


以上的演示都是使用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;

}

}

  • 用 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

我们下期再见



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存