I need to move already made BufferedImage by x,y coordinates and then draw another things on it with Graphics2D object. I tried to use this code to do that :
Graphics2D g = img.createGraphics();
g.translate(x, y);
but it doesn't work. Is there any way to move everything in Graphics2D object and then draw on it or I have to use this code to do that:
BufferedImage temp = new BufferedImage(img.getWidth(),img.getHeight(),BufferedImage.TYPE_INT_ARGB);
Graphics2D g = temp.createGraphics();
g.drawImage(img,x, y,null);
Using this code and then drawing only few elements rather than making whole image from scratch isn't big leap in performance so I think making new BufferedImage then drawing image on it isn't best way. I would rather just create Graphics2D object from already made image and then just move it by a few pixels diagonally, but I couldn't find the way to do that.
From the Graphics2d docs when you use translate:
All coordinates used in subsequent rendering operations on this graphics context are relative to this new origin.
You are defining a transformation that affects future operations. After calling translate if you were to call a method on graphics like draw3DRect(0, 0, ... snipped ... ) the starting coordinates 0,0 would be translated by x,y.
I think your best bet might be to use the methods of BufferedImage to move all the pixels before you get the graphics object. You have getRgb and setRgb
A naive example of moving the pixels:
BufferedImage buffImg = ImageIO.read(img);
int width = buffImg.getWidth();
int horizontalOffset = 10;
int verticalOffset = 10;
int widthToMove = width - horizontalOffset;
int heightToMove = buffImg.getHeight() - verticalOffset;
int[] rgb = buffImg.getRGB(0, 0, widthToMove, heightToMove, null, 0, widthToMove);
buffImg.setRGB(horizontalOffset,verticalOffset,widthToMove, heightToMove,rgb, 0, widthToMove);
This still leaves you with some work to do because there is a strip at the top and to the left that you need to fill with background colour.
If it's going to be used on big images you might want to use a buffer int[] and pass it to getRGB in a loop, getting and setting in chunks.
Related
I am drawing a radial gradient circle on an image like this
I have java code for this
private void drawRadialGradientCircleJava(String imagePath, double posX, double posY, float radius, String outputPath) throws IOException{
BufferedImage city = ImageIO.read(new File(imagePath));
BufferedImage mask = new BufferedImage(city.getWidth(), city.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = mask.createGraphics();
Color transparent = new Color(255, 0, 0, 0);
Color fill = Color.RED;
RadialGradientPaint rgp = new RadialGradientPaint(
new Point2D.Double(posX, posY),
radius,
new float[]{0f, 0.75f, 1f},
new Color[]{transparent, transparent, fill});
g2d.setPaint(rgp);
g2d.fill(new Rectangle(0, 0, mask.getWidth(), mask.getHeight()));
g2d.dispose();
BufferedImage masked = new BufferedImage(city.getWidth(), city.getHeight(), BufferedImage.TYPE_INT_ARGB);
g2d = masked.createGraphics();
g2d.setColor(Color.RED);
g2d.fillRect(0, 0, masked.getWidth(), masked.getHeight());
g2d.drawImage(city, 0, 0, null);
g2d.setComposite(AlphaComposite.DstAtop);
g2d.drawImage(mask, 0, 0, null);
g2d.dispose();
ImageIO.write(masked,"png", new File(outputPath));
}
I want to do same thing in Android, I have an image view in which I have an image, now I want to touch a point in image and draw this transparent circle around that point
I have following Android code as well but id doesn't draw anything on the image
private void drawRadialGradientCircleAndroid(ImageView imageView, float posX,
float posY, float radius) throws IOException {
RadialGradient gradient = new RadialGradient(posX, posY, radius, Color.TRANSPARENT,
Color.TRANSPARENT, android.graphics.Shader.TileMode.CLAMP);
Paint p = new Paint();
p.setDither(true);
p.setShader(gradient);
Bitmap bm = ((BitmapDrawable) imageView.getDrawable()).getBitmap();
Bitmap bmOverlay = Bitmap.createBitmap(bm.getWidth(), bm.getHeight(), bm.getConfig());
Canvas canvas = new Canvas(bmOverlay);
canvas.drawBitmap(bm, new Matrix(), null);
canvas.drawCircle(posY, posX, radius, p);
imageView.setImageBitmap(bmOverlay);
}
Please help how can I achieve this in Android.
We should migrate this to the answer boxes.
OP has basically got it here- and in fact the OP's revised gist is brilliant.
Some general tips regarding the first attempt in the question:
1) In protected void onSizeChanged(int w, int h, int oldw, int oldh):
width = w; there is no reason why you can't call getWidth() when you require this. The reason it's advisable is because the View's internal width is set quite late after onMeasure. Consequently, onDraw may be the next time you want a most up to date version, so use the getter there.
mBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);. Creating a bitmap is an expensive and memory intensive operation. Unless you want to write a bitmap to a file, or send it to a BitmapDrawable for an ImageView or something, you don't need to do this. Especially with effects drawn onto the UI with android's graphics library.
mCanvas = new Canvas(mBitmap); followed by a draw operation onto the new canvas. This is never needed. And yet I've seen it (not work) in many code bases and attempts. I think it's the fault of an old stack overflow post that got people doing this so that they could transform a canvas on a custom view without effecting the drawing onto the rest of the canvas. Incidentally, if you need this, use .restore() and .save() instead. If you see new Canvas, be suspicious.
2) onDraw(...):
Yes, you need to avoid doing things in onDraw, like, creating objects, or any heavy processing. But you still need to do the things in onDraw you need to do in onDraw!
So here you simply need to call : canvas.drawCircle(float cx, float cy, float radius, Paint paint) with arguments as per the docs.
This really isn't that sinful for onDraw. If you're worried about calling this too much, as might be the case if your entire button is animating across the screen, you need to use hardware acceleration available in later API versions, as will be detailed in an article called Optimizing the View; very helpful reading if you're using lots of custom drawn views.
3) That pesky radial gradient. The next issue you had is that you quite rightly created your paint in an initmethod so that the object creation was off the draw. But then quite rightly it will have IllegalArgumentExceptioned (I think) on you because at that stage the getHeight() of the view was 0. You tried passing in small pixel values- that won't work unless you know some magic about screen sizes.
This isn't your issue as much as the annoying view cycle at the heart of Android's design patterns. The fix though is easy enough: simply use a later part of the view's drawing process after the onMeasure call to set the paint filter.
But there are some issues with getting this right, namely that sometimes, annoyingly, onDraw gets called before the point at which you'd expect it. The result would be your paint is null and you wouldn't get the desired behavior.
I have found a more robust solution is simply to do a cheeky and naughty little null check in the onDraw and then once only construct the paint object there. It's not strictly speaking optimal, but given the complex way in which the Paint objects hook up with Android's graphics native layer better than trying to straddle the paint configuration and construction in many frequently called places. And it makes for darn clearer code.
This would look like (amending your gist):
#Override
protected void onDraw(final Canvas canvas) {
super.onDraw(canvas);
if (mPaint == null) {
mPaint = new Paint();
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(1);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mPaint.setShader(new RadialGradient(getWidth() / 2, getHeight() / 2,
getHeight() / 3, Color.TRANSPARENT, Color.BLACK, TileMode.MIRROR));
}
width = getWidth();
height = getHeight();
canvas.drawCircle(width / 2, height / 2, height / 3, mPaint);
}
So note a few changes- I think from your description you want the two colours swapped round in the arguments, also don't forget to center the center of your gradient in your view: width/2 and height/2 arguments.
Best of luck!
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.
I'm currently following a series on Java game development from scratch. I understand most java and oop concepts but have very little experience when dealing with graphics and hardware acceleration.
The lines of code that I am questioning are:
private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
The BufferedImage "image" variable is always the variable being drawn to the screen through a render method. Usually something like:
public void render() {
BufferStrategy bs = this.getBufferStrategy;
if(bs == null) { this.createBufferStrategy(3); return; }
Graphics g = bs.getDrawGraphics();
g.drawImage(image, 0, 0, WIDTH, HEIGHT, null);
g.dispose();
bs.show();
}
I understand the array of pixels contains every pixel within the BufferedImage, however, it seems as though everytime that array is filled with values it directly effects the contents of the "image" variable. There is never a method used to copy the pixels array values into the image.
Are these varibales actually linked in such a way? Does the use of:
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
create an automated link between the image and the array being created in the code above? Maybe I am going cray and just missed something but I have reviewed the code several times and not once is the "image" varibale manipulated after it's initial creation. (besides being rendered to the screen of course.) It's always the array "pixels" just being filled with different values that causes the change in the rendered image.
Some insight on this would be wonderful. Thank you in advance!
Why don't you call
image.getData()
instead of
private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();
image.getData() returns a copy of the Raster. getRaster() returns a WriteableRaster with the ability to modify pixels. I'm guessing but getRaster() probably returns a child of the image Raster and therefore is writeable if you modify the array. Try image.getData() to see if it works. If not, post back here and I'll take a closer look.
I looked into this further. The source code that comes with the JDK shows that image.getRaster().getDataBuffer().getData() returns the source data array. image.getData() indeed returns a copy. If the image is modified, the data in getData() will not be modified.
You can call getPixels on the returned Raster:
public int[] getPixels(int x,
int y,
int w,
int h,
int[] iArray)
Returns an int array containing all samples for a rectangle of pixels,
one sample per array element. An ArrayIndexOutOfBoundsException may be
thrown if the coordinates are not in bounds. However, explicit bounds
checking is not guaranteed.
Use int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); instead of image.getData(). image.getData() just returns a copy of the image data where image.getRaster() returns the original pixel array allowing to actually write pixels to it.
I have multiple transparent BufferedImage instances which I'd like to layer on top of each other (aka Photoshop layers) and bake into one BufferedImage output. How do I do this?
I would say the best bet would be to take the buffered images, and create an additional one in order to have an object to append to. Then simply use the Graphics.drawImage() to place them on top of each other.
So something along these lines:
BufferedImage a = ImageIO.read(new File(filePath, "a.png"));
BufferedImage b = ImageIO.read(new File(filePath, "b.png"));
BufferedImage c = new BufferedImage(a.getWidth(), a.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics g = c.getGraphics();
g.drawImage(a, 0, 0, null);
g.drawImage(b, 0, 0, null);
Let's pretend that the first BufferedImage is named bi1 and the second bi2, while the image you want to layer them onto is target.
BufferedImage target=new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
Graphics2D targetGraphics=target.createImage();
targetGraphics.drawImage(bi1,0,0,null);//draws the first image onto it
int[] pixels2=((DataBufferInt) bi2.getRaster().getDataBuffer()).getData();
int[] pixelsTgt=((DataBufferInt) target.getRaster().getDataBuffer()).getData();
for(int a=0;a<pixels2.length;a++)
{
pixelsTgt[a]+=pixels2[a];//this adds the pixels together
}
Make sure that all three BufferedImage objects are of TYPE_INT_ARGB so that alpha is turned on. This may not give you the exact results that you had wanted if the two added together are more than the max integer, so you might want to add in something to help fix that. Pixels use bit shifts to add to colors with the integer ordered as AARRGGBB.
Also consider the AlphaComposite modes available to the graphics context, discussed here.
Is there a way to use a Graphics object's 'setClip()' method to clip using a Line-ish shape? Right now I'm trying to use a Polygon shape but I'm having problems simulating the "width" of the line. I basically draw the line, and when I reach the end, I redraw it but this time subtract the line width from y-coordinate:
Polygon poly = new Polygon();
for(int i = 0; i < points.length; i++)
poly.addPoint(points.[i].x, points.[i].y);
// Retrace line to add 'width'
for(int i = points.length - 1; i >=0; i--)
poly.addPoint(points[i].x, points[i].y - lineHeight);
It almost works but the width of the line varies based upon its slope.
I can't use the BrushStroke and drawLine() methods because the line can change color once it passes some arbitrary reference line. Is there some implementation of Shape that I overlooked, or an easy one I can create, that will let me do this more easily?
If there is a better way, I've never run across it. The best I can think of is to use some trigonometry to make the line width more consistent.
OK, I managed to come up with a pretty nice solution without using the setClip() method. It involves drawing my background to an intermediate Graphics2D object, using setComposite() to specify how I want to mask the pixels, THEN drawing my line using drawLine() on top. Once I have this line, I draw it back on top of my original Graphics object via drawImage. Here's an example:
BufferedImage mask = g2d.getDeviceConfiguration().createCompatibleImage(width, height, BufferedImage.TRANSLUCENT);
Graphics2D maskGraphics = (Graphics2D) mask.getGraphics();
maskGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
maskGraphics.setStroke(new BasicStroke(lineWidth));
maskGraphics.setPaint(Color.BLACK);
// Draw line onto mask surface first.
Point prev = line.get(0);
for(int i = 1; i < line.size(); i++)
{
Point current = line.get(i);
maskGraphics.drawLine(prev.x, prev.y, current.x, current.y);
prev = current;
}
// AlphaComposite.SrcIn: "If pixels in the source and the destination overlap, only the source pixels
// in the overlapping area are rendered."
maskGraphics.setComposite(AlphaComposite.SrcIn);
maskGraphics.setPaint(top);
maskGraphics.fillRect(0, 0, width, referenceY);
maskGraphics.setPaint(bottom);
maskGraphics.fillRect(0, referenceY, width, height);
g2d.drawImage(mask, null, 0, 0);
maskGraphics.dispose();
Maybe you could use a Stroke.createClippedShape to do this? (May need to use an Area to add subtract the stroked shape from/to your original shape depending on what exactly you are trying to do.