Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/image-processing/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 使用M个预定义颜色中的N个进行颜色量化_Java_Image Processing_Color Palette - Fatal编程技术网

Java 使用M个预定义颜色中的N个进行颜色量化

Java 使用M个预定义颜色中的N个进行颜色量化,java,image-processing,color-palette,Java,Image Processing,Color Palette,我在尝试量化和抖动RGB图像时遇到了一个稍微奇怪的问题。理想情况下,我应该能够用Java实现一个合适的算法或使用Java库,但参考其他语言的实现可能也会有所帮助 以下为输入: 图像:24位RGB位图 调色板:用RGB值定义的颜色列表 max\u cols:输出图像中使用的最大颜色数 也许重要的是,调色板的大小以及允许的最大颜色数不一定是2的幂,并且可能大于255 因此,我们的目标是获取图像,从提供的调色板中选择最多max_cols颜色,并仅使用拾取的颜色输出图像,并使用某种错误扩散抖动进行渲

我在尝试量化和抖动RGB图像时遇到了一个稍微奇怪的问题。理想情况下,我应该能够用Java实现一个合适的算法或使用Java库,但参考其他语言的实现可能也会有所帮助

以下为输入:

  • 图像
    :24位RGB位图
  • 调色板
    :用RGB值定义的颜色列表
  • max\u cols
    :输出图像中使用的最大颜色数
也许重要的是,调色板的大小以及允许的最大颜色数不一定是2的幂,并且可能大于255

因此,我们的目标是获取
图像
,从提供的
调色板
中选择最多
max_cols
颜色,并仅使用拾取的颜色输出图像,并使用某种错误扩散抖动进行渲染。使用哪种抖动算法并不重要,但它应该是错误扩散变量(例如Floyd Steinberg),而不是简单的半色调或有序抖动

性能不是特别重要,预期数据输入的大小相对较小。图像很少大于500x500像素,提供的调色板可能包含3-400种颜色,颜色数量通常限制在100种以下。也可以安全地假设调色板包含广泛的颜色选择,包括色调、饱和度和亮度的变化

所使用的调色板选择和抖动将是理想的,但要使算法适应从已定义的调色板而不是任意颜色中选择颜色似乎并不容易

更准确地说,我遇到的问题是从提供的调色板中选择合适的颜色。例如,假设使用scolorq创建一个具有N种颜色的调色板,然后将scolorq定义的颜色替换为所提供调色板中最接近的颜色,然后将这些颜色与误差扩散抖动结合使用。这将产生至少与输入图像相似的结果,但由于所选颜色的色调不可预测,输出图像可能会产生强烈的、不需要的颜色投射。例如,当使用灰度输入图像和调色板时,只有少量中性灰色色调,但有大量棕色色调(或更一般地说,许多颜色具有相同色调、低饱和度和亮度变化很大),我的颜色选择算法似乎比中性灰色更喜欢这些颜色,因为棕色色调至少在数学上比灰色更接近所需的颜色。即使我将RGB值转换为HSB,并在尝试查找最近的可用颜色时对H、S和B通道使用不同的权重,同样的问题仍然存在

有没有建议如何正确地实现这一点,或者更好地使用一个库来执行这项任务

自从Xabster问起,我也可以解释这个练习的目的,尽管它与如何解决实际问题无关。输出图像的目标是刺绣或挂毯图案。在最简单的情况下,输出图像中的每个像素对应于某种载体织物上的缝合。调色板对应于可用的纱线,通常有几百种颜色。然而,出于实际原因,有必要限制实际工作中使用的颜色数量。谷歌搜索gobelin刺绣将给出几个例子

为了弄清问题的症结所在。。。解决方案实际上可以分为两个单独的步骤:

  • 选择原始调色板的最佳子集
  • 使用子集渲染输出图像
这里,第一步是实际问题。如果调色板选择工作正常,我可以简单地使用选定的颜色,例如Floyd Steinberg抖动,以产生合理的结果(这是相当琐碎的实现)

如果我正确理解了scolorq的实现,那么scolorq将结合这两个步骤,使用调色板选择中抖动算法的知识来创建更好的结果。这当然是首选解决方案,但scolorq中使用的算法稍微超出了我的数学知识。

首先,我想坚持一个事实,这不是高级距离颜色计算。 到目前为止,我假设第一个调色板是您根据图像配置的或预先计算的

在这里,我只对其进行了配置,并将重点放在子样本提取问题上。我没有使用算法,它很可能不是最好的

  • 将图像存储到画布2d上下文中,用作缓冲区,我将其称为
    ctxHidden
  • ctxHidden
    的像素数据存储到名为
    img
  • 使用函数
    constraintImageData(img,palette)
    循环整个
    img
    ,该函数接受作为参数的
    img
    palette
    ,借助距离函数
    最接近的颜色(palette,r,g,b,a)
    将当前
    img
    像素转换为给定的颜色。请注意,此函数返回一个
    见证
    ,它基本上统计调色板的每种颜色至少使用一次的次数。我的例子也应用了Floyd Steinberg抖动,尽管您提到这不是问题
  • 使用见证按颜色和幻影频率降序排序(从调色板)
  • 根据
    maxColors
    (或
    max\u colors
    )从初始调色板中提取这些颜色以获得子调色板
  • ctxHidden
    原始数据中,使用最终子标绘绘制图像

  • Y
    var image = document.getElementById('original'),
        palettePanel = document.getElementById('palette'),
        subPalettePanel = document.getElementById('subpalette'),
        canvas = document.getElementById('main'),
        maxColors = 12,
        palette = [
             0x7F8FB1FF,
             0x000000FF,
             0x404c00FF,
             0xe46501FF,
             0x722640FF,
             0x40337fFF,
             0x666666FF,
             0x0e5940FF,
             0x1bcb01FF,
             0xbfcc80FF,
             0x333333FF,
             0x0033CCFF,
             0x66CCFFFF,
             0xFF6600FF,
             0x000033FF,
             0xFFCC00FF,
             0xAA0033FF,
             0xFF00FFFF,
             0x00FFFFFF,
             0x123456FF
         ],
          nearestColor = function (palette, r, g, b, a) {
                var rr, gg, bb, aa, color, closest,
                    distr, distg, distb, dista,
                    dist,
                    minDist = Infinity;
                for (var i =  0; i < l; i++) {
                    color = palette[i];
                    rr = palette[i] >> 24 & 0xFF;
                    gg = palette[i] >> 16 & 0xFF;
                    bb = palette[i] >> 8  & 0xFF;
                    aa = palette[i]       & 0xFF;
    
                    if (closest === undefined) {
                        closest = color;
                    }
    
                    // compute abs value
                    distr = Math.abs(rr - r);
                    distg = Math.abs(gg - g);
                    distb = Math.abs(bb - b);
                    dista = Math.abs(aa - a);
                    dist = (distr + distg + distb + dista * .5) / 3.5;
    
                    if (dist < minDist) {
                        closest = color;
                        minDist = dist;
                    }
                }
    
                return closest; 
         },
         subpalette = [],
         i, l = palette.length,
         r, g, b, a,
         img,
         size = 5,
         cols = palettePanel.width / size,
         drawPalette = function (p, palette) {
                var i, l = palette.length;
    
                p.setup = function () {
                p.size(50,50);
                p.background(255);
                p.noStroke();
    
                for (i = 0; i < l; i++) {
    
                    r = palette[i] >> 24 & 0xFF;
                    g = palette[i] >> 16 & 0xFF;
                    b = palette[i] >> 8  & 0xFF;
                    a = palette[i]       & 0xFF;
    
                    p.fill(r,g,b,a);
                    p.rect (i%cols*size, ~~(i/cols)*size, size, size);
                }
            }
         },
            constraintImageDataToPalette = function (img, palette) {
                var i, l, x, y, index,
                    pixel, x, y,
                    right, bottom, bottomLeft, bottomRight,
                    color,
                    r, g, b, a, i, l,
                    pr, pg, pb, pa,
                    rErrorBase,
                    gErrorBase,
                    bErrorBase,
                    aErrorBase,
                    index,
                    w = img.width,
                    w4 = w*4,
                    h = img.height,
                    witness = {};
    
                for (i = 0, l = w*h*4; i < l; i += 4) {
                    x = (i%w);
                    y = ~~(i/w);
                    index = x + y*w;
                    right = index + 4,
                    bottomLeft = index - 4 + w4,
                    bottom = index + w4,
                    bottomRight = index + w4 + 4,
                    pixel = img.data;
    
                    r = pixel[index];
                    g = pixel[index+1];
                    b = pixel[index+2];
                    a = pixel[index+3];
    
                    color = nearestColor(palette, r,g,b,a);
                    witness[color] = (witness[color] || 0) + 1;
    
                    // explode channels
                    pr = color >> 24 & 0xFF;
                    pg = color >> 16 & 0xFF;
                    pb = color >> 8  & 0xFF;
                    pa = color       & 0xFF;
    
                    // set new color
                    pixel[index]   = pr;
                    pixel[index+1] = pg;
                    pixel[index+2] = pb;
                    pixel[index+3] = pa;
    
                    // calculate error
                    rErrorBase = (r - pr);
                    gErrorBase = (g - pg);
                    bErrorBase = (b - pb);
                    aErrorBase = (a - pa);
    
                    ///*
                    // diffuse error right 7/16 = 0.4375
                    pixel[right]   += 0.4375 * rErrorBase;
                    pixel[right+1] += 0.4375 * gErrorBase;
                    pixel[right+2] += 0.4375 * bErrorBase;
                    pixel[right+3] += 0.4375 * aErrorBase;
    
                    // diffuse error bottom-left 3/16 = 0.1875
                    pixel[bottomLeft]   += 0.1875 * rErrorBase;
                    pixel[bottomLeft+1] += 0.1875 * gErrorBase;
                    pixel[bottomLeft+2] += 0.1875 * bErrorBase;
                    pixel[bottomLeft+3] += 0.1875 * aErrorBase;
    
                    // diffuse error bottom 5/16 = 0.3125
                    pixel[bottom]   += 0.3125 * rErrorBase;
                    pixel[bottom+1] += 0.3125 * gErrorBase;
                    pixel[bottom+2] += 0.3125 * bErrorBase;
                    pixel[bottom+3] += 0.3125 * aErrorBase;
    
                    //diffuse error bottom-right 1/16 = 0.0625
                    pixel[bottomRight]   += 0.0625 * rErrorBase;
                    pixel[bottomRight+1] += 0.0625 * gErrorBase;
                    pixel[bottomRight+2] += 0.0625 * bErrorBase;
                    pixel[bottomRight+3] += 0.0625 * aErrorBase;
                    //*/
                }
                return witness;
            };
    
    new Processing(palettePanel, function (p) { drawPalette(p, palette); });
    
    image.onload = function () {
            var l = palette.length;
        new Processing(canvas, function (p) {
    
            // argb 24 bits colors
            p.setup = function () {
                p.size(300, 200);
                p.background(0);
                p.noStroke();
    
                var ctx = canvas.getContext('2d'),
                    ctxHidden = document.getElementById('buffer').getContext('2d'),
                    img, log = [],
                    witness = {};
    
                ctxHidden.drawImage(image, 0, 0);
                img = ctxHidden.getImageData(0, 0, canvas.width, canvas.height);
    
                // constraint colors to largest palette
                witness = constraintImageDataToPalette(img, palette);
                // show which colors have been picked from the panel
                new Processing(subPalettePanel, function (p) { drawPalette(p, Object.keys(witness)); });
                ctx.putImageData(img, 0, 0);
    
                var colorsWeights = [];
    
                for (var key in witness) {
                    colorsWeights.push([+key, witness[key]]);
                }
    
                // sort descending colors by most presents ones
                colorsWeights.sort(function (a, b) {
                    return b[1] - a[1];
                });
    
                // get the max_colors first of the colors picked to ensure a higher probability of getting a good color
                subpalette = colorsWeights
                    .slice(0, maxColors)
                    .map(function (colorValueCount) {
                        // return the actual color code
                        return colorValueCount[0];
                });
    
                // reset image we previously modified
                img = ctxHidden.getImageData(0, 0, canvas.width, canvas.height);
                // this time constraint with new subpalette
                constraintImageDataToPalette(img, subpalette);
    
                // wait 3 seconds to apply new palette and show exactly how it changed
                setTimeout(function () {
                    new Processing(subPalettePanel, function (p) { drawPalette(p, subpalette); });
                    ctx.putImageData(img, 0, 0);
                }, 3000);
            };
        });
    };
    
    function imo = recolor(im,new_colors,max_colors)
    
    % Convert to HSV
    im2 = rgb2hsv(im);
    new_colors = rgb2hsv(new_colors);
    
    % Get number of colors in palette
    num_colors = uint8(size(new_colors,1));
    
    % Reshape image so every row is a diferent pixel, and every column a channel
    % this is necessary for kmeans in Matlab
    im2 = reshape(im2, size(im,1)*size(im,2),size(im,3));
    
    % Seed kmeans with sample pallet, drop empty clusters
    [IDX, C] = kmeans(im2,max_colors,'emptyaction','drop');
    
    % For each pixel, IDX tells which cluster in C it corresponds to
    % C contains the centroids of each cluster
    
    
    % Because centroids are adjusted from seeds, we need to select which original color
    % in the palette it corresponds to. We cannot be sure that the centroids in C correspond
    % to their seed values
    % Note that Matlab starts indexing at 1 instead of 0
    for i=1:size(C,1)
        H = C(i,1);
        S = C(i,2);
        V = C(i,3);
        bdel = 100;
        % Find which color in the new_colors palette is closest
        for j=1:size(new_colors,1)
            H2 = new_colors(j,1);
            S2 = new_colors(j,2);
            V2 = new_colors(j,3);
            dH = (H2-H)^2*0.475;
            dS = (S2-S)^2*0.2875;
            dV = (V2-V)^2*0.2375;
            del = sqrt(dH+dS+dV);
            if isnan(del)
                continue
            end
            % update if the new delta is lower than the best
            if del<bdel
                bdel = del;
                C(i,:) = new_colors(j,:);
            end
        end
    end
    
    % Update the colors, this is equal to the following
    % for i=1:length(imo)
    %    imo(i,:) = C(IDX(i),:) 
    imo = C(IDX,:);
    
    % put it back in its original shape
    imo = reshape(imo, size(im));
    
    imo = hsv2rgb(imo);
    
    imshow(imo);
    
    import java.awt.Color;
    import java.awt.Image;
    import java.awt.image.PixelGrabber;
    import java.io.File;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.List;
    import java.util.Map;
    import java.util.Random;
    import java.util.Set;
    
    public class Quantize {
    
    public static class RGBTriple {
        public final int[] channels;
        public RGBTriple() { channels = new int[3]; }
    
        public RGBTriple(int color) { 
            int r = (color >> 16) & 0xFF;
            int g = (color >> 8) & 0xFF;
            int b = (color >> 0) & 0xFF;
            channels = new int[]{(int)r, (int)g, (int)b};
        }
        public RGBTriple(int R, int G, int B)
        { channels = new int[]{(int)R, (int)G, (int)B}; }
    }
    
    /* The authors of this work have released all rights to it and placed it
    in the public domain under the Creative Commons CC0 1.0 waiver
    (http://creativecommons.org/publicdomain/zero/1.0/).
    
    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
    
    Retrieved from: http://en.literateprograms.org/Floyd-Steinberg_dithering_(Java)?oldid=12476
     */
    public static class FloydSteinbergDither
    {
        private static int plus_truncate_uchar(int a, int b) {
            if ((a & 0xff) + b < 0)
                return 0;
            else if ((a & 0xff) + b > 255)
                return (int)255;
            else
                return (int)(a + b);
        }
    
    
        private static int findNearestColor(RGBTriple color, RGBTriple[] palette) {
            int minDistanceSquared = 255*255 + 255*255 + 255*255 + 1;
            int bestIndex = 0;
            for (int i = 0; i < palette.length; i++) {
                int Rdiff = (color.channels[0] & 0xff) - (palette[i].channels[0] & 0xff);
                int Gdiff = (color.channels[1] & 0xff) - (palette[i].channels[1] & 0xff);
                int Bdiff = (color.channels[2] & 0xff) - (palette[i].channels[2] & 0xff);
                int distanceSquared = Rdiff*Rdiff + Gdiff*Gdiff + Bdiff*Bdiff;
                if (distanceSquared < minDistanceSquared) {
                    minDistanceSquared = distanceSquared;
                    bestIndex = i;
                }
            }
            return bestIndex;
        }
    
        public static int[][] floydSteinbergDither(RGBTriple[][] image, RGBTriple[] palette)
        {
            int[][] result = new int[image.length][image[0].length];
    
            for (int y = 0; y < image.length; y++) {
                for (int x = 0; x < image[y].length; x++) {
                    RGBTriple currentPixel = image[y][x];
                    int index = findNearestColor(currentPixel, palette);
                    result[y][x] = index;
    
                    for (int i = 0; i < 3; i++)
                    {
                        int error = (currentPixel.channels[i] & 0xff) - (palette[index].channels[i] & 0xff);
                        if (x + 1 < image[0].length) {
                            image[y+0][x+1].channels[i] =
                                    plus_truncate_uchar(image[y+0][x+1].channels[i], (error*7) >> 4);
                        }
                        if (y + 1 < image.length) {
                            if (x - 1 > 0) {
                                image[y+1][x-1].channels[i] =
                                        plus_truncate_uchar(image[y+1][x-1].channels[i], (error*3) >> 4);
                            }
                            image[y+1][x+0].channels[i] =
                                    plus_truncate_uchar(image[y+1][x+0].channels[i], (error*5) >> 4);
                            if (x + 1 < image[0].length) {
                                image[y+1][x+1].channels[i] =
                                        plus_truncate_uchar(image[y+1][x+1].channels[i], (error*1) >> 4);
                            }
                        }
                    }
                }
            }
            return result;
        }
    
        public static void generateDither(int[] pixels, int[] p, int w, int h){
            RGBTriple[] palette = new RGBTriple[p.length];
            for (int i = 0; i < palette.length; i++) {
                int color = p[i];
                palette[i] = new RGBTriple(color);
            }
            RGBTriple[][] image = new RGBTriple[w][h];
            for (int x = w; x-- > 0; ) {
                for (int y = h; y-- > 0; ) {
                    int index = y * w + x;
                    int color = pixels[index];
                    image[x][y] = new RGBTriple(color);
                }
            }
    
            int[][] result = floydSteinbergDither(image, palette);
            convert(result, pixels, p, w, h);
    
        }
    
        public static void convert(int[][] result, int[] pixels, int[] p, int w, int h){
            for (int x = w; x-- > 0; ) {
                for (int y = h; y-- > 0; ) {
                    int index = y * w + x;
                    int index2 = result[x][y];
                    pixels[index] = p[index2];
                }
            }
        }
    }
    
    private static class PaletteColor{
        final int color;
        public PaletteColor(int color) {
            super();
            this.color = color;
        }
        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + color;
            return result;
        }
        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            PaletteColor other = (PaletteColor) obj;
            if (color != other.color)
                return false;
            return true;
        }
    
        public List<Integer> indices = new ArrayList<>();
    }
    
    
    public static int[] getPixels(Image image) throws IOException {
        int w = image.getWidth(null);
        int h = image.getHeight(null);        
        int pix[] = new int[w * h];
        PixelGrabber grabber = new PixelGrabber(image, 0, 0, w, h, pix, 0, w);
    
        try {
            if (grabber.grabPixels() != true) {
                throw new IOException("Grabber returned false: " +
                        grabber.status());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return pix;
    }
    
    /**
     * Returns the color distance between color1 and color2
     */
    public static float getPixelDistance(PaletteColor color1, PaletteColor color2){
        int c1 = color1.color;
        int r1 = (c1 >> 16) & 0xFF;
        int g1 = (c1 >> 8) & 0xFF;
        int b1 = (c1 >> 0) & 0xFF;
        int c2 = color2.color;
        int r2 = (c2 >> 16) & 0xFF;
        int g2 = (c2 >> 8) & 0xFF;
        int b2 = (c2 >> 0) & 0xFF;
        return (float) getPixelDistance(r1, g1, b1, r2, g2, b2);
    }
    
    public static double getPixelDistance(int r1, int g1, int b1, int r2, int g2, int b2){
        return Math.sqrt(Math.pow(r2 - r1, 2) + Math.pow(g2 - g1, 2) + Math.pow(b2 - b1, 2));
    }
    
    /**
     * Fills the given fillColors palette with the nearest colors from the given colors palette until
     * it has the given max_cols size.
     */
    public static void fillPalette(List<PaletteColor> fillColors, List<PaletteColor> colors, int max_cols){
        while (fillColors.size() < max_cols) {
            int index = -1;
            float minDistance = -1;
            for (int i = 0; i < fillColors.size(); i++) {
                PaletteColor color1 = colors.get(i);
                for (int j = 0; j < colors.size(); j++) {
                    PaletteColor color2 = colors.get(j);
                    if (color1 == color2) {
                        continue;
                    }
                    float distance = getPixelDistance(color1, color2);
                    if (index == -1 || distance < minDistance) {
                        index = j;
                        minDistance = distance;
                    }
                }
            }
            PaletteColor color = colors.get(index);
            fillColors.add(color);
        }
    }
    
    public static void reducePaletteByAverageDistance(List<PaletteColor> colors, int max_cols, ReductionStrategy reductionStrategy){
        while (colors.size() > max_cols) {
            int index = -1;
            float minDistance = -1;
            for (int i = 0; i < colors.size(); i++) {
                PaletteColor color1 = colors.get(i);
                float averageDistance = 0;
                int count = 0;
                for (int j = 0; j < colors.size(); j++) {
                    PaletteColor color2 = colors.get(j);
                    if (color1 == color2) {
                        continue;
                    }
                    averageDistance += getPixelDistance(color1, color2);
                    count++;
                }
                averageDistance/=count;
                if (minDistance == -1 || averageDistance < minDistance) {
                    minDistance = averageDistance;
                    index = i;
                }
            }
            PaletteColor removed = colors.remove(index);
            // find the color with the least distance:
            PaletteColor best = null;
            minDistance = -1;
            for (int i = 0; i < colors.size(); i++) {
                PaletteColor c = colors.get(i);
                float distance = getPixelDistance(c, removed);
                if (best == null || distance < minDistance) {
                    best = c;
                    minDistance = distance;
                }
            }
            best.indices.addAll(removed.indices);
    
        }
    }
    /**
     * Reduces the given color palette until it has the given max_cols size.
     * The colors that are closest in distance to other colors in the palette
     * get removed first.
     */
    public static void reducePalette(List<PaletteColor> colors, int max_cols, ReductionStrategy reductionStrategy){
        if (reductionStrategy == ReductionStrategy.AVERAGE_DISTANCE) {
            reducePaletteByAverageDistance(colors, max_cols, reductionStrategy);
            return;
        }
        while (colors.size() > max_cols) {
            int index1 = -1;
            int index2 = -1;
            float minDistance = -1;
            for (int i = 0; i < colors.size(); i++) {
                PaletteColor color1 = colors.get(i);
                for (int j = i+1; j < colors.size(); j++) {
                    PaletteColor color2 = colors.get(j);
                    if (color1 == color2) {
                        continue;
                    }
                    float distance = getPixelDistance(color1, color2);
                    if (index1 == -1 || distance < minDistance) {
                        index1 = i;
                        index2 = j;
                        minDistance = distance;
                    }
                }
            }
            PaletteColor color1 = colors.get(index1);
            PaletteColor color2 = colors.get(index2);
    
            switch (reductionStrategy) {
                case BETTER_CONTRAST:
                    // remove the color with the lower average distance to the other palette colors
                    int count = 0;
                    float distance1 = 0;
                    float distance2 = 0;
                    for (PaletteColor c : colors) {
                        if (c != color1 && c != color2) {
                            count++;
                            distance1 += getPixelDistance(color1, c);
                            distance2 += getPixelDistance(color2, c);
                        }
                    }
                    if (count != 0 && distance1 != distance2) {
                        distance1 /= (float)count;
                        distance2 /= (float)count;
                        if (distance1 < distance2) {
                            // remove color 1;
                            colors.remove(index1);
                            color2.indices.addAll(color1.indices);
                        } else{
                            // remove color 2;
                            colors.remove(index2);
                            color1.indices.addAll(color2.indices);
                        }
                        break;
                    }
                    //$FALL-THROUGH$
                default:
                    // remove the color with viewer mappings to the input pixels
                    if (color1.indices.size() < color2.indices.size()) {
                        // remove color 1;
                        colors.remove(index1);
                        color2.indices.addAll(color1.indices);
                    } else{
                        // remove color 2;
                        colors.remove(index2);
                        color1.indices.addAll(color2.indices);
                    }
                    break;
            }
    
        }
    }
    
    /**
     * Creates an initial color palette from the given pixels and the given palette by
     * selecting the colors with the nearest distance to the given pixels.
     * This method also stores the indices of the corresponding pixels inside the
     * returned PaletteColor instances.
     */
    public static List<PaletteColor> createInitialPalette(int pixels[], int[] palette){
        Map<Integer, Integer> used = new HashMap<>();
        ArrayList<PaletteColor> result = new ArrayList<>();
    
        for (int i = 0, l = pixels.length; i < l; i++) {
            double bestDistance = Double.MAX_VALUE;
            int bestIndex = -1;
    
            int pixel = pixels[i];
            int r1 = (pixel >> 16) & 0xFF;
            int g1 = (pixel >> 8) & 0xFF;
            int b1 = (pixel >> 0) & 0xFF;
            for (int k = 0; k < palette.length; k++) {
                int pixel2 = palette[k];
                int r2 = (pixel2 >> 16) & 0xFF;
                int g2 = (pixel2 >> 8) & 0xFF;
                int b2 = (pixel2 >> 0) & 0xFF;
                double dist = getPixelDistance(r1, g1, b1, r2, g2, b2);
                if (dist < bestDistance) {
                    bestDistance = dist;
                    bestIndex = k;
                }
            }
    
            Integer index = used.get(bestIndex);
            PaletteColor c;
            if (index == null) {
                index = result.size();
                c = new PaletteColor(palette[bestIndex]);
                result.add(c);
                used.put(bestIndex, index);
            } else{
                c = result.get(index);
            }
            c.indices.add(i);
        }
        return result;
    }
    
    /**
     * Creates a simple random color palette
     */
    public static int[] createRandomColorPalette(int num_colors){
        Random random = new Random(101);
    
        int count = 0;
        int[] result = new int[num_colors];
        float add = 360f / (float)num_colors;
        for(float i = 0; i < 360f && count < num_colors; i += add) {
            float hue = i;
            float saturation = 90 +random.nextFloat() * 10;
            float brightness = 50 + random.nextFloat() * 10;
            result[count++] = Color.HSBtoRGB(hue, saturation, brightness);
        }
        return result;
    }
    
    public static int[] createGrayScalePalette(int count){
        float[] grays = new float[count];
        float step = 1f/(float)count;
        grays[0] = 0;
        for (int i = 1; i < count-1; i++) {
            grays[i]=i*step;
        }
        grays[count-1]=1;
        return createGrayScalePalette(grays);
    }
    
    /**
     * Returns a grayscale palette based on the given shades of gray
     */
    public static int[] createGrayScalePalette(float[] grays){
        int[] result = new int[grays.length];
        for (int i = 0; i < result.length; i++) {
            float f = grays[i];
            result[i] = Color.HSBtoRGB(0, 0, f);
        }
        return result;
    }
    
    
    private static int[] createResultingImage(int[] pixels,List<PaletteColor> paletteColors, boolean dither, int w, int h) {
        int[] palette = new int[paletteColors.size()];
        for (int i = 0; i < palette.length; i++) {
            palette[i] = paletteColors.get(i).color;
        }
        if (!dither) {
            for (PaletteColor c : paletteColors) {
                for (int i : c.indices) {
                    pixels[i] = c.color;
                }
            }
        } else{
            FloydSteinbergDither.generateDither(pixels, palette, w, h);
        }
        return palette;
    }
    
    public static int[] quantize(int[] pixels, int widht, int heigth, int[] colorPalette, int max_cols, boolean dither, ReductionStrategy reductionStrategy) {
    
        // create the initial palette by finding the best match colors from the given color palette
        List<PaletteColor> paletteColors = createInitialPalette(pixels, colorPalette);
    
        // reduce the palette size to the given number of maximum colors
        reducePalette(paletteColors, max_cols, reductionStrategy);
        assert paletteColors.size() <= max_cols;
    
        if (paletteColors.size() < max_cols) {
            // fill the palette with the nearest remaining colors
            List<PaletteColor> remainingColors = new ArrayList<>();
            Set<PaletteColor> used = new HashSet<>(paletteColors);
            for (int i = 0; i < colorPalette.length; i++) {
                int color = colorPalette[i];
                PaletteColor c = new PaletteColor(color);
                if (!used.contains(c)) {
                    remainingColors.add(c);
                }
            }
            fillPalette(paletteColors, remainingColors, max_cols);
        }
        assert paletteColors.size() == max_cols;
    
        // create the resulting image
        return createResultingImage(pixels,paletteColors, dither, widht, heigth);
    
    }   
    
    static enum ReductionStrategy{
        ORIGINAL_COLORS,
        BETTER_CONTRAST,
        AVERAGE_DISTANCE,
    }
    
    public static void main(String args[]) throws IOException {
    
        // input parameters
        String imageFileName = args[0];
        File file = new File(imageFileName);
    
        boolean dither = true;
        int colorPaletteSize = 80;
        int max_cols = 3;
        max_cols =  Math.min(max_cols, colorPaletteSize);
    
        // create some random color palette
        //  int[] colorPalette = createRandomColorPalette(colorPaletteSize);
        int[] colorPalette = createGrayScalePalette(20);
    
        ReductionStrategy reductionStrategy = ReductionStrategy.AVERAGE_DISTANCE;
    
        // show the original image inside a frame
        ImageFrame original = new ImageFrame();
        original.setImage(file);
        original.setTitle("Original Image");
        original.setLocation(0, 0);
    
        Image image = original.getImage();
        int width = image.getWidth(null);
        int heigth = image.getHeight(null);
        int pixels[] = getPixels(image);
        int[] palette = quantize(pixels, width, heigth, colorPalette, max_cols, dither, reductionStrategy);
    
        // show the reduced image in another frame
        ImageFrame reduced = new ImageFrame();
        reduced.setImage(width, heigth, pixels);
        reduced.setTitle("Quantized Image (" + palette.length + " colors, dither: " + dither + ")");
        reduced.setLocation(100, 100);
    
    }
    }
    
    public class ColorQuantizationExample {
    
        public ColorQuantizationExample(){
            MarvinImage imageOriginal = MarvinImageIO.loadImage("./res/quantization/lena.jpg");
            MarvinImage imageOutput = new MarvinImage(imageOriginal.getWidth(), imageOriginal.getHeight());
    
            Set<Color> palette = loadPalette("./res/quantization/palette_7.png");
            quantitize(imageOriginal, imageOutput, palette, 32);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_32.jpg");
    
            quantitize(imageOriginal, imageOutput, palette, 8);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_8.jpg");
    
            quantitize(imageOriginal, imageOutput, palette, 4);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_7_4.jpg");
    
            palette = loadPalette("./res/quantization/palette_8.png");
            quantitize(imageOriginal, imageOutput, palette, 32);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_32.jpg");
    
            quantitize(imageOriginal, imageOutput, palette, 8);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_8.jpg");
    
            quantitize(imageOriginal, imageOutput, palette, 4);
            MarvinImageIO.saveImage(imageOutput, "./res/quantization/lena_8_4.jpg");
        }
    
        /**
         * Load a set of colors from a palette image.
         */
        private Set<Color> loadPalette(String path){
            Set<Color> ret = new HashSet<Color>();
            MarvinImage image = MarvinImageIO.loadImage(path);
            String key;
            for(int y=0; y<image.getHeight(); y++){
                for(int x=0; x<image.getWidth(); x++){
                    Color c = new Color
                    (
                        image.getIntComponent0(x, y),
                        image.getIntComponent1(x, y),
                        image.getIntComponent2(x, y)
                    );
    
                    ret.add(c);
                }
            }
            return ret;
        }
    
        private void quantitize(MarvinImage imageIn, MarvinImage imageOut, Set<Color> palette, int colors){
            applyPalette(imageIn, imageOut, palette);
            HashMap<Color, Integer> hist = getColorHistogram(imageOut);
    
    
            List<Map.Entry<Color, Integer>> list = new LinkedList<Map.Entry<Color, Integer>>( hist.entrySet() );
    
            Collections.sort( list, new Comparator<Map.Entry<Color, Integer>>()
            {
                @Override
                public int compare( Map.Entry<Color, Integer> o1, Map.Entry<Color, Integer> o2 )
                {
                    return (o1.getValue() > o2.getValue() ? -1: 1);
                }
            } );
    
            Set<Color> newPalette = reducedPalette(list, colors);
            applyPalette(imageOut.clone(), imageOut, newPalette);
        }
    
        /**
         * Apply a palette to an image.
         */
        private void applyPalette(MarvinImage imageIn, MarvinImage imageOut, Set<Color> palette){
            Color color;
            for(int y=0; y<imageIn.getHeight(); y++){
                for(int x=0; x<imageIn.getWidth(); x++){
                    int red = imageIn.getIntComponent0(x, y);
                    int green = imageIn.getIntComponent1(x, y);
                    int blue = imageIn.getIntComponent2(x, y);
    
                    color = getNearestColor(red, green, blue, palette);
                    imageOut.setIntColor(x, y, 255, color.getRed(), color.getGreen(), color.getBlue());
                }
            }
        }
    
        /**
         * Reduce the palette colors to a given number. The list is sorted by usage.
         */
        private Set<Color> reducedPalette(List<Map.Entry<Color, Integer>> palette, int colors){
            Set<Color> ret = new HashSet<Color>();
            for(int i=0; i<colors; i++){
                ret.add(palette.get(i).getKey());
            }
            return ret;
        }
    
        /**
         * Compute color histogram
         */
        private HashMap<Color, Integer> getColorHistogram(MarvinImage image){
            HashMap<Color, Integer> ret = new HashMap<Color, Integer>();
    
            for(int y=0; y<image.getHeight(); y++){
                for(int x=0; x<image.getWidth(); x++){
                    Color c = new Color
                    (
                        image.getIntComponent0(x, y),
                        image.getIntComponent1(x, y),
                        image.getIntComponent2(x, y)
                    );
    
                    if(ret.get(c) == null){
                        ret.put(c, 0);
                    }
                    ret.put(c, ret.get(c)+1);
                }
            }
            return ret;
        }
    
        private Color getNearestColor(int red, int green, int blue, Set<Color> palette){
            Color nearestColor=null, c;
            double nearestDistance=Integer.MAX_VALUE;
            double tempDist;
            Iterator<Color> it = palette.iterator();
    
            while(it.hasNext()){
                c = it.next();
                tempDist = distance(red, green, blue, c.getRed(), c.getGreen(), c.getBlue());
                if(tempDist < nearestDistance){
                    nearestDistance = tempDist;
                    nearestColor = c;
                }
            }
            return nearestColor;
        }
    
        private double distance(int r1, int g1, int b1, int r2, int g2, int b2){
            double dist= Math.pow(r1-r2,2) + Math.pow(g1-g2,2) + Math.pow(b1-b2,2);
            return Math.sqrt(dist);
        }
    
        public static void main(String args[]){
            new ColorQuantizationExample();
        }
    }