探索颜色渐变绘制算法(基于Processing语言) 第一部分
突然间意识到连续变化的颜色在程序中是如何实现的这一问题。没错,就想有事找事,我会分好几部分慢慢探寻,其实笔者也不会,咱一起研究。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中的编辑器一样,其次渐变风格可以切换,如圆型渐变、菱形渐变等,再次是非线性渐变算法等,好吧,是有难度的,慢慢来吧 ~ 希望可以借这篇文章给读者一些参考和借鉴,感谢阅读!!!
探索颜色渐变绘制算法(基于Processing语言) 第一部分的更多相关文章
- 数据结构与算法 基于c语言篇
学习数据结构与算法走向深蓝之路 第一章:数据结构与算法概念型 数据结构:数据之间的相互关系,即是数据的组织形式. 基本组成:{ 数据:信息的载体 数据元素:数据基本单位: } 其结构形式有四种: 1, ...
- iOS 动画绘制线条颜色渐变的折线图
效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...
- 机器学习-K-means聚类及算法实现(基于R语言)
K-means聚类 将n个观测点,按一定标准(数据点的相似度),划归到k个聚类(用户划分.产品类别划分等)中. 重要概念:质心 K-means聚类要求的变量是数值变量,方便计算距离. 算法实现 R语言 ...
- 探索canvas画布绘制技术
图片来自KrzysztofBanaś 下面我们开始尝试研究不同的绘图风格和技术 - 边缘平滑,贝塞尔曲线,墨水和粉笔,笔和印章和图案 -等等.事实证明,网上没有太多关于此的内容.在下面的示例中,您请大 ...
- Twitter基于R语言的时序数据突变检测(BreakoutDetection)
Twitter开源的时序数据突变检测(BreakoutDetection),基于无参的E-Divisive with Medians (EDM)算法,比传统的E-Divisive算法快3.5倍以上,并 ...
- 坐标轴刻度取值算法-基于魔数数组-源于echarts的y轴刻度计算需求
本文链接:https://blog.csdn.net/qq_26909801/article/details/96966372数值型坐标轴刻度计算算法前言算法描述上代码代码运行效果结语前言因实习的公司 ...
- 基于Processing图像序列处理保存导出的流程梳理
做一个基于processing的图像序列处理保存导出的流程梳理.本案例没有什么实质性的目的,仅为流程梳理做演示. 准备 把需要处理的影像渲染成序列图片,可以在PR中剪辑并导出PNG序列[格式倒是没什么 ...
- 【CImg】三角形绘制算法实现
这周的CV基础练习是简单的图形绘制:比如说矩形.三角形和圆心什么的.会发现其实矩形和圆形的实现思路都很直白,矩形只需要确认两个对角坐标就可以了,圆心只需要确认圆心和半径,接着就是简单的遍历各个像素点判 ...
- javascript实现颜色渐变
渐变(Gradient)是美学中一条重要的形式美法则,与其相对应的是突变.形状.大小.位置.方向.色彩等视觉因素都可以进行渐变.在色彩中,色相.明度.纯度也都可以产生渐变效果,并会表现出具有丰富层次的 ...
随机推荐
- linux环境下/etc/hosts文件详解
linux环境下/etc/hosts文件详解 就没一个昵称能用关注 0.0632017.09.12 17:04:28字数 623阅读 27,096 介绍 hosts文件是linux系统中负责ip地址与 ...
- Docker——Jenkins + Git + Registry构建自动化持续集成环境(CI/CD)
前言 在互联网时代,对于每一家公司,软件开发和发布的重要性不言而喻,目前已经形成一套标准的流程,最重要的组成部分就是持续集成(CI)及持续部署.交付(CD). 本文基于Jenkins+Docker+G ...
- # useradd -u 700 -g users vbird2
[root@linux ~]# ls -l /homedrwxr-xr-x 3 vbird1 vbird1 4096 Aug 30 17:33 vbird1[root@linux ~]# grep v ...
- 『动善时』JMeter基础 — 25、JMeter参数化补充练习
目录 1.使用"CSV数据文件设置"组件实现参数化 (1)测试计划中的元件 (2)数据文件内容 (3)线程组元件内容 (4)HTTP信息头管理器组件内容 (5)CSV数据文件设置组 ...
- 【GIS风暴】30米分辨率地表覆盖数据GlobeLand30原始数据集简介及下载地址
数据集预览: GlobeLand30是30米空间分辨率全球地表覆盖数据,目前可供下载使用的有3年的数据:2000-2010-2020,本文主要讲述GlobeLand30的官网下载地址和数据集简介. 数 ...
- Archlinux常用软件推荐 更新于2021年4月
记录一下常用软件 必装软件 包管理工具 yay 代替pacman的包管理 yaourt 备用 终端工具 zsh oh-my-zsh-git 搭配zsh利器` proxychains4 终端代理工具` ...
- Linux下RAID磁盘阵列的原理与搭建
RAID概念 磁盘阵列(Redundant Arrays of Independent Disks,RAID),有"独立磁盘构成的具有冗余能力的阵列"之意. 磁盘阵列是由很多价格较 ...
- Python+Selenium自动化-安装模块和浏览器驱动操作方法
Python+Selenium自动化-安装模块和浏览器驱动操作方法 1.安装模块文件 pip install selenium 2.安装浏览器驱动 我们主要用的浏览器驱动有chrome浏览器.fire ...
- C# 移除字符串头尾指定字符
1 private void button1_Click(object sender, EventArgs e) 2 {//去掉字符串头尾指定字符 3 string MyInfo= "--中 ...
- The Superego 实验四 团队作业1:软件研发团队组建
项目 内容 课程班级博客链接 班级博客链接 这个作业要求链接 作业要求链接 团队名称 The Superego 团队的课程学习目标 (1)组建团队,建设团队文化,申请开通团队博客 (2)团队之间相互协 ...