Added new class Color IV

This commit is contained in:
Robert Vokac 2024-10-01 19:04:37 +02:00
parent b1c3ff2d28
commit 7d39047402
Signed by: robertvokac
GPG Key ID: FB9CE8E20AADA55F
7 changed files with 313 additions and 65 deletions

View File

@ -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

View File

@ -18,7 +18,7 @@ package com.pixelgamelibrary.api.graphics;
// along with this program. If not, see
// <https://www.gnu.org/licenses/> 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

View File

@ -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) {

View File

@ -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
// <https://www.gnu.org/licenses/> 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);
}
}

View File

@ -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;
}
}

View File

@ -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
// <https://www.gnu.org/licenses/> 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);
}

View File

@ -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
// <https://www.gnu.org/licenses/> 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;
}
}