突然间意识到连续变化的颜色在程序中是如何实现的这一问题。没错,就想有事找事,我会分好几部分慢慢探寻,其实笔者也不会,咱一起研究。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下script命令录制、回放和共享终端操作script -t 2> timing.log -a output.session # 开始录制

    Linux下script命令录制.回放和共享终端操作 [日期:2018-09-04] 来源:cnblogs.com/f-ck-need-u  作者:骏马金龙 [字体:大 中 小]   另一篇终端会话共 ...

  2. centos下查看网卡,主板,CPU,显卡,硬盘型号等硬件信息

    centos下查看网卡,主板,CPU,显卡,硬盘型号等硬件信息 rose_willow rose_willow 发布于 2016/06/16 11:32 字数 902 阅读 405 收藏 0 点赞 0 ...

  3. mysql 配置文件概述

    mysql 配置文件概述 mysql 配置文件 mysql 的配置文件为 /etc/my.cnf 配置文件查找次序:若在多个配置文件中均有设定,则最后找到的最终生效 /etc/my.cnf --> ...

  4. C++知识点案例 笔记-4

    1.纯虚函数 2.抽象类 3.内部类 4.运算符重载 5.类的函数重载 6.友元的函数重载 1.纯虚函数 ==纯虚函数== //有时基类中无法给出函数的具体体现,定义纯虚函数可以为派生函数保留一个函数 ...

  5. Python 简单的龟鱼游戏

    游戏编程:按一下要求定义一个乌龟类和鱼类并尝试编程 假设游戏场景为范围(x,y)为 0<=x<=10,0<=y<=10 游戏生成1只乌龟和10条鱼 他们的移动方向均随机 乌龟的 ...

  6. C语言中位运算异或“∧”的作用

    1.概念异或运算符"∧"也称XOR运算符.它的规则是若参加运算的两个二进位同号,则结果为0(假):异号则为1(真).即 0∧0=0,0∧1=1, 1^0=1,1∧1=0.运算    ...

  7. 关于MongoDB ObjectId的那些事儿

    ObjectId 是什么? 一句话,ObjectId 是 MongoDB 文档 _id(主键)的默认类型. ObjectId 的组成? ObjectId 使用 12 字节的存储空间,是一个由 24 个 ...

  8. TensorFlow解析常量、变量和占位符

    TensorFlow解析常量.变量和占位符 最基本的 TensorFlow 提供了一个库来定义和执行对张量的各种数学运算.张量,可理解为一个 n 维矩阵,所有类型的数据,包括标量.矢量和矩阵等都是特殊 ...

  9. 『动善时』JMeter基础 — 40、JMeter中ForEach控制器详解

    目录 1.什么是逻辑控制器 2.ForEach控制器介绍 3.ForEach控制器的使用 (1)测试计划内包含的元件 (2)获取学院列表请求内容 (3)JSON提取器内容 (4)ForEach控制器内 ...

  10. 【NX二次开发】判断面是否相切,相切面。

    判断面是否相切,相切面. 用到的函数: UF_MODL_ask_minimum_dist 获取两个对象之间的最短距离,以及点坐标 UF_MODL_ask_face_parm 给定一个参考点,返回曲面上 ...