生成美观色板的随机算法

330
我正在寻找一个简单的算法来生成大量随机的、美观的颜色,不要出现疯狂的霓虹色、类似粪便颜色等。我已经找到了解决这个问题的方法,但它们都依赖于RGB以外的其他调色板。我宁愿只使用纯RGB而不是来回映射。这些其他解决方案最多只能生成32种令人愉悦的随机颜色。有什么好的想法吗?

你可以查看HSL和HSV。 - Jim Keener
1
那不是一个算法。 - Matt The Ninja
问题已关闭,我会添加一条评论。虽然我没有尝试过,但使用分形或程序化的方式会很有趣,例如使用Midpoint Displacement算法或其变体(http://www.lighthouse3d.com/opengl/terrain/index.php?mpd2)或Perlin噪声,但不是用高度来混合颜色的RGB分量。 - alfoks
请查看我构建的调色板生成器及其源代码:https://hypejunction.github.io/color-wizard/ 你基本上可以选择一系列色调,确定你喜欢的颜色所需的亮度步骤,并使用二次方程式确定饱和度。 - hypeJunction
请参考以下链接:https://dev59.com/PXI-5IYBdhLWcg3wy7-n - vsync
16个回答

456
你可以使用一个固定颜色和随机颜色的RGB值进行平均:

(Java示例)

public Color generateRandomColor(Color mix) {
  Random random = new Random();
  int red = random.nextInt(256);
  int green = random.nextInt(256);
  int blue = random.nextInt(256);

  // mix the color
  if (mix != null) {
      red = (red + mix.getRed()) / 2;
      green = (green + mix.getGreen()) / 2;
      blue = (blue + mix.getBlue()) / 2;
  }

  Color color = new Color(red, green, blue);
  return color;
}

混合白色(255,255,255)和任意颜色可以生成中性柔和色,增加亮度同时保持原始颜色的色调。这些随机生成的柔和色通常在一起搭配得很好,尤其是在大量使用时。
以下是使用上述方法生成的一些柔和色:

First

您可以将随机颜色与恒定的柔和色混合,这会产生一组带色调的中性颜色。例如,使用浅蓝色会创建出这样的颜色:

Second

进一步地,你可以为你的生成器添加启发式算法,考虑到互补颜色或阴影层次,但这完全取决于你想要通过随机颜色实现的印象。

一些额外的资源:


5
唯一需要注意的是,可能会失去可区分性。通过将某种颜色(如白色)的红、绿、蓝三个颜色通道平均,自然地使所有颜色都更接近于白色,减小它们之间的差异。例如,如果您看第一张截图:当这些绿色相邻时,我肯定可以区分它们,但如果它们在某些生成的图形中不相邻呢?可能会出现问题。 - Zelphir Kaltstahl
在生成随机颜色组件时,尝试使用比较小的上限值,例如200。 - Nickolodeon

91

我会使用色轮,并在随机位置添加黄金角度(137.5度)(Golden Angle),以便每次获得不重叠的不同颜色。

通过调整色轮的亮度,您还可以获得不同的明/暗颜色组合。

我发现这篇博客文章非常好地解释了使用黄金比例解决问题的方法和解决方案。

(http://martin.ankerl.com/2009/12/09/how-to-create-random-colors-programmatically/)

更新:我刚刚发现了另一种方法:

它被称为RYB(红、黄、蓝)方法,并在这篇论文中描述:(http://threekings.tk/mirror/ryb_TR.pdf),作为“绘画启发式颜色合成”。该算法生成颜色,并选择每个新颜色以最大化其与先前选择的颜色之间的欧几里得距离。

在这里,您可以找到javascript的良好实现:

(http://afriggeri.github.com/RYB/)

更新2:

Sciences Po Medialb刚刚发布了一个名为“I want Hue”的工具,为数据科学家生成颜色调色板。使用不同的颜色空间,并通过使用k-means聚类或力向量(排斥图)生成调色板。这些方法的结果非常好,他们在其网页上展示了理论和实现。

http://tools.medialab.sciences-po.fr/iwanthue/index.php

这是一个网站链接,可以用于生成配色方案。

24

不完全正确:你也需要对个位数进行零填充... 无论如何,这是一个适用于查看者的工作 fiddle:http://jsfiddle.net/RedDevil/LLYBQ/
抛开那个... 我没有注意到 +127 部分... 但是这样就不会生成深色调。
- kumarharsh
3
最初的想法是生成任意漂亮的颜色。深色调对我来说不太令人愉悦 ;) - motobói
请查看此链接获取JavaScript相关信息 - https://dev59.com/MVgQ5IYBdhLWcg3wLQ2W#54279008 - Gangula

11

转换到另一个调色板是更优秀的方式。他们这样做是有原因的:其他调色板是“感知”的——也就是说,它们将看起来相似的颜色放在一起,而且调整其中一个变量会以可预测的方式改变颜色。在RGB中,所有这些都不成立,因为没有明显的关系可以让颜色“搭配得好”。


9

我使用 TriadMixingCIE94 成功避免了相似的颜色。下面的图片使用了红、黄和白三种颜色。请看这里

// http://devmag.org.za/2012/07/29/how-to-choose-colours-procedurally-algorithms/#:~:text=120%20and%20240.-,7.%20Triad%20Mixing,-This%20algorithm%20takes

public static Color RandomMix(Color color1, Color color2, Color color3, 
   float greyControl)
{
   int randomIndex = random.NextByte() % 3;
   float mixRatio1 =
      (randomIndex == 0) ? random.NextFloat() * greyControl : random.NextFloat();
   float mixRatio2 = 
      (randomIndex == 1) ? random.NextFloat() * greyControl : random.NextFloat();
   float mixRatio3 = 
      (randomIndex == 2) ? random.NextFloat() * greyControl : random.NextFloat();
   float sum = mixRatio1 + mixRatio2 + mixRatio3;

   mixRatio1 /= sum;
   mixRatio2 /= sum;
   mixRatio3 /= sum;

   return Color.FromArgb(
      255,
      (byte)(mixRatio1 * color1.R + mixRatio2 * color2.R + mixRatio3 * color3.R),
      (byte)(mixRatio1 * color1.G + mixRatio2 * color2.G + mixRatio3 * color3.G),
      (byte)(mixRatio1 * color1.B + mixRatio2 * color2.B + mixRatio3 * color3.B));
}

TriadMixing + CIE94


5

这是一个不可忽视的答案,因为它简单易懂且具有优势。那就是采样现实生活中的照片和绘画。随机采样现代艺术图片、塞尚、梵高、莫奈、照片等缩略图上的任意像素,以获得任意数量的随机颜色。其优点是可以按主题获取颜色,而且它们是有机的颜色。只需将 20-30 张图片放入文件夹中,每次随机抽取一张即可。

转换为HSV值是一种基于心理学的调色板常见的代码算法。hsv 更容易随机化。


1
这是一个在线工具,可以分析真实照片并返回RGB值的图表,让您了解真实照片在图表中的颜色分布... 这个非常有用的网站对于尝试设计前卫算法来处理随机颜色至关重要。 - bandybabboon

4
在PHP中:
function pastelColors() {
    $r = dechex(round(((float) rand() / (float) getrandmax()) * 127) + 127);
    $g = dechex(round(((float) rand() / (float) getrandmax()) * 127) + 127);
    $b = dechex(round(((float) rand() / (float) getrandmax()) * 127) + 127);

    return "#" . $r . $g . $b;
}

来源: https://dev59.com/mHVD5IYBdhLWcg3wO5ED#12266311

在Java中,您可以使用以下代码获取当前时间的毫秒数: long currentTimeMillis = System.currentTimeMillis(); 这将返回自1970年1月1日00:00:00 GMT以来经过的毫秒数。

4
使用 distinct-colors
这是一款使用 JavaScript 编写的工具,它可以生成一组视觉上独特的颜色调色板。
distinct-colors 具有高度可配置性:
  • 可以选择调色板中的颜色数量
  • 可以限制色相范围
  • 可以限制色彩饱和度范围
  • 可以限制亮度范围
  • 可以配置调色板的总体质量

3

David Crow的R语言两行代码方法:

GetRandomColours <- function(num.of.colours, color.to.mix=c(1,1,1)) {
  return(rgb((matrix(runif(num.of.colours*3), nrow=num.of.colours)*color.to.mix)/2))
}

3

这是一个在C#中快速而简单的颜色生成器(使用本文章中描述的“RYB方法”)。它是从JavaScript重写而来。

用法:

List<Color> ColorPalette = ColorGenerator.Generate(30).ToList();

前两种颜色通常是白色和黑色的一种。我经常这样跳过它们(使用Linq):

List<Color> ColorsPalette = ColorGenerator
            .Generate(30)
            .Skip(2) // skip white and black
            .ToList(); 

实现:

public static class ColorGenerator
{

    // RYB color space
    private static class RYB
    {
        private static readonly double[] White = { 1, 1, 1 };
        private static readonly double[] Red = { 1, 0, 0 };
        private static readonly double[] Yellow = { 1, 1, 0 };
        private static readonly double[] Blue = { 0.163, 0.373, 0.6 };
        private static readonly double[] Violet = { 0.5, 0, 0.5 };
        private static readonly double[] Green = { 0, 0.66, 0.2 };
        private static readonly double[] Orange = { 1, 0.5, 0 };
        private static readonly double[] Black = { 0.2, 0.094, 0.0 };

        public static double[] ToRgb(double r, double y, double b)
        {
            var rgb = new double[3];
            for (int i = 0; i < 3; i++)
            {
                rgb[i] = White[i]  * (1.0 - r) * (1.0 - b) * (1.0 - y) +
                         Red[i]    * r         * (1.0 - b) * (1.0 - y) +
                         Blue[i]   * (1.0 - r) * b         * (1.0 - y) +
                         Violet[i] * r         * b         * (1.0 - y) +
                         Yellow[i] * (1.0 - r) * (1.0 - b) *        y +
                         Orange[i] * r         * (1.0 - b) *        y +
                         Green[i]  * (1.0 - r) * b         *        y +
                         Black[i]  * r         * b         *        y;
            }

            return rgb;
        }
    }

    private class Points : IEnumerable<double[]>
    {
        private readonly int pointsCount;
        private double[] picked;
        private int pickedCount;

        private readonly List<double[]> points = new List<double[]>();

        public Points(int count)
        {
            pointsCount = count;
        }

        private void Generate()
        {
            points.Clear();
            var numBase = (int)Math.Ceiling(Math.Pow(pointsCount, 1.0 / 3.0));
            var ceil = (int)Math.Pow(numBase, 3.0);
            for (int i = 0; i < ceil; i++)
            {
                points.Add(new[]
                {
                    Math.Floor(i/(double)(numBase*numBase))/ (numBase - 1.0),
                    Math.Floor((i/(double)numBase) % numBase)/ (numBase - 1.0),
                    Math.Floor((double)(i % numBase))/ (numBase - 1.0),
                });
            }
        }

        private double Distance(double[] p1)
        {
            double distance = 0;
            for (int i = 0; i < 3; i++)
            {
                distance += Math.Pow(p1[i] - picked[i], 2.0);
            }

            return distance;
        }

        private double[] Pick()
        {
            if (picked == null)
            {
                picked = points[0];
                points.RemoveAt(0);
                pickedCount = 1;
                return picked;
            }

            var d1 = Distance(points[0]);
            int i1 = 0, i2 = 0;
            foreach (var point in points)
            {
                var d2 = Distance(point);
                if (d1 < d2)
                {
                    i1 = i2;
                    d1 = d2;
                }

                i2 += 1;
            }

            var pick = points[i1];
            points.RemoveAt(i1);

            for (int i = 0; i < 3; i++)
            {
                picked[i] = (pickedCount * picked[i] + pick[i]) / (pickedCount + 1.0);
            }

            pickedCount += 1;
            return pick;
        }

        public IEnumerator<double[]> GetEnumerator()
        {
            Generate();
            for (int i = 0; i < pointsCount; i++)
            {
                yield return Pick();
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }

    public static IEnumerable<Color> Generate(int numOfColors)
    {
        var points = new Points(numOfColors);

        foreach (var point in points)
        {
            var rgb = RYB.ToRgb(point[0], point[1], point[2]);
            yield return Color.FromArgb(
                (int)Math.Floor(255 * rgb[0]),
                (int)Math.Floor(255 * rgb[1]),
                (int)Math.Floor(255 * rgb[2]));
        }
    }
}

我已经删除了Java的答案,但如果需要,Java版本可以在此Gist中查看:https://gist.github.com/lotsabackscatter/3f6a658fd7209e010dad - Dylan Watson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接