在 Android @drawable 中查找图像的主色

Posted

技术标签:

【中文标题】在 Android @drawable 中查找图像的主色【英文标题】:Finding the dominant color of an image in an Android @drawable 【发布时间】:2012-01-18 06:21:51 【问题描述】:

如果您使用 Windows 7,您可以理解我为什么要在图像中查找主色。当您将鼠标悬停在任务栏中的某个程序上时,该特定程序的背景会根据图标。我注意到其他程序中也使用了这种技术,但我想不起来了。

我可以看到这对我用于开发应用程序的许多 UI 技术很有帮助,我想知道如何从 android 可绘制资源中找到最常见的颜色。

【问题讨论】:

Lollipop 添加了一个新的 API,可帮助从位图中提取突出的颜色。有关详细信息,请参阅my answer below。由于提到的 Palette 类在 support7 库中,它应该也可以在旧版本的 Android 中使用。 android v7 调色板支持库为我们做这件事。任何正在寻找演示的人code2concept.blogspot.in/2015/10/… 哎呀,博客不存在了:c 【参考方案1】:

在 Android 5.0 Lollipop 中,添加了一个类来帮助从位图中提取有用的颜色。 android.support.v7.graphics 中的Palette 类可以提取以下颜色:

充满活力 充满活力的黑暗 充满活力的灯光 静音 柔和的黑暗 柔和的灯光

此 Android 培训页面提供了使用该课程所需的所有详细信息(我自己在 Android Studio 中尝试过,非常简单):http://developer.android.com/training/material/drawables.html#ColorExtract

引用:

Android 支持库 r21 及更高版本包括 Palette 类,它可以让您从图像中提取突出的颜色。到 提取这些颜色,将 Bitmap 对象传递给 Palette.generate() 加载图像的后台线程中的静态方法。如果 你不能使用那个线程,调用 Palette.generateAsync() 方法和 而是提供一个监听器。*

您可以使用 getter 从图像中检索突出的颜色 Palette 类中的方法,例如 Palette.getVibrantColor。

要在您的项目中使用 Palette 类,请添加以下 Gradle 对应用模块的依赖:

dependencies 
    ...
    implementation 'com.android.support:palette-v7:21.0.+'

或者,如果您使用的是 androidx:

implementation 'androidx.palette:palette:1.0.0'

如果您需要使用 generateAsync(),方法如下:

Palette.generateAsync(bitmap, new Palette.PaletteAsyncListener() 
    public void onGenerated(Palette palette) 
        // Do something with colors...
    
);

编辑: 由于问题询问如何从可绘制资源中提取颜色,因此您首先必须将可绘制对象转换为位图才能使用我描述的技术。幸运的是,使用 BitmapFactory 非常简单:

Bitmap icon = BitmapFactory.decodeResource(context.getResources(),
                                       R.drawable.icon_resource);`

【讨论】:

如果您想试验 Palette 类提取的颜色,请查看我在 Play 商店中的应用:play.google.com/store/apps/…。你可以在 GitHub 上找到它的源代码:github.com/tony-w/PaletteColorExtraction 优秀的答案。这使得大多数可用的库和方法变得多余。 Palette.generateAsync 方法已被弃用。改用 Palette.from(bitmap).generate(paletteListener) @TonyWickham generateAsync 现在已弃用。你知道如何从 url 获取 favicon 的颜色吗? @Spritzig 使用来自:Palette.from(bitmap).generate(new PaletteAsyncListener() public void onGenerated(Palette p) // Use generated instance );【参考方案2】:

还有另一种解决方案,它更近似,但如果您不想长时间延迟搜索颜色,它可以完成这项工作。

public static int getDominantColor(Bitmap bitmap) 
    Bitmap newBitmap = Bitmap.createScaledBitmap(bitmap, 1, 1, true);
    final int color = newBitmap.getPixel(0, 0);
    newBitmap.recycle();
    return color;

【讨论】:

这是一个非常有用的答案。我正在使用循环方法,但这使 UI 生涩。现在非常流畅。 从 support:palette-v7:25.0.0 开始,这不再需要自定义方法 - 只需使用 palette.getDominantSwatch()。 还需要一些时间才能完成,需要异步完成。 非常感谢您的回答。短而甜 公平地说,您也不想在 UI 线程上缩放位图。但还是不错的方法【参考方案3】:

要从图像中找到主导/鲜艳/柔和的颜色,请使用Palette:

导入:

implementation 'androidx.palette:palette:1.0.0'

用法:

    val bitmap = BitmapFactory.decodeResource(resources, R.drawable.image)

    Palette.Builder(bitmap).generate  it?.let   palette ->
        val dominantColor = palette.getDominantColor(ContextCompat.getColor(context!!, R.color.defaultColor))

        // TODO: use dominant color

     

【讨论】:

最新版本在developer.android.com/jetpack/androidx/releases/palette【参考方案4】:

该类遍历位图并返回最主要的颜色。 如有必要,请随时清理代码。

public class ImageColour 

String colour;


public ImageColour(Bitmap image) throws Exception 

     int height = image.getHeight();
     int width = image.getWidth();

     Map m = new HashMap();

        for(int i=0; i < width ; i++)

            for(int j=0; j < height ; j++)

                int rgb = image.getPixel(i, j);
                int[] rgbArr = getRGBArr(rgb);                

                if (!isGray(rgbArr))    

                        Integer counter = (Integer) m.get(rgb);   
                        if (counter == null)
                            counter = 0;
                        counter++;                                
                        m.put(rgb, counter);       

                                
            
                

        String colourHex = getMostCommonColour(m);
    



    public static String getMostCommonColour(Map map) 

        List list = new LinkedList(map.entrySet());
        Collections.sort(list, new Comparator() 
              public int compare(Object o1, Object o2) 

                return ((Comparable) ((Map.Entry) (o1)).getValue())
                  .compareTo(((Map.Entry) (o2)).getValue());

              

        );    

        Map.Entry me = (Map.Entry )list.get(list.size()-1);
        int[] rgb= getRGBArr((Integer)me.getKey());

        return Integer.toHexString(rgb[0])+" "+Integer.toHexString(rgb[1])+" "+Integer.toHexString(rgb[2]);        
        


    public static int[] getRGBArr(int pixel) 

        int red = (pixel >> 16) & 0xff;
        int green = (pixel >> 8) & 0xff;
        int blue = (pixel) & 0xff;

        return new int[]red,green,blue;

  

    public static boolean isGray(int[] rgbArr) 

        int rgDiff = rgbArr[0] - rgbArr[1];
        int rbDiff = rgbArr[0] - rgbArr[2];

        int tolerance = 10;

        if (rgDiff > tolerance || rgDiff < -tolerance) 
            if (rbDiff > tolerance || rbDiff < -tolerance)  

                return false;

                            

        return true;
    


public String returnColour() 

    if (colour.length() == 6) 
        return colour.replaceAll("\\s", "");
     else 
        return "ffffff";
    

获取十六进制只需调用 returnColour();

【讨论】:

我建议任何使用这种方法的人来玩公差变量。根据您设置的值,算法运行得更快或更慢。【参考方案5】:

添加到依赖项

implementation 'androidx.palette:palette:1.0.0'

和..

 AppCompatImageView imageView = findViewById(R.id.image_view);

 Bitmap bitmap = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
 Palette.from(bitmap).generate(palette -> 
      int vibrant = palette.getVibrantColor(0x000000); // <=== color you want
      int vibrantLight = palette.getLightVibrantColor(0x000000);
      int vibrantDark = palette.getDarkVibrantColor(0x000000);
      int muted = palette.getMutedColor(0x000000);
      int mutedLight = palette.getLightMutedColor(0x000000);
      int mutedDark = palette.getDarkMutedColor(0x000000);
 );

【讨论】:

【参考方案6】:

我编写了自己的方法来获得主色:

方法一(我的技术)

    减少到ARGB_4444色彩空间 计算单个 RGB 元素的最大出现次数并获得 3 个不同的最大值

    将最大值与主要 RGB 颜色组合

    public int getDominantColor1(Bitmap bitmap) 
    
    if (bitmap == null)
        throw new NullPointerException();
    
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int size = width * height;
    int pixels[] = new int[size];
    
    Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
    
    bitmap2.getPixels(pixels, 0, width, 0, 0, width, height);
    
    final List<HashMap<Integer, Integer>> colorMap = new ArrayList<HashMap<Integer, Integer>>();
    colorMap.add(new HashMap<Integer, Integer>());
    colorMap.add(new HashMap<Integer, Integer>());
    colorMap.add(new HashMap<Integer, Integer>());
    
    int color = 0;
    int r = 0;
    int g = 0;
    int b = 0;
    Integer rC, gC, bC;
    for (int i = 0; i < pixels.length; i++) 
        color = pixels[i];
    
        r = Color.red(color);
        g = Color.green(color);
        b = Color.blue(color);
    
        rC = colorMap.get(0).get(r);
        if (rC == null)
            rC = 0;
        colorMap.get(0).put(r, ++rC);
    
        gC = colorMap.get(1).get(g);
        if (gC == null)
            gC = 0;
        colorMap.get(1).put(g, ++gC);
    
        bC = colorMap.get(2).get(b);
        if (bC == null)
            bC = 0;
        colorMap.get(2).put(b, ++bC);
    
    
    int[] rgb = new int[3];
    for (int i = 0; i < 3; i++) 
        int max = 0;
        int val = 0;
        for (Map.Entry<Integer, Integer> entry : colorMap.get(i).entrySet()) 
            if (entry.getValue() > max) 
                max = entry.getValue();
                val = entry.getKey();
            
        
        rgb[i] = val;
    
    
    int dominantColor = Color.rgb(rgb[0], rgb[1], rgb[2]);
    
    return dominantColor;
     
    

方法2(旧技术)

    减少到ARGB_4444色彩空间

    计算每种颜色的出现次数并找出最大的一种作为主色

    public int getDominantColor2(Bitmap bitmap) 
    if (bitmap == null)
        throw new NullPointerException();
    
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int size = width * height;
    int pixels[] = new int[size];
    
    Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
    
    bitmap2.getPixels(pixels, 0, width, 0, 0, width, height);
    
    HashMap<Integer, Integer> colorMap = new HashMap<Integer, Integer>();
    
    int color = 0;
    Integer count = 0;
    for (int i = 0; i < pixels.length; i++) 
        color = pixels[i];
        count = colorMap.get(color);
        if (count == null)
            count = 0;
        colorMap.put(color, ++count);
    
    
    int dominantColor = 0;
    int max = 0;
    for (Map.Entry<Integer, Integer> entry : colorMap.entrySet()) 
        if (entry.getValue() > max) 
            max = entry.getValue();
            dominantColor = entry.getKey();
        
    
    return dominantColor;
    
    

【讨论】:

问题:bitmap2 是干什么用的?从原始位图复制后,您似乎没有使用它。【参考方案7】:

遍历所有像素的颜色数据并平均颜色值,忽略任何灰色或透明阴影。根据最近的一篇博文,我相信这就是微软在 Windows 7 中所做的。

编辑 博文:http://blogs.msdn.com/b/oldnewthing/archive/2011/12/06/10244432.aspx

此链接显示 Chrome 如何选择主色也可能会有所帮助。 http://www.quora.com/Google-Chrome/How-does-Chrome-pick-the-color-for-the-stripes-on-the-Most-visited-page-thumbnails

【讨论】:

我希望有一个 API 函数深埋在某个地方。这是很好的信息 我发现了一个简单的技巧:复制为 1x1 位图并获取颜色:aerilys.fr/blog/?p=1341【参考方案8】:

其他答案都不适合我,我不排除问题的原因。

这是我最终使用的:

public static int getDominantColor(Bitmap bitmap) 
    if (bitmap == null) 
        return Color.TRANSPARENT;
    
    int width = bitmap.getWidth();
    int height = bitmap.getHeight();
    int size = width * height;
    int pixels[] = new int[size];
    //Bitmap bitmap2 = bitmap.copy(Bitmap.Config.ARGB_4444, false);
    bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
    int color;
    int r = 0;
    int g = 0;
    int b = 0;
    int a;
    int count = 0;
    for (int i = 0; i < pixels.length; i++) 
        color = pixels[i];
        a = Color.alpha(color);
        if (a > 0) 
            r += Color.red(color);
            g += Color.green(color);
            b += Color.blue(color);
            count++;
        
    
    r /= count;
    g /= count;
    b /= count;
    r = (r << 16) & 0x00FF0000;
    g = (g << 8) & 0x0000FF00;
    b = b & 0x000000FF;
    color = 0xFF000000 | r | g | b;
    return color;

【讨论】:

谢谢! Android 的调色板生成器似乎有意避免获取最常用的颜色

以上是关于在 Android @drawable 中查找图像的主色的主要内容,如果未能解决你的问题,请参考以下文章

Android中的Drawable

Android:R.drawable 不显示我的图像?

如何将图像从 Android 中的 Drawable 附加到彩信?

Android:无法将@drawable/picture 转换为drawable

Android Drawable的9种子类 介绍

Android Drawable的9种子类 介绍