Basically what I want to do is make a cursor for a JComponent that's pixels appear as the inverse of the color they are over. For example, take your mouse and hover it over the letters in the url for this page. If you look closely, the pixels of the cursor that are over black pixels of a letter turn white. I know you can invert an RGB color by subtracting the current red, green, and blue colors from 255 for the each corresponding field, but I don't know how to implement this the way I want.
This is part of a basic paint program I am making. The JComponent I mentioned before is my "canvas" that you can draw on with various tools. I am NOT using java.awt.Cursor to change my cursor because I want the cursor to change dynamically according to what is under it. The "cursor" that I am using is defined as a .png image, and I am creating a BufferedImage from this file that I can then draw on top of the existing BufferedImage of the whole component. I redraw this image using a point defined by a MouseListener.
I looked into AlphaComposite and it looks close to what I want, but there is nothing about inverting the colors underneath the cursor like I want. Please help.
EDIT:
So I just had to do it the hard way with an algorithm because there's nothing built in for this purpose. Here's the code a little out of context:
/**
* Changes the color of each pixel under the cursor shape in the image
* to the complimentary color of that pixel.
*
* #param points an array of points relative to the cursor image that
* represents each black pixel of the cursor image
* #param cP the point relative to the cursor image that is used
* as the hotSpot of the cursor
*/
public void drawCursorByPixel(ArrayList<Point> points, Point cP) {
Point mL = handler.getMouseLocation();
if (mL != null) {
for (Point p : points) {
int x = mL.x + p.x - cP.x;
int y = mL.y + p.y - cP.y;
if (x >= 0 && x < image.getWidth() && y >= 0 && y < image.getHeight()) {
image.setRGB(x, y, getCompliment(image.getRGB(x, y)));
}
}
}
}
public int getCompliment(int c) {
Color col = new Color(c);
int newR = 255 - col.getRed();
int newG = 255 - col.getGreen();
int newB = 255 - col.getBlue();
return new Color(newR, newG, newB).getRGB();
}
I believe what you are looking for is an image filter. It sounds like you even have all the pieces for it built already. Your filter will be the image of the cursor, which will get drawn on top of everything else. The trick is, as you say, to draw each pixel of that cursor such that said pixel's color is a calculated "opposite" of the pixel color in the drawn space behind the cursor.
I do not know the best way to go about this, but I know one way you might be able to improve on. Paint whatever your background is to a buffered image, then go get the color of the pixels your cursor will hover over using the BufferedImage's color model. This example is one I found here from another question.
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = image.createGraphics();
_mainPanel.paint(g2);
image.getColorModel().getRGB(pixel);
g2.dispose();
Ultimately you'll use this buffered image of your background to get the pixels (and their colors) that your cursor overlaps, and then you can run some algorithm on the colors to invert them in your cursor, then redraw the cursor with the new colors.
This question has a couple of solutions for that algorithm, though I have not personally tried them to see their effects.
Related
I wasn't able to find the solution to this problem online, sorry if this question is already asked before, also I think my question's title wasn't specific enough, so, I will explain in more details.
So, what I did is created a BufferedImage with a specific size and type, let's say for example:
BufferedImage img1 = new BufferedImage(500, 500, BufferedImage.TYPE_INT_ARGB);
And what I am trying to do, is to use a "for" loop and loop thru every single image's pixel inside of an imaginary circle, lets say that circle starts on (X, Y)(0, 0), and ends on (X, Y)(500, 500).
Now, what I am trying to do, is to loop thru every single pixel inside of that imaginary circle, and then later, do something with that pixel (changing it's color for example).
Can anyone please help me do it? Thanks!
I came up with an idea that could solve this problem not only for circles, but also for any other shapes.
So, my idea is to create another Buffered Image, make it the same size as the one i want to edit (work on), make sure that all of it's pixels are blank (transparent), and draw a shape that you want on that new image (a circle for example) also, we keep the track of the shape's color, and the shape needs to be filled.
After that, we loop thru each pixel on the image that we want to edit using the for loops and the x, y integer variables, each time the loop repeats, we check on the shape's image to see if the shape is drawn there, and to do that we check if the pixel color is same as the shape's color on the shape's image. And if it is, the loop has detected the coordinates to the pixel inside of the shape, and then we can do something with it. (The for loops use the X and Y coordinate integers)
Here is an example code:
public static BufferedImage yourMethodName(BufferedImage inputImage, Point circlePosition, Dimension circleSize)
{
BufferedImage outputImage = inputImage;
BufferedImage circleImage = new BufferedImage(inputImage.getWidth(), inputImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics drawOnCircleImg = circleImage.getGraphics();
Color circleColor = new Color(0, 255, 0);
drawOnCircleImg.setColor(circleColor);
drawOnCircleImg.fillOval(circlePosition.x, circlePosition.y, circleSize.width, circleSize.height);
for(int y = 0; y < outputImage.getHeight(); y++)
{
for(int x = 0; x < outputImage.getWidth(); x++)
{
if(circleImage.getRGB(x, y) == circleColor.getRGB())
{
//PIXEL IS INSIDE OF THE SHAPE, IT IS DETECTED, DO SOMETHING NOW
//VARIABLES FOR THE PIXEL POSITION ARE: X, Y
}
}
}
return outputImage;
}
I'm making a game with a mouse cursor, and I'd like to represent the health by overlaying the cursor with a green version of the image, but only a geometric sector of it corresponding to the health percentage. Solutions from posts like these: Drawing slices of a circle in java? & How to draw portions of circles based on percentages in Graphics2D? are pretty much what I want to do, but with a BufferedImage as opposed to a solid color fill.
//Unfortunately all this does is cause nothing to draw, but commenting this out allows the overlay image to draw
Arc2D.Double clip = new Arc2D.Double(Arc2D.PIE);
double healthAngle = Math.toRadians((((Double)data.get("health")).doubleValue() * 360.0 / 100.0) - 270.0);
clip.setAngles(0, -1, Math.cos(healthAngle), Math.sin(healthAngle));
System.out.println(Math.cos(healthAngle) + " " + Math.sin(healthAngle));
g.setClip(clip);
In short, how do I draw a sector of a BufferedImage given any angle?
If you read the API docs for setClip(Shape) you'll see that the only shape that is guaranteed to work, is a rectangle. So, setting the clip probably won't work.
However, there are other options. The most obvious is probably to use a TexturePaint to fill your arc with the BufferedImage. Something like:
TexturePaint healthTexture = new TexturePaint(healthImg, new Rectangle(x, y, w, h));
g.setPaint(healthTexture);
g.fill(arc); // "arc" is same as you used for "clip" above
Another option is to first draw the arc in solid color, over a transparent background, then paint the image over that, using the SRC_IN Porter-Duff mode. Something like:
g.setPaint(Color.WHITE);
g.fill(arc); // arc is same as your clip
g.setComposite(AlphaComposite.SrcIn); // (default is SrcOver)
g.drawImage(x, y, healthImg, null);
I'm drawing sprites, from a spritesheet, to the screen. The sprites have a black background. I would like the black, and only black, to be rendered as transparent, if it's possible. So, say I have a red background. Then I have a sprite that has a blue circle with a black background. When I draw it to the screen, I want only the blue circle (and obviously the red background) to be visible. Here is the code for my current project. Help greatly appreciated!
public void render(float delta) {
Gdx.gl.glClearColor(1f, 1f, 1f, 1f);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
batch.setProjectionMatrix(camera.combined);
batch.setBlendFunction(GL20.GL_SRC_ALPHA, GL20.GL_ONE_MINUS_SRC_ALPHA);
batch.enableBlending();
camera.update();
generalUpdate();
batch.begin();
// Rendering Code
Assets.sprite_moon.draw(batch);
if (Assets.accelerated) {
Assets.sprite_flame.draw(batch);
}
Assets.sprite_rocket.draw(batch);
Assets.sprite_blue.draw(batch);
// End render code
batch.end();
batch.disableBlending();
}
You're looking for "chroma key" or "color key" support (and you want black to be your "chroma color"). In general, this isn't something supported by libgdx. See Removing a Sprites Color Key libGDX
However, most tools (and Libgdx) support an "alpha channel" which is a way of specifying the transparency of any pixel, independently of its color. You should be able to set the transparency when generating the image, or even when packing the image into a spritesheet. Or you can build a pre-processing pass on the images yourself. See How do you tell libgdx to use a specific color for transparency?
If you really want to go the chroma key approach, you can try using a custom shader, but I would avoid that approach unless you've got a really strong reason to play with shaders.
I know this is old but for precedence sake if nobody wants to download GIMP, I used this free website to make my sprites a transparent png:
http://www.online-image-editor.com/
I wrote the following script in python3 to replace a bunch of images with color key transparency with PNGs with an alpha channel. Feel free to use it, call it like: process_image("a.bmp", "a.png")
It requires scipy and numpy, which you can install with pip3.
#!/usr/bin/env python3
from scipy import misc
import numpy as np
def process_image(filepath, outfile):
image = misc.imread(filepath)
if image.shape[2] == 3:
alpha = np.expand_dims(np.full(image.shape[:2], 255), axis=2)
image = np.append(image, alpha, axis=2)
width, height = image.shape[1], image.shape[0]
color_key = image[0,0,:3]
for x in range(width):
for y in range(height):
pixel = image[y,x,:3]
if np.array_equal(pixel, color_key):
image[y,x,3] = 0
misc.imsave(outfile, image)
Here's some code I'm using to make a color transparent. In this case I have a series of keyframes from a Blender render output, which use full-green as a colorkey ie. green screen.
import pygame
OUTPUT_FN = 'spritesheet.png'
SPRITE_SIZE = 256
COLUMNS = 4
ROWS = 4
TRANSP_COLOR = (0,255,0)
screen_size = ((SPRITE_SIZE * COLUMNS, SPRITE_SIZE * COLUMNS))
screen = pygame.display.set_mode(screen_size)
x, y = 0, 0
column, row = 0,0
sheet = pygame.Surface(screen_size)
for i in range(1,25):
if i < 10:
fn = "%s%i.png" % ("000", i)
else:
fn = "%s%i.png" % ("00", i)
keyframe = pygame.image.load(fn)
keyframe.set_colorkey(TRANSP_COLOR)
x = column * SPRITE_SIZE
y = row * SPRITE_SIZE
sheet.blit(keyframe, (x,y))
if column + 1 == COLUMNS:
column = 0
row += 1
else:
column += 1
running = True
while running:
pygame.display.update()
screen.blit(sheet, (0,0))
for e in pygame.event.get():
if e.type == pygame.QUIT:
running = False
if e.type == pygame.KEYDOWN:
kname = pygame.key.name(e.key)
if kname == 'q':
running = False
if kname == 's':
pygame.image.save(screen, OUTPUT_FN)
pygame.quit()
Situation: I have a canvas on an Android game, I have some objects (I will keep it as simple as possible):World(where are storaged all Laser and Block objects), Block and Laser. I can draw all this objects in the canvas.
I would like to 'hide' them behind a black 'background', and then draw a blurry 'transparent' circle, so all objects are hidden behind the black background, except the objects behing the circle.
I have thought about it, but I can't think of an approach to do this.
Images:
This is my actual situation:
This is the expected:
Do something like this:
public void drawBitmapsInCanvas(Canvas c){
c.drawBitmap(block, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawBitmap(block2, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawBitmap(laser, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
c.drawColor(Color.BLACK);//this hides everything under your black background.
c.drawBitmap(circle, new Rect(/*coordinates here*/), new Rect(/*More coordinates*/),null);
}
If you want transparency:
Paint paint =new Paint();
paint.setARGB(120,0,0,0); //for the "120" parameter, 0 is completely transparent, 255 is completely opaque.
paint.setAntiAlias(true);
c.drawBitmap(bmp,Rect r,Rect rr, paint);
or if you are trying to change the opacity of individual pixels, the approach is a bit more complicated (I have not tested the code, but you get the gist of it):
public static final Bitmap getNewBitmap(Bitmap bmp, int circleCenterX,
int circleCenterY,int circleRadius){
//CIRCLE COORDINATES ARE THE DISTANCE IN RESPECT OF (0,0) of the bitmap
//, not (0,0) of the canvas itself. The circleRadius is the circle's radius.
Bitmap temp=bmp.copy(Bitmap.Config.ARGB_8888, true);
int[]pixels = new int[temp.getWidth()*temp.getHeight()];
temp.getPixels(pixels,0 ,temp.getWidth(),0,0,temp.getWidth(), temp.getHeight());
int counter=0;
for(int i=0;i<pixels.length;i++){
int alpha=Color.alpha(pixels[i]);
if(alpha!=0&&!((Math.pow(counter/temp.getWidth()-circleCenterY,2.0)+
Math.pow(counter%temp.getWidth()-circleCenterX,2.0))<Math.pow(circleRadius,2.0))){
//if the pixel itself is not completely transparent and the pixel is NOT within range of the circle,
//set the Alpha value of the pixel to 0.
pixels[i]=Color.argb(0,Color.red(pixels[i]),Color.green(pixels[i]),Color.blue(pixels[i]));
}
counter++;
}
temp.setPixels(pixels,0, temp.getWidth(),0,0,temp.getWidth(),temp.getHeight());
return temp;
}
and then draw temp.
I'm not completely sure what you are trying to ask, so you may have to modify as necessary.
If you try the second answer of qwertyuiop5040, you will get a ver low - perfomance when you try to apply it to a large image. Let's say a 1000*800 pixels image. Then you will have a loop:
for (int i = 0 ; i < 1000*800; i++)
You could create an image that's a black rectangle with a transparent hole in it. The hole would be the circle that you can see through, and the image would be rendered over the spot you want to be visible. Then, you can draw four black rectangles around the image to cover the rest of the screen.
What is the most efficient way to do lighting for a tile based engine in Java?
Would it be putting a black background behind the tiles and changing the tiles' alpha?
Or putting a black foreground and changing alpha of that? Or anything else?
This is an example of the kind of lighting I want:
There are many ways to achieve this. Take some time before making your final decision. I will briefly sum up some techiques you could choose to use and provide some code in the end.
Hard Lighting
If you want to create a hard-edge lighting effect (like your example image),
some approaches come to my mind:
Quick and dirty (as you suggested)
Use a black background
Set the tiles' alpha values according to their darkness value
A problem is, that you can neither make a tile brighter than it was before (highlights) nor change the color of the light. Both of these are aspects which usually make lighting in games look good.
A second set of tiles
Use a second set of (black/colored) tiles
Lay these over the main tiles
Set the new tiles' alpha value depending on how strong the new color should be there.
This approach has the same effect as the first one with the advantage, that you now may color the overlay tile in another color than black, which allows for both colored lights and doing highlights.
Example:
Even though it is easy, a problem is, that this is indeed a very inefficent way. (Two rendered tiles per tile, constant recoloring, many render operations etc.)
More Efficient Approaches (Hard and/or Soft Lighting)
When looking at your example, I imagine the light always comes from a specific source tile (character, torch, etc.)
For every type of light (big torch, small torch, character lighting) you
create an image that represents the specific lighting behaviour relative to the source tile (light mask). Maybe something like this for a torch (white being alpha):
For every tile which is a light source, you render this image at the position of the source as an overlay.
To add a bit of light color, you can use e.g. 10% opaque orange instead of full alpha.
Results
Adding soft light
Soft light is no big deal now, just use more detail in light mask compared to the tiles. By using only 15% alpha in the usually black region you can add a low sight effect when a tile is not lit:
You may even easily achieve more complex lighting forms (cones etc.) just by changing the mask image.
Multiple light sources
When combining multiple light sources, this approach leads to a problem:
Drawing two masks, which intersect each other, might cancel themselves out:
What we want to have is that they add their lights instead of subtracting them.
Avoiding the problem:
Invert all light masks (with alpha being dark areas, opaque being light ones)
Render all these light masks into a temporary image which has the same dimensions as the viewport
Invert and render the new image (as if it was the only light mask) over the whole scenery.
This would result in something similar to this:
Code for the mask invert method
Assuming you render all the tiles in a BufferedImage first,
I'll provide some guidance code which resembles the last shown method (only grayscale support).
Multiple light masks for e.g. a torch and a player can be combined like this:
public BufferedImage combineMasks(BufferedImage[] images)
{
// create the new image, canvas size is the max. of all image sizes
int w, h;
for (BufferedImage img : images)
{
w = img.getWidth() > w ? img.getWidth() : w;
h = img.getHeight() > h ? img.getHeight() : h;
}
BufferedImage combined = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
// paint all images, preserving the alpha channels
Graphics g = combined.getGraphics();
for (BufferedImage img : images)
g.drawImage(img, 0, 0, null);
return combined;
}
The final mask is created and applied with this method:
public void applyGrayscaleMaskToAlpha(BufferedImage image, BufferedImage mask)
{
int width = image.getWidth();
int height = image.getHeight();
int[] imagePixels = image.getRGB(0, 0, width, height, null, 0, width);
int[] maskPixels = mask.getRGB(0, 0, width, height, null, 0, width);
for (int i = 0; i < imagePixels.length; i++)
{
int color = imagePixels[i] & 0x00ffffff; // Mask preexisting alpha
// get alpha from color int
// be careful, an alpha mask works the other way round, so we have to subtract this from 255
int alpha = (maskPixels[i] >> 24) & 0xff;
imagePixels[i] = color | alpha;
}
image.setRGB(0, 0, width, height, imagePixels, 0, width);
}
As noted, this is a primitive example. Implementing color blending might be a bit more work.
Raytracing might be the simpliest approach.
you can store which tiles have been seen (used for automapping, used for 'remember your map while being blinded', maybe for the minimap etc.)
you show only what you see - maybe a monster of a wall or a hill is blocking your view, then raytracing stops at that point
distant 'glowing objects' or other light sources (torches lava) can be seen, even if your own light source doesn't reach very far.
the length of your ray gives will be used to check amount light (fading light)
maybe you have a special sensor (ESP, gold/food detection) which would be used to find objects that are not in your view? raytrace might help as well ^^
how is this done easy?
draw a line from your player to every point of the border of your map (using Bresehhams Algorithm http://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm
walk along that line (from your character to the end) until your view is blocked; at this point stop your search (or maybe do one last final iteration to see what did top you)
for each point on your line set the lighning (maybe 100% for distance 1, 70% for distance 2 and so on) and mark you map tile as visited
maybe you won't walk along the whole map, maybe it's enough if you set your raytrace for a 20x20 view?
NOTE: you really have to walk along the borders of viewport, its NOT required to trace every point.
i'm adding the line algorithm to simplify your work:
public static ArrayList<Point> getLine(Point start, Point target) {
ArrayList<Point> ret = new ArrayList<Point>();
int x0 = start.x;
int y0 = start.y;
int x1 = target.x;
int y1 = target.y;
int sx = 0;
int sy = 0;
int dx = Math.abs(x1-x0);
sx = x0<x1 ? 1 : -1;
int dy = -1*Math.abs(y1-y0);
sy = y0<y1 ? 1 : -1;
int err = dx+dy, e2; /* error value e_xy */
for(;;){ /* loop */
ret.add( new Point(x0,y0) );
if (x0==x1 && y0==y1) break;
e2 = 2*err;
if (e2 >= dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
if (e2 <= dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
}
return ret;
}
i did this whole lightning stuff some time ago, a* pathfindin feel free to ask further questions
Appendum:
maybe i might simply add the small algorithms for raytracing ^^
to get the North & South Border Point just use this snippet:
for (int x = 0; x <map.WIDTH; x++){
Point northBorderPoint = new Point(x,0);
Point southBorderPoint = new Point(x,map.HEIGHT);
rayTrace( getLine(player.getPos(), northBorderPoint), player.getLightRadius()) );
rayTrace( getLine(player.getPos(), southBorderPoint, player.getLightRadius()) );
}
and the raytrace works like this:
private static void rayTrace(ArrayList<Point> line, WorldMap map, int radius) {
//int radius = radius from light source
for (Point p: line){
boolean doContinue = true;
float d = distance(line.get(0), p);
//caclulate light linear 100%...0%
float amountLight = (radius - d) / radius;
if (amountLight < 0 ){
amountLight = 0;
}
map.setLight( p, amountLight );
if ( ! map.isViewBlocked(p) ){ //can be blockeb dy wall, or monster
doContinue = false;
break;
}
}
}
I've been into indie game development for about three years right now. The way I would do this is first of all by using OpenGL so you can get all the benefits of the graphical computing power of the GPU (hopefully you are already doing that). Suppose we start off with all tiles in a VBO, entirely lit. Now, there are several options of achieving what you want. Depending on how complex your lighting system is, you can choose a different approach.
If your light is going to be circular around the player, no matter the fact if obstacles would block the light in real life, you could choose for a lighting algorithm implemented in the vertex shader. In the vertex shader, you could compute the distance of the vertex to the player and apply some function that defines how bright things should be in function of the computed distance. Do not use alpha, but just multiply the color of the texture/tile by the lighting value.
If you want to use a custom lightmap (which is more likely), I would suggest to add an extra vertex attribute that specifies the brightness of the tile. Update the VBO if needed. Same approach goes here: multiply the pixel of the texture by the light value. If you are filling light recursively with the player position as starting point, then you would update the VBO every time the player moves.
If your lightmap depends on where the sunlight hits your level, you could combine two sort of lighting techniques. Create one vertex attribute for the sun brightness and another vertex attribute for the light emitted by light points (like a torch held by the player). Now you can combine those two values in the vertex shader. Suppose the your sun comes up and goes down like the day and night pattern. Let's say the sun brightness is sun, which is a value between 0 and 1. This value can be passed to the vertex shader as a uniform. The vertex attribute that represents the sun brightness is s and the one for light, emitted by light points is l. Then you could compute the total light for that tile like this:
tileBrightness = max(s * sun, l + flicker);
Where flicker (also a vertex shader uniform) is some kind of waving function that represents the little variants in the brightness of your light points.
This approach makes the scene dynamic without having to recreate continuously VBO's. I implemented this approach in a proof-of-concept project. It works great. You can check out what it looks like here: http://www.youtube.com/watch?v=jTcNitp_IIo. Note how the torchlight is flickering at 0:40 in the video. That is done by what I explained here.