【问题标题】:Why is Color.colorToHSV and HSVToColor inaccurate?为什么 Color.colorToHSV 和 HSVToColor 不准确?
【发布时间】:2015-09-12 13:43:03
【问题描述】:

我正在编写一个 Android 游戏。在游戏中,不同颜色的方块会掉到地上。当两个具有相同颜色的块相互重叠时,它们将合并为一个颜色较深的块。但是,我观察到具有相同颜色的块有时不会合并。经过一番调试,我发现它们的颜色实际上略有不同:-16757700 和 -16757444。我使用Color.colorToHSVColor.HSVToColor 使颜色变深,如下面的代码所示(checkLanded 方法)。所以我认为这两种hsv方法一定有问题。这是我的代码(其实我认为代码没有其他问题,你可以看看checkLanded方法。但是我粘贴了其他代码,因为问题可能不是我认为的问题。只是几乎滚动到底部,您将看到checkLanded 方法):

package com.shades;

import android.graphics.Color;
import android.support.annotation.IntDef;

import com.shades.util.BlockView;
import com.shades.util.Timer;

import java.util.HashMap;
import java.util.Random;

public class Block {

    private static Random r = new Random ();
    private static HashMap<Integer, Float> colorValuesMap;

    @IntDef ({LEFT, RIGHT})
    public @interface Direction {}
    public static final int LEFT = -1;
    public static final int RIGHT = 1;

    static {
        colorValuesMap = new HashMap<> ();
        colorValuesMap.put (4, 0.1F);
        colorValuesMap.put (3, 0.3F);
        colorValuesMap.put (2, 0.5F);
        colorValuesMap.put (1, 0.7F);
        colorValuesMap.put (0, 0.9F);
    }

    private int x;
    private int y;
    private int color;
    protected Timer timer;

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }

    private void setY(int y) {
        Block[][] matrix = Game.getInstance ().getBlockMatrix ();
        matrix[this.x][this.y] = null;
        this.y = y;
        matrix[this.x][this.y] = this;
        BlockView.updateViews ();
    }

    private void setX(int x) {
        Block[][] matrix = Game.getInstance ().getBlockMatrix ();
        matrix[this.x][this.y] = null;
        this.x = x;
        matrix[this.x][this.y] = this;
        BlockView.updateViews ();
    }

    private void setXY (int x, int y) {
        Block[][] matrix = Game.getInstance ().getBlockMatrix ();
        matrix[this.x][this.y] = null;
        this.y = y;
        this.x = x;
        matrix[x][y] = this;
        BlockView.updateViews ();
    }

    public int getColor () {
        return color;
    }

    public Block(int x, int y) {
        setXY (x, y);
        this.y = y;
        float[] hsv = new float[3];
        hsv[0] = Game.getInstance ().getHueNumber ();
        hsv[1] = 1F;
        hsv[2] = colorValuesMap.get (r.nextInt (4));
        color = Color.HSVToColor (hsv);
        BlockView.updateViews ();
        timer = new Timer (new Runnable () {
            @Override
            public void run() {
                moveDown ();
            }
        }, 1000, true);
    }

    private void moveDown () {

        //if reached bottom
        if (getY () == Game.MATRIX_HEIGHT - 1) {
            timer.stopTimer ();
            Game.getInstance ().nextBlock (r);
            return;
        }
        color = Game.getInstance ().getBlockViewMatrix ()[x][y].getColor ();
        Block[][] matrix = Game.getInstance ().getBlockMatrix ();
        if (checkLanded (matrix[x][y + 1])) {
            Game.getInstance ().nextBlock(r);
            return;
        }


        setY (getY () + 1);
    }

    private boolean checkLanded(Block blockBelow) {

        //if it can merge into another block
        float[] currentHSV = new float[3];
        Color.colorToHSV (color, currentHSV);//here is the possible inaccurate method
        if (blockBelow != null &&
                blockBelow.getColor () == color &&
                currentHSV[2] != colorValuesMap.get (4)) {
            Color.colorToHSV (blockBelow.getColor (), currentHSV);
            currentHSV[2] -= 0.2F;
            blockBelow.color = Color.HSVToColor (currentHSV); //here is the possible inaccurate method
            selfDestroy ();
            timer.stopTimer ();

            //if the block below is not the block at the bottom
            if (blockBelow.getY () != Game.MATRIX_HEIGHT - 1) {
                blockBelow.checkLanded (Game.getInstance ().
                        getBlockMatrix ()[blockBelow.getX ()][blockBelow.getY () + 1]);
            }

            return true;
        }

        //if a block is touched
        if (blockBelow != null &&
                blockBelow.getColor () != color) {
            timer.stopTimer ();
            return true;
        }

        return false;
    }

    private void selfDestroy () {
        Game.getInstance ().getBlockMatrix ()[x][y] = null;
        BlockView.updateViews ();
    }

    private boolean checkPositionValid (int x, int y) {
        return !(x < 0 || x >= Game.MATRIX_WIDTH ||
                y < 0 || y >= Game.MATRIX_HEIGHT)
                && Game.getInstance ().getBlockMatrix ()[x][y] == null;

    }

    public void moveHorizontally (@Direction int direction) {
        if (checkPositionValid (x + direction, y)) {
            setX (x + direction);
        }
    }
}

谁能告诉我Color.colorToHSVColor.HSVToColor 是否不准确或者我哪里做错了?

只是让您知道,Game.getInstance().getHueNumber() 如果有帮助会返回 167

【问题讨论】:

  • HSV 表示使用floats。如果您在基于int 的RGB 和HSV 之间进行转换,您通常会损失一些精度。如果你想要准确不变的表示,你应该坚持使用 RGB。
  • 但是如何增加 RGB 中颜色的暗度? @Cinnam
  • 要使 RGB 颜色更深,您可以将 R、G、B 分量除以某个常数。这将保持色调和饱和度。理想情况下,像newR = oldR * 75 / 100 这样可以获得 75% 的亮度。 G、B 也一样。
  • (通过“将 R、G、B 分量除以某个常数”我的意思是按相同的百分比减少 R、G、B 分量)

标签: java android colors


【解决方案1】:

我可能会为此创建一个自定义颜色类,我会在其中保留准确的颜色信息,并且仅在需要输出时转换为另一种格式。在这种情况下,转换无关紧要,因为转换后的值不会用于比较。

类似的东西应该可以解决你的问题:

class CustomColor {

    private static final Random RANDOM = new Random();
    private static final int DARKNESS_LEVEL_COUNT = 5;
    private static final float[] DARKNESS_LEVELS = new float[] {0.9f, 0.7f, 0.5f, 0.3f, 0.1f};
    private static final float SATURATION = 1f;

    private float hue;
    private int darknessLevel;

    public CustomColor(float hue) {
        this.hue = hue;
        darknessLevel = RANDOM.nextInt(DARKNESS_LEVEL_COUNT - 1); // I'm guessing you don't create the darkest blocks
    }

    public int getColor() {
        float[] hsv = new float[] {hue, SATURATION, DARKNESS_LEVELS[darknessLevel]};
        return Color.HSVToColor(hsv);
    }

    public boolean equals(CustomColor other) {
        return (hue == other.hue) && (darknessLevel == other.darknessLevel);
    }

    public boolean darken() {
        darknessLevel++;
        return darknessLevel == DARKNESS_LEVEL_COUNT - 1; // are we at max darkness?
    }
}

我不知道你如何选择色调,但你可能可以使用与此处的黑暗类似的数组方法。

【讨论】:

  • 但它仍然使用浮点数,这意味着它仍然不准确,对吧?
  • @Sweeper 它使用浮点数,但它们保留您在构造函数中给出的值。关键是您不比较作为某些转换计算结果的浮点数。不过,您可以拥有 huesMaparray 并以与 darknessLevel 在此处索引 darkness 类似的方式对它们进行索引。然后你只需比较ints。
【解决方案2】:

一段时间后,我找到了解决方案。从 @Cinnam 的 cmets,我知道 HSV 颜色使用 floats 并且它们不准确。 RGB 对我来说不是一个好主意,因为我不能轻易知道色调。

根据 Steve McConnell 的 Code Complete 2nd Edition,第 12.3 节:

... 一种有效的方法是确定这些值是否足够接近。通常,您会编写一个 equals() 函数,如果值足够接近则返回 true,否则返回 false。

而且我认为这两种稍微不同的颜色“大致相等”,所以我写了一个这样的方法:

private boolean intColorEquals(int color1, int color2) {
    final int MAXIMUM_ERROR = 1000;
    return Math.abs (color1 - color2) < MAXIMUM_ERROR;
}

private boolean floatEquals (float a, float b) {
    final float MAXIMUM_ERROR = 0.05F;
    return Math.abs (a - b) < MAXIMUM_ERROR;
}

我替换了所有颜色之间的比较和浮动之间的比较。

我真的不知道我自己的问题的答案是否应该采用这样的风格。所以如果我做错了什么,请告诉我。

【讨论】:

    猜你喜欢
    • 2012-12-28
    • 2021-08-30
    • 1970-01-01
    • 2022-01-15
    • 2014-11-12
    • 2015-04-07
    • 2014-02-28
    • 2019-07-24
    • 2020-11-13
    相关资源
    最近更新 更多