I'm trying to render text on top of a rounded rectangle, but I want the part of the rounded rectangle under the text to be cut out. Here's what I want it to look like:
The problem is that I can't find any easy way of doing this. I tried using clearRect, but that just creates a black rectangle, and I want to have an image underneath (for now it's just white).
I then had the idea that maybe I could just fill the area I want to remove of the rectangle with white, then filter out all the white pixels. This didn't work as well as I hoped, as there are still white pixels left over:
Here's the code I have currently:
public static void createRoundedRectImg(int width, int height)
{
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics g = img.getGraphics();
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
int padding = 50;
g.setColor(Color.BLUE);
g.drawRoundRect(padding, padding, width - (padding * 2), height - (padding * 2), 50, 50);
float textSize = 84f;
Font font = g.getFont().deriveFont(textSize).deriveFont(Font.BOLD);
g.setFont(font);
String text = "TEXT";
Rectangle2D stringBounds = g.getFontMetrics(font).getStringBounds(text, g);
int textWidth = (int) stringBounds.getWidth();
int textHeight = (int) (stringBounds.getHeight() + g.getFontMetrics(font).getDescent());
int textX = (width / 2) - (textWidth / 2);
int textY = g.getFontMetrics(font).getDescent() * 2 + padding;
//g.clearRect(textX, textY - textHeight, textWidth, textHeight);
g.setColor(Color.WHITE);
g.fillRect(textX, textY - textHeight, textWidth, textHeight);
g.setColor(Color.GREEN);
g.drawString(text, textX, textY);
for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
Color c = new Color(img.getRGB(x, y));
if (c.getRGB() == Color.WHITE.getRGB())
img.setRGB(x, y, new Color(0, 0, 0, 255).getRGB());
}
}
g.dispose();
}
Is there a simpler way of just clearing the part of the rounded rectangle under the text? After that is done I want to overlay the whole thing on top of an image, so I need the background to be transparent.
You could use the subtract method of the Area class to remove a rectangular section from a stroked RoundRectangle2D.
float strokeWidth = 1.5f;
RoundRectangle2D roundedRect = new RoundRectangle2D.Double(padding, padding, width - (padding * 2), height - (padding * 2), 50, 50);
Rectangle2D rectMask = new Rectangle2D.Double(textX, padding-strokeWidth, textWidth, 2*strokeWidth);
Stroke stroke = new BasicStroke(strokeWidth);
Area roundedRectArea = new Area(stroke.createStrokedShape(roundedRect));
roundedRectArea.subtract(new Area(rectMask));
g.setColor(Color.BLACK);
g.fill(roundedRectArea);
g.drawString(text, textX, textY);
Which produces:
Full code:
public static void createRoundedRectImg(int width, int height)
{
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = img.createGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
float textSize = 84f;
Font font = g.getFont().deriveFont(textSize).deriveFont(Font.BOLD);
g.setFont(font);
int padding = 50;
String text = "TEXT";
Rectangle2D stringBounds = g.getFontMetrics(font).getStringBounds(text, g);
int textWidth = (int) stringBounds.getWidth();
int textX = (width / 2) - (textWidth / 2);
int textY = g.getFontMetrics(font).getDescent() * 2 + padding;
float strokeWidth = 1.5f;
RoundRectangle2D roundedRect = new RoundRectangle2D.Double(padding, padding, width - (padding * 2),
height - (padding * 2), 50, 50);
Rectangle2D rectMask = new Rectangle2D.Double(textX, padding - strokeWidth, textWidth, 2 * strokeWidth);
Stroke stroke = new BasicStroke(strokeWidth);
Area roundedRectArea = new Area(stroke.createStrokedShape(roundedRect));
roundedRectArea.subtract(new Area(rectMask));
g.setColor(Color.BLACK);
g.fill(roundedRectArea);
g.drawString(text, textX, textY);
g.dispose();
try
{
ImageIO.write(img, "png", new File("round.png"));
} catch (IOException e)
{
e.printStackTrace();
}
}
Try this.
public static void createRoundedRectImg(int width, int height)
{
BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g = (Graphics2D)img.getGraphics();
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, width, height);
int padding = 50;
g.setComposite(AlphaComposite.Clear);
g.fillRoundRect(padding, padding, width - (padding * 2), height - (padding * 2), 50, 50);
g.setComposite(AlphaComposite.SrcOver);
g.setColor(Color.BLUE);
g.drawRoundRect(padding, padding, width - (padding * 2), height - (padding * 2), 50, 50);
float textSize = 84f;
Font font = g.getFont().deriveFont(textSize).deriveFont(Font.BOLD);
g.setFont(font);
String text = "TEXT";
Rectangle2D stringBounds = g.getFontMetrics(font).getStringBounds(text, g);
int textWidth = (int) stringBounds.getWidth();
int textHeight = (int) (stringBounds.getHeight() + g.getFontMetrics(font).getDescent());
int textX = (width / 2) - (textWidth / 2);
int textY = g.getFontMetrics(font).getDescent() * 2 + padding;
g.setColor(Color.WHITE);
g.fillRect(textX, textY - textHeight, textWidth, textHeight);
g.setColor(Color.GREEN);
g.drawString(text, textX, textY);
g.dispose();
}
You could just use a TitledBorder. Since the curve is proportional to the line size I created a RoundedBorder class with most of the code from the paintBorder() method in the API to allow the size of the arc corners to be specified. It is now a simple pixel amount for both width and height of the arc radius.
first, create a RoundedBorder instance. Try 30 for the arc radius.
then, using the BorderFactor, create a TitledBorder instance and pass the rounded instance as the first agument.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.border.Border;
import javax.swing.border.LineBorder;
import javax.swing.border.TitledBorder;
public class TitledBorderDemo extends JPanel {
JFrame frame = new JFrame();
public static void main(String[] args) {
SwingUtilities
.invokeLater(() -> new TitledBorderDemo().start());
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
public void start() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Border b = new RoundedBorder(Color.black, 2, 30);
Border titled = BorderFactory.createTitledBorder(b, "Text",
TitledBorder.CENTER, TitledBorder.DEFAULT_POSITION,
new Font("Arial", Font.BOLD, 48));
setBorder(titled);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class RoundedBorder extends LineBorder {
private int arc;
public RoundedBorder(Color color, int lineThickness, int arc) {
super(color, lineThickness);
this.arc = arc;
}
#Override
public void paintBorder(Component c, Graphics g, int x, int y,
int width, int height) {
if ((this.thickness > 0) && (g instanceof Graphics2D)) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Color oldColor = g2d.getColor();
g2d.setColor(this.lineColor);
Shape outer;
Shape inner;
int offs = this.thickness;
int size = offs + offs;
outer = new RoundRectangle2D.Float(x, y, width, height,
arc, arc);
inner = new RoundRectangle2D.Float(x + offs, y + offs,
width - size, height - size, arc, arc);
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(outer, false);
path.append(inner, false);
g2d.fill(path);
g2d.setColor(oldColor);
}
}
}
The above, when run, produces the following image.
I was wondering if there is a way to resize a BufferedImage without creating a new instance of another image. I am wondering this because I think that it will be inefficient to create a new image each time I want to resize a BufferedImage for my application. Here is some code I've seen that explains what I don't want:
public static BufferedImage resize(BufferedImage img, int newW, int newH) {
Image tmp = img.getScaledInstance(newW, newH, Image.SCALE_SMOOTH);
BufferedImage dimg = new BufferedImage(newW, newH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = dimg.createGraphics();
g2d.drawImage(tmp, 0, 0, null);
g2d.dispose();
return dimg;
}
public static BufferedImage scale(BufferedImage src, int w, int h)
{
BufferedImage img =
new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
int x, y;
int ww = src.getWidth();
int hh = src.getHeight();
int[] ys = new int[h];
for (y = 0; y < h; y++)
ys[y] = y * hh / h;
for (x = 0; x < w; x++) {
int newX = x * ww / w;
for (y = 0; y < h; y++) {
int col = src.getRGB(newX, ys[y]);
img.setRGB(x, y, col);
}
}
return img;
}
private BufferedImage resize(BufferedImage src, int targetSize) {
if (targetSize <= 0) {
return src; //this can't be resized
}
int targetWidth = targetSize;
int targetHeight = targetSize;
float ratio = ((float) src.getHeight() / (float) src.getWidth());
if (ratio <= 1) { //square or landscape-oriented image
targetHeight = (int) Math.ceil((float) targetWidth * ratio);
} else { //portrait image
targetWidth = Math.round((float) targetHeight / ratio);
}
BufferedImage bi = new BufferedImage(targetWidth, targetHeight, src.getTransparency() == Transparency.OPAQUE ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); //produces a balanced resizing (fast and decent quality)
g2d.drawImage(src, 0, 0, targetWidth, targetHeight, null);
g2d.dispose();
return bi;
}
Thank you for any responses!
For some reason painting an image does not appear to work on my Graphics object, please see the Triangle -> paintComponent() method.
Could I be missing something critical? Perhaps to do with not casting the Graphics object to Graphics2D? A transparency mask?
final JFrame jFrame = new JFrame();
jFrame.add(new Triangle());
jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jFrame.setSize(500,500);
jFrame.repaint();
jFrame.setVisible(true);
Triangle class:
public class Triangle extends JPanel {
private int x = 50;
private int y = 50;
private int width = 100;
private int length = 300;
private int direction = 0;
private BufferedImage renderedImage = null;
private Image getRenderedImage() {
if (renderedImage == null) {
BufferedImage image = new BufferedImage(100, 300, BufferedImage.TYPE_3BYTE_BGR);
drawGraphics(image.getGraphics());
}
return renderedImage;
}
private void drawGraphics(Graphics graphics) {
graphics.setColor(Color.BLUE);
// left
graphics.drawLine(
(int) Math.round(x - (width / 2.0)), y,
(int) x, y + length);
// right
graphics.drawLine(
(int) x, y + length,
(int) Math.round(x + (width / 2.0)), y);
// bottom
graphics.drawLine(
(int) Math.round(x - (width / 2.0)), y,
(int) Math.round(x + (width / 2.0)), y);
}
protected void paintComponent(Graphics graphics) {
// this works
//drawGraphics(graphics);
// this doesn't work
graphics.drawImage(getRenderedImage(), 0, 0, null);
}
}
I need to put a random image inside a screen with given resolution (640x480, 1280x720, etc). I finished it in Java. In Android do not support the BufferedImage and Graphics2D, I wonder if there is a way to replace this code from Java to Android. Here is my code from Java:
public BufferedImage resizeImage(BufferedImage originalImage, int type){
BufferedImage resizedImage = new BufferedImage(screenWidth, screenHeight, type);
Graphics2D g = resizedImage.createGraphics();
int imgWidth = originalImage.getWidth();
int imgHeight = originalImage.getHeight();
int newImgWidth = 0;
int newImgHeight = 0;
int X = 0;
int Y = 0;
if (imgWidth > screenWidth){
// scale width to fit
newImgWidth = screenWidth;
//scale height to maintain aspect ratio
newImgHeight = (newImgWidth * imgHeight) / imgWidth;
}
if (newImgHeight > screenHeight) {
//scale height to fit instead
newImgHeight = screenHeight;
//scale width to maintain aspect ratio
newImgWidth = (newImgHeight * imgWidth) / imgHeight;
}
if (imgWidth < screenWidth && imgHeight < screenHeight) {
X = screenWidth/2 - imgWidth/2;
Y = screenHeight/2 - imgHeight/2;
g.drawImage(originalImage, X, Y, imgWidth, imgHeight, null);
g.dispose();
return resizedImage;
}
X = screenWidth/2 - newImgWidth/2;
Y = screenHeight/2 - newImgHeight/2;
g.drawImage(originalImage, X, Y, newImgWidth, newImgHeight, null);
g.dispose();
return resizedImage;
}
Thank you in advance!
I am using following code to rotate an given image.
public int getImageWidth(BufferedImage img) {
if (rotate == Rotate.UPSIDE_DOWN || rotate == Rotate.ABOUT_CENTER)
return img.getWidth();
else
return img.getHeight();
}
public int getImageHeight(BufferedImage img) {
if (rotate == Rotate.UPSIDE_DOWN || rotate == Rotate.ABOUT_CENTER)
return img.getHeight();
else
return img.getWidth();
}
This is the Rotation enums
public enum Rotate {
DOWN, UP, UPSIDE_DOWN, ABOUT_CENTER;
}
Actual rotation method
public BufferedImage rotateImage(BufferedImage source, int x, int y,
float orientation) throws Exception {
int newWidth = getImageWidth(source);
int newHeight = getImageHeight(source);
int cWidth = newWidth / 2;
int cHeight = newHeight / 2;
int imgType = source.getType() == 0 ? 5 : source.getType();
BufferedImage result = new BufferedImage(getImageWidth(source),
getImageHeight(source),imgType);
Graphics2D g2 = result.createGraphics();
if (rotate == Rotate.DOWN) {
g2.translate(x + cHeight, y + cWidth);
g2.rotate(Math.toRadians(90));
g2.drawImage(source, 0, 0, newWidth, newHeight, null);
} else if (rotate == Rotate.UP) {
g2.translate(x + cHeight, y + cWidth);
g2.rotate(Math.toRadians(-90));
g2.drawImage(source, 0, 0, newWidth, newHeight, null);
} else if (rotate == Rotate.UPSIDE_DOWN) {
g2.translate(x + cWidth, y + cHeight);
g2.rotate(Math.toRadians(180));
g2.drawImage(source, 0, 0, newWidth, newHeight, null);
} else if (rotate == Rotate.ABOUT_CENTER) {
Rectangle r = new Rectangle(x, y, newWidth, newHeight);
g2.setClip(r);
AffineTransform original = g2.getTransform();
AffineTransform at = new AffineTransform();
at.concatenate(original);
at.rotate(orientation, x + cWidth, y + cHeight);
g2.setTransform(at);
g2.drawImage(source, 0, 0, newWidth, newHeight, null);
g2.setTransform(original);
}
g2.dispose();
g2 = null;
return result;
}
The client code
// rotate derived & filtered image to 90 degree
// using Affine transform
setRotate(Rotate.UP);
BufferedImage rSubImage = rotateImage(fSubImage, 0, 0, -90);
Now the following is an source image,
When i rotate this image using above code , the result is very strange
What did i do wrong ?
also the quality is lost after rotation , please notice it.
I think the solution is to use AffineTranform, translating the Image to reamin in the center. I modified your code and tested it, for me it works:
public BufferedImage rotateImage(BufferedImage source, int x, int y,
float orientation) throws Exception {
int newWidth = getImageWidth(source);
int newHeight = getImageHeight(source);
int imgType = source.getType() == 0 ? 5 : source.getType();
BufferedImage result = new BufferedImage(getImageWidth(source),
getImageHeight(source), imgType);
if (rotate == Rotate.DOWN) {
AffineTransform tranform = new AffineTransform();
tranform.translate(newWidth / 2, newHeight / 2);
tranform.rotate(Math.toRadians(90));
tranform.translate(-source.getWidth()/2, -source.getHeight()/2);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(source, tranform, null);
} else if (rotate == Rotate.UP) {
AffineTransform tranform = new AffineTransform();
tranform.translate(newWidth / 2, newHeight / 2);
tranform.rotate(Math.toRadians(-90));
tranform.translate(-source.getWidth()/2, -source.getHeight()/2);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(source, tranform, null);
} else if (rotate == Rotate.UPSIDE_DOWN) {
AffineTransform tranform = new AffineTransform();
tranform.translate(newWidth / 2, newHeight / 2);
tranform.rotate(Math.toRadians(180));
tranform.translate(-source.getWidth()/2, -source.getHeight()/2);
Graphics2D g2d = result.createGraphics();
g2d.drawImage(source, tranform, null);
} else if (rotate == Rotate.ABOUT_CENTER) {
//......
}
return result;
}
Hope it helps!