I have a region in my JPanel bounded by the point (0,0) and (width,height). It is a square.
I have a word
String s;
I'd like to find the maximum font size that I can use for s. Now, I know there is a way to do it using FontMetrics and making a for loop to keep increasing the size of the font until it doesn't fit inside the region. But this is SO inefficient and there must be a way to compute the font size of a given font type, such as "Courier" that will fit in this region.
Example of BAD way:
Font f = new Font("Courier", Font.PLAIN, 1);
FontMetrics fm = this.getFontMetrics(f); //this is a JPanel
do {
f = new Font("Courier", Font.PLAIN, f.getSize()+1);
fm = this.getFontMetrics(f);
while(fm.stringWidth(s) < width && fm.getHeight() < height);
I had the same problem and found a solution that is a little bit optimized, compared to just iterating over all font sizes. I try to converge towards the optimal font size by adjusting diffs that I either add or subtract until I find a diff font size below 1.
Graphics2D graphics = image.createGraphics();
graphics.setColor(Color.black);
if (subtitleFont == null) {
//create rectangle first (from a separate library
int[] rect = matrix.getEnclosingRectangle();
// define the maximum rect for the text
Rectangle2D maxRect = new Rectangle2D.Float(0, 0, w - 7, h - rect[0] - rect[3] - 10);
subtitleX = 0;
subtitleY = 0;
// starting with a very big font due to a high res image
float size = 80f * 4f;
// starting with a diff half the size of the font
float diff = size / 2;
subtitleFont = graphics.getFont().deriveFont(Font.BOLD).deriveFont(size);
FontMetrics fontMetrics = graphics.getFontMetrics(subtitleFont);
Rectangle2D stringBounds = null;
while (Math.abs(diff) > 1) {
subtitleFont = subtitleFont.deriveFont(size);
graphics.setFont(subtitleFont);
fontMetrics = graphics.getFontMetrics(subtitleFont);
stringBounds = fontMetrics.getStringBounds(options.subtitle, graphics);
stringBounds = new Rectangle2D.Float(0f, 0f, (float) (stringBounds.getX() + stringBounds.getWidth()), (float) ( stringBounds.getHeight()));
if (maxRect.contains(stringBounds)) {
if (0 < diff) {
diff = Math.abs(diff);
} else if (diff < 0) {
diff = Math.abs(diff) / 2;
}
} else {
if (0 < diff) {
diff = - Math.abs(diff) / 2;
} else if (diff < 0) {
if (size <= Math.abs(diff)) {
diff = - Math.abs(diff) / 2;
} else {
diff = - Math.abs(diff);
}
}
}
size += diff;
}
subtitleX = (int) ((w/2) - (stringBounds.getWidth() / 2));
subtitleY = (int) (h - maxRect.getHeight() + fontMetrics.getAscent());
}
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
graphics.drawString(options.subtitle, subtitleX, subtitleY);
I have tried that with different resolutions of the image and the sizes of the font. It takes 10 to 12 iterations until a font is found that will fit the max rectangle. I hope it will be helpful to somebody.
yes, I met the same problem as well, and I know what's your meaning of 'inefficient'.
I tried a solution that measuring the unit count for a string by font size: 1 (default).
for example:
String line = "This is a test";
Font font = Font.createFont(Font.TRUETYPE_FONT, new File("/var/fonts/times.ttf"));
FontRenderContext ctx = new FontRenderContext(font.getTransform(), false, false);
Rectangle2D rect = font.getStringBounds(line, ctx);
BigDecimal widthUnits = BigDecimal.valueof(rect.getWidth());
then, you get the widthUnits of line by font size 1. if it divided by region width and reserve the integer part (round down), you'll the the max font size in horizontally.
int maxSizeHor = BigDecimal.valueOf(width)
.divide(widthUnits, 1, BigDecimal.ROUND_DOWN)
.intValue();
But, it's not enougth. you should also calculate the max font size in vertically. Fortunately, each line with the same font style and font size has the same height. if there are multiple lines, you can use:
lines.length * each height in font size 1.
such as the following:
String[] lines = new String{"This is a test", "Hello World!"};
BigDecimal heightUnits = BigDecimal.valueOf(rect.getHeight())
.multiply(BigDecimal.valueOf(lines.length));
int maxSizeVer = BigDecimal.valueOf(height)
.divide(heightUnits
.multiply(BigDecimal.valueOf(lines.length)),
1,BigDecimal.ROUND_DOWN)
.intValue();
Finally, compare and fetch the minium value as the maximum font size:
Math.min(maxSizeHor, maxSizeVer)
But, I met another problem: the single char 'i' as input string at font size:1, you will get the the width units: 0.0 . However, in actually, it won't be zero though it's a very tiny value. so I set 1000 as default font size, and divide 1000 after each progress. then I get the non-zero value.
Related
I have the following method that "resizes" all the pages of a document to A4 page dimensions:
for (PdfDocument doc : pdfDocuments) {
int n = doc.getNumberOfPages();
for (int i = 1; i <= n; i++) {
PdfPage page = doc.getPage(i);
Rectangle media = page.getCropBox();
if (media == null) {
media = page.getMediaBox();
}
Rectangle crop = new Rectangle(0, 0, 210, 297);
page.setMediaBox(crop);
page.setCropBox(crop);
// The content, placed on a content stream before, will be rendered before the other content
// and, therefore, could be understood as a background (bottom "layer")
new PdfCanvas(page.newContentStreamBefore(),
page.getResources(), doc).writeLiteral("\nq 0.5 0 0 0.5 0 0 cm\nq\n");
// The content, placed on a content stream after, will be rendered after the other content
// and, therefore, could be understood as a foreground (top "layer")
new PdfCanvas(page.newContentStreamAfter(),
page.getResources(), doc).writeLiteral("\nQ\nQ\n");
}
}
However , this is not working as expected , the pages are being transformed to A4 (297x210) BUT the content is not being fitted inside (scaled) , the content appears cutted because the original pages are larger than 297X210 . How can I fix this ?
In a comment you clarified
I want the bounding box of the former content be scaled and a margin be added in the target
So, we first have to determine the bounding box of the original page content. This can be done using the MarginFinder class from this answer. Beware: That class determines the bounding box of all content, even if it is merely a white rectangle visually not distinct from no content or something formerly outside the crop box... If your use case requires it, you may have to extend that class to take such circumstances into consideration, too.
With the content bounding box determined all that remains to do is a bit of calculation.
The following method determines the bounding box using the class above, transforms the content accordingly, and changes the result crop box.
void scale(PdfDocument pdfDocument, Rectangle pageSize, Rectangle pageBodySize) {
int n = pdfDocument.getNumberOfPages();
for (int i = 1; i <= n; i++) {
PdfPage page = pdfDocument.getPage(i);
MarginFinder marginFinder = new MarginFinder();
PdfCanvasProcessor pdfCanvasProcessor = new PdfCanvasProcessor(marginFinder);
pdfCanvasProcessor.processPageContent(page);
Rectangle boundingBox = marginFinder.getBoundingBox();
if (boundingBox == null || boundingBox.getWidth() == 0 || boundingBox.getHeight() == 0) {
System.err.printf("Cannot scale page %d contents with bounding box %s\n", i , boundingBox);
continue;
} else {
// Scale and move content into A4 with margin
double scale = 0, xDiff= 0, yDiff = 0;
double xScale = pageBodySize.getWidth()/boundingBox.getWidth();
double yScale = pageBodySize.getHeight()/boundingBox.getHeight();
if (xScale < yScale) {
yDiff = boundingBox.getHeight() * (yScale / xScale - 1) / 2;
scale = xScale;
} else {
xDiff = boundingBox.getWidth() * (xScale / yScale - 1) / 2;
scale = yScale;
}
AffineTransform transform = AffineTransform.getTranslateInstance(pageBodySize.getLeft() + xDiff, pageBodySize.getBottom() + yDiff);
transform.scale(scale, scale);
transform.translate(-boundingBox.getLeft(), -boundingBox.getBottom());
new PdfCanvas(page.newContentStreamBefore(), page.getResources(), pdfDocument)
.concatMatrix(transform);
}
page.setMediaBox(pageSize);
page.setCropBox(pageSize);
}
}
(ScaleToA4 method scale)
For an A4 result page size with an inch of margin on each side you can call it like this for a PdfDocument pdfDocument:
Rectangle pageSize = PageSize.A4;
Rectangle pageBodySize = pageSize.clone().applyMargins(72, 72, 72, 72, false);
scale(pdfDocument, pageSize, pageBodySize);
(excerpt from ScaleToA4 test testFdaRequiresUseOfEctdFormatAndStandardizedStudyDataInFutureRegulatorySubmissionsSept)
How can one get the screen resolution (width x height) in pixels?
I am using a JFrame and the java swing methods.
You can get the screen size with the Toolkit.getScreenSize() method.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth();
double height = screenSize.getHeight();
On a multi-monitor configuration you should use this :
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int width = gd.getDisplayMode().getWidth();
int height = gd.getDisplayMode().getHeight();
If you want to get the screen resolution in DPI you'll have to use the getScreenResolution() method on Toolkit.
Resources :
javadoc - Toolkit.getScreenSize()
Java bug 5100801- Toolkit.getScreenSize() does not return the correct dimension on multimon, linux
This code will enumerate the graphics devices on the system (if multiple monitors are installed), and you can use that information to determine monitor affinity or automatic placement (some systems use a little side monitor for real-time displays while an app is running in the background, and such a monitor can be identified by size, screen colors, etc.):
// Test if each monitor will support my app's window
// Iterate through each monitor and see what size each is
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice[] gs = ge.getScreenDevices();
Dimension mySize = new Dimension(myWidth, myHeight);
Dimension maxSize = new Dimension(minRequiredWidth, minRequiredHeight);
for (int i = 0; i < gs.length; i++)
{
DisplayMode dm = gs[i].getDisplayMode();
if (dm.getWidth() > maxSize.getWidth() && dm.getHeight() > maxSize.getHeight())
{ // Update the max size found on this monitor
maxSize.setSize(dm.getWidth(), dm.getHeight());
}
// Do test if it will work here
}
This call will give you the information you want.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
Here's some functional code (Java 8) which returns the x position of the right most edge of the right most screen. If no screens are found, then it returns 0.
GraphicsDevice devices[];
devices = GraphicsEnvironment.
getLocalGraphicsEnvironment().
getScreenDevices();
return Stream.
of(devices).
map(GraphicsDevice::getDefaultConfiguration).
map(GraphicsConfiguration::getBounds).
mapToInt(bounds -> bounds.x + bounds.width).
max().
orElse(0);
Here are links to the JavaDoc.
GraphicsEnvironment.getLocalGraphicsEnvironment()
GraphicsEnvironment.getScreenDevices()
GraphicsDevice.getDefaultConfiguration()
GraphicsConfiguration.getBounds()
These three functions return the screen size in Java. This code accounts for multi-monitor setups and task bars. The included functions are: getScreenInsets(), getScreenWorkingArea(), and getScreenTotalArea().
Code:
/**
* getScreenInsets, This returns the insets of the screen, which are defined by any task bars
* that have been set up by the user. This function accounts for multi-monitor setups. If a
* window is supplied, then the the monitor that contains the window will be used. If a window
* is not supplied, then the primary monitor will be used.
*/
static public Insets getScreenInsets(Window windowOrNull) {
Insets insets;
if (windowOrNull == null) {
insets = Toolkit.getDefaultToolkit().getScreenInsets(GraphicsEnvironment
.getLocalGraphicsEnvironment().getDefaultScreenDevice()
.getDefaultConfiguration());
} else {
insets = windowOrNull.getToolkit().getScreenInsets(
windowOrNull.getGraphicsConfiguration());
}
return insets;
}
/**
* getScreenWorkingArea, This returns the working area of the screen. (The working area excludes
* any task bars.) This function accounts for multi-monitor setups. If a window is supplied,
* then the the monitor that contains the window will be used. If a window is not supplied, then
* the primary monitor will be used.
*/
static public Rectangle getScreenWorkingArea(Window windowOrNull) {
Insets insets;
Rectangle bounds;
if (windowOrNull == null) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
insets = Toolkit.getDefaultToolkit().getScreenInsets(ge.getDefaultScreenDevice()
.getDefaultConfiguration());
bounds = ge.getDefaultScreenDevice().getDefaultConfiguration().getBounds();
} else {
GraphicsConfiguration gc = windowOrNull.getGraphicsConfiguration();
insets = windowOrNull.getToolkit().getScreenInsets(gc);
bounds = gc.getBounds();
}
bounds.x += insets.left;
bounds.y += insets.top;
bounds.width -= (insets.left + insets.right);
bounds.height -= (insets.top + insets.bottom);
return bounds;
}
/**
* getScreenTotalArea, This returns the total area of the screen. (The total area includes any
* task bars.) This function accounts for multi-monitor setups. If a window is supplied, then
* the the monitor that contains the window will be used. If a window is not supplied, then the
* primary monitor will be used.
*/
static public Rectangle getScreenTotalArea(Window windowOrNull) {
Rectangle bounds;
if (windowOrNull == null) {
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
bounds = ge.getDefaultScreenDevice().getDefaultConfiguration().getBounds();
} else {
GraphicsConfiguration gc = windowOrNull.getGraphicsConfiguration();
bounds = gc.getBounds();
}
return bounds;
}
This is the resolution of the screen that the given component is currently assigned (something like most part of the root window is visible on that screen).
public Rectangle getCurrentScreenBounds(Component component) {
return component.getGraphicsConfiguration().getBounds();
}
Usage:
Rectangle currentScreen = getCurrentScreenBounds(frameOrWhateverComponent);
int currentScreenWidth = currentScreen.width // current screen width
int currentScreenHeight = currentScreen.height // current screen height
// absolute coordinate of current screen > 0 if left of this screen are further screens
int xOfCurrentScreen = currentScreen.x
If you want to respect toolbars, etc. you'll need to calculate with this, too:
GraphicsConfiguration gc = component.getGraphicsConfiguration();
Insets screenInsets = Toolkit.getDefaultToolkit().getScreenInsets(gc);
Unfortunately Toolkit.getDefaultToolkit() does not help if you have multiple displays, and on Windows it also reports scaled values if you have changed font setting "Scale and Layout" from 100%. For example at 150% font scale my 1920x1080 screen is reported as 1280x720 which (unhelpfully) changes the resolution my app uses.
You can access all details of the screen devices under:
GraphicsDevice[] devices = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
// Windows scaled sizes (eg 1280x720 for my case at 150% scaling)
Rectangle bounds = devices[nnn].getDefaultConfiguration().getBounds();
// Display sizes (same as above at 100% scale, 1920x1080 for my case)
DisplayMode dm = devices[nnn].getDefaultConfiguration().getDevice().getDisplayMode();
Rectangle orig = new Rectangle((int)bounds.getX(), (int)bounds.getY(), dm.getWidth(), dm.getHeight());
I use this method which reads the default display modes of each GraphicsDevice to access the original screen position+dimensions, and returns set of rectangles sorted in left->right X position order per screen:
/** Get actual screen display sizes, ignores Windows font scaling, sort left to right */
public static List<Rectangle> getDisplays() {
return Arrays.stream(GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
.map(GraphicsDevice::getDefaultConfiguration)
// For scaled sizes use .map(GraphicsConfiguration::getBounds) instead of:
.map(c -> {
var dm = c.getDevice().getDisplayMode();
var bounds = c.getBounds();
return new Rectangle((int)bounds.getX(), (int)bounds.getY(), dm.getWidth(), dm.getHeight());
})
.sorted(Comparator.comparing(Rectangle::getX))
.toList();
}
The above code runs under Windows and WSL. If you wish to have a version which returns the scaled values, just switch the commented out line above.
Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
double width = screenSize.getWidth();
double height = screenSize.getHeight();
framemain.setSize((int)width,(int)height);
framemain.setResizable(true);
framemain.setExtendedState(JFrame.MAXIMIZED_BOTH);
Here is a snippet of code I often use. It returns the full available screen area (even on multi-monitor setups) while retaining the native monitor positions.
public static Rectangle getMaximumScreenBounds() {
int minx=0, miny=0, maxx=0, maxy=0;
GraphicsEnvironment environment = GraphicsEnvironment.getLocalGraphicsEnvironment();
for(GraphicsDevice device : environment.getScreenDevices()){
Rectangle bounds = device.getDefaultConfiguration().getBounds();
minx = Math.min(minx, bounds.x);
miny = Math.min(miny, bounds.y);
maxx = Math.max(maxx, bounds.x+bounds.width);
maxy = Math.max(maxy, bounds.y+bounds.height);
}
return new Rectangle(minx, miny, maxx-minx, maxy-miny);
}
On a computer with two full-HD monitors, where the left one is set as the main monitor (in Windows settings), the function returns
java.awt.Rectangle[x=0,y=0,width=3840,height=1080]
On the same setup, but with the right monitor set as the main monitor, the function returns
java.awt.Rectangle[x=-1920,y=0,width=3840,height=1080]
int resolution =Toolkit.getDefaultToolkit().getScreenResolution();
System.out.println(resolution);
There's many answers but I still feel they're not adequate enough, my approach computes global variables related to the screen size once and also using a single loop of all the monitors:
public final class ScreenArea {
public static final Rectangle RECTANGLE;
public static final int
LEFT, RIGHT,
TOP, BOTTOM,
MIN_WIDTH, MAX_WIDTH,
MIN_HEIGHT, MAX_HEIGHT,
TOTAL_WIDTH, TOTAL_HEIGHT;
static {
// Initialise local vars
int left, right, top, bottom, minWidth, maxWidth, minHeight, maxHeight;
left = top = minWidth = minHeight = Integer.MAX_VALUE;
right = bottom = maxWidth = maxHeight = Integer.MIN_VALUE;
// In a single loop process all bounds
Rectangle bounds;
for (GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
bounds = device.getDefaultConfiguration().getBounds();
if (left > bounds.x)
left = bounds.x;
if (right < bounds.x + bounds.width)
right = bounds.x + bounds.width;
if (top > bounds.y)
top = bounds.y;
if (bottom < bounds.y + bounds.height)
bottom = bounds.y + bounds.height;
if (minWidth > bounds.width)
minWidth = bounds.width;
if (maxWidth < bounds.width)
maxWidth = bounds.width;
if (minHeight > bounds.height)
minHeight = bounds.height;
if (maxHeight < bounds.height)
maxHeight = bounds.height;
}
TOTAL_WIDTH = right - left;
TOTAL_HEIGHT = bottom - top;
RECTANGLE = new Rectangle(TOTAL_WIDTH, TOTAL_HEIGHT);
// Transfer local to immutable global vars
LEFT = left; RIGHT = right; TOP = top; BOTTOM = bottom;
MIN_WIDTH = minWidth; MAX_WIDTH = maxWidth;
MIN_HEIGHT = minHeight; MAX_HEIGHT = maxHeight;
}
}
Then you can use anytime as is just like this :
System.out.printf("LEFT=%d, ", ScreenArea.LEFT);
System.out.printf("RIGHT=%d%n", ScreenArea.RIGHT);
System.out.printf("TOP=%d, ", ScreenArea.TOP);
System.out.printf("BOTTOM=%d%n", ScreenArea.BOTTOM);
System.out.printf("MIN_WIDTH=%d, ", ScreenArea.MIN_WIDTH);
System.out.printf("MAX_WIDTH=%d%n", ScreenArea.MAX_WIDTH);
System.out.printf("MIN_HEIGHT=%d, ", ScreenArea.MIN_HEIGHT);
System.out.printf("MAX_HEIGHT=%d%n", ScreenArea.MAX_HEIGHT);
System.out.printf("SCREEN_AREA=%s%n", ScreenArea.RECTANGLE);
Which on my dual monitor setup it prints :
LEFT=0, RIGHT=3840
TOP=0, BOTTOM=1080
MIN_WIDTH=1920, MAX_WIDTH=1920
MIN_HEIGHT=1080, MAX_HEIGHT=1080
SCREEN_AREA=java.awt.Rectangle[x=0,y=0,width=3840,height=1080]
int screenResolution = Toolkit.getDefaultToolkit().getScreenResolution();
System.out.println(""+screenResolution);
I have created a watermark text that is vertically centered.
This is the line I used
PdfPatternPainter.showTextAlignedKerned(Element.ALIGN_MIDDLE,
string, x, y, -90);
What i want to do now is to make the watermark diagonal. changing the angle value will make it diagonal, but then its x,y position is not centered anymore.
this is my current method
public static void createWaterMarkPDF(ArrayList<String> watermark, PdfReader reader, PdfStamper stamper) throws Exception {
Rectangle pageSize = reader.getPageSize(1);
final float WATERMARK_PAGE_ANGLE = 270;
BaseFont font = BaseFont.createFont(BaseFont.HELVETICA, BaseFont.WINANSI, BaseFont.NOT_EMBEDDED);
PdfPatternPainter painter = stamper.getOverContent(1).createPattern(pageSize.getWidth(), pageSize.getHeight());
painter.setColorStroke(new BaseColor(192, 192, 192));
int FONT_SIZE = 80;
painter.beginText();
painter.setTextRenderingMode(PdfPatternPainter.TEXT_RENDER_MODE_FILL);
painter.setFontAndSize(font, FONT_SIZE);
for (int i=0; i<watermark.size(); i++) {
String string = watermark.get(i);
// Values are opposite since we are doing a vertical alignment angle
float width = font.getAscentPoint(string, FONT_SIZE) + font.getDescentPoint(string, FONT_SIZE);
float height = painter.getEffectiveStringWidth(string, true);
float x = (pageSize.getWidth() - width) / 2;
if (i == 0)
x += (width * 3);
else if (i == 2)
x -= (width * 3);
painter.showTextAlignedKerned(Element.ALIGN_MIDDLE,
string, x,
Util.transformY(pageSize, (pageSize.getHeight() - height) / 2),
WATERMARK_PAGE_ANGLE);
}
painter.endText();
// Transparency of watermark
PdfGState state = new PdfGState();
state.setFillOpacity(0.1f);
for (int i=reader.getNumberOfPages(); i>0; i--) {
Rectangle thisPageSize = reader.getPageSize(i);
PdfContentByte overContent = stamper.getOverContent(i);
overContent.setColorFill(new PatternColor(painter));
overContent.setGState(state);
overContent.rectangle(thisPageSize.getLeft(), thisPageSize.getBottom(), thisPageSize.getWidth(), thisPageSize.getHeight());
overContent.fill();
}
}
it draws vertical lines and centered. watermark is an ArrayList. you can add 3 strings.
if i change the angle to , say 300 (or -60) to make it diagonal, the positioning is whacked. do i need to use an AffineTransform here? although i already tried, didnt work for me unless i lacked something here?
I'm using biojava to create a chromatogram.
The chromatogram is an image. Each basecall is held within a rectange.
To get x coordinate (left side): gfx.getCallboxBounds(int i).getX();
To get width: gfx.getCallboxBounds(int i).getX()
where the integer i represents the rectangle in the arrangement of rectangles that build the chromatogram.
To get the confidence value of a given base:
(I create an array called confidence) confidence[i - 1];
where i is, again, the base in question.
Confidence values are reported between 1 and 70. The height of the image is 240 pixels.
I want to print 2 pixel thick, gray lines at relatives heights along the sequence, for the width of each basecall.
For instance, if the quality of basecall (Rectangle) 60 is 40 and its width is 20, a gray line will be drawn at 137 pixels (40 / 70 * 240) for its width.
Here is the method that draws the trace:
ChromatogramFactory chromFactory = new ChromatogramFactory();
Chromatogram chrom = ChromatogramFactory.create(abi);
ChromatogramGraphic gfx = new ChromatogramGraphic(chrom);
BufferedImage bi = new BufferedImage(
gfx.getWidth(),
gfx.getHeight(),
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = bi.createGraphics();
g2.setBackground(Color.white);
g2.clearRect(0, 0, bi.getWidth(), bi.getHeight());
if (g2.getClip() == null) {
g2.setClip(new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
}
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
gfx.drawTo(g2);
g2.draw(new java.awt.Rectangle(-10, -10, 5, 5));
EDIT: Also, the sequence length is the same as the number of rectangles. Each letter in the sequence represents a basecall, as does each rectangle.
Using the code below, I was able to add lines at relative locations to give the confidence values:
int leftBound = 0;
int rightBound = 0;
double confVal = 0;
double heightDouble = 0;
int height = 0;
g2.setColor(Color.LIGHT_GRAY);
for (int i = 0; i < gfx.getCallboxCount(); i++) {
leftBound = (int) gfx.getCallboxBounds(i).getX();
rightBound = (int) ((int) leftBound + gfx.getCallboxBounds(i).getWidth());
confVal = confidence[i] * 2.67;
heightDouble = 200 - confVal;
height = (int) heightDouble;
g2.drawLine(leftBound, height, rightBound, height);
}
I’m relatively new to ImageMagick and java and working on a project to display text around the outside of a circle centered at 0 degrees on the circle using ImageMagick 6.3.9 Q16 and jmagick 6.3.9 Q16 on windows. We’re porting existing image magick code that does this from PHP MagickWand but the placement of each letter on the arc of the circle comes out a little off in the java version I think because of the following difference.
In MagickWand, it’s placed on the arc by this one line of code which uses a float x, y coordinate value and float angle value (for greater precision) for annotating the drawing wand (equivalent of DrawInfo in jmagick) and works beautifully:
MagickAnnotateImage($magick_wand, $drawing_wand, $origin_x + $x, $origin_y - $y, $angle, $character);
In jmagick though, the annotateImage method only takes one argument which is the DrawInfo so I ended up with what I think is the only other alternative, the compositeImage method. So in order to do that, I’m drawing each character as separate draw info, then annotating that to a transparent png image, then rotating that image via rotateImage method, then using compositeImage to place it on my canvas image but compositeImage only deals with x & y as int values (and doesn’t consider angle) so I’m rounding my x & y double values (to get same number of decimals or more like php version is using just to rule that out) at that point which I suspect is the main reason it’s placing the characters a little off on the circle.
My code performing the work is the following where Article is a local path to a font file (ex: E:\WCDE_ENT70\workspace\Stores\WebContent\AdminArea\CoordsCenterSection\fonts\ARIALN.TTF), nameNumStr is the string to render on the circle (ex: SAMUELSON), fsize is the point size of the font (ex: 32), colorStr is font color name (ex: black), radVal is radius (ex: 120), poix is x origin start coordinate (ex: 150), poiy is y origin start coordinate (ex: 150):
public byte[] getArcedImage(String Article, String nameNumStr, int fsize, String colorStr, int radVal, int poix, int poiy)
{
try {
Font f = null;
try {
f = Font.createFont(Font.TRUETYPE_FONT, new FileInputStream(Article.replaceAll("%20"," ")));
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (FontFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
String fontName = f.getName();
// Use awt's font metrics since jmagick doesn't have font metrics built in like php magickwand does
FontMetrics fm = createFontMetrics(new Font(fontName, Font.PLAIN, fsize));
int strImgW = fm.stringWidth(nameNumStr);
int strImgH = fm.getHeight();
String spacerImg = "E:\\WCDE_ENT70\\workspace\\Stores\\WebContent\\AdminArea\\CoordsCenterSection\\images\\600x600.png";
//Read in large 600 png first as our main canvas
ImageInfo bi = new ImageInfo(spacerImg);
MagickImage bmi = new MagickImage(bi);
// Make canvas image transparent
bmi.setMatte(true);
bmi.setBackgroundColor(PixelPacket.queryColorDatabase("#FFFF8800"));
//defaults or param vals
final int radius = radVal;
final int origin_x = poix;
final int origin_y = poiy;
final int center_text_on = 0;
final int charXGeom = 150;
final int charYGeom = 150;
double circumference = 0;
double percentage = 0;
double degrees = 0;
double start = 0;
double current_degree = 0;
double angle = 0;
double angle_adjustment = 0;
double character_center = 0;
/**
* Calculate the circumference of the drawn circle and label the image
* with it.
*/
circumference = (2 * Math.PI * radius);
/**
* Calculate the percentage of the circumference that the string will
* consume.
*/
percentage = strImgW / circumference;
/**
* Convert this percentage into something practical - degrees.
*/
degrees = 360 * percentage;
/**
* Because the string is centered, we need to calculate the starting point
* of the string by subtracting half of the required degrees from the
* anticipated center mark.
*/
start = center_text_on - (degrees / 2);
/**
* Initialize our traversal starting point.
*/
current_degree = start;
//
ImageInfo ci = null;
MagickImage cmi = null;
double x = 0;
double y = 0;
int finalStrWidth = 0;
int charImgW = 0;
int charImgH = 0;
for (int i=0; i<nameNumStr.length(); i++)
{
/**
* Isolate the appropriate character.
*/
String charVal = nameNumStr.substring(i, i+1);
charImgW = fm.stringWidth(charVal);
charImgH = strImgH;
ci = new ImageInfo(spacerImg);
cmi = new MagickImage(ci);
// Create Rectangle for cropping character image canvas to final width and height
Rectangle charRect = new Rectangle(0,0,charImgW,charImgH);
// Crop image to final width and height
cmi = cmi.cropImage(charRect);
// Make image transparent
cmi.setMatte(true);
cmi.setBackgroundColor(PixelPacket.queryColorDatabase("#FFFF8800"));
// Set a draw info for each character
DrawInfo cdi = new DrawInfo(ci);
// Set Opacity
cdi.setOpacity(0);
// Set Gravity
cdi.setGravity(GravityType.CenterGravity);
// Set Fill Color
cdi.setFill(PixelPacket.queryColorDatabase(colorStr));
// Set Font Size
cdi.setPointsize(fsize);
// Set Font
cdi.setFont(Article.replaceAll("%20"," "));
// Set the text
cdi.setText(charVal);
// Make the text smoother
cdi.setTextAntialias(true);
// Annotate the draw info to make the character image
cmi.annotateImage(cdi);
// For debug purposes
finalStrWidth += charImgW;
/**
* Calculate the percentage of the circumference that the character
* will consume.
*/
percentage = charImgW / circumference;
/**
* Convert this percentage into something practical - degrees.
*/
degrees = 360 * percentage;
/**
* Calculate the x and y axis adjustments to make, based on the origin
* of the circle, so we can place each letter.
*/
x = radius * Math.sin(Math.toRadians(current_degree));
y = radius * Math.cos(Math.toRadians(current_degree));
// Rotate the character image to the angle
cmi = cmi.rotateImage(angle);
// Composite character image to main canvas image
bmi.compositeImage(CompositeOperator.HardLightCompositeOp, cmi, (int)Math.round((origin_x+x)), (int)Math.round((origin_y-y)));
// Increment the degrees
current_degree += degrees;
}
bmi = bmi.trimImage();
byte[] pi = bmi.imageToBlob(ci);
return pi;
} catch (MagickException e) {
e.printStackTrace();
return null;
}
}
private FontMetrics createFontMetrics(Font font)
{
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
g.dispose();
bi = null;
return fm;
}
private Rectangle2D createFontRectangle(Font font, String strVal)
{
BufferedImage bi = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB_PRE);
Graphics g = bi.getGraphics();
FontMetrics fm = g.getFontMetrics(font);
Rectangle2D rect = fm.getStringBounds(strVal, g);
g.dispose();
bi = null;
return rect;
}
I've since found that it’s possible to use DrawInfo’s setGeometry method to set the x, y and saw on the one example I found on jmagick.org's wiki that it supposedly can be used for much more than x, y placement but can’t find any other examples or documentation showing how else it can be used (hopefully for specifying an angle as well).
I’m not positive but it seems that setGeometry would be the only way to potentially specify an angle since jmagick’s implementation of annotateImage only takes a Draw Info as it’s argument.
Does anyone know a way to use DrawInfo’s setGeometry method to set the x, y and angle? I think it may solve my problem. Also, if anyone has any working example of using jmagick to draw text around a circle that they’d be willing to share, I’d be greatly appreciative.
Thanks