Scaled "straight line" image jumps out of line - java

a have a problem with scaling images up. I have a png file that looks like this:
raw png
The image is 8px * 8px and has some red straight lines on it.
But when i draw this image with java and scale it up this happens: java image
And as you can barly see, the line is not exactly straight. It is always one pixel off and makes this kind of wavy shape. If the image gets drawn somewhere else on the frame the "waves" are somewhere else. The image is rotated 90° but I tested it without rotation and it was still there. Apart from this I do need rotated images.
Is there any way to fix this? I enabled text-antialiasing in the same Graphics2D object. Is there also some sort of anitaliasing for this?
Code
private void loadImage(String path, int field, int imageNumber) {
BufferedImage image;
image = new Resource().readImg(path);
images[field][imageNumber][0] = image;
images[field][imageNumber][1] = rotateClockwise90(image);
images[field][imageNumber][2] = rotateClockwise90(rotateClockwise90(image));
images[field][imageNumber][3] = rotateClockwise90(rotateClockwise90(rotateClockwise90(image)));
}
private BufferedImage rotateClockwise90(BufferedImage src) {
int width = src.getWidth();
int height = src.getHeight();
BufferedImage dest = new BufferedImage(height, width, src.getType());
Graphics2D graphics2D = dest.createGraphics();
graphics2D.translate((height - width) / 2, (height - width) / 2);
graphics2D.rotate(Math.PI / 2, height / 2, width / 2);
graphics2D.drawRenderedImage(src, null);
return dest;
}
When the program starts I load the image I rotate it in all 4 directions, so I don't have to do this over and over again while the program is running.
public BufferedImage getTile(int type, int part, int rotation) {
return images[type][part][rotation];
}
And then all I have to do is calling this get method and draw the image:
g2d.drawImage(tiles.getShipTile(type, part, rotation), x, y, null);

I actually found a way to avoid these weird pixels but this method makes the image a little bit blurry.
Instead of using
g2d.drawImage(img, x, y, width, height, null);
you can simply use
g2d.drawImage(img.getScaledInstance(width, height, Image.SCALE_AREA_AVERAGING), x, y, null);
which does basically the same thing but wehn I scale it up it uses this smooth making key.
I tried this and noticed that its not verry comfortable because it lags a lot.
So I just scale it up in the beginning when I also rotate the images.
As I said this method is a bit blurry but if there are no other ways avoiding this problem I have to get use of this. You almost can't see the blur, so this would be an option for me.

Related

Scale up small bitmap pixel perfectly

I am making a pixel game for android. I am using 32x32 images. To make the game appear the same regardless of screen-dimensions i dynamically scale up the images. My issue is that when scaling up, parts of the images does not keep their original color:
6 tiles, originally 32x32. As you can see, just before the black edges there is an unwanted shadowy line (presumably the average of the black and the redish color).
This is the code i use for scaling:
public abstract class Drawable {
protected int x;
protected int y;
protected Bitmap image;
Drawable(Bitmap image, int x, int y, float scale) {
this.x = x;
this.y = y;
this.image = Bitmap.createScaledBitmap(image, (int)(image.getWidth()*scale), (int)(image.getHeight()*scale), false);
}
abstract void draw(Canvas canvas);
}
As you can see i am not using the filter. This would make the edge-area more blurry. Are there another filter that in contrast to if i where to use true when scaling up actually perserves the crispness of the image?
Edit:
I now tried this approach instead:
scaledRect = new RectF(x, y, x+image.getWidth()*scale, y+image.getHeight()*scale);
paint = new Paint();
paint.setAntiAlias(false);
paint.setDither(false);
paint.setFilterBitmap(false);
And in the draw call:
canvas.drawBitmap(this.image, null, scaledRect, paint);
With no success...
Android processes bitmap scaling with bilinear interpolation algorithm by default. What you're trying to do is nearest neighbor interpolation.
Make a Paint, turn off dither and anti-alias, don't draw by createScaledBitmap and try this:
paint.setDither(false);
paint.setAntiAlias(false);
canvas.drawBitmap(bitmap, null, new RectF(left, top, width, height), paint);

Black area using java 2D graphics rotation?

I'll start of by showing examples of what's wrong then I'll explain how, and finally I'll ask my question.
This is the picture I want to rotate.
I am rotating it 90 degrees and 270 degrees, on multiple occasions and then combiningthose into a big buffered-image.
The code I am using to rotate a single bufferedImage is this:
public static BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.rotate(Math.toRadians(angle), w/2, h/2);
g.drawImage(img, null, 0, 0);
return dimg;
}
The out come of the rotation looks something like this.
The reason those black bars are these is because in the code you can clearly see I create a separate buffered-image which will be the final image.
Which uses the original width and hight, since the image is rotated the with and height switch so I compensated for this by changing BufferedImage dimg = new BufferedImage(w, h, img.getType()); to BufferedImage dimg = new BufferedImage(h, w, img.getType());.
I though it would be logical that this would solve my problem.
But I was wrong now the rotational outcome is this.
So from this point on is where I have no clue why it's doing this.
I might just be overlooking a tiny thing, or it's a common error even though I can't find any instance of this occurring.
So here is my question to you, why does it do this? And how do I fix this.
The image isn't square. If you rotate it by 90°, then you will create a gap that you need to fill.
Solutions:
Make sure the image is square
"Rotate" the size: When you rotate by 90° or 270°, you need to create a target image with swapped width and height (i.e. 200x100 -> 100x200)
Crop the image. Good in your case since scaling will make the arrow look bad but it might be out of center
Scale the image. If it's 609x579, scale it down to 579x579 (scaling down will usually look a little bit better).
Find the border color and fill the gap with the border color after the rotation
I figured it out.
The thing I was doing in the start was rotating the host image (dimg),
and then drawing the original image to it.
I could just as well have tried to fit a square in a circle my earlier rotation actually makes no sense at all.
So what I need to do is first create the host, draw the image to the host, the rotate the host and return it as the final image.
public static BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth();
int h = img.getHeight();
BufferedImage dimg = new BufferedImage(w, h, img.getType());
Graphics2D g = dimg.createGraphics();
g.drawImage(img, null, 0, 0); //Draw before rotating
g.rotate(Math.toRadians(angle), w/2, h/2); //Rotating after drawing
return dimg;
}
I hope this helps out some other people as well
if you want to use a similar code as first code
this may help ( if you remove the comments and debug lines (such as painting the background) it has only the translate((W-w)/2,(H-h)/2) line in addition )
// do not forget to import static java.lang.Math.*
public static BufferedImage rotate(BufferedImage img, int angle) {
int w = img.getWidth(null);
int h = img.getHeight(null);
double rad = toRadians(angle);
double eps = 1e-3;
int W=(int)(abs(cos(rad))*w+abs(sin(rad))*h-eps)+1;//W after rotation(calculated by using a little geometry )
int H=(int)(abs(sin(rad))*w+abs(cos(rad))*h-eps)+1;//H after rotation
//you may use max value ( diameter of the rectangle ) instead of dynamic value but in that case you must be careful of the black edges ( in this case red edges )
// if 90 is not a divisor of angle then you can't fit a rectangle with that angle in another one so the red edges are inevitable
// but with calculated W and H this edges are minimum
BufferedImage dimg = new BufferedImage(W,H, BufferedImage.TYPE_INT_RGB);// you can change it to any type you want it's just a sample
Graphics2D g = dimg.createGraphics();
g.setColor(Color.RED); // background color of red for displaying the red edges when image is not completely fit
g.fillRect(0, 0, W, H);
int x=(W-w)/2;
int y=(H-h)/2;
g.translate(x, y); // moving dimg center to img center ( this was what first code lack in )
g.rotate(-rad, w/2, h/2); // now rotating dimg around the center of img ( which is now same as center of dimg )
// we rotate dimg by -rad and draw img normally , it's like rotating img by rad instead of dimg by -rad
g.drawImage(img,null,0,0); // and drawing
return dimg;
}

How to scale text on a texture (quad object)?

I have found a good way to draw text in openGL by creating a Bitmap, drawing text on it, and making it into a texture.
I have screenCoordinates(320,~~480) and gameCoordinates(20,28), in which quads' dimensions are defined. Example constructor: GameObject(x, y, width, height, text).
To create the bitmap I create a Canvas with this size:
// get width and height in px (p is a Paint() object)
p.setTextSize(textSize * Main.getMainResources().getDisplayMetrics().density);
final int width = (int) p.measureText(text);
final int height = (int) (p.descent() + -p.ascent());
canvas.drawText(text, 0, -p.ascent(), p);
Create a bitmap of this, scaled to the next power of 2, and you have your texture-bitmap.
However the problem is that because the quad's dimensions are not adapted to the bitmap(text) dimensions, the text isn't very smooth. I have the possibility to change the quad's dimensions with quad.setDimensions(width, height), but what would be the size I'd have to give it, and also shouldn't I also have to worry about the textSize(sp) of the text i'm drawing?
This all is in 2D, and some simple math's might get involved, but how should I get the right dimensions?

Using JAI to rotate a grey scale image increases contrast

I am trying to use JAI to perform a rotate task on an image. I can get this working no problem. However, there is severe loss of midtones in the image. The image can be rotated in photoshop without this lack of contrast.
Please see the following 3 images stacked next to each other here, to see what I mean;
http://imgur.com/SYPhZ.jpg
The top image is the original, the middle is rotated in photoshop to prove that it can be done, and the bottom is from the result of my code.
To see the actual images, please see here;
Before rotate: http://imgur.com/eiAOO.jpg
After rotate : http://imgur.com/TTUKS.jpg
You can see the issue most clearly if you load the images in two different tabs, and flick between them.
In terms of code, I load the image as follows;
public void testIt() throws Exception {
File source = new File("c:\\STRIP.jpg");
FileInputStream fis = new FileInputStream(source);
BufferedImage sourceImage = ImageIO.read(fis);
fis.close();
BufferedImage rotatedImage = doRotate(sourceImage, 15);
FileOutputStream output = new FileOutputStream("c:\\STRIP_ROTATED.jpg");
ImageIO.write(rotatedImage, "JPEG", output);
}
and then here is the rotate function;
public BufferedImage doRotate(BufferedImage input, int angle) {
int width = input.getWidth();
int height = input.getHeight();
double radians = Math.toRadians(angle / 10.0);
// Rotate about the input image's centre
AffineTransform rotate = AffineTransform.getRotateInstance(radians, width / 2.0, height / 2.0);
Shape rect = new Rectangle(width, height);
// Work out how big the rotated image would be..
Rectangle bounds = rotate.createTransformedShape(rect).getBounds();
// Shift the rotated image into the centre of the new bounds
rotate.preConcatenate(
AffineTransform.getTranslateInstance((bounds.width - width) / 2.0, (bounds.height - height) / 2.0));
BufferedImage output = new BufferedImage(bounds.width, bounds.height, input.getType());
Graphics2D g2d = (Graphics2D) output.getGraphics();
// Fill the background with white
g2d.setColor(Color.WHITE);
g2d.fill(new Rectangle(width, height));
RenderingHints hints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
hints.put(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
g2d.drawImage(input, rotate, null);
return output;
}
This is apparently a bug in JAI that has existed for a while:
The earliest mention that I was able to find of this issue appears here. That original article points to an old jai-core issue here. Having read that resolution, it appears that there is a root bug that is still open and described here.
Whether or not all of that detective work is relevant to your application, it may be possible to construct a color space that is more tolerant than the default that JAI is using for your test code.
In the absolute worst case, you could write the pixel traversal yourself to create a rotated image. That isn't the optimal solution but I mention it for completeness if you absolutely need a solution to this problem today.

Java DirectColorModel vs. IndexColorModel when dealing with alphas

I have a BufferedImage that I get that has an IndexColorModel. I then wish to apply an AffineTransform with AffineTransformOP in order to create a transformed version of displayImage.
Here's a code snippet:
int type = isRGB() ? AffineTransformOp.TYPE_BILINEAR : AffineTransformOp.TYPE_NEAREST_NEIGHBOR;
AffineTransformOp op = new AffineTransformOp(atx, type);
displayImage = op.filter(displayImage, null);
I'm running this with many images, and from an earlier post I discovered that if I set the transform type to bilinear, then I was running out of memory because I was getting an image back with a DirectColorModel. However, this DirectColorModel had a correct alpha channel (when I drew the image an a green background after translating it, I could see green around the whole image). When I set the interpolation type to nearest neighbor, pixels above and to the left of the image appear black no matter what the background is. I'm assuming this means that the alpha is not getting set.
Can anyone tell me how to correctly set the alpha channel with an IndexColorModel, or change the AffineTransformOP parameters such that I get an IndexColorModel with the correct alpha?
Thanks!!
EDIT:
Here is the desired effect, with AffineTransformOp.TYPE_BINLINEAR:
Here is the effect that I'm seeing with AffineTransformOp.TYPE_NEAREST_NEIGHBOR:
The whole background is initially painted green for effect and in both cases the image is drawn at position (0, 0).
I'm not sure what effect you're trying to achieve, but I get expected results when I adjust the alpha before and/or after transforming. Typically, I start with setComposite(AlphaComposite.Clear) followed by fillRect(). If all else fails, you can filter() to a WritableRaster, and brute-force the result you want. Also, you might look at RenderingHints related to KEY_ALPHA_INTERPOLATION. Here's a toy I use to experiment with various combinations of alpha, mode and color.
I seem to recall seeing the effect your images show. Recalling that the AffineTransformOp may return an image with different co-ordinates, especially with rotation, I'm guessing the added "empty" space isn't getting initialized correctly. You can get a transparent border with the code below, which also makes rotation around the center somewhat more symmetric:
private BufferedImage getSquareImage(BufferedImage image) {
int w = image.getWidth();
int h = image.getHeight();
int max = Math.max(w, h);
BufferedImage square = new BufferedImage(
max, max, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = square.createGraphics();
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, max, max);
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(image, (max - w) / 2, (max - h) / 2, null);
g2d.dispose();
return square;
}

Categories

Resources