From 7d39047402fc7c0efc19de8a17157569eae7c6e2 Mon Sep 17 00:00:00 2001 From: Robert Vokac Date: Tue, 1 Oct 2024 19:04:37 +0200 Subject: [PATCH] Added new class Color IV --- .../pixelgamelibrary/api/graphics/Color.java | 163 +++++++++++------- .../api/graphics/Color4BitPalette.java | 2 +- .../api/graphics/ColorDepth.java | 28 ++- .../api/graphics/ColorDepthHelper.java | 66 +++++++ .../api/interfaces/Utils.java | 4 + .../api/utils/BinaryUtils.java | 33 ++++ .../api/utils/BinaryUtilsImpl.java | 82 +++++++++ 7 files changed, 313 insertions(+), 65 deletions(-) create mode 100644 src/main/java/com/pixelgamelibrary/api/graphics/ColorDepthHelper.java create mode 100644 src/main/java/com/pixelgamelibrary/api/utils/BinaryUtils.java create mode 100644 src/main/java/com/pixelgamelibrary/api/utils/BinaryUtilsImpl.java diff --git a/src/main/java/com/pixelgamelibrary/api/graphics/Color.java b/src/main/java/com/pixelgamelibrary/api/graphics/Color.java index f2272c9..90f7e4e 100644 --- a/src/main/java/com/pixelgamelibrary/api/graphics/Color.java +++ b/src/main/java/com/pixelgamelibrary/api/graphics/Color.java @@ -19,12 +19,14 @@ /////////////////////////////////////////////////////////////////////////////////////////////// package com.pixelgamelibrary.api.graphics; +import com.pixelgamelibrary.api.Pixel; import com.pixelgamelibrary.api.PixelException; import static com.pixelgamelibrary.api.graphics.ColorDepth.BITS_16; import static com.pixelgamelibrary.api.graphics.ColorDepth.BITS_24; import static com.pixelgamelibrary.api.graphics.ColorDepth.BITS_32; import static com.pixelgamelibrary.api.graphics.ColorDepth.BITS_4; import static com.pixelgamelibrary.api.graphics.ColorDepth.BITS_8; +import java.util.BitSet; import lombok.Data; /** @@ -55,7 +57,8 @@ import lombok.Data; @Data public final class Color { - private static final int BITS_IN_ONE_BYTE = 255; + private static final int EIGHT_BITS_TO_DECIMAL_WITHOUT_ONE = 255; + private float red, green, blue, alpha; @@ -75,15 +78,15 @@ public final class Color { return this; } - private static int floatElementToInt(float f) { + static int floatElementToInt(float f) { - return (int) (f * BITS_IN_ONE_BYTE); + return (int) (f * EIGHT_BITS_TO_DECIMAL_WITHOUT_ONE); } - private static float intElementToFloat(int i) { + static float intElementToFloat(int i) { float iFloat = i; - return iFloat / BITS_IN_ONE_BYTE; + return iFloat / EIGHT_BITS_TO_DECIMAL_WITHOUT_ONE; } // Counts distance between two colors @@ -433,10 +436,8 @@ public final class Color { * right to discard the lower bits. This method modifies the existing color * instance. */ - public void set16Bit() { - this.red = floatElementToInt(this.red) >> 3; // 5 bits for red - this.green = floatElementToInt(this.green) >> 2; // 6 bits for green - this.blue = floatElementToInt(this.blue) >> 3; // 5 bits for blue + public void convertTo16Bit() { + convertToXBit(ColorDepth.BITS_16); } /** @@ -446,10 +447,8 @@ public final class Color { * blue (maximum value of 3). This method modifies the existing color * instance. */ - public void set8Bit() { - this.red = floatElementToInt(this.red) >> 5; // 3 bits for red (max 7) - this.green = floatElementToInt(this.green) >> 5; // 3 bits for green (max 7) - this.blue = floatElementToInt(this.blue) >> 6; // 2 bits for blue (max 3) + public void convertTo8Bit() { + convertToXBit(ColorDepth.BITS_8); } /** @@ -458,33 +457,59 @@ public final class Color { * achieved by shifting the integer values right to discard the lower bits. * This method modifies the existing color instance. */ - public void set4Bit() { - this.red = floatElementToInt(this.red) >> 1; // 2 bits for red - this.green = floatElementToInt(this.green) >> 1; // 2 bits for green - this.blue = floatElementToInt(this.blue) >> 1; // 2 bits for blue + public void convertTo4Bit() { + set(Color4BitPalette.findClosestColor(this)); + } + + private void convertToXBit(ColorDepth colorDepth) { + convertToXBit(colorDepth.getRedBitCount(), colorDepth.getGreenBitCount(), colorDepth.getBlueBitCount()); + } + + private void convertToXBit(int rBits, int gBits, int bBits) { + ColorDepthHelper colorDepthHelper = new ColorDepthHelper(this, rBits, gBits, bBits); + final int r = colorDepthHelper.r; + this.red = intElementToFloat(r); + final int g = colorDepthHelper.g; + this.green = intElementToFloat(g); + final int b = colorDepthHelper.b; + this.blue = intElementToFloat(b); + } + + public void convertToWhiteBlack8Bit() { + convertToWhiteBlackXBit(8); } - public void setWhiteBlack8Bit() { - int grayscale = (int) ((red + green + blue) / 3 * 255); - this.red = this.green = this.blue = grayscale / 255f; + public void convertToWhiteBlack4Bit() { + convertToWhiteBlackXBit(4); + } + + public void convertToWhiteBlack2Bit() { + convertToWhiteBlackXBit(2); } - public void setWhiteBlack4Bit() { - int grayscale = (int) ((red + green + blue) / 3 * 15); - this.red = this.green = this.blue = grayscale / 15f; + public void convertToWhiteBlack1Bit() { + convertToWhiteBlackXBit(1); } - public void setWhiteBlack2Bit() { - int grayscale = (int) ((red + green + blue) / 3 * 3); - this.red = this.green = this.blue = grayscale / 3f; + private void convertToWhiteBlackXBit(int bitCount) { + this.red = this.green = this.blue = colorToGreyScaleElement(this, bitCount); + } + + private static float colorToGreyScaleElement(Color color, int bitCount) { + // Calculate the maximum value based on the bit count + int maxValue = (1 << bitCount) - 1; // 2^bitCount - 1 + + // Calculate the average of the color components and normalize to 255 + float grayscale = (color.getRed() + color.getGreen() + color.getBlue()) / 3.0f; + + // Normalize the grayscale value to the range [0, maxValue] + return (grayscale / 255.0f) * maxValue; // Scale to [0, maxValue] } - public void setWhiteBlack1Bit() { - int grayscale = (int) ((red + green + blue) / 3 * 1); - this.red = this.green = this.blue = grayscale / 1f; - } - public void setWithBitCount(int bitCount) { + + + public void convertToWithBitCount(int bitCount) { ColorDepth colorDepth = ColorDepth.from(bitCount); switch (colorDepth) { case BITS_32: @@ -492,82 +517,104 @@ public final class Color { case BITS_24: return; case BITS_16: - set16Bit(); + convertTo16Bit(); break; case BITS_8: - set8Bit(); + convertTo8Bit(); break; case BITS_4: - set4Bit(); + convertTo4Bit(); break; default: throw new PixelException("Unsupported color depth: " + bitCount); } } - public void setWhiteBlackWithBitCount(int bitCount) { + public void convertToWhiteBlackWithBitCount(int bitCount) { switch (bitCount) { case 8: - setWhiteBlack8Bit(); + convertToWhiteBlack8Bit(); break; case 4: - setWhiteBlack4Bit(); + convertToWhiteBlack4Bit(); break; case 2: - setWhiteBlack2Bit(); + convertToWhiteBlack2Bit(); break; case 1: - setWhiteBlack1Bit(); + convertToWhiteBlack1Bit(); break; default: throw new PixelException("Unsupported bit count: " + bitCount); } } - public void set(ColorMode colorMode, int bitCount) { + public void convertTo(ColorMode colorMode, int bitCount) { if (colorMode == ColorMode.COLOR) { - setWithBitCount(bitCount); + convertToWithBitCount(bitCount); return; } if (colorMode == ColorMode.BLACK_AND_WHITE) { - setWhiteBlackWithBitCount(bitCount); + convertToWhiteBlackWithBitCount(bitCount); return; } throw new PixelException("Unsupported ColorMode: " + colorMode); } - public byte[] getByteRepresentation32Bit() { - return null;//todo + public BitSet getByteRepresentation32Bit() { + return getByteRepresentation(ColorMode.COLOR, 32); } - public byte[] getByteRepresentation24Bit() { - return null;//todo + public BitSet getByteRepresentation24Bit() { + return getByteRepresentation(ColorMode.COLOR, 24); } - public byte[] getByteRepresentation16Bit() { - return null;//todo + public BitSet getByteRepresentation16Bit() { + return getByteRepresentation(ColorMode.COLOR, 16); } - public byte[] getByteRepresentation8Bit() { - return null;//todo + public BitSet getByteRepresentation8Bit() { + return getByteRepresentation(ColorMode.COLOR, 8); } - public byte[] getByteRepresentation4Bit() { - return null;//todo + public BitSet getByteRepresentation4Bit() { + return getByteRepresentation(ColorMode.COLOR, 4); + } + + public BitSet getWhiteBlackByteRepresentation8Bit() { + return getByteRepresentation(ColorMode.BLACK_AND_WHITE, 8); } - public byte[] getByteRepresentation2Bit() { - return null;//todo + public BitSet getWhiteBlackByteRepresentation4Bit() { + return getByteRepresentation(ColorMode.BLACK_AND_WHITE, 4); } - public byte[] getByteRepresentation1Bit() { - return null;//todo + public BitSet getWhiteBlackByteRepresentation2Bit() { + return getByteRepresentation(ColorMode.BLACK_AND_WHITE, 2); } - public byte[] getByteRepresentation(ColorMode colorMode, int bitCount) { - return null;//todo + public BitSet getWhiteBlackByteRepresentation1Bit() { + return getByteRepresentation(ColorMode.BLACK_AND_WHITE, 1); + } + + public BitSet getByteRepresentation(ColorMode colorMode, int bitCount) { + if (colorMode == ColorMode.COLOR) { + Color colorClone = this.copy(); + colorClone.convertTo(colorMode, bitCount); + ColorDepthHelper colorDepthHelper = new ColorDepthHelper(colorClone, ColorDepth.from(bitCount)); + return colorDepthHelper.getBitSet(); + } + + if (colorMode == ColorMode.BLACK_AND_WHITE) { + Color colorClone = this.copy(); + colorClone.convertTo(colorMode, bitCount); + float element = colorToGreyScaleElement(colorClone, bitCount); + double number = Math.pow(2, bitCount) * element; + return Pixel.utils().binary().convertIntToBitSet((int) number, bitCount); + } + throw new PixelException("Unsupported ColorMode: " + colorMode); } @Override diff --git a/src/main/java/com/pixelgamelibrary/api/graphics/Color4BitPalette.java b/src/main/java/com/pixelgamelibrary/api/graphics/Color4BitPalette.java index 7e45c68..0b2a539 100644 --- a/src/main/java/com/pixelgamelibrary/api/graphics/Color4BitPalette.java +++ b/src/main/java/com/pixelgamelibrary/api/graphics/Color4BitPalette.java @@ -18,7 +18,7 @@ package com.pixelgamelibrary.api.graphics; // along with this program. If not, see // or write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -///////////////////////////////////////////////////////////////////////////////////////////////package com.pixelgamelibrary.api.graphics; +/////////////////////////////////////////////////////////////////////////////////////////////// /** * * @author robertvokac diff --git a/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepth.java b/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepth.java index 77f83ce..fb7912c 100644 --- a/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepth.java +++ b/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepth.java @@ -27,17 +27,33 @@ import lombok.Getter; * @author robertvokac */ public enum ColorDepth { - BITS_32(32), - BITS_24(24), - BITS_16(16), - BITS_8(8), + BITS_32(32, 8,8,8), + BITS_24(24,8,8,8), + BITS_16(16,5,6,5), + BITS_8(8,3,3,2), BITS_4(4); - + @Getter private final int bitCount; + @Getter + private final int redBitCount, greenBitCount, blueBitCount; - ColorDepth(int bitCount) { + ColorDepth(int bitCount, int redBitCount, int greenBitCount, int blueBitCount) { + int sum = (redBitCount + greenBitCount + blueBitCount); + if(sum == 24 && bitCount == 32) { + sum = 32; + } + if(sum != 0 && bitCount != sum) { + throw new PixelException("Fatal internal error: " + "bitCount != (redBitCount + greenBitCount + blueBitCount)"); + } this.bitCount = bitCount; + this.redBitCount = redBitCount; + this.greenBitCount = greenBitCount; + this.blueBitCount = blueBitCount; + + } + ColorDepth(int bitCount) { + this(bitCount, 0, 0, 0); } public static ColorDepth from(int bitCount) { switch (bitCount) { diff --git a/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepthHelper.java b/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepthHelper.java new file mode 100644 index 0000000..191395e --- /dev/null +++ b/src/main/java/com/pixelgamelibrary/api/graphics/ColorDepthHelper.java @@ -0,0 +1,66 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel: Game library. +// Copyright (C) 2024 the original author or authors. +// +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see +// or write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +/////////////////////////////////////////////////////////////////////////////////////////////// +package com.pixelgamelibrary.api.graphics; + +import com.pixelgamelibrary.api.Pixel; +import com.pixelgamelibrary.api.utils.BinaryUtils; +import java.util.BitSet; +import lombok.AllArgsConstructor; +import lombok.Getter; + +/** + * + * @author robertvokac + */ +@AllArgsConstructor +@Getter +public class ColorDepthHelper { + + final int r, g, b; + final int redBits, greenBits, blueBits; + + public ColorDepthHelper(Color color, ColorDepth colorDepth) { + this(color, colorDepth.getRedBitCount(), colorDepth.getGreenBitCount(), colorDepth.getBlueBitCount()); + } + public ColorDepthHelper(Color color, int redBits, int greenBits, int blueBits) { + // Create masks based on the number of bits for each color component + int redMask = (1 << redBits) - 1; // Mask for red + int greenMask = (1 << greenBits) - 1; // Mask for green + int blueMask = (1 << blueBits) - 1; // Mask for blue + + // Reduce each color component to the specified number of bits and apply the mask + this.r = (Color.floatElementToInt(color.getRed()) >> (BinaryUtils.BITS_PER_BYTE - redBits)) & redMask; + this.g = (Color.floatElementToInt(color.getGreen()) >> (BinaryUtils.BITS_PER_BYTE - greenBits)) & greenMask; + this.b = (Color.floatElementToInt(color.getBlue()) >> (BinaryUtils.BITS_PER_BYTE - blueBits)) & blueMask; + this.redBits = redBits; + this.greenBits = greenBits; + this.blueBits = blueBits; + } + public BitSet getBitSet() { + BinaryUtils bu = Pixel.utils().binary(); + return bu.merge3BitSets( + bu.convertIntToBitSet(r, redBits), + bu.convertIntToBitSet(g, greenBits), + bu.convertIntToBitSet(b, blueBits), + redBits, + greenBits, + blueBits); + } +} diff --git a/src/main/java/com/pixelgamelibrary/api/interfaces/Utils.java b/src/main/java/com/pixelgamelibrary/api/interfaces/Utils.java index b8a60fd..d29a278 100644 --- a/src/main/java/com/pixelgamelibrary/api/interfaces/Utils.java +++ b/src/main/java/com/pixelgamelibrary/api/interfaces/Utils.java @@ -19,6 +19,7 @@ /////////////////////////////////////////////////////////////////////////////////////////////// package com.pixelgamelibrary.api.interfaces; +import com.pixelgamelibrary.api.utils.BinaryUtilsImpl; import com.pixelgamelibrary.api.utils.CollectionUtils; import com.pixelgamelibrary.api.utils.ReflectionUtils; import java.util.Arrays; @@ -57,5 +58,8 @@ public interface Utils { } ReflectionUtils reflection(); CollectionUtils collections(); + default BinaryUtilsImpl binary() { + return BinaryUtilsImpl.INSTANCE; + } } diff --git a/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtils.java b/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtils.java new file mode 100644 index 0000000..3e50eb1 --- /dev/null +++ b/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtils.java @@ -0,0 +1,33 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel: Game library. +// Copyright (C) 2024 the original author or authors. +// +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see +// or write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +/////////////////////////////////////////////////////////////////////////////////////////////// + +package com.pixelgamelibrary.api.utils; + +import java.util.BitSet; + +/** + * + * @author robertvokac + */ +public interface BinaryUtils { + public int BITS_PER_BYTE = 8; + BitSet convertIntToBitSet(int value, int bitCount); + BitSet merge3BitSets(BitSet bitSet1, BitSet bitSet2, BitSet bitSet3, int bitSet1Size, int bitSet2Size, int bitSet3Size); +} diff --git a/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtilsImpl.java b/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtilsImpl.java new file mode 100644 index 0000000..9b0aa1c --- /dev/null +++ b/src/main/java/com/pixelgamelibrary/api/utils/BinaryUtilsImpl.java @@ -0,0 +1,82 @@ +/////////////////////////////////////////////////////////////////////////////////////////////// +// Pixel: Game library. +// Copyright (C) 2024 the original author or authors. +// +// This program is free software: you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation, either version 3 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see +// or write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +/////////////////////////////////////////////////////////////////////////////////////////////// +package com.pixelgamelibrary.api.utils; + +import java.util.BitSet; + +/** + * + * @author robertvokac + */ +public class BinaryUtilsImpl implements BinaryUtils { + + public static final BinaryUtilsImpl INSTANCE = new BinaryUtilsImpl(); + private BinaryUtilsImpl() { + + } + public BitSet convertIntToBitSet(int value, int bitCount) { + BitSet bitSet = new BitSet(bitCount); + + int index = 0; + + while (value != 0) { + if ((value & 1) != 0) { + bitSet.set(index); + } + value >>= 1; + index++; + } + return bitSet; + } + public BitSet merge3BitSets(BitSet bitSet1, BitSet bitSet2, BitSet bitSet3, int bitSet1Size, int bitSet2Size, int bitSet3Size) { + // Create a new BitSet with a size corresponding to the sum of the sizes of all input BitSets + int totalSize = bitSet1Size + bitSet2Size + bitSet3Size; + BitSet result = new BitSet(totalSize); + + int currentIndex = 0; // Current index for the output BitSet + + // Add bits from the first BitSet + for (int i = 0; i < bitSet1Size; i++) { + if (bitSet1.get(i)) { + result.set(currentIndex); + } + currentIndex++; + } + + // Add bits from the second BitSet + for (int i = 0; i < bitSet2Size; i++) { + if (bitSet2.get(i)) { + result.set(currentIndex); + } + currentIndex++; + } + + // Add bits from the third BitSet + for (int i = 0; i < bitSet3Size; i++) { + if (bitSet3.get(i)) { + result.set(currentIndex); + } + currentIndex++; + } + + return result; + } + +}