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