前言
由p5.js实现创意绘板,我分成了动态创意绘板和静态创意绘板。
静态创意绘板
上图仿照水粉画。
上述图中实现了对于水粉笔的模仿。
可以调节颜色的变化,对rgb直接进行调节,大小形状也可调节,上图展示的是使用正方形笔刷,颜色是根据画布横坐标来表现,可以看出,左边偏玫红,右边偏绿,笔刷的形状会对深浅有影响。交互的时候还挺好玩的,一个动作在整个画布中不局限于一种颜色,实现画布上颜色的渐变。
不足之处可能也是上述原因,位置的限制,不能做到自由区域颜色的选择。
主要原理是一定区域随机性,实现笔刷的绘制,实际绘制是一个一个小圆,就形成了粉状的质感。
实现代码:
function pastel(mx,my,sx,sy,r,g,b,a)
{ for(var i = 0; i < 1000; i++)
{
var ratio = mx/width;
var x = mx + random(-sx, sy);
var y = my + random(-sx, sy);
fill(r, ratio * g, b * (1 - ratio), a);
//fill(ratio * 255,255 * (1 - ratio) ,0, 30);
//fill(ratio *r,g * (1 - ratio) ,b, a);
ellipse(x, y, 2, 2);
}
}
if (keyCode === 65)
{ //A
colorMode(RGB,255,255,255,255);
pastel(mouseX,mouseY,sx,sy,r,g,b,30);
}
var r=0;
var g=255;
var b=255;
var sx=20;
var sy=20;
function keyPressed()
{
if(keyCode==82)//R
{ r=r+30;
}
if(keyCode==71)//G
{ g=g-30; }
if(keyCode==66)//B
{ b=b-30; }
if(keyCode==97)//1
{ sx=sx-5; }
if(keyCode==98)//2
{ sx=sx+5; }
if(keyCode==97)//3
{ sx=sy-5; }
if(keyCode==98)//4
{ sx=sy+5; }
}
上图模仿马克笔。图中线条的粗细变化,我觉得还挺好看的,从图中可以看出,重涂和轻涂,所展示的轨迹不一样,缓慢重涂,出来的效果跟接近于平常所使用马克笔,是很浓重的一抹;但是轻涂,特别是快速的划过,就可能画出另一种圆形,一个一个的圆,也体现模仿马克笔所采用的原理本质,是由一个一个的圆所叠加构成。
区别于传统马克笔,上图画笔的运用方式,如快速涂抹和缓慢重涂,可以展现不同的图案,展现了多边形,但是细腻方面,笔锋方面可能不太比得上传统马克笔,做不到快速细腻的笔锋也是上图所表现的一个不足之处。
(小学生涂鸦。。。)
图中是为了仿照现实水彩笔。
与传统最大的区别就是不用替换颜色呐(可能在选取颜色的时候不太方便),颜色的展现随机相连(适合懒人,一支笔有多种颜色),除此之外,就是笔刷颜色的透明度的变化,我觉得这是传统水彩笔实现不了的,快慢会导致笔刷颜色变化,相应也会导致不图透明度的变化,体现不一样色彩效果。
笔触细腻的处理也是上述图中一大不足。
上述图对水彩马克笔的模仿,缓慢厚涂和快速浅涂的区别。快涂,会使得的透明度升高,所展现的图案也和慢涂不同,原理是运用了插值变化。
实现代码:
// 彩球的位置
var x;var y;
// 彩球位置的插值变化速率
var lerpPosSpd = 31.0;
// 彩球当前大小s和颜色rgbaf(a:alpha)
var s = 15;var r = 0;var g = 0;var b = 0;var a = 0.1;
// 彩球颜色插值速率
var lerpSpd = 3.0;
// 彩球的目标大小和颜色
var st = 30;var rt = 0;var gt = 0;var bt = 0;var at = 0.1;
// //黑色圈圈
if (keyCode == 77)
{ //M c
olorMode(RGB,255,255,255,1);
circleColor();
}
//彩色圈圈
if (keyCode == 68)
{ //D
colorMode(RGB,1,1,1,1);
circleColor();
}
var lastTime = -1;
function circleColor()
{
var tNow = millis()/1000;
if(lastTime<0)
{
lastTime=tNow;
return;
}
var dt = tNow-lastTime;
var lerpPosT = lerpPosSpd*dt;
if(mouseIsPressed)
{
// 让x的数值向mouseX的数值靠拢,
// 靠拢方式为“线性插值”,靠拢程度(快慢)为lerpPosT
x = lerp(x,mouseX,lerpPosT);
y = lerp(y,mouseY,lerpPosT);
drawThing(x,y); }
var lerpT = lerpSpd * dt;
s = lerp(s,st,lerpT);
r = lerp(r,rt,lerpT);
g = lerp(g,gt,lerpT);
b = lerp(b,bt,lerpT);
a = lerp(a,at,lerpT);
randomChange(dt);
lastTime = tNow;
}
function drawThing(x,y)
{
fill(r,g,b,a);
noStroke();
ellipse(x,y,s,s);}
var TimeToChange = -1;
function randomChange(dt)
{
TimeToChange-= dt;
if(TimeToChange<0)
{
st = random(10,60);
rt = random(0,1);
gt = random(0,1);
bt = random(0,1);
at = random(0.1,0.3);
TimeToChange = randomValue();
print("TimeToChange:" + TimeToChange);
}
}
var lamda = 5;
function randomValue()
{
var x = 5/lamda;
var cnt = 0;
do
{ x = random(0,5/lamda);
var y = random(0,1);
var prob = exp(-lamda*x); /
if(cnt>30)
{
break;
}
cnt ++;
}while(prob<y)
}
动态创意绘板
动态创意绘板,可以选择多种笔刷,有圆形、三角形、长条、五边形、四边形、六角星星(星星真好看),六种笔刷,颜色也可多选,画板也可以多选,有夜间模式与白天模式。刷的笔刷都是会动的(图中使用的是静止截图)。
虽然有仿照传统绘画板,保留了绘画板的一些功能,如必刷的选择、清屏(点击C),橡皮擦(点击E),保存文件(点击S),以及多种颜色的可选性,但是从根本上,笔刷所得到的图形,最后是会动的,点击||使得图像静止。
与传统绘画板最大不同是,画出的图案是实时动态变化的,且笔刷也比较灵活,交互性上,也更有趣活波,相对的笔刷可能不太容易制作。
主要实现代码:
对于按钮和物体都需要创建数组来进行管理。
var objs = [];
var btns = [];
显示图案的结构定义。
//显示图案预定义
function Node(position, givenSize, givenR, givenG, givenB)
{
this.R = givenR;
this.G = givenG;
this.B = givenB; /
/图案位置
this.position = createVector(position.x, position.y);
this.position.x += (random(20) - 10);
this.position.y += (random(20) - 10); //
this.size = createVector(0, 0);
this.sizeScale = 0.5; var randomSize = givenSize / 2 + random(10);
this.baseSize = createVector(randomSize, randomSize); //
this.timepast = 0; this.isPlaying = isPlaying; //
this.rotateAngle = random(2 * PI); //笔刷类型
this.shapeType = brushType;
this.pmouseX = pmouseX;
this.pmouseY = pmouseY;
this.mouseX = mouseX;
this.mouseY = mouseY;
}
//功能按钮
function FuncBtn(X, Y, W, H, CMD)
{
this.x = X;
this.y = Y;
this.w = W;
this.h = H;
this.cmd = CMD;
//命令
}
//鼠标在颜色选框内
FuncBtn.prototype.isMouseInBtn = function()
{
if (mouseX >= this.x && mouseX <= this.x + this.w && mouseY >= this.y && mouseY <= this.y + this.h)
{ return true; }
else
{
return false;
}
}
点击按钮响应。
FuncBtn.prototype.clickBtn = function() { print("ClickBtn!"); //
if (this.cmd == "sun")
{
bR = 255;
bG = 255;
bB = 255;
this.cmd = "moon";
} else if (this.cmd == "moon") {
//改变画布的颜色
bR = 0;
bG = 0;
bB = 0;
this.cmd = "sun";
} else if (this.cmd == "pause") {
//运动状态停止
isPlaying = false;
for (var i = 0; i < objs.length; i++)
{
objs[i].isPlaying = false;
}
this.cmd = "play";
} else if (this.cmd == "play")
{ isPlaying = true; f
or (var i = 0; i < objs.length; i++)
{
objs[i].isPlaying = true;
}
this.cmd = "pause";
}
}
//显示颜色按钮 位置 宽高 颜色
function ColorBtn(X, Y, W, H, givenR, givenG, givenB)
{
this.x = X;
this.y = Y;
this.w = W;
this.h = H;
this.r = givenR;
this.g = givenG;
this.b = givenB;
}
星星笔刷和多边形笔刷。
// draw a regular n-gon with n sides
function ngon(n, x, y, d)
{
beginShape();
for(var i = 0; i < n; i++)
{
var angle = TWO_PI / n * i;
var px = x + sin(angle) * d / 2;
var py = y - cos(angle) * d / 2;
vertex(px, py);
}
endShape(CLOSE);
}
// draw a regular n-pointed star
function star(n, x, y, d1, d2)
{ beginShape();
for(var i = 0; i < 2 * n; i++)
{
var d = (i % 2 === 1) ? d1 : d2;
var angle = PI / n * i; v
ar px = x + sin(angle) * d / 2;
var py = y - cos(angle) * d / 2;
vertex(px, py);
}
endShape(CLOSE);
}
笔刷功能实现,以CIRCLE和SQUARE为例,使用sin函数和cos函数改变图像显示的中心位置,使得图像可以运动。
Node.prototype.drawing = function()
{
noStroke();
if (this.shapeType == "CIRCLE")
{
translate(this.position.x, this.position.y);
fill(this.size.x * this.R / 10, this.size.x * this.G / 10, this.size.x * this.B / 10, round(sin(this.timepast) * 128));
ellipse(sin(this.timepast) * this.baseSize.x, cos(this.timepast) * this.baseSize.y, this.size.x * 1.25, this.size.y * 1.25);
fill(this.size.x * this.R / 10, this.size.x * this.G / 10, this.size.x * this.B / 10, 255);
ellipse(sin(this.timepast) * this.baseSize.x, cos(this.timepast) * this.baseSize.y, this.size.x, this.size.y); resetMatrix();
}else if(this.shapeType == "SQUARE")
{
translate(this.position.x, this.position.y);
rotate(this.rotateAngle);
fill(this.size.x * this.R / 10, this.size.x * this.G / 10, this.size.x * this.B / 10, round(sin(this.timepast) * 128));
// rectMode(CENTER);
rect(sin(this.timepast) * this.baseSize.x, cos(this.timepast) * this.baseSize.y, this.size.x * 1.25, this.size.y * 1.25);
fill(this.size.x * this.R / 10, this.size.x * this.G / 10, this.size.x * this.B / 10, 255); // rectMode(CENTER);
rect(sin(this.timepast) * this.baseSize.x, cos(this.timepast) * this.baseSize.y, this.size.x,this.size.y);
resetMatrix();
}
显示图像的更新。
Node.prototype.update = function()
{ this.size = createVector(this.baseSize.x + sin(this.timepast) * this.baseSize.x * this.sizeScale,
this.baseSize.y + sin(this.timepast) * this.baseSize.y * this.sizeScale);
if (this.isPlaying) { this.timepast += 1 / FPS;
}
}
判断是否处于运动中,便于静止处理。
if(isPlaying)
{
btns.push(new FuncBtn(5, 5 + 30 * 14, 30, 30, "pause")); }
else{
btns.push(new FuncBtn(5, 5 + 30 * 14, 30, 30, "play")); }
btns.push(new FuncBtn(5, 5 + 30 * 15, 30, 30, "timer"));
btns.push(new FuncBtn(5, 5 + 30 * 16, 30, 30, "eraser"));
btns.push(new FuncBtn(5, 5 + 30 * 17, 30, 30, "clear"));
btns.push(new FuncBtn(5, 5 + 30 * 18, 30, 30, "save"));
}
鼠标按下的处理,处于可选择区域后的点击的处理。
if (mouseIsPressed && (mouseX > 40 || isMenuHide))
{
if (brushType == "CIRCLE" || brushType == "LINES" || brushType == "TRIANGLE" ||brushType == "SQUARE"||brushType == "PENTAGON"||brushType == "STAR" ||brushType == "COLORCIRCLE"||brushType == "PASTEL")
{
var position = createVector(mouseX, mouseY); //
objs.push(new Node(position, sqrt(sq(mouseX - pmouseX) + sq(mouseY - pmouseY)), R, G, B)); }
//Eraser
else if (brushType == "ERASER" && objs.length > 0)
{
for (var i = 0; i < objs.length; i++)
{
if (sqrt(sq(objs[i].position.x - mouseX) + sq(objs[i].position.y - mouseY)) <= eraserRange)
{
objs.splice(i, 1);
break;
}
}
}
else if (brushType == "TIMER" && objs.length > 0)
{ for (var i = 0; i < objs.length; i++)
{ if (sqrt(sq(objs[i].position.x - mouseX) + sq(objs[i].position.y - mouseY)) <= timerRange)
{ objs[i].timepast += 2 / FPS; objs[i].isPlaying = false;
}
}
}
}
图像的显示与更新。
for (var i = 0; i < objs.length; i++)
{
objs[i].drawing();
objs[i].update();
}
参考文献:
http://mc.dfrobot.com.cn/forum.php?mod=viewthread&tid=22951