import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import java.util.Map; /** * One-off tool: pre-render emoji icons to PNG assets for IconClickImageCaptchaGenerator. * Must run on a machine that has a color/outline emoji font (e.g. Windows Segoe UI Emoji). * Output: META-INF/captcha-icons/.png (96x96, white backing disc + glyph). * * Usage: java tools/icon-render/RenderIcons.java */ public class RenderIcons { private static final int CANVAS = 96; private static final float GLYPH_SIZE = 60f; private static final Map ICONS = new LinkedHashMap<>() {{ // fruits put("apple", "\uD83C\uDF4E"); put("banana", "\uD83C\uDF4C"); put("cherry", "\uD83C\uDF52"); put("grapes", "\uD83C\uDF47"); put("lemon", "\uD83C\uDF4B"); put("orange", "\uD83C\uDF4A"); put("strawberry", "\uD83C\uDF53"); put("watermelon", "\uD83C\uDF49"); // animals put("dog", "\uD83D\uDC36"); put("cat", "\uD83D\uDC31"); put("mouse", "\uD83D\uDC2D"); put("hamster", "\uD83D\uDC39"); put("rabbit", "\uD83D\uDC30"); put("fox", "\uD83E\uDD8A"); put("bear", "\uD83D\uDC3B"); put("panda", "\uD83D\uDC28"); // sports put("soccer", "\u26BD"); put("basketball", "\uD83C\uDFC0"); put("tennis", "\uD83C\uDFBE"); put("volleyball", "\uD83C\uDFD0"); put("billiards", "\uD83C\uDFB1"); put("pingpong", "\uD83C\uDFD3"); put("trophy", "\uD83C\uDFC6"); put("boxing", "\uD83E\uDD4A"); // vehicles put("car", "\uD83D\uDE97"); put("taxi", "\uD83D\uDE95"); put("suv", "\uD83D\uDE99"); put("bus", "\uD83D\uDE8C"); put("racecar", "\uD83C\uDFCE"); put("police", "\uD83D\uDE93"); put("ambulance", "\uD83D\uDE91"); put("firetruck", "\uD83D\uDE92"); // tech put("phone", "\uD83D\uDCF1"); put("laptop", "\uD83D\uDCBB"); put("desktop", "\uD83D\uDDA5"); put("printer", "\uD83D\uDDA8"); put("keyboard", "\u2328"); put("computer-mouse", "\uD83D\uDDB1"); put("cd", "\uD83D\uDCBF"); put("camera", "\uD83D\uDCF7"); }}; public static void main(String[] args) throws IOException { Path outDir = Paths.get(args.length > 0 ? args[0] : "."); Files.createDirectories(outDir); GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); Font emojiFont = pickFont(ge); System.out.println("Using font: " + emojiFont.getFontName()); int failures = 0; for (Map.Entry e : ICONS.entrySet()) { BufferedImage img = render(e.getValue(), emojiFont); File out = outDir.resolve(e.getKey() + ".png").toFile(); ImageIO.write(img, "png", out); if (emojiFont.canDisplayUpTo(e.getValue()) != -1) { System.out.println("[WARN] partial glyph coverage: " + e.getKey()); failures++; } System.out.println("OK " + out.getName()); } System.out.println("Done. failures(warn)=" + failures); } private static Font pickFont(GraphicsEnvironment ge) { for (String name : new String[]{"Segoe UI Emoji", "Apple Color Emoji", "Noto Color Emoji", "EmojiOne"}) { for (Font f : ge.getAllFonts()) { if (f.getFontName().equalsIgnoreCase(name)) { return f.deriveFont(Font.PLAIN, GLYPH_SIZE); } } } System.out.println("[WARN] no known emoji font found, falling back to Dialog"); return new Font(Font.DIALOG, Font.PLAIN, (int) GLYPH_SIZE); } private static BufferedImage render(String emoji, Font font) { BufferedImage img = new BufferedImage(CANVAS, CANVAS, 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.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON); g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); try { // translucent white backing disc -> keeps icons readable on any photo background g.setColor(new Color(255, 255, 255, 216)); g.fillOval(2, 2, CANVAS - 5, CANVAS - 5); g.setColor(new Color(0, 0, 0, 46)); g.setStroke(new BasicStroke(2f)); g.drawOval(3, 3, CANVAS - 7, CANVAS - 7); g.setColor(Color.BLACK); g.setFont(font); FontMetrics fm = g.getFontMetrics(); int x = (CANVAS - fm.stringWidth(emoji)) / 2; int y = (CANVAS - fm.getHeight()) / 2 + fm.getAscent(); g.drawString(emoji, x, y); } finally { g.dispose(); } return img; } }