Generating Custom Color Palette for Julia set

后端 未结 1 647
栀梦
栀梦 2020-12-09 21:46

I need an algorithm or a method to generate a color palette to Color the Julia set images. When using the escape time algorithm to generate the image I for example come up w

1条回答
  •  爱一瞬间的悲伤
    2020-12-09 22:38

    There are many possible approaches for such a color mapping. The simplest is sketched in the program below.

    Custom Color Maps Updated

    The core of this snippet is the initColorMap method. It takes a number of interpolation steps and an array of colors to interpolate between. In the screenshot, these have been

    • red
    • red, green
    • red, green, blue (like in the first image of the question)
    • red, yellow, green, cyan, blue, magenta
    • black, orange, white, blue, dark blue (an attempt to obtain a color map similar to that from the second image in the question)
    • red, green, blue, sampled with a sine function

    The method returns an int array containing the RGB values of the interpolated colors. This might be used directly. But for an improved versatility, these arrays are wrapped into a ColorMap1D interface, which offers a method that returns an RGB color for any given value between 0.0 and 1.0.

    For your application case, this could probably used like this:

    double value = (double)iterations / maxIterations;
    int rgb = colorMap.getColor(value);
    

    (EDIT: The following description and the code have been updated and extended based on the request in the comment)

    Such a "normalization" to the range [0.0, 1.0] and the abstraction using interfaces is often beneficial.

    As a demonstration of the effects that are possible with this abstraction: The ColorMaps1D class contains several methods to create ColorMap1D instances:

    • ColorMaps1D#createDefault(int steps, Color ... colors): Creates a default color map that interpolates over a given sequence of colors with a predefined number of steps (the "resolution" of the color map)
    • ColorMaps1D#create(ColorMap1D delegate, DoubleFunction function) : This method creates a color map where the argument of the getColor method is transformed with the given function before it is passed the the getColor method of the given delegate.

    Thus, one can easily create a ColorMap1D that interpolates non-linearly between colors. One could even create a ColorMap1D implementation that interpolates over several other color maps.

    As an example, I have added a color map that uses the default, simple Red->Green->Blue color map, but accesses it with a function that computes the sine of the argument. This way, it is possible to "cycle" through the Red->Green->Blue color map several times.

    import java.awt.BorderLayout;
    import java.awt.Color;
    import java.awt.Graphics;
    import java.awt.GridLayout;
    import java.util.Arrays;
    
    import javax.swing.JFrame;
    import javax.swing.JLabel;
    import javax.swing.JPanel;
    import javax.swing.SwingUtilities;
    
    public class ColorMapsTest
    {
        public static void main(String[] args)
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    createAndShowGUI();
                }
            });
        }
    
        private static void createAndShowGUI()
        {
            JFrame f = new JFrame();
            f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            f.getContentPane().setLayout(new GridLayout(0,1));
    
            int steps = 1024;
            f.getContentPane().add(
                createPanel(steps, Color.RED));
            f.getContentPane().add(
                createPanel(steps, Color.RED, Color.GREEN));
            f.getContentPane().add(
                createPanel(steps, Color.RED, Color.GREEN, Color.BLUE));
            f.getContentPane().add(
                createPanel(steps,
                    Color.RED, Color.YELLOW,
                    Color.GREEN, Color.CYAN,
                    Color.BLUE, Color.MAGENTA));
            f.getContentPane().add(
                createPanel(steps,
                    Color.BLACK, Color.ORANGE, Color.WHITE,
                    Color.BLUE, new Color(0,0,128)));
    
    
            JPanel panel = new JPanel(new BorderLayout());
            Color colors[] = new Color[]{ Color.RED, Color.GREEN, Color.BLUE };
            String info = "With sine over "+createString(colors);
            panel.add(new JLabel(info), BorderLayout.NORTH);
            ColorMapPanel1D colorMapPanel =
                new ColorMapPanel1D(
                    ColorMaps1D.createSine(
                        ColorMaps1D.createDefault(steps, colors), Math.PI * 4));
            panel.add(colorMapPanel, BorderLayout.CENTER);
            f.getContentPane().add(panel);
    
    
            f.setSize(500, 400);
            f.setLocationRelativeTo(null);
            f.setVisible(true);
        }
    
    
    
        private static JPanel createPanel(int steps, Color ... colors)
        {
            JPanel panel = new JPanel(new BorderLayout());
            String info = "In "+steps+" steps over "+createString(colors);
            panel.add(new JLabel(info), BorderLayout.NORTH);
            ColorMapPanel1D colorMapPanel =
                new ColorMapPanel1D(ColorMaps1D.createDefault(steps, colors));
            panel.add(colorMapPanel, BorderLayout.CENTER);
            return panel;
        }
    
        private static String createString(Color ... colors)
        {
            StringBuilder sb = new StringBuilder();
            for (int i=0; i
    {
        R apply(double value);
    }
    
    
    
    /**
     * Interface for classes that can map a single value from the range
     * [0,1] to an int that represents an RGB color
     */
    interface ColorMap1D
    {
        /**
         * Returns an int representing the RGB color, for the given value in [0,1]
         *
         * @param value The value in [0,1]
         * @return The RGB color
         */
        int getColor(double value);
    }
    
    /**
     * Default implementation of a {@link ColorMap1D} that is backed by
     * a simple int array
     */
    class DefaultColorMap1D implements ColorMap1D
    {
        /**
         * The backing array containing the RGB colors
         */
        private final int colorMapArray[];
    
        /**
         * Creates a color map that is backed by the given array
         *
         * @param colorMapArray The array containing RGB colors
         */
        DefaultColorMap1D(int colorMapArray[])
        {
            this.colorMapArray = colorMapArray;
        }
    
        @Override
        public int getColor(double value)
        {
            double d = Math.max(0.0, Math.min(1.0, value));
            int i = (int)(d * (colorMapArray.length - 1));
            return colorMapArray[i];
        }
    }
    
    
    /**
     * Methods to create {@link ColorMap1D} instances
     */
    class ColorMaps1D
    {
        /**
         * Creates a {@link ColorMap1D} that walks through the given delegate
         * color map using a sine function with the given frequency
         *
         * @param delegate The delegate
         * @param frequency The frequency
         * @return The new {@link ColorMap1D}
         */
        static ColorMap1D createSine(ColorMap1D delegate, final double frequency)
        {
            return create(delegate, new DoubleFunction()
            {
                @Override
                public Double apply(double value)
                {
                    return 0.5 + 0.5 * Math.sin(value * frequency);
                }
            });
        }
    
        /**
         * Creates a {@link ColorMap1D} that will convert the argument
         * with the given function before it is looking up the color
         * in the given delegate
         *
         * @param delegate The delegate {@link ColorMap1D}
         * @param function The function for converting the argument
         * @return The new {@link ColorMap1D}
         */
        static ColorMap1D create(
            final ColorMap1D delegate, final DoubleFunction function)
        {
            return new ColorMap1D()
            {
                @Override
                public int getColor(double value)
                {
                    return delegate.getColor(function.apply(value));
                }
            };
        }
    
    
        /**
         * Creates a new ColorMap1D that maps a value between 0.0 and 1.0
         * (inclusive) to the specified color range, internally using the
         * given number of steps for interpolating between the colors
         *
         * @param steps The number of interpolation steps
         * @param colors The colors
         * @return The color map
         */
        static ColorMap1D createDefault(int steps, Color ... colors)
        {
            return new DefaultColorMap1D(initColorMap(steps, colors));
        }
    
        /**
         * Creates the color array which contains RGB colors as integers,
         * interpolated through the given colors.
         *
         * @param steps The number of interpolation steps, and the size
         * of the resulting array
         * @param colors The colors for the array
         * @return The color array
         */
        static int[] initColorMap(int steps, Color ... colors)
        {
            int colorMap[] = new int[steps];
            if (colors.length == 1)
            {
                Arrays.fill(colorMap, colors[0].getRGB());
                return colorMap;
            }
            double colorDelta = 1.0 / (colors.length - 1);
            for (int i=0; i

    (Regarding the color smoothing: This is something that should probably be asked in a separate question. Or maybe not, because there already are many questions about that on StackOverflow. For example, see Smooth spectrum for Mandelbrot Set rendering (or many others))

    0 讨论(0)
提交回复
热议问题