线性余弦调色板这有点像是生成艺术的内容但主要是作者为了向自己证明她能写一篇简短的博客文章而不是把它变成一篇冗长的专题论文。作者是丹妮尔·纳瓦罗文章发布于2025年9月14日。回顾在这个博客上的发文历程作者注意到自己倾向于写长篇文章深知这是个性格缺陷。当想了解某件事时心理上就会有一种强烈的冲动驱使自己深入探究细节尽可能挖掘所有相关的具体信息围绕这些细节整理思路然后把这一堆复杂的内容公之于众让那些长期忍受自己文章的读者惊恐地窥视自己内心世界的“怪诞景象”。作者知道这样做或许并不明智。反思这一弱点后在这个美好的周日作者给自己设定了一个挑战能写出一篇简洁的博客文章吗像自己这样水平一般的人能写出一篇简短的文章而不是把它变成一篇恐怖的专题论文吗鉴于以往的表现自己是否有能力做到自我克制还真不太确定。那就看看能否成功吧。这个想法源于迈克·程Mike Cheng在 [Mastodon](https://fosstodon.org/coolbutuseless/115173701685084866) 上发布的一篇帖子他提出了一种在 R 语言中随机生成连续调色板的简单方法。最初的灵感来自伊尼戈·基莱斯Inigo Quilez的一篇关于 [简单程序调色板](https://iquilezles.org/articles/palettes/) 的博客文章其思路极其简单。假设我们有长度为 3 的向量 \(\mathbf{a}\)、\(\mathbf{b}\)、\(\mathbf{c}\) 和 \(\mathbf{d}\)它们代表四种“基础”颜色用于生成连续的调色板。在 R 语言中我们可以使用 colors() 函数选择这些基础颜色。选定后我们可以使用以下函数定义一个平滑的调色板\[ f(t) \mathbf{a} \mathbf{b} \ \cos(2 \pi(\mathbf{c} t \mathbf{d})) \]其中\(t\) 的取值范围是从 0 到 1。这种调色规则的优点是速度很快因为据了解相关知识的人说现代 CPU 和 GPU 对余弦计算进行了大量优化。不过在作者的生成艺术作品中速度并不是特别关心的问题因为调色板生成在作者的代码中远不是瓶颈而且作者比较懒。下面是一个 R 函数它对迈克实现的伊尼戈·基莱斯的余弦调色板做了一点小改动cosine_palette - function(n, base NULL, seed NULL) { if (!is.null(seed)) set.seed(seed) if (is.null(base)) base - colors(distinct TRUE) a - c(0.5, 0.5, 0.5) b - (sample(base, 1) | col2rgb() | as.vector()) / 255 c - (sample(base, 1) | col2rgb() | as.vector()) / 255 d - (sample(base, 1) | col2rgb() | as.vector()) / 255 pal - vapply( seq(0, 1, length.out n), function(t) a b * cos(2 * pi * (c * t d)), double(3) ) pal[pal 1] - 1 rgb(t(abs(pal)))}cosine_palette(n 16, seed 11)[1] #7F1616 #6F1A17 #362A20 #22442F #8A6642 #F18A5A #FFAD74 [8] #FFCB8F #FFE0A9 #FFE9C0 #FFE6D3 #AAD6E1 #40BDE8 #1F9CE9[15] #6377E3 #7F54D6这很不错但由于作者的视觉系统不太擅长解读十六进制的 RGB 颜色代码所以觉得用图像来展示调色板会更方便。为此作者将使用 shade_strip() 函数作者有时会用它把连续变化的调色板显示为一个色带shade_strip - function(cols) { withr::with_par( list(mar c(0,0,0,0)), image( matrix(seq_along(cols), ncol 1), col cols, axes FALSE) )}seeds - 11:22seeds | purrr::map(\(s) cosine_palette(n 256, seed s)) | purrr::walk(shade_strip)作者想了解这些调色板在生成艺术系统中的表现所以选择了 12 个连续的随机种子。序列从 seed 11 开始因为作者恰好喜欢用这个调色板生成的第一幅作品除此之外作者没有刻意调整种子来影响输出结果。在生成艺术中的应用为了感受这些调色板在生成艺术中的效果下面是一些使用它们创作的作品。这些作品是使用作者在几年前举办的 [代码生成艺术工作坊](../../posts/2024-12-23_art-from-code-6/) 中介绍的 subdivision() 系统创作的。subdivision() 函数代码choose_rectangle - function(blocks) { sample(nrow(blocks), 1, prob blocks$area)}choose_break - function(lower, upper) { round((upper - lower) * runif(1))}create_rectangles - function(left, right, bottom, top, value) { tibble::tibble( left left, right right, bottom bottom, top top, width right - left, height top - bottom, area width * height, value value )}split_rectangle_x - function(rectangle, new_value) { with(rectangle, { split - choose_break(left, right) new_left - c(left, left split) new_right - c(left split, right) new_value - c(value, new_value) create_rectangles(new_left, new_right, bottom, top, new_value) })}split_rectangle_y - function(rectangle, new_value) { with(rectangle, { split - choose_break(bottom, top) new_bottom - c(bottom, bottom split) new_top - c(bottom split, top) new_value - c(value, new_value) create_rectangles(left, right, new_bottom, new_top, new_value) })}split_rectangle - function(rectangle, value) { split_fn - ifelse(runif(1) .5, split_rectangle_x, split_rectangle_y) split_fn(rectangle, value)}split_block - function(blocks, value) { old - choose_rectangle(blocks) new - split_rectangle(blocks[old, ], value) dplyr::bind_rows(blocks[-old, ], new)}subdivision - function(ncol 100, nrow 100, nsplits 256, border NULL, seed NULL) { if (!is.null(seed)) set.seed(seed) pal - cosine_palette(n 256, seed seed) if (is.null(border)) border - pal[128] rct - create_rectangles( left 1, right ncol, bottom 1, top nrow, value 0 ) div - purrr::reduce( 1:nsplits, split_block, .init rct ) plt - div | ggplot2::ggplot(ggplot2::aes( xmin left, xmax right, ymin bottom, ymax top, fill value )) ggplot2::geom_rect( show.legend FALSE, color border, linewidth 1 ) ggplot2::scale_fill_gradientn(colours pal) ggplot2::scale_x_continuous(expand ggplot2::expansion(mult .15)) ggplot2::scale_y_continuous(expand ggplot2::expansion(mult .15)) ggplot2::coord_equal() ggplot2::theme_void() ggplot2::theme(plot.background ggplot2::element_rect( color border, fill border )) plt}seeds | purrr::walk(\(s) plot(subdivision(seed s)))效果还不错。有些作品很糟糕有一些很出色大多数都还可以。考虑到作者完全没有优化调色板与作品结构的匹配方式这个结果已经相当不错了。作为第二个例子下面是一系列基于 [利萨如曲线Lissajous](https://art.djnavarro.net/gallery/lissajous/) 系统创作的作品都使用了相同的调色板seeds | purrr::walk(\(s) lissajous(seed s))效果也还可以。在具体应用中作者可能会稍微调整一下以适应系统所追求的特定美学风格但总体来说作者很满意。这么简单的方法能有比预期更好的效果真是不错。好了就到这里。文章完成没什么可补充的了。不知怎么的作者居然写出了一篇简短的博客文章而没有把它变成一篇长篇的计算类文章整个过程从开始到结束只花了几个小时。复用本文采用 [知识共享署名 4.0 国际许可协议](https://creativecommons.org/licenses/by/4.0/)。引用BibTeX 引用格式online{navarro2025, author {Navarro, Danielle}, title {Linear Cosine Palettes}, date {2025-09-14}, url {https://blog.djnavarro.net/posts/2025-09-14_cosine-palettes/}, langid {en}}如需引用请按以下格式Navarro, Danielle. 2025. “Linear Cosine Palettes.” September 14, 2025. https://blog.djnavarro.net/posts/2025-09-14_cosine-palettes/.[blog.djnavarro.net](https://blog.djnavarro.net)