How to draw a sector of a BufferedImage? - java

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);

Related

Quickly rotate image section in Java

I would like to rotate, scale, and translate a section of an image. For example, I have a sprite-sheet with columns and rows of sprites. I can draw the section I want onto a temporary BufferedImage, then transform that temporary image onto the main graphics, but this is a very slow operation.
How can I make this much much faster? It needs to occur more than 100 * 60 times per second.
public void Draw_WorldSpace(Graphics2D g, double x, double y, double angle, double deltaTime) {
// setup portion of image to transform
BufferedImage tempImage = new BufferedImage(sourceRectangle.width, sourceRectangle.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = tempImage.createGraphics();
g2.drawImage(image, 0, 0, sourceRectangle.width, sourceRectangle.height, sourceRectangle.x, sourceRectangle.y, sourceRectangle.width, sourceRectangle.height, null);
g2.dispose();
// setup a transformation object
AffineTransform transform = new AffineTransform();
// match view rotation
transform.translate(GameLogic.viewPort.GetCanvasCenter().x, GameLogic.viewPort.GetCanvasCenter().y);
transform.rotate(Math.toRadians(GameLogic.viewPort.GetAngle()));
transform.translate(-GameLogic.viewPort.GetCanvasCenter().x, -GameLogic.viewPort.GetCanvasCenter().y);
// set to position
transform.translate(x - GameLogic.viewPort.GetViewPortCenter().x + GameLogic.viewPort.GetCanvasCenter().x, y - GameLogic.viewPort.GetViewPortCenter().y + GameLogic.viewPort.GetCanvasCenter().y);
// rotate
transform.rotate(Math.toRadians(angle));
// center on sprite
transform.translate(-sourceRectangle.width / 2, -sourceRectangle.height() / 2);
// draw the sprite
g.drawImage(tempImage, transform, null);
}
Ultimately I did what Hovercraft Full Of Eels suggested. I wasn't the biggest fan of the solution simply because it requires a lot of memory overhead. But ultimately, it worked like a charm and even game me more streamlined control over the graphics, so that's really cool.

Clip a BufferedImage to an Area

I'm trying to draw an image within a certain area. Right now I have code that fills an area with a RadialGradientPaint.
Area lightArea = ...
// fill the polygon with the gradient paint
g.setPaint(light.paint);
g.fill(lightArea);
I would like to draw a BufferedImage in that area instead of drawing a RadialGradientPaint. Is there a way I can do that?
You could use BufferdImage#getSubimage
Rectangle bounds = area.getBounds();
BufferedImage img = master.getSubImage(0, 0, Math.min(bounds.width, master.getWidth()), Math.min(bounds.height, master.getHeight());
This assumes that the area is rectangular. If it's not, you cold create a mask image, based on the shape of the Area and use it to generate masked image (cookie cutting the image out of the shape)
As demonstrated here. The benefit of which is it allows for antialiasing
Use Graphics.setClip:
g.setClip(lightArea);
g.drawImage(yourImage, x, y, null);
See http://docs.oracle.com/javase/tutorial/2d/advanced/clipping.html for more details.

Java Graphics Invert Color Composite

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.

Trying to do a spotlight effect in android canvas

I have a canvas and a simple bitmap for background image, fills the whole screen. I created a rect painted black and set it's alpha to 250 in order to make a "dark" effect on the background image. My aim to make a simple circle object that reveals the place it's hovering above. I tried thinking in many ways how to excecute it and failed.
I think the best way is to create a simple circle that manages to decrease the darkness alpha on the position it hovers above, but I have no idea how to do it.
The relevant part of my code:
private ColorFilter filter = new LightingColorFilter(Color.BLACK, 1);
private Paint darkPaint = new Paint(Color.BLACK), paint = new Paint(), paint2 = new Paint();//The style of the text and dark.
public DarkRoomView(Context context) {
super(context);
myChild = this;
darkPaint.setColorFilter(filter);
darkPaint.setAlpha(250);
paint2.setAlpha(10);
paint.setAlpha(50);
}
private void loadGFX() {//Loads all of this view GFX file.
backgroundImage = BitmapFactory.decodeResource(getResources(), R.drawable.darkroomscreen);
lightImage = BitmapFactory.decodeResource(getResources(), R.drawable.light);
}
private void drawGFX(Canvas canvas) {
canvas.drawBitmap(backgroundImage, 0, 0, paint2);//The backgeound image.
canvas.drawRect(0, 0, WIDTH, HEIGHT, darkPaint);//The darkness.
canvas.drawBitmap(lightImage, 50, 50, paint);//A spotlight.
}
Any ideas how I should get it done?
Thanks!
For the spotlight, you could draw a circle of the original image over the darkness. You'd simply need to find the correct rectangle of the original image (based on where your finger is), and then draw a circle of that particular rectangle over the darkness. Trying to look "through" the darkness won't really get you anywhere; you need to place something over it.
By the time you draw the "spotlight", you've already darkened the image with the rectangle. It would be difficult to recover information lost during that draw.
A more flexible approach would be to draw a dark rectangle with a spotlight in a separate image (that is, compose the "darkness" and spotlight alpha and color mask image first), and then draw that mask image on top of the background as a separate step. This would also let you easily do things like e.g. give the spotlight fuzzy borders.

Drawing objects behind circle except the ones behind 'background'

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.

Categories