突然间意识到连续变化的颜色在程序中是如何实现的这一问题。没错,就想有事找事,我会分好几部分慢慢探寻,其实笔者也不会,咱一起研究。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语言) 第一部分的更多相关文章

  1. 数据结构与算法 基于c语言篇

    学习数据结构与算法走向深蓝之路 第一章:数据结构与算法概念型 数据结构:数据之间的相互关系,即是数据的组织形式. 基本组成:{ 数据:信息的载体 数据元素:数据基本单位: } 其结构形式有四种: 1, ...

  2. iOS 动画绘制线条颜色渐变的折线图

    效果图 .................... 概述 现状 折线图的应用比较广泛,为了增强用户体验,很多应用中都嵌入了折线图.折线图可以更加直观的表示数据的变化.网络上有很多绘制折线图的demo,有 ...

  3. 机器学习-K-means聚类及算法实现(基于R语言)

    K-means聚类 将n个观测点,按一定标准(数据点的相似度),划归到k个聚类(用户划分.产品类别划分等)中. 重要概念:质心 K-means聚类要求的变量是数值变量,方便计算距离. 算法实现 R语言 ...

  4. 探索canvas画布绘制技术

    图片来自KrzysztofBanaś 下面我们开始尝试研究不同的绘图风格和技术 - 边缘平滑,贝塞尔曲线,墨水和粉笔,笔和印章和图案 -等等.事实证明,网上没有太多关于此的内容.在下面的示例中,您请大 ...

  5. Twitter基于R语言的时序数据突变检测(BreakoutDetection)

    Twitter开源的时序数据突变检测(BreakoutDetection),基于无参的E-Divisive with Medians (EDM)算法,比传统的E-Divisive算法快3.5倍以上,并 ...

  6. 坐标轴刻度取值算法-基于魔数数组-源于echarts的y轴刻度计算需求

    本文链接:https://blog.csdn.net/qq_26909801/article/details/96966372数值型坐标轴刻度计算算法前言算法描述上代码代码运行效果结语前言因实习的公司 ...

  7. 基于Processing图像序列处理保存导出的流程梳理

    做一个基于processing的图像序列处理保存导出的流程梳理.本案例没有什么实质性的目的,仅为流程梳理做演示. 准备 把需要处理的影像渲染成序列图片,可以在PR中剪辑并导出PNG序列[格式倒是没什么 ...

  8. 【CImg】三角形绘制算法实现

    这周的CV基础练习是简单的图形绘制:比如说矩形.三角形和圆心什么的.会发现其实矩形和圆形的实现思路都很直白,矩形只需要确认两个对角坐标就可以了,圆心只需要确认圆心和半径,接着就是简单的遍历各个像素点判 ...

  9. javascript实现颜色渐变

    渐变(Gradient)是美学中一条重要的形式美法则,与其相对应的是突变.形状.大小.位置.方向.色彩等视觉因素都可以进行渐变.在色彩中,色相.明度.纯度也都可以产生渐变效果,并会表现出具有丰富层次的 ...

随机推荐

  1. Linux shell sed命令在文件行首行尾添加字符

    昨天写一个脚本花了一天的2/3的时间,而且大部分时间都耗在了sed命令上,今天不总结一下都对不起昨天流逝的时间啊~~~ 用sed命令在行首或行尾添加字符的命令有以下几种: 假设处理的文本为test.f ...

  2. Linux 如何查看系统负载

    Linux 如何查看系统负载 310 博客 /  Linux/ 4个月前/  534 /  0   操作系统的负载状态,反映了应用程序的资源使用情况,从中能找出应用程序优化的瓶颈所在. 系统平均负载, ...

  3. Linux ll查看文件属性详解-软硬链接详解

    Linux文件属性及类型 [root@localhost ~]# ll anaconda-ks.cfg 文件类型 权限 硬连接数 文件的大小 文件的创建,修改时间 - rw-------. 1 roo ...

  4. Stm32高级定时器(转自:luowei_memory)

    1 定时器的用途 2 高级定时器框图 3 时基单元 4 通道 1 定时器的用途 已知一个波形求另一个未知波形(信号长度和占空比) 已知波形的信号长度和占空比产生一个相应的波形 增量正交编码器驱动电机获 ...

  5. Jmeter+Ant+Jenkins接口自动化框架

    最近应公司要求,搭建一套接口自动化环境.看到通知邮件,没有多想就确定了Jmeter路线.可能有些人会 说,为啥不用python,相对而言高大上一些.因为公司内部现在项目有用到Jmeter,正好可以结合 ...

  6. Linux(CentOS7)下安装jdk1.8

    Linux(CentOS7) 下安装 jdk1.8 操作过程. 一.检查是否自带jdk rpm -qa|grep java 如果存在则用下面命令删除,xxx yyy zzz代表查询出来的自带jdk名称 ...

  7. 重新整理 .net core 实践篇—————配置系统之强类型配置[十]

    前言 前文中我们去获取value值的时候,都是通过configurationRoot 来获取的,如configurationRoot["key"],这种形式. 这种形式有一个不好的 ...

  8. 微服务系列(二)GRPC的介绍与安装

    微服务系列(二)GRPC的介绍与安装 1.GPRC简介 GRPC是Google公司基于Protobuf开发的跨语言的开源RPC框架.GRPC基于HTTP/2协议设计,可以基于一个HTTP/2链接提供多 ...

  9. 如何使用 IoC

    创建Maven工程,pom.xml添加依赖 <?xml version="1.0" encoding="UTF-8"?> <project x ...

  10. Step By Step(Lua-C API简介)

    Step By Step(Lua-C API简介) Lua是一种嵌入式脚本语言,即Lua不是可以单独运行的程序,在实际应用中,主要存在两种应用形式.第一种形式是,C/C++作为主程序,调用Lua代码, ...