Java Smooth Color Transition - java

Let's say I am given two colors.
public final static Color FAR = new Color(237, 237, 30);
public final static Color CLOSE = new Color(58, 237, 221);
How would I transition from one color to the next without dipping into dark colors?
I have come up with ideas such as
double ratio = diff / range; // goes from 1 to 0
int red = (int)Math.abs((ratio * FAR.getRed()) - ((1 - ratio) * CLOSE.getRed()));
int green = (int)Math.abs((ratio * FAR.getGreen()) - ((1 - ratio) * CLOSE.getGreen()));
int blue = (int)Math.abs((ratio * FAR.getBlue()) - ((1 - ratio) * CLOSE.getBlue()));
OR
double ratio = diff / range; // goes from 1 to 0
int red = (int) ((1 - (diff / range)) * FAR.getRed() + CLOSE.getRed() - FAR.getRed());
int green = (int) ((1 - (diff / range)) * FAR.getGreen() + CLOSE.getGreen() - FAR.getGreen());
int blue = (int) ((1 - (diff / range)) * FAR.getBlue() + CLOSE.getBlue() - FAR.getBlue());
But unfortunately none of them smoothly transition from one color to the next.
Would anyone know how to do so while keeping the color bright and not dipping into darker colors, or how to ensure that gradient transition is smooth rather than slow at first then fast and then slow again?
I really ca not come up with any formula.

You're using the wrong sign in the calcuations. Should be plus, not minus, to apply the ratio properly.
int red = (int)Math.abs((ratio * FAR.getRed()) + ((1 - ratio) * CLOSE.getRed()));
int green = (int)Math.abs((ratio * FAR.getGreen()) + ((1 - ratio) * CLOSE.getGreen()));
int blue = (int)Math.abs((ratio * FAR.getBlue()) + ((1 - ratio) * CLOSE.getBlue()));
The reason you are getting dark colours with your existing implementation is that with (-), they would often fall close to zero (less than 50? or negative but greater than -50?) and in the negative case, well, you are taking the absolute value so it becomes a small positive number, i.e. a dark colour.

(ratio * FAR.getGreen()) + ((1 - ratio) * CLOSE.getGreen())
if ratio goest from 0 to 1, then this is weighted average, lets say ratio = 1/2, then it would be aritmetical average, if ratio = 1/3, then it is weighted average where FAR has weight 1 and CLOSE has weight 2

This works nicely for me:
// Steps between fading from one colour to another.
private static final int FadeSteps = 25;
private void fade(Label panel, Color colour) throws InterruptedException {
final Color oldColour = panel.getBackground();
final int dRed = colour.getRed() - oldColour.getRed();
final int dGreen = colour.getGreen() - oldColour.getGreen();
final int dBlue = colour.getBlue() - oldColour.getBlue();
// No point if no difference.
if (dRed != 0 || dGreen != 0 || dBlue != 0) {
// Do it in n steps.
for (int i = 0; i <= FadeSteps; i++) {
final Color c = new Color(
oldColour.getRed() + ((dRed * i) / FadeSteps),
oldColour.getGreen() + ((dGreen * i) / FadeSteps),
oldColour.getBlue() + ((dBlue * i) / FadeSteps));
panel.setBackground(c);
Thread.sleep(10);
}
}
}
Not the neatest bit of code but it works.

Related

Handling borders when applying a filter to an image

I'm trying to tidy up some of my github projects for a portfolio and was hoping for some help.
I have a basic image convolution kernel program in java to apply a filter to an input image.
The kernels are 3x3 and 5x5 2D arrays and are applied across each pixel in the image. Obviously this causes issues around the edges where 3-5 cells in the kernels will be multiplied against empty values (causing the outer pixels in the image to be black or white)
Below are some examples of what I mean (the white pixel bar, not the grey part)
The following is the crux of the code involving the image processing.
for (int xCoord = 0; xCoord < (width); xCoord++) { // -2
for (int yCoord = 0; yCoord < (height); yCoord++) {
// Output Red, Green and Blue Values
int outR, outG, outB, outA;
// Temp red, green, blue values that will contain the total r/g/b values after
// kernel multiplication
double red = 0, green = 0, blue = 0, alpha = 0;
int outRGB = 0;
/*
* Loop over the Kernel (For each cell in kernel)
The offset is added to the xCoord and yCoord to follow the footprint of the
kernel
The logic behind below is that all kernels are uneven numbers (so they can
have a centre pixel, 3x3 5x5 etc),
the offset needs to run from negative half their length (rounded down) to the
positive of half their length (rounded down)
This allows us to input kernels of various sizes (5x5, 9x9 etc) and the
calculation should not be thrown off
*/
try {
for (int xOffset = Math.negateExact(k.getKernels().length / 2); xOffset <= k.getKernels().length
/ 2; xOffset++) {
for (int yOffset = Math.negateExact(k.getKernels().length / 2); yOffset <= k.getKernels().length
/ 2; yOffset++) {
The first 2 loops iterate over each pixel. The second 2 loops are looping over the kernel (A 2D array enum class) The offset is used to loop across the 3x3 or 5x5 kernel at each pixel in the image.
Follows is my very basic attempt to wrap the pixels sampled so edge pixels will use values at the opposite side of the image but my main question is how would be best to handle these edge cases. If anyone has any pointers of where to start solving this it would be much appreciated.
// TODO //very basic wrapping logic
int realX = (xCoord - k.getKernels().length / 2 + xOffset + width) % width;
int realY = (yCoord - k.getKernels().length / 2 + yOffset + height) % height;
int RGB = image.getRGB((realX), (realY)); // The RGB value for the pixel, will be split out
// below
int A = (RGB >> 24) & 0xFF; // Bitshift 24 to get alpha value
int R = (RGB >> 16) & 0xFF; // Bitshift 16 to get Red Value
int G = (RGB >> 8) & 0xFF; // Bit Shift 8 to get Green Value
int B = (RGB) & 0xFF;
// actual rgb * kernel logic
red += (R * (k.getKernels()[yOffset + k.getKernels().length / 2])[xOffset
+ k.getKernels().length / 2] * multiFactor);
green += (G * k.getKernels()[yOffset + k.getKernels().length / 2][xOffset
+ k.getKernels().length / 2] * multiFactor);
blue += (B * k.getKernels()[yOffset + k.getKernels().length / 2][xOffset
+ k.getKernels().length / 2] * multiFactor);
alpha += (A * k.getKernels()[yOffset + k.getKernels().length / 2][xOffset
+ k.getKernels().length / 2] * multiFactor);
}
}
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Error");
}
// Logic here prevents pixel going over 255 or under 0
// The "winner"of the max between the Colour value or 0(min pixel value) will be
// Math.min with 255 (the max value for a pixel)
outR = (int) Math.min(Math.max((red + bias), 0), 255);
outG = (int) Math.min(Math.max((green + bias), 0), 255);
outB = (int) Math.min(Math.max((blue + bias), 0), 255);
outA = (int) Math.min(Math.max((alpha + bias), 0), 255);
// Reassembling the separate color channels into one variable again.
outRGB = outRGB | (outA << 24);
outRGB = outRGB | (outR << 16);
outRGB = outRGB | (outG << 8);
outRGB = outRGB | outB;
// Setting with the reassembled RGB value
// output.setRGB(xCoord, yCoord, outRGB);
I'm a bit stumped how to proceed but if anyone can suggest efficient ways to handle these edge cases or even point me in the right direction or even some constructive criticism for how to improve any of the code. If anyone's interested the full project is here

Somewhere in my array of objects, values change after running the same line of code?

Basically, I have 2 arrays and handlers. One for spawning items, and one handles the obstacles. What happens though, is after a while the values change and are out of center. I'll provide a screenshot.
https://i.stack.imgur.com/nR7ae.png But, the code for spawning the items and obstacles doesn't change, what could be effecting this?
I've looked over my code so many times and I can't find what's causing this.
I am populating the coins in the center of the obstacles and once one is picked up or goes off screen, that bit of code is run.
Here is how I am populating my coins and obstacles.
private void populateCoins() {
currY = (-5 * Constants.SCREEN_HEIGHT / 4) - (obstacleGap / 2) + (obstacleHeight);
while (currY < 0) {
xStartCoin = (int) (Math.random() * (Constants.SCREEN_WIDTH - obstacleHeight));
coins.add(new Coin(obstacleHeight, colour, xStartCoin, currY));
currY += obstacleHeight + obstacleGap;
}
}
private void populateObstacles() {
int currY = (-5 * Constants.SCREEN_HEIGHT / 4);
while (currY < 0) {
int xStart = (int) (Math.random() * (Constants.SCREEN_WIDTH - playerGap));
obstacles.add(new Obstacle(obstacleHeight, colour, xStart, currY, playerGap));
currY += obstacleHeight + obstacleGap;
}
}
Here is how I am spawning my obstacles and coins.
obstacles.add(0, new Obstacle(obstacleHeight, colour, xStart,
obstacles.get(0).getRectangle().top - Constants.OBSTACLE_HEIGHT - Constants.OBSTACLE_GAP,
(int)Constants.PLAYER_GAP));
coins.add(0, new Coin(obstacleHeight, colour, xStartCoin,
coins.get(0).getRectangle().top - Constants.OBSTACLE_GAP + Constants.OBSTACLE_HEIGHT));
It's after playing the game for about 60s the coins start to creep up the screen, no longer in the centre.
All I need is for the coins to stay centered in between the obstacles.

Node drifts of after 3D rotation in JavaFx

I get the x- and y-orientation from an RFID-Tag and want to animate the movement in a JavaFX application. It's my first java project so I'm sorry if there are stupid mistakes.
I rotate an box-node in the way of this and this thread.
Two pictures of how the green node rotates in front of the RFID-reader image in the background
xAxis=red, yAxis=green, zAxis=blue
.
I call the rotateNode method like that:
// calculate necessary variables:
delta_x = -x_angle + x_angle_old;
delta_y = -y_angle + y_angle_old;
delta_x_radians = Math.toRadians(delta_x);
delta_y_radians = Math.toRadians(delta_y);
pitch_rad = delta_y_radians;
yaw_rad = 0d; // not used at the moment
roll_rad = delta_x_radians;
if (!((roll_rad == 0d) && (pitch_rad == 0d) && (yaw_rad == 0d))) {
rotateNode(model3D, pitch_rad, yaw_rad, roll_rad);
}
My box-node has the position (0,0,-200) at the beginning and the center of the object should stay in that position the hole time. Just the orientation in two directions should change. My rotateNode method looks like this:
public static void rotateNode(Group n, double pitch_rad, double yaw_rad, double roll_rad) {// , TranslateTransition
// tt_z) {
double A11 = Math.cos(roll_rad) * Math.cos(yaw_rad);
double A12 = Math.cos(pitch_rad) * Math.sin(roll_rad)
+ Math.cos(roll_rad) * Math.sin(pitch_rad) * Math.sin(yaw_rad);
double A13 = Math.sin(roll_rad) * Math.sin(pitch_rad)
- Math.cos(roll_rad) * Math.cos(pitch_rad) * Math.sin(yaw_rad);
double A21 = -Math.cos(yaw_rad) * Math.sin(roll_rad);
double A22 = Math.cos(roll_rad) * Math.cos(pitch_rad)
- Math.sin(roll_rad) * Math.sin(pitch_rad) * Math.sin(yaw_rad);
double A23 = Math.cos(roll_rad) * Math.sin(pitch_rad)
+ Math.cos(pitch_rad) * Math.sin(roll_rad) * Math.sin(yaw_rad);
double A31 = Math.sin(yaw_rad);
double A32 = -Math.cos(yaw_rad) * Math.sin(pitch_rad);
double A33 = Math.cos(pitch_rad) * Math.cos(yaw_rad);
double d = Math.acos((A11 + A22 + A33 - 1d) / 2d);
if (d != 0d) {
double den = 2d * Math.sin(d);
if (den != 0d) {
Point3D p = new Point3D((A32 - A23) / den, (A13 - A31) / den, (A21 - A12) / den);
x_pos_node = (n.localToScene(n.getBoundsInLocal()).getMaxX()
+ n.localToScene(n.getBoundsInLocal()).getMinX()) / 2d;
y_pos_node = (n.localToScene(n.getBoundsInLocal()).getMaxY()
+ n.localToScene(n.getBoundsInLocal()).getMinY()) / 2d;
z_pos_node = (n.localToScene(n.getBoundsInLocal()).getMaxZ()
+ n.localToScene(n.getBoundsInLocal()).getMinZ()) / 2d;
r.setPivotX(x_pos_node);
r.setPivotY(y_pos_node);
r.setPivotZ(z_pos_node);
r.setAxis(p);
r.setAngle(Math.toDegrees(d));
n.getTransforms().add(r);
Transform all = n.getLocalToSceneTransform();
n.getTransforms().clear();
n.getTransforms().add(all);
}
}
}
Printing the following variables shows that the node moves in y although I don't want that to happen. Also I see, that slowly with time the pivot point of the rotation isn't in the center of the node anymore and when I turn the RFID-Tag it doesn't spin around the middle of the node it spins in a circle which gets bigger and bigger..
from:
x_pos_node: 0,00
y_pos_node: 0,39
z_pos_node: -200,00
MaxX: 199,00
MinX: -199,00
MaxY: 2,78
MinY: -2,00
MaxZ: -176,12
MinZ: -223,88
Depth: 47,76
Height: 4,78
Width: 398,00
to:
x_pos_node: 0,00
y_pos_node: 15,52
z_pos_node: -200,00
MaxX: 198,51
MinX: -198,51
MaxY: 38,35
MinY: -7,31
MaxZ: -130,85
MinZ: -269,15
Depth: 138,30
Height: 45,67
Width: 397,02
Picture from the side that shows how the green node moves under blue z-Axis / zero line:
.
Where is my mistake? Why does the object slowly moves instead of just rotating?
It is possible to fix the wrong position when I add an Translation:
n.getTransforms().add(new Translate(0, -y_pos_node, 0));
But that's just an hotfix and you can see how the object moves down and up again.. I think there is an error in the calculations or the positioning of the pivot point. It also turns a bit around the green y-Axis although "yaw_rad" is set to 0;

Project a smaller grid onto a bigger one

I have been writing a image editing application for fun and all is well but i have ran into a problem with the zoom feature. The image editor plane is 512 x 512 pixels large but the image i want to edit is only 16 x 16. I want to know how to project my mouse coordinates to the smaller image to edit it pixel by pixel.
i have devised this algorithm to to such.
/**
*
* #param pointx The x position of the point thats being bound
* #param pointy The y position of the point thats being bound
* #param oldsizeX The old grid size x of which the point is currently in. ( eg ==> 512*512)
* #param oldsizeY The old grid size y of which the point is currently in. ( eg 512* ==> 512)
* #param newsizeX The new grid size x for the new grid size of the point. ( eg ==> 16*16)
* #param newsizeY The new grid size y for the new grid size of the point. ( eg 16* ==> 16)
* #param normalOffsetX The offset x, if any, the grid has in the normal plane ( eg ==> 32*32 # (512*512))
* #param normalOffsetY The offset y, if any, the grid has in the normal plane ( eg 32* ==> 32 # (512*512)
* #return A Vector2 containing the bound points in the new plane.
*/
public static Vector2 bindPoint(int pointx, int pointy, int oldsizeX, int oldsizeY, int newsizeX, int newsizeY,int normalOffsetX,int normalOffsetY) {
Vector2 vec = new Vector2();
int tileSizeX = oldsizeX / newsizeX;
int tileSizeY = oldsizeY / newsizeY;
int offsetX = normalOffsetX, offsetY = normalOffsetY;
vec.x = (int) (pointx / 2) / (oldsizeX / tileSizeX) - (offsetX / tileSizeX);
vec.y = (int) (pointy / 2) / (oldsizeY / tileSizeY) - (offsetY / tileSizeY);
if(pointx >= normalOffsetX && pointx <= normalOffsetX + oldsizeX && pointy >= normalOffsetY && pointy <= normalOffsetY + oldsizeY) {
return vec;
}else {
return new Vector2(-1,-1);
}
}
This works as long as the smaller resolution is 16x16 and i have found that if i change the 2 after the pointX and pointY division to 0.5 and an image of 32x32 works. What i want to know is if there is a better way to do so, so that i can use any size image at any zoom level?
You should not use integers to represent the position. Use double instead when you do the calculations. In the end, when you have calculated everything and need a pixel value, round the double to an integer. Otherwise you will have lose precision all over the place (which explains the problems you see).
You get different results depending on how you use your brackets. For example, from a math point of view the below Systems out's should give you the same result, but they don't:
int i = 700;
int j = 70;
int k = 30;
System.out.println((i / 2) / (j / k)); --> 175
System.out.println(i / 2 / j * k); --> 150
I figured it out on my own lol, sorry, its late and i spaced and forgot how to proportion.
Here is the answer for anyone else who needs it!
/**
*
* #param pointx The x position of the point thats being bound
* #param pointy The y position of the point thats being bound
* #param oldsizeX The old grid size x of which the point is currently in. ( eg ==> 512*512)
* #param oldsizeY The old grid size y of which the point is currently in. ( eg 512* ==> 512)
* #param newsizeX The new grid size x for the new grid size of the point. ( eg ==> 16*16)
* #param newsizeY The new grid size y for the new grid size of the point. ( eg 16* ==> 16)
* #param normalOffsetX The offset x, if any, the grid has in the normal plane ( eg ==> 32*32 # (512*512))
* #param normalOffsetY The offset y, if any, the grid has in the normal plane ( eg 32* ==> 32 # (512*512)
* #return A Vector2 containing the bound points in the new plane.
*/
public static Vector2 bindPoint(int pointx, int pointy, int oldsizeX, int oldsizeY, int newsizeX, int newsizeY,int normalOffsetX,int normalOffsetY) {
Vector2 vec = new Vector2();
int tileSizeX = oldsizeX / newsizeX;
int tileSizeY = oldsizeY / newsizeY;
int offsetX = normalOffsetX, offsetY = normalOffsetY;
vec.x = (int) Math.floor(pointx * ((float) newsizeX) / (float) oldsizeX) - (offsetX / tileSizeX);
vec.y = (int) Math.floor(pointy * ((float) newsizeY) / (float) oldsizeY) - (offsetY / tileSizeY);
return vec;
}

Get the average out of an array of colors

I was using various color utilities to mix in colors but due to an unorganised order it produces incorrect values. I've looked around and only found single colors or two color blends.
So instead I've placed the colors into an array and I'm currently trying to figure out how to blend them but now I'm stuck.
My attempt:
Array<Color> colorsArray;
for(Color eachColor : colors)
colorsArray.add(new Color(
eachColor.r, eachColor.g, eachColor.b,
strength //<<Varies.
);
));
/We have an array of play colors and there strengths, process them into an average.
float totalRed = 0f, totalBlue = 0f, totalGreen = 0f;
for(ColorStorage colorStorage : colorVectorsWithInfectionStrength)
{
totalRed += (colorStorage.getRed() * colorStorage.getAlpha());
totalBlue += (colorStorage.getBlue() * colorStorage.getAlpha());
totalGreen += (colorStorage.getGreen() * colorStorage.getAlpha());
}
/* Makes dark colors. HMM.
totalRed /= colorVectorsWithInfectionStrength.size;
totalBlue /= colorVectorsWithInfectionStrength.size;
totalGreen /= colorVectorsWithInfectionStrength.size;
*/
ColorStorage averageColor = new ColorStorage(totalRed, totalBlue, totalGreen);
//varying var goes from 0-1 depending on the max strength.
endColor = ColorUtils.blend(averageColor, endColor, varyingVar);
And the blend function:
public static ColorStorage blend(ColorStorage color1, ColorStorage color2, double ratio)
{
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = color1.getColorComponents();
float rgb2[] = color2.getColorComponents();
return new ColorStorage (
rgb1[0] * r + rgb2[0] * ir,
rgb1[1] * r + rgb2[1] * ir,
rgb1[2] * r + rgb2[2] * ir
);
}
EDIT
The color object here is custom that always returns 0-1f for RGBA. (Each value)
You tried dividing by the array size and it produced colours that were too dark. That's due to the fact that you multiply each component by the alpha value (and not 1, like you would without an alpha channel). If you want to normalize the colours correctly, you need to divide by the sum of alphas.
float totalAlpha = 0;
for(ColorStorage colorStorage : colorVectorsWithInfectionStrength)
{
totalRed += (colorStorage.getRed() * colorStorage.getAlpha());
totalBlue += (colorStorage.getBlue() * colorStorage.getAlpha());
totalGreen += (colorStorage.getGreen() * colorStorage.getAlpha());
totalAlpha += colorStorage.getAlpha();
}
totalRed /= totalAlpha;
totalBlue /= totalAlpha;
totalGreen /= totalAlpha;
This should give you the correct scale.
Please note though that it won't give you an accurate blend either because the RGB colour space isn't linear. But it's close enough for casual use.

Categories

Resources