All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.alkacon.simapi.IdentIcon Maven / Gradle / Ivy


package com.alkacon.simapi;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Renders an identity icon calculated from the given initialization String.

* * Default size of the icon is 32x32 pixel. * The icons are generated from a grid of 4x4 patches.

* * This is intended to be used for generating individual icons for uses bases on their * unique user name / OU combination. * * Based on the original Identicon implementation of Don Park, * see https://github.com/donpark/identicon.

* * Original copyright notice: *

 * Copyright (c) 2007-2012 Don Park <[email protected]>
 * 
*/ public class IdentIcon { /** Constant to identify a transparent background fill color. */ public static final Color COLOR_TRANSPARENT = new Color(255, 255, 255, 0); /** Size of the individual patch when drawing. */ private static final float DEFAULT_PATCH_SIZE = 16.0f; /** Salt String for generation better hashes. */ public static final String IDENTICON_SALT = "(§!$/%.&#?@-_)"; /** * Grid size of the patches is 5x5. * * Each patch is a polygon created from a list of vertices on a 5 by 5 grid. * Vertices are numbered from 0 to 24, starting from top-left corner of the * grid, moving left to right and top to bottom. */ private static final int PATCH_GRIDS = 5; /** Flag to indicate if a specific patch is inverted by default. */ private static final byte PATCH_INVERTED = 1; /** Flag to indicate a move to operation while drawing a patch. */ private static final int PATCH_MOVETO = -1; /** Patch types that can be used in the center. */ private static final int PATCH_TYPES_CENTER[] = {0, 4, 8, 15, 1, 5, 7, 21, 23, 24, 25, 31, 6, 12, 11, 30}; /** Patch shape definition. */ private static final byte[] patch00 = {0, 4, 24, 20}; /** Patch shape definition. */ private static final byte[] patch01 = {0, 4, 20}; /** Patch shape definition. */ private static final byte[] patch02 = {2, 24, 20}; /** Patch shape definition. */ private static final byte[] patch03 = {0, 2, 20, 22}; /** Patch shape definition. */ private static final byte[] patch04 = {2, 14, 22, 10}; /** Patch shape definition. */ private static final byte[] patch05 = {0, 14, 24, 22}; /** Patch shape definition. */ private static final byte[] patch06 = {2, 24, 22, 13, 11, 22, 20}; /** Patch shape definition. */ private static final byte[] patch07 = {0, 14, 22}; /** Patch shape definition. */ private static final byte[] patch08 = {6, 8, 18, 16}; /** Patch shape definition. */ private static final byte[] patch09 = {4, 20, 10, 12, 2}; /** Patch shape definition. */ private static final byte[] patch10 = {0, 2, 12, 10}; /** Patch shape definition. */ private static final byte[] patch11 = {10, 14, 22}; /** Patch shape definition. */ private static final byte[] patch12 = {20, 12, 24}; /** Patch shape definition. */ private static final byte[] patch13 = {10, 2, 12}; /** Patch shape definition. */ private static final byte[] patch14 = {0, 2, 10}; /** Patch shape definition. */ private static final byte[] patch15 = {0, 4, 10}; /** Patch shape definition. */ private static final byte[] patch16 = {20, 24, 10}; /** Patch shape definition. */ private static final byte[] patch17 = {0, 20, 3}; /** Patch shape definition. */ private static final byte[] patch18 = {1, 4, 24}; /** Patch shape definition. */ private static final byte[] patch19 = {0, 1, 14, 21, 20}; /** Patch shape definition. */ private static final byte[] patch20 = {10, 0, 2, 22, 24, 14}; /** Patch shape definition. */ private static final byte[] patch21 = {5, 9, 22}; /** Patch shape definition. */ private static final byte[] patch22 = {10, 2, 22, 14}; /** Patch shape definition. */ private static final byte[] patch23 = {0, 20, 4, 24}; /** Patch shape definition. */ private static final byte[] patch24 = {0, 7, 4, 13, 24, 17, 20, 11}; /** Patch shape definition. */ private static final byte[] patch25 = {0, 3, 24}; /** Patch shape definition. */ private static final byte[] patch26 = {0, 15, 24}; /** Patch shape definition. */ private static final byte[] patch27 = {0, 2, 19}; /** Patch shape definition. */ private static final byte[] patch28 = {0, 10, 23}; /** Patch shape definition. */ private static final byte[] patch29 = {0, 2, 18, 10}; /** Patch shape definition. */ private static final byte[] patch30 = {0, 8, 16}; /** Flags for patches */ private static final byte PATCH_FLAGS[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, PATCH_INVERTED, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; /** All available patch types in an array */ private static final byte[] PATCH_TYPES[] = { patch00, patch01, patch02, patch03, patch04, patch05, patch06, patch07, patch08, patch09, patch10, patch11, patch12, patch13, patch14, patch00, // inverted square, equals empty space patch15, patch16, patch17, patch18, patch19, patch20, patch21, patch22, patch23, patch24, patch25, patch26, patch27, patch28, patch29, patch30}; /** Background color of the patches. */ private Color m_backgroundColor; /** Digester to use for generating hashes. */ private MessageDigest m_digest; /** Reserved color for patches, if patch color is to close to this color the opposite color is used. */ private Color m_reservedColor; /** Offset for patch drawing. */ private float m_patchOffset; /** The calculated patch shapes / polygons. */ private GeneralPath[] m_patchShapes; /** Size of the individual patch, default is 16. */ private float m_patchSize; /** Target size for the rendered patch, default is 32. */ private int m_size; /** * Constructor. */ public IdentIcon() { setPatchSize(DEFAULT_PATCH_SIZE); setBackgroundColor(COLOR_TRANSPARENT); setSize(32); setReservedColor(null); try { m_digest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException e) { // should better not happen e.printStackTrace(); } } /** * Returns the background color of the IdentIcon.

* * @return the background color of the IdentIcon */ public Color getBackgroundColor() { return m_backgroundColor; } /** * Returns the size in pixels at which each patch will be rendered before * they are scaled down to requested IdentIcon size.

* * @return the size in pixels at which each patch will be rendered */ public float getPatchSize() { return m_patchSize; } /** * Returns the reserved color for the IdentIcon renderer.

* * The default is null which disables the reserved color feature.

* * The reserved color can be set to make sure a certain color * is only used for specific input Strings. A practical example would be * "draw all admin users in orange, but no other users". In case the color * calculated for the user name would be to close to the reserved color, * the opposite color from the spectrum is used instead. * * @return the reserved color for the IdentIcon renderer */ public Color getReservedColor() { return m_reservedColor; } /** * Returns the target image size (height, width) for the rendered patch, default is 32.

* * @return the target image size (height, width) for the rendered patch */ public int getSize() { return m_size; } /** * Renders the IdentIcon for the given input String.

* * The protected color in this case is NOT allowed to be used for the generated icon.

* * @param input the input String to render the IdentIcon for * * @return the IdentIcon for the given input String */ public BufferedImage render(String input) { return render(input, false, getSize()); } /** * Renders the IdentIcon for the given input String.

* * @param input the input String to render the IdentIcon for * @param allowProtected controls if the reserves color set by {@link #getReservedColor()} can be used or not * * @return the IdentIcon for the given input String */ public BufferedImage render(String input, boolean allowProtected) { return render(input, allowProtected, getSize()); } /** * Renders the IdentIcon for the given input String with the given size.

* * Good values for the size parameter should set in dependency with the patch size set with * {@link #getPatchSize()}. The largest size should be the patch size multiplied by four.

* * @param input the input String to render the IdentIcon for * @param allowProtected controls if the reserves color set by {@link #getReservedColor()} can be used or not * @param size the target size of the output image * * @return the IdentIcon for the given input String */ public BufferedImage render(String input, boolean allowProtected, int size) { byte[] hash = hash(input); hash[6] = allowProtected ? (byte)1 : (byte)0; return renderIcon(hash, size); } /** * Renders the IdentIcon for the given input String.

* * Good values for the size parameter should set in dependency with the patch size set with * {@link #getPatchSize()}. The largest size should be the patch size multiplied by four.

* * The protected color in this case is NOT allowed to be used for the generated icon.

* * @param input the input String to render the IdentIcon for * @param size the target size of the output image * * @return the IdentIcon for the given input String */ public BufferedImage render(String input, int size) { return render(input, false, size); } /** * Renders the IdentIcon for the given input String in the given color.

* * The protected color in this case is NOT allowed to be used for the generated icon, * as this method exists mostly for test purposes.

* * @param input the input String to render the IdentIcon for * @param red the red color component * @param green the green color component * @param blue the blue color component * * @return the IdentIcon for the given input String */ public BufferedImage render(String input, int red, int green, int blue) { byte[] hash = hash(input); hash[2] = (byte)blue; hash[3] = (byte)green; hash[4] = (byte)red; return renderIcon(hash, getSize()); } /** * The background color to be used for the IdentIcon.

* * @param backgroundColor the background color to set */ public void setBackgroundColor(Color backgroundColor) { this.m_backgroundColor = backgroundColor; } /** * Set the size in pixels at which each patch will be rendered before they * are scaled down to requested identicon size.

* * Default size is 16 pixels which means, for 16-block identicon, * a 64x64 image will be rendered and scaled down. * * @param size patch size in pixels */ public void setPatchSize(float size) { this.m_patchSize = size; this.m_patchOffset = m_patchSize / 2.0f; // used to center patch shape at float patchScale = m_patchSize / 4.0f; // origin. this.m_patchShapes = new GeneralPath[PATCH_TYPES.length]; for (int i = 0; i < PATCH_TYPES.length; i++) { GeneralPath patch = new GeneralPath(Path2D.WIND_NON_ZERO); boolean moveTo = true; byte[] patchVertices = PATCH_TYPES[i]; for (int j = 0; j < patchVertices.length; j++) { int v = patchVertices[j]; if (v == PATCH_MOVETO) { moveTo = true; } float vx = ((v % PATCH_GRIDS) * patchScale) - m_patchOffset; float vy = (((float)Math.floor(((float)v) / PATCH_GRIDS)) * patchScale) - m_patchOffset; if (!moveTo) { patch.lineTo(vx, vy); } else { moveTo = false; patch.moveTo(vx, vy); } } patch.closePath(); this.m_patchShapes[i] = patch; } } /** * Returns the reserved color for the IdentIcon renderer.

* * The default is null which disables the reserved color feature.

* * The reserved color can be set to make sure a certain color * is only used for specific input Strings. A practical example would be * "draw all admin users in orange, but no other users". In case the color * calculated for the user name would be to close to the reserved color, * the opposite color from the spectrum is used instead. * * @param reservedColor the reserved color for the IdentIcon renderer to set */ public void setReservedColor(Color reservedColor) { m_reservedColor = reservedColor; } /** * Sets the target image size (height, width) for the rendered patch, default is 32.

* * @param size the target image size to set */ public void setSize(int size) { m_size = size; } /** * @param g the graphic to draw on * @param x x position * @param y y position * @param size size of the tile * @param patch patch type to draw * @param turn turn of the patch * @param invert invert color or not * @param fillColor fill color * @param strokeColor stroke color */ protected void drawPatch( Graphics2D g, float x, float y, float size, int patch, int turn, boolean invert, Color fillColor, Color strokeColor) { assert patch >= 0; assert turn >= 0; patch %= PATCH_TYPES.length; turn %= 4; if ((PATCH_FLAGS[patch] & PATCH_INVERTED) != 0) { invert = !invert; } Shape shape = m_patchShapes[patch]; double scale = ((double)size) / ((double)m_patchSize); float offset = size / 2.0f; // paint background g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC)); g.setColor(invert ? fillColor : m_backgroundColor); g.fill(new Rectangle2D.Float(x, y, size, size)); AffineTransform savet = g.getTransform(); g.translate(x + offset, y + offset); g.scale(scale, scale); g.rotate(Math.toRadians(turn * 90)); // if stroke color was specified, apply stroke // stroke color should be specified if fore color is too close to the // back color. if (strokeColor != null) { g.setColor(strokeColor); g.draw(shape); } // render rotated patch using fore color (back color if inverted) g.setColor(invert ? m_backgroundColor : fillColor); g.fill(shape); g.setTransform(savet); } /** * Method to draw a single shape, useful for visual testing the shape output.

* * @param shape the shape to draw * * @return a buffered image of the shape */ protected BufferedImage drawShape(int shape) { int size = 60; Color fill = new Color(0xb3, 0x1b, 0x34); Color stroke = null; BufferedImage targetImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); Graphics2D g = targetImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setBackground(getBackgroundColor()); g.clearRect(0, 0, size, size); drawPatch(g, 0, 0, size, shape, 0, false, fill, stroke); g.dispose(); return targetImage; } /** * Returns the distance between two colors. * * @param c1 the first color * @param c2 the second color * * @return the distance between the two colors */ protected float getColorDistance(Color c1, Color c2) { float dx = c1.getRed() - c2.getRed(); float dy = c1.getGreen() - c2.getGreen(); float dz = c1.getBlue() - c2.getBlue(); return (float)Math.sqrt((dx * dx) + (dy * dy) + (dz * dz)); } /** * Returns the complementary color for the given color.

* * @param color the base to calculate the complementary color for * * @return the complementary color */ protected Color getComplementaryColor(Color color) { return new Color(color.getRGB() ^ 0x00FFFFFF); } /** * Generates an MD5 hash array from the given input String.

* * The {@link #IDENTICON_SALT} is added to the String for better results.

* * @param input the input to generate the hash for * * @return an MD5 hash array from the given input String */ protected byte[] hash(String input) { byte[] hash = null; try { hash = m_digest.digest((IDENTICON_SALT + input + IDENTICON_SALT).getBytes("UTF-8")); hash[6] = 0; // used for allowing reserved color later, default is 0 = false } catch (UnsupportedEncodingException e) { e.printStackTrace(System.err); } return hash; } /** * Renders the IdentIcon based on the input hash, using the internal parameters.

* * @param hash the hash to render * @param size the size of the image to generate * * @return the IdentIcon based on the input hash */ protected BufferedImage renderIcon(byte[] hash, int size) { // ------------------------------------------------- // PREPARE // int hash0 = 0xff & hash[0]; int hash1 = 0xff & hash[1]; int hash2 = 0xff & hash[5]; int blue = 0xff & hash[2]; int green = 0xff & hash[3]; int red = 0xff & hash[4]; boolean allowProtected = hash[6] > 0 ? true : false; // int middleType = hash0 & 0x1f; int middleType = PATCH_TYPES_CENTER[hash0 & 0xf]; boolean middleInvert = ((hash0 >> 5) & 0x1) != 0; int middleTurn = 0; // ((hash0 >> 6) & 0x3); int cornerType = hash1 & 0x1f; boolean cornerInvert = ((hash1 >> 5) & 0x1) != 0; int cornerTurn = ((hash1 >> 6) & 0x3); int sideType = hash2 & 0x01f; boolean sideInvert = ((hash2 >> 5) & 0x1) != 0; int sideTurn = ((hash2 >> 6) & 0x3); // color components are used at top of the range for color difference Color fill = new Color(red, green, blue); Color middleColor = fill; if (m_reservedColor != null) { if (!allowProtected) { float distance = getColorDistance(fill, m_reservedColor); if (distance < 96.0f) { fill = getComplementaryColor(fill); middleColor = fill; } } else { middleColor = m_reservedColor; fill = middleColor.brighter(); sideInvert = false; cornerInvert = false; } } // outline shapes with a noticeable color (complementary will do) if // shape color and background color are too similar (measured by color // distance). Color stroke = null; if (getColorDistance(fill, getBackgroundColor()) < 32.0f) { stroke = getComplementaryColor(fill); } // ------------------------------------------------- // RENDER // BufferedImage targetImage = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); Graphics2D g = targetImage.createGraphics(); g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); g.setBackground(getBackgroundColor()); g.clearRect(0, 0, size, size); float s = size / 4.0f; float bs1 = s; float bs2 = s * 2.0f; float bs3 = s * 3.0f; // middle patches drawPatch(g, bs1, bs1, s, middleType, middleTurn++, middleInvert, middleColor, stroke); drawPatch(g, bs2, bs1, s, middleType, middleTurn++, middleInvert, middleColor, stroke); drawPatch(g, bs2, bs2, s, middleType, middleTurn++, middleInvert, middleColor, stroke); drawPatch(g, bs1, bs2, s, middleType, middleTurn++, middleInvert, middleColor, stroke); // side patches, starting from top and moving clock-wise Color sideColor = sideInvert ? fill.darker() : fill; sideInvert = false; drawPatch(g, bs1, 0, s, sideType, sideTurn++, sideInvert, sideColor, stroke); drawPatch(g, bs2, 0, s, sideType, sideTurn, sideInvert, sideColor, stroke); drawPatch(g, bs3, bs1, s, sideType, sideTurn++, sideInvert, sideColor, stroke); drawPatch(g, bs3, bs2, s, sideType, sideTurn, sideInvert, sideColor, stroke); drawPatch(g, bs2, bs3, s, sideType, sideTurn++, sideInvert, sideColor, stroke); drawPatch(g, bs1, bs3, s, sideType, sideTurn, sideInvert, sideColor, stroke); drawPatch(g, 0, bs2, s, sideType, sideTurn++, sideInvert, sideColor, stroke); drawPatch(g, 0, bs1, s, sideType, sideTurn, sideInvert, sideColor, stroke); // corner patches, starting from top left and moving clock-wise Color cornerColor = cornerInvert ? fill.brighter() : fill; cornerInvert = false; drawPatch(g, 0, 0, s, cornerType, cornerTurn++, cornerInvert, cornerColor, stroke); drawPatch(g, bs3, 0, s, cornerType, cornerTurn++, cornerInvert, cornerColor, stroke); drawPatch(g, bs3, bs3, bs1, cornerType, cornerTurn++, cornerInvert, cornerColor, stroke); drawPatch(g, 0, bs3, s, cornerType, cornerTurn++, cornerInvert, cornerColor, stroke); g.dispose(); return targetImage; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy