Flipping an image - java

I am creating a tile game where I need to flip images. With the code I have it produces this error:
Exception in thread "main" java.lang.IllegalArgumentException: Width (-1) and height (-1) cannot be <= 0
from this line:
Image newImage = gc.createCompatibleImage(
image.getWidth(null),
image.getHeight(null),
Transparency.BITMASK);
here is the coding i have:
public Image getMirrorImage(Image image) {
return getScaledImage(image, -1, 1);
}
private Image getScaledImage(Image image, float x, float y) {
// set up the transform
AffineTransform transform = new AffineTransform();
transform.scale(x, y);
transform.translate(
(x-1) * image.getWidth(null) / 2,
(y-1) * image.getHeight(null) / 2);
// create a transparent (not translucent) image
Image newImage = gc.createCompatibleImage(
image.getWidth(null),
image.getHeight(null),
Transparency.BITMASK);
// draw the transformed image
Graphics2D g = (Graphics2D)newImage.getGraphics();
g.drawImage(image, transform, null);
g.dispose();
return newImage;
}
Can anyone explain to me what i might need to do different or how i can make it work? Many thanks

It's possible for image.getWidth(null) and image.getHeight(null) to return -1 if they're not known yet. Image processing can be done concurrently and it might still be going when you try to call those methods (even locally it'll do this IIRC). Here's a snippet of code I've used before to wait for an image to be loaded before trying to access its width and height:
private void waitForImage(Image image) {
Container container = new Container();
MediaTracker tracker = new MediaTracker(container);
tracker.addImage(image, 0);
try {
tracker.waitForID(0, 1000);
} catch (Exception e) {
}
}
I just tried a local test and the wait times were generally 1ms for some small images. It's not really any additional time, it just forces your code to wait for the last stage to complete to get the width and height.

The easiest way to flip the image is by negative scaling it. Example:
g2.drawImage(image, x, y, -width, height, null);
That will flip it vertically. This will flip it horizontally:
g2.drawImage(image, x, y, width, -height, null);

Related

RadialGradientPaint Equivalent in Android

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!

How to copy a rotated square BufferedImage part using Java?

I want to copy a part of BufferedImage, but the copying form is not a simple square but rather a square rotated on some angle. As an output I want to get the BufferedImage with width and height equals to the copying square size and contents from the initial image, where copying square intersects the initial image.
What is the simplest way to do it?
A simple approach would be to create a new image with the desired size, transform a Graphics2D of this image according to the selected square region, and then simply paint the original image into this graphics:
private static BufferedImage extractRegion(
BufferedImage source, Point2D center, Point2D size, double angleRad)
{
int w = (int)size.getX();
int h = (int)size.getY();
BufferedImage result =
new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.setRenderingHint(
RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g.translate(size.getX() / 2, size.getY() / 2);
g.rotate(angleRad);
g.translate(-center.getX(), -center.getY());
g.drawImage(source, 0, 0, null);
g.dispose();
return result;
}

Retrieving pixel data from Java Image

Recently I have been attempting to scale pixel arrays (int[]) in Java. I used .setRGB() to add all my pixel data into the BufferedImage. BufferedImage then offers a function called .getScaledInstance(). This should work great for my purposes, but I ran into a problem. .getScaledInstance() returns a Image, not a BufferedImage. With an Image object, I cannot use .getRGB() to add all the pixel data (in int[] form) from the scaled Image back into an array. Is there a way to get raw pixel data from an Image file? Am I missing something? I looked at other questions and did a bit of googling, and they only seemed to be wanting to get picture data in a different form of array (int[][]) or in bytes. Any help would be appreciated, thanks. Also, Sprite is a class I made that is being used. Here is my code:
public Sprite scaleSprite(Sprite s, int newWidth, int newHeight){
BufferedImage image = new BufferedImage(s.getWidth(), s.getHeight(), BufferedImage.TYPE_INT_RGB);
for(int y = 0; y < s.getHeight(); y++){
for(int x = 0; x < s.getWidth(); x++){
image.setRGB(x, y, s.getPixel(x, y));
}
}
Image newImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
Sprite newS = new Sprite(newWidth, newHeight);
int[] pixels = new int[newWidth * newHeight];
newImage.getRGB(0, 0, newWidth, newHeight, pixels, 0, newWidth); //This is where I am running into problems. newImage is an Image and I cannot retrieve the raw pixel data from it.
newS.setPixels(pixels);
return newS;
}
You can draw the resulting Image onto a BufferedImage like this:
Image newImage = image.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
BufferedImage buffImg = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) buffImg.getGraphics();
g2.drawImage(newImage, 0, 0, 10, 10, null);
g2.dispose();
Or you can scale the image directly by drawing it on another BufferedImage:
BufferedImage scaled = new BufferedImage(newWidth, newWidth, BufferedImage.TYPE_4BYTE_ABGR);
Graphics2D g2 = (Graphics2D) scaled.getGraphics();
g2.drawImage(originalImage, 0, 0, newWidth, newWidth, 0, 0, originalImage.getWidth(), originalImage.getHeight(), null);
g2.dispose();
The second approach will work correctly if the two BufferedImages have the same aspect ratio.
To be clear, getScaledInstance() is a method of Image, not BufferedImage. You don't generally want to revert to working directly with the Image superclass once you're working with BufferedImage; Image is really not easy to work with.
Please see if this will help: How to scale a BufferedImage
Or from Scaling a BufferedImage, where they yield the following example:
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
public class Main {
public static void main(String[] argv) throws Exception {
BufferedImage bufferedImage = new BufferedImage(200, 200,
BufferedImage.TYPE_BYTE_INDEXED);
AffineTransform tx = new AffineTransform();
tx.scale(1, 2);
AffineTransformOp op = new AffineTransformOp(tx,
AffineTransformOp.TYPE_BILINEAR);
bufferedImage = op.filter(bufferedImage, null);
}
This will give you the ability to scale entirely at the level of BufferedImage. From there you can apply whatever sprite specific or array data algorithm you wish.

I want to merge 4 pictures together in Java

I'm struggling to understand how to merge 4 pictures together in java, I want to copy each image to the merged image with the overlapping 20 pixels blended in a 50% merge. To give the merged image a 20 pixel boundary that is a blend of the appropriate portion of each image.
So a 4 image box with the images blended into each other by 20 pixels. Not sure how I should use the width and height of the images as it is very confusing.
Something like this. How to do it?
I got all of my info from: AlphaComposite, Compositing Graphics, Concatenating Images.
The following program is improved. It uses two methods: joinHorizontal and joinVertical to join the images. Inside the methods, the following happens
the second image is copied, but only the part that overlaps
the copied image is set at half alpha (transparency)
on the canvas of the 'return image', the first image is painted, followed by the second without the overlapping part
the copied image is painted onto the canvas.
the image is returned
Why do I only set one image at half alpha and not both?
Picture a clear, glass window:
Paint random points red so that half of the window is covered with red. Now, treat the window with the red dots as your new canvas.
Paint random points blue so that the new "canvas" is half covered with blue. The window won't be completely covered; you will still be able to see through it.
But let's imagine that we first painted the window red, and then painted half of it blue. Now, it will be half blue and half red, but not transparent at all.
public class ImageMerger {
/**
* #param args
* #throws IOException
*/
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
BufferedImage img1 = //some code here
BufferedImage img2 = //some code here
BufferedImage img3 = //some code here
BufferedImage img4 = //some code here
int mergeWidth = 20; // pixels to merge.
BufferedImage merge = ImageMerger.joinVertical(
ImageMerger.joinHorizontal(img1, img2, mergeWidth),
ImageMerger.joinHorizontal(img3, img4, mergeWidth),mergeWidth);
//do whatever you want with merge. gets here in about 75 milliseconds
}
public static BufferedImage joinHorizontal(BufferedImage i1, BufferedImage i2, int mergeWidth){
if (i1.getHeight() != i2.getHeight()) throw new IllegalArgumentException("Images i1 and i2 are not the same height");
BufferedImage imgClone = new BufferedImage(mergeWidth, i2.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D cloneG = imgClone.createGraphics();
cloneG.drawImage(i2, 0, 0, null);
cloneG.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, 0.5f));
cloneG.drawImage(i2, 0, 0, null);
BufferedImage result = new BufferedImage(i1.getWidth() + i2.getWidth()
- mergeWidth, i1.getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.drawImage(i1, 0, 0, null);
g.drawImage(i2.getSubimage(mergeWidth, 0, i2.getWidth() - mergeWidth,
i2.getHeight()), i1.getWidth(), 0, null);
g.drawImage(imgClone, i1.getWidth() - mergeWidth, 0, null);
return result;
}
public static BufferedImage joinVertical(BufferedImage i1, BufferedImage i2, int mergeWidth){
if (i1.getWidth() != i2.getWidth()) throw new IllegalArgumentException("Images i1 and i2 are not the same width");
BufferedImage imgClone = new BufferedImage(i2.getWidth(), mergeWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D cloneG = imgClone.createGraphics();
cloneG.drawImage(i2, 0, 0, null);
cloneG.setComposite(AlphaComposite.getInstance(AlphaComposite.DST_IN, 0.5f));
cloneG.drawImage(i2, 0, 0, null);
BufferedImage result = new BufferedImage(i1.getWidth(),
i1.getHeight() + i2.getHeight() - mergeWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = result.createGraphics();
g.drawImage(i1, 0, 0, null);
g.drawImage(i2.getSubimage(0, mergeWidth, i2.getWidth(),
i2.getHeight() - mergeWidth), 0, i1.getHeight(), null);
g.drawImage(imgClone, 0, i1.getHeight() - mergeWidth, null);
return result;
}
}

Java Graphics Font - How to make sure the characters lie in particular area?

I have an Image. On the bottom portion of the image, I would like to create a colored strip say, of height 100. I am already done with creating the strip and I can basically write strings in that portion (e.g. copyright of the image etc). The following is my method.
public static BufferedImage captionMyImage(BufferedImage sourceImage) {
int height=sourceImage.getHeight();
int width=sourceImage.getWidth();
System.out.println("Image Height: "+height);
System.out.println("Image Width: "+width);
int min=20; //fifty pixels
int newHeight=(int) (0.1*height>min ? 1.1*height : height+min);
int difference=newHeight-height;
System.out.println("new height:"+newHeight);
System.out.println("Difference:"+difference);
BufferedImage bgImage=new BufferedImage(width,newHeight,BufferedImage.TYPE_INT_RGB);
/**Create a Graphics from the background image**/
Graphics2D g = bgImage.createGraphics();
//g.drawRect(0, 0, width, ((int)0.1*height>min) ? (int)1.1*height : height+min);
g.setColor(Color.RED);
g.fillRect(0, 0, bgImage.getWidth(), bgImage.getHeight());
g.setColor(Color.YELLOW);
/**Set Antialias Rendering**/
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
/**
* Draw background image at location (0,0)
* You can change the (x,y) value as required
*/
g.drawImage(sourceImage, 0, 0, null);
int strX=sourceImage.getWidth()/2;
int strY=sourceImage.getHeight()+difference/2;
System.out.println("X: "+strX);
System.out.println("Y: "+strY);
g.setFont(new Font(g.getFont().getName(),Font.PLAIN,20));
g.drawString("(c)www.sanjaal.com",strX ,strY);
g.dispose();
return bgImage;
}
I know the way I have calculated the values for x and y for drawString() method is just a simple one and has problem that the text goes outside the boundary sometimes (depending on image size and text length)
I would like to make sure that the text in the image on the bottom strip that I have defined always aligns to the right portion (boundary) of the image, but does not go out of boundary. How can I achieve that? Remember the text length can be dynamic.
Would the java Graphics experts out there share your ideas on how this can be achieved?
String msg = "(c)www.sanjaal.com";
int margin = 2;
g.drawString(msg, getWidth() - g.getFontMetrics().stringWidth(msg) - margin, strY);

Categories