Is there any way to bend a BufferedImage in Java?
I thought that if I crop the image into smaller pieces and rotate them then I would essentially bend the image, but it doesn't seem to work.
Here is the method I created:
/**
* This is a recursive method that will accept an image the point where the bending will start and the point where the bending will end, as well as the angle of bending
*
* #param original:the original image
* #param startingPoint: the point where the bending should start
* #param endingPoint: the point where the bending should end
* #param radiands: the angle
* #return the bent image
*/
public static BufferedImage getBentImage(BufferedImage original, int startingPoint, int endingPoint, double radians) {
if (startingPoint >= endingPoint)
return original;
int type = BufferedImage.TYPE_INT_ARGB;
int width = original.getWidth();
int height = original.getHeight();
BufferedImage crop = original.getSubimage(0, 0, startingPoint, height);
BufferedImage crop0 = original.getSubimage(startingPoint, 0, width - startingPoint, height);
BufferedImage bendCrop = new BufferedImage(width, height, type);
BufferedImage image = new BufferedImage(width, height, type);
AffineTransform rotation = new AffineTransform();
rotation.translate(0, 0);
rotation.rotate(radians);
Graphics2D g = bendCrop.createGraphics();
g.drawImage(crop0, rotation, null);
g.dispose();
g = image.createGraphics();
g.drawImage(crop, 0, 0, null);
g.drawImage(bendCrop, startingPoint, 0, null);
g.dispose();
return getBentImage(image, startingPoint + 1, endingPoint, radians);
}
This is the original Image:
And this is the result of this getBentImage(image, 200, 220, Math.toRadians(1)):
I was expecting something closer to:
Any ideas on how to actually implement a getBentImage() method?
As suggested in the comments, a simple approach is to divide the image into 3 parts:
Identical to the original.
Bent according to the bending transformation.
Constant diagonal continuation.
Here is a quick and a bit messy example that shows the original shape and the resulting shape below it. I just used a label icon for the images instead of doing custom painting. (Also I didn't adhere to the Java naming conventions with final variables because it's math and not typical coding.)
Since there are quite a few variables in the calculation code, I added a sketch at the end that shows what the variables represent.
public class Main extends JFrame {
static BufferedImage image;
public static void main(String[] args) {
try {
image = ImageIO.read(ClassLoader.getSystemResource("img.png"));
} catch (IOException e) {
e.printStackTrace();
}
new Main();
}
public Main() {
getContentPane().setLayout(new BorderLayout(5, 10));
BufferedImage img2 = transform(15, 100, 300);
JLabel label1 = new JLabel(new ImageIcon(image));
label1.setHorizontalAlignment(JLabel.LEFT);
label1.setOpaque(true);
label1.setBackground(Color.YELLOW);
add(label1, BorderLayout.NORTH);
JLabel label2 = new JLabel(new ImageIcon(img2));
label2.setHorizontalAlignment(JLabel.LEFT);
label2.setOpaque(true);
label2.setBackground(Color.CYAN);
add(label2);
pack();
setDefaultCloseOperation(EXIT_ON_CLOSE);
setVisible(true);
}
static BufferedImage transform(int t, int x1, int x2) {
final double TH = Math.toRadians(t);
final int D = x2 - x1;
final int W = image.getWidth();
final int H = image.getHeight();
final int dD = (int) (D / (2 * TH) * Math.sin(2 * TH));
final int dH = (int) (D / TH * Math.pow(Math.sin(TH), 2));
final int pH = (int) ((W - x2) * Math.tan(2 * TH));
final int width = W - (D - dD);
final int height = (int) (H + dH + pH);
System.out.println(W + " " + H + " -> " + width + " " + height);
BufferedImage img2 = new BufferedImage(width, height, image.getType());
for (int x = 0; x < x1; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
img2.setRGB(x, y, rgb);
}
}
for (int x = x1; x < x2; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dx = (int) (D / (2 * TH) * Math.sin(2 * (x-x1) * TH / D));
int dy = (int) (D / TH * Math.pow(Math.sin((x-x1) * TH / D), 2));
img2.setRGB(x1 + dx, y + dy, rgb);
}
}
for (int x = x2; x < W; x++) {
for (int y = 0; y < H; y++) {
int rgb = image.getRGB(x, y);
int dp = (int) ((x - x2) * Math.tan(2 * TH));
img2.setRGB(x - (D - dD), y + dH + dp, rgb);
}
}
return img2;
}
}
As for the calculations, I'll leave it for you as homework; it's just geometry/trigonometry which belongs on Math.SE more than on SO. If you can't figure it out I'll give you a direction.
Note that this method might not be fast at all and could certainly be optimized, I'll leave that to you also. Oh, and rounding doubles to ints carelessly, so the result is not pixel-perfect.
I dont know what you mean by bending but essentially you have a rectangle and you break one piece of it and rotate it:
so the algorithm is as follows:
rotate line(x, 0, width-1, 0)
rotate line(x, height-1, width-1, height-1)
connect the pieces
So essentially you are looking for rotate line.
Related
I am trying to draw a grid over an image using the underlaying colours of the image to fill the circles. But some pixels are not getting the correct colour.
In this case the circles are drawn white but they should not be drawn white...
See my code below:
import processing.pdf.*;
PImage img;
color background = color(255);
void setup() {
size(1038, 525);
ellipseMode(CORNER);
noStroke();
//img = loadImage("noise2.jpg");
//img = loadImage("air.png");
img = loadImage("accidents.png");
image(img, 0, 0, width, height);
visualGrid(20, 0.4, false);
}
//void draw() {
// fill(noise.get(mouseX, mouseY));
// rect(width - 100, height - 100, 100, 100);
//}
void visualGrid(int circleSize, float fillSmoothing, boolean debug) {
float halfCircle = circleSize / 2.0;
int amountX = floor(width / circleSize);
int amountY = floor(height / circleSize);
amountY += floor(amountY * 0.1);
float offsetX = (width - (amountX * circleSize + halfCircle)) / 2 + halfCircle;
float offsetY = (height - amountY * circleSize + amountY * circleSize * 0.1) / 2;
for (int x = 0; x < amountX; x++) {
for (int y = 0; y < amountY; y++) {
float styledOffsetX = (y % 2 == 0) ? offsetX - halfCircle : offsetX;
float xpos = x * circleSize + styledOffsetX;
float ypos = circleSize * 0.9 * y + offsetY;
int sectionSize = round(circleSize * fillSmoothing);
float sectionOffset = (circleSize - sectionSize) / 2;
color c = getAvgImgColor(img.get(round(xpos + sectionOffset), round(ypos + sectionOffset), sectionSize, sectionSize));
//fill(noise.get(round(xpos), round(ypos)));
if(debug) {
stroke(255, 0, 255);
strokeWeight(1);
}
fill(c);
ellipse(xpos, ypos, circleSize, circleSize);
if(debug) {
noStroke();
fill(255, 0, 255);
rect(round(xpos + sectionOffset), round(ypos + sectionOffset), sectionSize, sectionSize);
}
}
}
}
color getAvgImgColor(PImage section) {
section.loadPixels();
int avgR = 0, avgG = 0, avgB = 0;
int totalPixels = section.pixels.length;
for (int i = 0; i < totalPixels; i++) {
color pixel = section.pixels[i];
//if(pixel == background) continue;
avgR += red(pixel);
avgG += green(pixel);
avgB += blue(pixel);
}
return color(
round(avgR / totalPixels),
round(avgG / totalPixels),
round(avgB / totalPixels)
);
}
This is what i get when drawing my grid on the image in question:
As you can see in the circled area not all circles should be filled with white... This happens in more places than just the circled are just compare this image with the one below.
I will upload the original image below, so you can use it to debug.
There's a mismatch between the dimensions of your sketch (1038 x 525) and the image you're sampling (2076 x 1048) which might explain the misalignment.
If size(2076, 1048) isn't an option try resizing the image once it's loaded in setup():
...
img = loadImage("accidents.png");
img.resize(width, height);
...
I have a static number of slots for the number in the wheel game. Start from number 00 to number 1 (clockwise). The position count is 0 to 37 in my Collection array. Currently, I successful place the ball position in the middle of the number with 1 degree. But I unable to calculate a correct value for each of the position when I tried to pass in the number position.
I already tried messing with the calculation, it either the ball sit in the middle between the number or out of range!
public class WheelDisplay extends JPanel implements ConstantVariable {
private Image image;
private ImageObserver imageObserver;
private float degrees = 1;
private int post = 0;
public WheelDisplay() {
ImageIcon icon = new ImageIcon(IMAGE_LOCATION);
image = icon.getImage();
imageObserver = icon.getImageObserver();
}
/** Credit to stackoverflow forum : https://stackoverflow.com/questions/25923480/simple-circle-rotation-simulate-motion **/
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int circleDiameter = Math.min(getWidth(), getHeight());
double circleRadius = circleDiameter / 2;
int offSetX = (getWidth() - circleDiameter) / 2;
int offSetY = (getHeight() - circleDiameter) / 2;
g2d.drawImage(image, offSetX, offSetY, circleDiameter, circleDiameter, imageObserver);
g2d.setColor(Color.BLUE);
int ballDiameter = (int) (circleDiameter * 0.02);
int ballRadius = ballDiameter / 2;
Point p = getPointOnCircle(this.degrees, circleRadius * 0.9, circleRadius);
int valueX = offSetX + p.x - ballRadius;
int valueY = offSetY + p.y - ballRadius;
g2d.fillOval(valueX, valueY, ballDiameter, ballDiameter);
g2d.dispose();
}
private Point getPointOnCircle(float degress, double circleRadius, double innerCircleRadius) {
//The calculation that allow ball sit in the middle of each number when spin
double rads = ((Math.PI * degress) / 38);
// Calculate the outter point of the line
int xCordinate = Math.round((float) (innerCircleRadius + Math.cos(rads) * circleRadius));
int yCordinate = Math.round((float) (innerCircleRadius + Math.sin(rads) * circleRadius));
return new Point(xCordinate, yCordinate);
}
public void setDegree(int x) {
this.degrees += 2;
this.post = x; // The number of position coming from Collection (0-37)
}
}
The ball supposes to sit in the middle of each 38 numbers in the wheel image.
Hope that someone can point out which part I'm doing wrong!
The image that I'm using is this.
I am implementing captcha feature in our project. Its basically Tapestry framework application. I am generating random alfa-numeric string and convert it to image and display it in web page.
Now what i need is, i want to add random noise like dots lines etc to make image with text unclear. How to proceed please help.
keeping the code for reference .
-- This method gives text captcha.
`
public String generateCaptcha() {
Random random = new Random();
int min = 4; // Inclusive
int max = 9; // Exclusive
int length = random.nextInt(max-min) + min;
StringBuilder captchaStringBuffer = new StringBuilder();
for (int i = 0; i < length; i++) {
int captchaNumber = Math.abs(random.nextInt()) % 60;
int charNumber = 0;
if (captchaNumber < 26) {
charNumber = 65 + captchaNumber;
}
else if (captchaNumber < 52){
charNumber = 97 + (captchaNumber - 26);
}
else {
charNumber = 48 + (captchaNumber - 52);
}
captchaStringBuffer.append((char)charNumber);
}
return captchaStringBuffer.toString();
}
`
-- This method converts generated captcha to Image with out any noise.
`
public void textToImage(String displayCode){
String text = displayCode;
BufferedImage img = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = img.createGraphics();
Font font = new Font("Arial", Font.PLAIN, 48);
g2d.setFont(font);
FontMetrics fm = g2d.getFontMetrics();
int width = fm.stringWidth(text);
int height = fm.getHeight();
g2d.dispose();
img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
g2d = img.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION,
RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_OFF);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING,
RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING,
RenderingHints.VALUE_DITHER_DISABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS,
RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_DEFAULT);
g2d.setFont(font);
fm = g2d.getFontMetrics();
g2d.setColor(Color.BLACK);
g2d.drawString(text, 0, fm.getAscent());
g2d.dispose();
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(img, "png", baos);
byte[] res=baos.toByteArray();
setBinaryImage("data:image/png;base64,"+Base64.encode(res));
} catch (IOException e) {
}
}
`
I am newbie so please tell in clear what ever you say.
Thanks in Advance :-)
When trying to obstruct the text that is being displayed, you can use:
Dots/Circles, spread randomly over the image with varying size and color
Lines, coming from a random point on the edge of the image to another point on the edge of the image. These can also vary in color and thickness.
As far as i know, you can use g2d.drawLine(x1, y1, x2, y2) to draw a line. Since you want it to go from the edge to another point on the edge, you have to limit your random-point-generation. You can use this approach:
public Point pointOnEdge(int width, int height) {
int side = (int) (Math.random() * 3); //0=top, 1=bot, 2=left, 3=right
int x = 0;
int y = 0;
switch(side) {
case 0:
//when on top, y is at the top of the image (0) and x is something in [0, width]
y = 0;
x = (int) (Math.random() * width);
break;
case 1:
//when on bottom, y is at the bottom of the image (image height) and x is something in [0, width]
y = height;
x = (int) (Math.random() * width);
case 2:
//when on left, x is at the left side (0) of the image and y is something in [0, height]
y = (int) (Math.random() * height);
x = 0;
break;
case 3:
//when on left, x is at the left side (0) of the image and y is something in [0, height]
y = (int) (Math.random() * height);
x = width;
break;
}
return new Point(x, y);
}
If you create two Points like that, and connect them with a line, then you have a pretty simple way of obstructing your Image Partially, thus distorting it.
Now to the Circles:
public void drawCircles(Graphics2D g2d, int width, int height) {
//draw 10 of them
for(int i = 0; i < 10; i++) {
//select a random size
int x = 10 + (int) (Math.random() * 10);
//draw circle at random position with the created size
g2d.fillOval((int) (Math.random() * width), (int) (Math.random() * height), x, x);
}
}
And like that you are now able to distort your image to make it hard to read.
I hope you have enough common code understanding to know where to put these function calls. If not, I can add it if nescessary.
EDIT 1
If you want a dotted Background for your Captcha, you can use this code before rendering the String or anything else:
boolean r = false;
boolean g = false;
for(int y = 0; y < height; y++) {
r = !r;
g = r;
for(int x = 0; x < width; x++) {
g = !g;
if(g) {
g2d.setColor(Color.GRAY);
}else {
g2d.setColor(Color.WHITE);
}
g2d.drawLine(x, y, x, y);
}
}
EDIT 2
I would recommend, that you use a different font. Then you dont have to do any streching. Good fonts for that are e.g. Gigi. You could also select a font randomly by using GraphicsEnvironment.getLocalGraphicsEnvironment().getAvailableFontFamilyNames() which returns all Fonts that Java has.
I am trying to properly rotate a sword in my 2D game. I have a sword image file, and I wish to rotate the image at the player's location. I tried using Graphics2D and AffineTransform, but the problem is that the player moves on a different coordinate plane, the Screen class, and the Graphics uses the literal location of the pixels on the JFrame. So, I realized that I need to render the sword by rotating the image itself, and then saving it into a pixel array for my screen class to render. However, I don't know how to do this. Here is the code for my screen rendering method:
public void render(double d, double yOffset2, BufferedImage image, int colour,
int mirrorDir, double scale, SpriteSheet sheet) {
d -= xOffset;
yOffset2 -= yOffset;
boolean mirrorX = (mirrorDir & BIT_MIRROR_X) > 0;
boolean mirrorY = (mirrorDir & BIT_MIRROR_Y) > 0;
double scaleMap = scale - 1;
for (int y = 0; y < image.getHeight(); y++) {
int ySheet = y;
if (mirrorY)
ySheet = image.getHeight() - 1 - y;
int yPixel = (int) (y + yOffset2 + (y * scaleMap) - ((scaleMap * 8) / 2));
for (int x = 0; x < image.getWidth(); x++) {
int xPixel = (int) (x + d + (x * scaleMap) - ((scaleMap * 8) / 2));
int xSheet = x;
if (mirrorX)
xSheet = image.getWidth() - 1 - x;
int col = (colour >> (sheet.pixels[xSheet + ySheet
* sheet.width])) & 255;
if (col < 255) {
for (int yScale = 0; yScale < scale; yScale++) {
if (yPixel + yScale < 0 || yPixel + yScale >= height)
continue;
for (int xScale = 0; xScale < scale; xScale++) {
if (x + d < 0 || x + d >= width)
continue;
pixels[(xPixel + xScale) + (yPixel + yScale)
* width] = col;
}
}
}
}
}
}
Here is one of my poor attempts to call the render method from the Sword Class:
public void render(Screen screen) {
AffineTransform at = new AffineTransform();
at.rotate(1, image.getWidth() / 2, image.getHeight() / 2);
AffineTransformOp op = new AffineTransformOp(at,
AffineTransformOp.TYPE_BILINEAR);
image = op.filter(image, null);
screen.render(this.x, this.y, image, SwordColor, 1, 1.5, sheet);
hitBox.setLocation((int) this.x, (int) this.y);
for (Entity entity : level.getEntities()) {
if (entity instanceof Mob) {
if (hitBox.intersects(((Mob) entity).hitBox)) {
// ((Mob) entity).health--;
}
}
}
}
Thank you for any help you can provide, and please feel free to tell me if theres a better way to do this.
You can rotate() the image around an anchor point, also seen here in a Graphics2D context. The method concatenates translate(), rotate() and translate() operations, also seen here as explicit transformations.
Addendum: It rotates the image, but how do I save the pixels of the image as an array?
Once you filter() the image, use one of the ImageIO.write() methods to save the resulting RenderedImage, for example.
I am trying to implement position-sensitive zooming inside a JScrollPane. The JScrollPane contains a component with a customized paint that will draw itself inside whatever space it is allocated - so zooming is as easy as using a MouseWheelListener that resizes the inner component as required.
But I also want zooming into (or out of) a point to keep that point as central as possible within the resulting zoomed-in (or -out) view (this is what I refer to as 'position-sensitive' zooming), similar to how zooming works in google maps. I am sure this has been done many times before - does anybody know the "right" way to do it under Java Swing?. Would it be better to play with Graphic2D's transformations instead of using JScrollPanes?
Sample code follows:
package test;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class FPanel extends javax.swing.JPanel {
private Dimension preferredSize = new Dimension(400, 400);
private Rectangle2D[] rects = new Rectangle2D[50];
public static void main(String[] args) {
JFrame jf = new JFrame("test");
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(400, 400);
jf.add(new JScrollPane(new FPanel()));
jf.setVisible(true);
}
public FPanel() {
// generate rectangles with pseudo-random coords
for (int i=0; i<rects.length; i++) {
rects[i] = new Rectangle2D.Double(
Math.random()*.8, Math.random()*.8,
Math.random()*.2, Math.random()*.2);
}
// mouse listener to detect scrollwheel events
addMouseWheelListener(new MouseWheelListener() {
public void mouseWheelMoved(MouseWheelEvent e) {
updatePreferredSize(e.getWheelRotation(), e.getPoint());
}
});
}
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
getParent().doLayout();
// Question: how do I keep 'p' centered in the resulting view?
}
public Dimension getPreferredSize() {
return preferredSize;
}
private Rectangle2D r = new Rectangle2D.Float();
public void paint(Graphics g) {
super.paint(g);
g.setColor(Color.red);
int w = getWidth();
int h = getHeight();
for (Rectangle2D rect : rects) {
r.setRect(rect.getX() * w, rect.getY() * h,
rect.getWidth() * w, rect.getHeight() * h);
((Graphics2D)g).draw(r);
}
}
}
Tested this, seems to work...
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
int offX = (int)(p.x * d) - p.x;
int offY = (int)(p.y * d) - p.y;
setLocation(getLocation().x-offX,getLocation().y-offY);
getParent().doLayout();
}
Update
Here is an explanation: the point p is the location of the mouse relative to the FPanel. Since you are scaling the size of the panel, the location of p (relative to the size of the panel) will scale by the same factor. By subtracting the current location from the scaled location, you get how much the point 'shifts' when the panel is resized. Then it is simply a matter of shifting the panel location in the scroll pane by the same amount in the opposite direction to put p back under the mouse cursor.
Here's a minor refactoring of #Kevin K's solution:
private void updatePreferredSize(int wheelRotation, Point stablePoint) {
double scaleFactor = findScaleFactor(wheelRotation);
scaleBy(scaleFactor);
Point offset = findOffset(stablePoint, scaleFactor);
offsetBy(offset);
getParent().doLayout();
}
private double findScaleFactor(int wheelRotation) {
double d = wheelRotation * 1.08;
return (d > 0) ? 1 / d : -d;
}
private void scaleBy(double scaleFactor) {
int w = (int) (getWidth() * scaleFactor);
int h = (int) (getHeight() * scaleFactor);
preferredSize.setSize(w, h);
}
private Point findOffset(Point stablePoint, double scaleFactor) {
int x = (int) (stablePoint.x * scaleFactor) - stablePoint.x;
int y = (int) (stablePoint.y * scaleFactor) - stablePoint.y;
return new Point(x, y);
}
private void offsetBy(Point offset) {
Point location = getLocation();
setLocation(location.x - offset.x, location.y - offset.y);
}
Your MouseWheelListener also has to locate the cursor, move it to the center of the JScrollPane and adjust the xmin/ymin and xmax/ymax of the content to be viewed.
I think smt like this should be working...
private void updatePreferredSize(int n, Point p) {
double d = (double) n * 1.08;
d = (n > 0) ? 1 / d : -d;
int w = (int) (getWidth() * d);
int h = (int) (getHeight() * d);
preferredSize.setSize(w, h);
// Question: how do I keep 'p' centered in the resulting view?
int parentWdt = this.getParent( ).getWidth( ) ;
int parentHgt = this.getParent( ).getHeight( ) ;
int newLeft = p.getLocation( ).x - ( p.x - ( parentWdt / 2 ) ) ;
int newTop = p.getLocation( ).y - ( p.y - ( parentHgt / 2 ) ) ;
this.setLocation( newLeft, newTop ) ;
getParent().doLayout();
}
EDIT:
Changed a couple things.