All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.reandroid.graphics.AndroidColor Maven / Gradle / Ivy

/*
 *  Copyright (C) 2022 github.com/REAndroid
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.reandroid.graphics;

import com.reandroid.utils.HexUtil;
import com.reandroid.utils.ObjectsUtil;
import com.reandroid.utils.StringsUtil;

import java.util.Iterator;
import java.util.NoSuchElementException;

public class AndroidColor {

    private int alpha;
    private int red;
    private int green;
    private int blue;

    private Type type;

    private boolean upperCase;

    public AndroidColor(int alpha, int red, int green, int blue){
        this.alpha = alpha;
        this.red = red;
        this.green = green;
        this.blue = blue;
    }

    public AndroidColor(){
        this(0, 0, 0, 0);
    }

    public void setUpperCase(boolean upperCase) {
        this.upperCase = upperCase;
    }
    public AndroidColor copy(){
        AndroidColor color = new AndroidColor(this.alpha, this.red, this.green, this.blue);
        color.setType(getType());
        color.setUpperCase(upperCase);
        return color;
    }
    public AndroidColor inverse(){
        AndroidColor color = copy();
        int mask = getType().mask();
        color.red(~color.red() & mask);
        color.green(~color.green() & mask);
        color.blue(~color.blue() & mask);
        return color;
    }
    public AndroidColor argb(){
        AndroidColor color = new AndroidColor(0, this.red, this.green, this.blue);
        Type type = getType();
        if(type.isEightBit()){
            type = Type.ARGB8;
        }else {
            type = Type.ARGB4;
        }
        color.setType(type);
        color.setUpperCase(upperCase);
        color.alpha(this.alpha());
        return color;
    }
    public AndroidColor rgb(){
        AndroidColor color = new AndroidColor(0, this.red, this.green, this.blue);
        Type type = getType();
        if(type.isEightBit()){
            type = Type.RGB8;
        }else {
            type = Type.RGB4;
        }
        color.setType(type);
        color.setUpperCase(upperCase);
        return color;
    }
    public AndroidColor toEightBit(){
        if(isEightBit()){
            return this;
        }
        AndroidColor color = copy();
        color.setEightBit(true);
        return color;
    }
    public AndroidColor toFourBit(){
        if(!isEightBit()){
            return this;
        }
        AndroidColor color = copy();
        color.setEightBit(false);
        return color;
    }
    public boolean hasAlpha(){
        return getType().hasAlpha();
    }
    public void setHasAlpha(boolean hasAlpha){
        Type type = getType();
        if(hasAlpha != type.hasAlpha()){
            if(type == Type.ARGB4){
                type = Type.RGB4;
            }else if(type == Type.RGB4){
                type = Type.ARGB4;
            }else if(type == Type.ARGB8){
                type = Type.RGB8;
            }else if(type == Type.RGB8){
                type = Type.ARGB8;
            }
            int alpha = 0;
            if(hasAlpha){
                alpha = type.fit(alpha());
            }
            this.setType(type);
            alpha(alpha);
        }
    }

    public int alpha() {
        return this.alpha;
    }
    public void alpha(int alpha) {
        if(this.alpha != alpha || alpha == 0){
            this.alpha = getType().fit(alpha);
        }
    }
    public int red() {
        return this.red;
    }
    public void red(int red) {
        this.red = getType().fit(red);
    }
    public int green() {
        return this.green;
    }
    public void green(int green) {
        this.green = getType().fit(green);
    }
    public int blue() {
        return this.blue;
    }
    public void blue(int blue) {
        this.blue = getType().fit(blue);
    }

    public AndroidColor addRgb(int amount){
        if(amount == 0){
            return this;
        }
        AndroidColor color = copy();
        int max = 0xf;
        if(color.isEightBit()){
            max = 0xff;
        }
        int i = color.red + amount;
        if(i < 0 || i > max){
            return this;
        }
        color.red = i;
        i = color.green + amount;
        if(i < 0 || i > max){
            return this;
        }
        color.green = i;
        i = color.blue + amount;
        if(i < 0 || i > max){
            return this;
        }
        color.blue = i;
        return color;
    }
    public boolean isEightBit(){
        return getType().isEightBit();
    }
    public void setEightBit(boolean eightBit){
        Type type = this.getType();
        if(eightBit == type.isEightBit()){
            return;
        }
        int max;
        int scale;
        if(eightBit){
            max = 0xff;
            scale = 0xf;
            type = type.toEightBit();
        }else {
            max = 0xf;
            scale = 0xff;
            type = type.toFourBit();
        }
        this.alpha = (this.alpha * max) / scale;
        this.red = (this.red * max) / scale;
        this.green = (this.green * max) / scale;
        this.blue = (this.blue * max) / scale;
        this.type = type;
    }
    public Type getType() {
        Type type = this.type;
        if(type == null){
            type = Type.getFor(alpha(), red(), green(), blue());
        }
        return type;
    }
    public void setType(Type type) {
        this.type = type;
    }

    public String toHexString(){
        Type type = getType();
        StringBuilder builder = new StringBuilder();
        builder.append('#');
        int width = 1;
        if(type.isEightBit()){
            width = 2;
        }
        if(type.hasAlpha()){
            builder.append(HexUtil.toHex(null, alpha, width));
        }
        builder.append(HexUtil.toHex(null, red, width));
        builder.append(HexUtil.toHex(null, green, width));
        builder.append(HexUtil.toHex(null, blue, width));
        String hex = builder.toString();
        if(upperCase) {
            hex = StringsUtil.toUpperCase(hex);
        }
        return hex;
    }

    public int intValue() {
        int value = alpha << 24;
        value |= red << 16;
        value |= green << 8;
        value |= blue;
        return value;
    }

    public int compareTo(AndroidColor reference, AndroidColor color) {
        return Double.compare(reference.distance(this), reference.distance(color));
    }

    public double distance(AndroidColor other) {
        if(other == this){
            return 0;
        }
        AndroidColor color = this;
        if(color.isEightBit() != other.isEightBit()){
            if(color.isEightBit()){
                other = other.toEightBit();
            }else {
                color = color.toEightBit();
            }
        }
        double d = deltaSquare(other.red(), color.red());
        d += deltaSquare(other.green(), color.green());
        d += deltaSquare(other.blue(), color.blue());
        d = Math.sqrt(d);
        d = d * 100.0;
        long l = Math.round(d);
        d = l / 100.0;
        return d;
    }
    public boolean equalsRgb(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof AndroidColor)) {
            return false;
        }
        AndroidColor other = (AndroidColor) obj;
        AndroidColor color = this;
        if(color.isEightBit() != other.isEightBit()){
            if(color.isEightBit()){
                other = other.toEightBit();
            }else {
                color = color.toEightBit();
            }
        }
        return color.red == other.red &&
                color.green == other.green &&
                color.blue == other.blue;
    }

    public boolean equals(AndroidColor color, float percent) {
        if (color == null) {
            return false;
        }
        if (color == this){
            return true;
        }
        return distance(color) <= toDistance(percent);
    }
    public boolean equals(AndroidColor color, double distanceTolerance) {
        if (color == null) {
            return false;
        }
        if (color == this){
            return true;
        }
        return distance(color) <= distanceTolerance;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (!(obj instanceof AndroidColor)) {
            return false;
        }
        AndroidColor other = (AndroidColor) obj;
        AndroidColor color = this;
        if(color.isEightBit() != other.isEightBit()){
            if(color.isEightBit()){
                other = other.toEightBit();
            }else {
                color = color.toEightBit();
            }
        }
        return color.alpha == other.alpha &&
                color.red == other.red &&
                color.green == other.green &&
                color.blue == other.blue;
    }

    @Override
    public int hashCode() {
        return intValue();
    }
    @Override
    public String toString() {
        return toHexString();
    }
    public static ColorIterator decodeAll(String text) {
        return new ColorIterator(text);
    }
    public static AndroidColor decode(String text) {
        if(text == null){
            return null;
        }
        int length = text.length();
        if(length < 4){
            return null;
        }
        if(text.charAt(0) != '#'){
            return null;
        }
        StringBuilder builder = new StringBuilder(9);
        builder.append('#');
        for(int i = 1; i < length; i++){
            char ch = text.charAt(i);
            if(!HexUtil.isHexChar(ch)){
                break;
            }
            if(i > 8){
                return null;
            }
            builder.append(ch);
        }
        return parseHex(builder.toString(), false);
    }
    public static AndroidColor parseHex(String hexColor) {
        return parseHex(hexColor, true);
    }
    private static AndroidColor parseHex(String hexColor, boolean fail) {
        if(hexColor == null ||
                hexColor.length() == 0 ||
                hexColor.charAt(0) != '#'){
            if(fail){
                throw new NumberFormatException("Invalid hex color: " + hexColor);
            }
            return null;
        }
        Type type = Type.valueOf(hexColor.length());
        if(type == null){
            if(fail){
                throw new NumberFormatException("Invalid hex color string length: " + hexColor);
            }
            return null;
        }
        String current = hexColor;
        AndroidColor color = new AndroidColor();
        color.type = type;
        current = current.substring(1);
        color.setUpperCase(StringsUtil.containsUpperAZ(current));
        int width = 1;
        if(type.isEightBit()){
            width = 2;
        }
        int i;
        int error = -1;
        if(type.hasAlpha()){
            i = HexUtil.decodeHex(current.substring(0, width), error);
            if(i == error){
                if(fail){
                    throw new NumberFormatException("Invalid hex: " + hexColor);
                }
                return null;
            }
            color.alpha = i;
            current = current.substring(width);
        }
        i = HexUtil.decodeHex(current.substring(0, width), error);
        if(i == error){
            if(fail){
                throw new NumberFormatException("Invalid hex: " + hexColor);
            }
            return null;
        }
        color.red = i;
        current = current.substring(width);
        i = HexUtil.decodeHex(current.substring(0, width), error);
        if(i == error){
            if(fail){
                throw new NumberFormatException("Invalid hex: " + hexColor);
            }
            return null;
        }
        color.green = i;
        current = current.substring(width);
        i = HexUtil.decodeHex(current.substring(0, width), error);
        if(i == error){
            if(fail){
                throw new NumberFormatException("Invalid hex: " + current);
            }
            return null;
        }
        color.blue = i;
        return color;
    }

    private static int deltaSquare(int i1, int i2) {
        int i = i1 - i2;
        return i * i;
    }
    public static float toPercent(double distance){
        return (float) ((distance * 100.0) / LENGTH);
    }
    public static double toDistance(float percent){
        return (percent * LENGTH) / 100.0;
    }

    public static class Type {

        public static final Type RGB4 = new Type("RGB4", false, false);
        public static final Type ARGB4 = new Type("ARGB4", true, false);
        public static final Type RGB8 = new Type("RGB8", false, true);
        public static final Type ARGB8 = new Type("ARGB8", true, true);

        private final String name;
        private final boolean hasAlpha;
        private final boolean eightBit;

        private Type(String name, boolean hasAlpha, boolean eightBit){
            this.name = name;
            this.hasAlpha = hasAlpha;
            this.eightBit = eightBit;
        }

        public boolean fits(int alpha, int red, int green, int blue){
            if(alpha != 0 && !hasAlpha){
                return false;
            }
            int mask = mask();
            return (alpha & mask) == alpha &&
                    (red & mask) == red &&
                    (red & mask) == green &&
                    (red & mask) == blue;
        }
        public int fit(int value){
            int mask = mask();
            if(value > mask) {
                return mask;
            }
            if(value < 0){
                return 0;
            }
            return value;
        }
        public int mask(){
            if(eightBit){
                return 0xff;
            }
            return 0xf;
        }
        public boolean hasAlpha() {
            return hasAlpha;
        }
        public boolean isEightBit() {
            return eightBit;
        }
        public Type toEightBit(){
            if(isEightBit()){
                return this;
            }
            if(this == RGB4){
                return RGB8;
            }
            return ARGB8;
        }
        public Type toFourBit(){
            if(!isEightBit()){
                return this;
            }
            if(this == RGB8){
                return RGB4;
            }
            return ARGB4;
        }

        @Override
        public boolean equals(Object o) {
            return o == this;
        }
        @Override
        public int hashCode() {
            return name.hashCode();
        }

        @Override
        public String toString() {
            return name;
        }

        public static Type getFor(int alpha, int red, int green, int blue){
            if(RGB4.fits(alpha, red, green, blue)){
                return RGB4;
            }
            if(ARGB4.fits(alpha, red, green, blue)){
                return ARGB4;
            }
            if(RGB8.fits(alpha, red, green, blue)){
                return RGB8;
            }
            return ARGB8;
        }
        public static Type valueOf(int hexStringLength){
            if(hexStringLength == 4){
                return RGB4;
            }
            if(hexStringLength == 5){
                return ARGB4;
            }
            if(hexStringLength == 7){
                return RGB8;
            }
            if(hexStringLength == 9){
                return ARGB8;
            }
            return null;
        }
    }

    public static class ColorIterator implements Iterator {

        private String text;
        private AndroidColor current;
        private String lastColor;
        private int index;
        private int lastIndex;

        public ColorIterator(String text){
            this.text = text;
            this.lastIndex = -1;
        }

        public String getText() {
            return text;
        }
        public String replace(AndroidColor color) {
            String text = getText();
            if(color == null || lastColor == null){
                return text;
            }
            String left = text.substring(0, lastIndex);
            String right = text.substring(lastIndex + lastColor.length());
            lastColor = color.toHexString();
            text = left + lastColor + right;
            this.text = text;
            return text;
        }

        @Override
        public void remove() {
            String lastColor = this.lastColor;
            if(lastColor == null){
                return;
            }
            String text = getText();
            this.lastColor = null;
            String left = text.substring(0, lastIndex);
            String right = text.substring(lastIndex + lastColor.length());
            this.text = left + right;
        }

        @Override
        public boolean hasNext() {
            return getNext() != null;
        }

        @Override
        public AndroidColor next() {
            AndroidColor color = getNext();
            if(color == null){
                throw new NoSuchElementException();
            }
            lastColor = color.toHexString();
            current = null;
            return color;
        }
        private int nextIndex(){
            if(text == null){
                return -1;
            }
            int length = text.length();
            while (index < length){
                if(text.charAt(index) == '#'){
                    return index;
                }
                index ++;
            }
            return -1;
        }
        private AndroidColor getNext(){
            while (current == null && nextIndex() != -1){
                current = decode(text.substring(index));
                if(current != null){
                    lastIndex = index;
                }
                index ++;
            }
            return current;
        }
    }

    // the distance between "#000000" and "#ffffff";
    public static final double LENGTH = ObjectsUtil.of(441.67);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy