突然間意識到連續變化的顏色在程式中是如何實現的這一問題。沒錯,就想有事找事,我會分好幾部分慢慢探尋,其實筆者也不會,我們一起研究。ok,我們開始!?
第一部分
初始部分就從官方案例來入手學習。官方給了三個相似問題的解決方案:
其中LinearGradient
是線性漸變,即兩點漸變,RadialGradient
是基於圓心漸變,WaveGradient
是基於sin函式
來繪製漸變色。我們從第一個入手,從兩點開始【拉漸變】。
開始
官方示例很明確是採用繪製多條Line來達成效果,即每根線都緊挨著,在巨集觀上看呈現連續的色塊,即:
/**
* Simple Linear Gradient
*
* The lerpColor() function is useful for interpolating
* between two colors.
*/
// Constants
int Y_AXIS = 1;
int X_AXIS = 2; //設立橫縱兩軸拉漸變的方法
color b1, b2, c1, c2;
void setup() {
size(640, 360);
// Define colors
b1 = color(255);
b2 = color(0);
c1 = color(204, 102, 0);
c2 = color(0, 102, 153);
noLoop();
}
void draw() {
// Background
setGradient(0, 0, width/2, height, b1, b2, X_AXIS);
setGradient(width/2, 0, width/2, height, b2, b1, X_AXIS);
// Foreground
setGradient(50, 90, 540, 80, c1, c2, Y_AXIS);
setGradient(50, 190, 540, 80, c2, c1, X_AXIS);
}
void setGradient(int x, int y, float w, float h, color c1, color c2, int axis ) {
noFill();
if (axis == Y_AXIS) { // Top to bottom gradient
for (int i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(x, i, x+w, i);
}
}
else if (axis == X_AXIS) { // Left to right gradient
for (int i = x; i <= x+w; i++) {
float inter = map(i, x, x+w, 0, 1);
color c = lerpColor(c1, c2, inter); //取兩色之間的差值
stroke(c); //每次劃線都採取相鄰的顏色值
line(i, y, i, y+h); //繪製連續的直線
}
}
}
程式碼中設定了橫縱兩軸方向性,然後新建了自己的函式setGradient()
。引數有起始位置以及寬高數值,還有兩個顏色極值參考,使用lerpColor()
算出介於兩顏色間的中間值並定義劃線顏色,然後統一在for迴圈
中畫出:
那麼我們可以借它的思想來修改。setGradient()
重新編寫:
void setGradient(int x, int y, float w, float h, color c1, color c2) { //方向性選擇去掉
noFill();
for (int i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(x, i, x+w, i);
}
}
然後可以用該方法繪製出特定方向[橫縱兩方向]的漸變色,並且可以實時繪製。如:
setGradient(50, 0, width, mouseY, c1, c2);
接著
如果想不定方向地繪製漸變呢?現在的思路是,隨意的拖拽滑鼠,記錄兩點,一點為起始點選位置,一點為終點拖拽位置,基於這兩點的長度和方向來繪製line線,其中線的顏色基於兩個顏色值進行lerpColor()
計算得來。先上程式碼:
PVector p1;
PVector p2;
PVector p3;
PVector p4;
PVector p5, p6;
float len;
color c1, c2;
int index = 0;
boolean showUI = true;
void setup()
{
size(800, 600);
//fullScreen();
c1 = color(204, 102, 0);
c2 = color(0, 102, 153);
}
void draw()
{
background(0);
//setGradient(50, 0, width, mouseY, c1, c2);
if (showUI)
{
push();
noFill();
stroke(250);
if (p1 != null)
circle(p1.x, p1.y, 30);
if (p2 != null)
circle(p2.x, p2.y, 30);
if (p2 != null && p1 != null)
{
line(p2.x, p2.y, p1.x, p1.y);
p3 = PVector.sub(p2, p1).normalize().rotate(HALF_PI);
p3.mult(60).add(p1);
p4 = PVector.sub(p2, p1).normalize().rotate(-HALF_PI);
p4.mult(60).add(p1);
line(p4.x, p4.y, p3.x, p3.y);
p5 = PVector.sub(p2, p1).normalize().rotate(HALF_PI);
p5.mult(60).add(p2);
p6 = PVector.sub(p2, p1).normalize().rotate(-HALF_PI);
p6.mult(60).add(p2);
line(p6.x, p6.y, p5.x, p5.y);
len = PVector.sub(p1,p2).mag();
for (float i = 0; i <= len; i+=1.0) {
float x = lerp(p3.x, p5.x, i/len); //使用lerp函式求得兩點之間的中間差值點位置,下同
float y = lerp(p3.y, p5.y, i/len);
point(x, y);
float x2 = lerp(p4.x, p6.x, i/len);
float y2 = lerp(p4.y, p6.y, i/len);
point(x2, y2);
float inter = map(i, 0, len, 0.0, 1.0);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(x, y, x2, y2);
}
}
pop();
}
}
void setGradient(int x, int y, float w, float h, color c1, color c2) {
noFill();
for (int i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1);
color c = lerpColor(c1, c2, inter);
stroke(c);
line(x, i, x+w, i);
}
}
void mousePressed() {
p1 = null; //復位
p2 = null;
p1 = new PVector(mouseX, mouseY);
}
void mouseDragged(){
p2 = new PVector(mouseX, mouseY); //實時更新第二個點位置
}
void mouseReleased(){
//p2 = new PVector(mouseX, mouseY);
println(len); //將兩點距離列印出來
}
void keyPressed() {
showUI = !showUI;
}
其中滑鼠的操作通過mousePressed()
mouseDragged()
mouseReleased()
等事件達成。至於漸變方塊的方向計算,具體大小確定,都基於基本的向量運算得來,詳情請參考原始碼。效果如下:
說一下不足。很明顯,這樣拉出來的漸變帶有空隙,不能完美的填充所有畫素點,和理想狀態差很多,但至少已經達成了初步的想法,在Processing中【拉漸變】!?
改進
我們能不能沿用這個思路來改進一下?借用討巧的方法---矩陣變換。我們先拉出橫平豎直的漸變,然後旋轉它,最後呈現出來。在P5中預設是畫在了一個PGraphics g
的圖層上,所以漸變讓其繪製在單獨的一層上方便旋轉等變換操作,修改上文程式碼:
PVector p1;
PVector p2;
PVector p3;
PVector p4;
PVector p5, p6;
PGraphics pg;
float len;
color c1, c2;
int index = 0;
boolean showUI = true;
void setup()
{
size(800, 600);
//fullScreen();
c1 = color(204, 102, 0);
c2 = color(0, 102, 153);
float pgsize = sqrt(sq(width)+sq(height));
pg = createGraphics(120, (int)pgsize);
}
void draw()
{
background(0);
if (showUI)
{
push();
noFill();
stroke(250);
if (p1 != null)
circle(p1.x, p1.y, 30);
if (p2 != null)
circle(p2.x, p2.y, 30);
if (p2 != null && p1 != null)
{
line(p2.x, p2.y, p1.x, p1.y);
p3 = PVector.sub(p2, p1).normalize().rotate(HALF_PI);
p3.mult(60).add(p1);
p4 = PVector.sub(p2, p1).normalize().rotate(-HALF_PI);
p4.mult(60).add(p1);
line(p4.x, p4.y, p3.x, p3.y);
p5 = PVector.sub(p2, p1).normalize().rotate(HALF_PI);
p5.mult(60).add(p2);
p6 = PVector.sub(p2, p1).normalize().rotate(-HALF_PI);
p6.mult(60).add(p2);
line(p6.x, p6.y, p5.x, p5.y);
len = PVector.sub(p1, p2).mag();
setGradient(0,0, 60+60, len, c1, c2); //在新圖層上繪製漸變 注意這裡寬度設為120,預設基於原點開始畫
push();
translate(p3.x, p3.y);
rotate(PVector.sub(p2, p1).heading()-HALF_PI); //作旋轉矩陣變換
push();
//translate(-p3.x, -p3.y);
image(pg, 0, 0); //渲染新圖層
pop();
pop();
}
pop();
}
}
void setGradient(float x, float y, float w, float h, color c1, color c2) {
pg.beginDraw();
pg.background(0, 0);
pg.noFill();
for (float i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1.0);
color c = lerpColor(c1, c2, inter);
pg.stroke(c);
pg.line(x, i, x+w, i);
}
pg.endDraw();
}
void mousePressed() {
p1 = null;
p2 = null;
p1 = new PVector(mouseX, mouseY);
}
void mouseDragged() {
p2 = new PVector(mouseX, mouseY);
}
void mouseReleased() {
//p2 = new PVector(mouseX, mouseY);
println(len);
}
void keyPressed() {
showUI = !showUI;
}
新建PGraphics pg
,然後繪製函式改成:
void setGradient(float x, float y, float w, float h, color c1, color c2) {
pg.beginDraw();
pg.background(0, 0);
pg.noFill();
for (float i = y; i <= y+h; i++) {
float inter = map(i, y, y+h, 0, 1.0);
color c = lerpColor(c1, c2, inter);
pg.stroke(c);
pg.line(x, i, x+w, i);
}
pg.endDraw();
}
將漸變線繪製在新的圖層上,這樣呼叫rotate()
:
push();
translate(p3.x, p3.y);
rotate(PVector.sub(p2, p1).heading()-HALF_PI);
image(pg, 0, 0);
pop();
效果如下圖:
很顯然,這種方法雖然討巧,不通用,但是效果很理想,沒有之前的細縫問題,而且效率很高,如果寬度調大,可以看成是全幅性的PS【拉漸變】了 ?~
(下圖為Processing全幅兩點漸變效果以及P5製作環境)
尾聲
最初的預想效果正是兩點線性漸變,那麼接下來要在此基礎上進行擴充,比如視覺化取點,像ps中的編輯器一樣,其次漸變風格可以切換,如圓型漸變、菱形漸變等,再次是非線性漸變演算法等,好吧,是有難度的,慢慢來吧 ~ 希望可以借這篇文章給讀者一些參考和借鑑,感謝閱讀!!!