com.talanlabs.avatargenerator.element.identicon.NineBlockIdenticonRenderer Maven / Gradle / Ivy
The newest version!
package com.talanlabs.avatargenerator.element.identicon;
import com.talanlabs.avatargenerator.utils.AvatarUtils;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
/**
* 9-block Identicon renderer.
*
*
* Current implementation uses only the lower 32 bits of identicon code.
*
*
* @author don
*/
public class NineBlockIdenticonRenderer {
/*
* 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;
private static final float DEFAULT_PATCH_SIZE = 20.0f;
private static final byte PATCH_SYMMETRIC = 1;
private static final byte PATCH_INVERTED = 2;
private static final int PATCH_MOVETO = -1;
private static final byte[] patch0 = {0, 4, 24, 20};
private static final byte[] patch1 = {0, 4, 20};
private static final byte[] patch2 = {2, 24, 20};
private static final byte[] patch3 = {0, 2, 20, 22};
private static final byte[] patch4 = {2, 14, 22, 10};
private static final byte[] patch5 = {0, 14, 24, 22};
private static final byte[] patch6 = {2, 24, 22, 13, 11, 22, 20};
private static final byte[] patch7 = {0, 14, 22};
private static final byte[] patch8 = {6, 8, 18, 16};
private static final byte[] patch9 = {4, 20, 10, 12, 2};
private static final byte[] patch10 = {0, 2, 12, 10};
private static final byte[] patch11 = {10, 14, 22};
private static final byte[] patch12 = {20, 12, 24};
private static final byte[] patch13 = {10, 2, 12};
private static final byte[] patch14 = {0, 2, 10};
private static final byte[] patchTypes[] = {patch0, patch1, patch2,
patch3, patch4, patch5, patch6, patch7, patch8, patch9, patch10,
patch11, patch12, patch13, patch14, patch0};
private static final byte patchFlags[] = {PATCH_SYMMETRIC, 0, 0, 0,
PATCH_SYMMETRIC, 0, 0, 0, PATCH_SYMMETRIC, 0, 0, 0, 0, 0, 0,
PATCH_SYMMETRIC + PATCH_INVERTED};
private static int centerPatchTypes[] = {0, 4, 8, 15};
private float patchSize;
private GeneralPath[] patchShapes;
// used to center patch shape at origin because shape rotation works
// correctly.
private float patchOffset;
private Color backgroundColor = Color.WHITE;
/**
* Constructor.
*/
public NineBlockIdenticonRenderer() {
setPatchSize(DEFAULT_PATCH_SIZE);
}
/**
* Returns the size in pixels at which each patch will be rendered before
* they are scaled down to requested identicon size.
*
* @return
*/
public float getPatchSize() {
return patchSize;
}
/**
* Set the size in pixels at which each patch will be rendered before they
* are scaled down to requested identicon size. Default size is 20 pixels
* which means, for 9-block identicon, a 60x60 image will be rendered and
* scaled down.
*
* @param size patch size in pixels
*/
public void setPatchSize(float size) {
this.patchSize = size;
this.patchOffset = patchSize / 2.0f; // used to center patch shape at
float patchScale = patchSize / 4.0f;
// origin.
this.patchShapes = new GeneralPath[patchTypes.length];
for (int i = 0; i < patchTypes.length; i++) {
GeneralPath patch = new GeneralPath(GeneralPath.WIND_NON_ZERO);
boolean moveTo = true;
byte[] patchVertices = patchTypes[i];
for (int j = 0; j < patchVertices.length; j++) {
int v = (int) patchVertices[j];
if (v == PATCH_MOVETO) {
moveTo = true;
}
float vx = ((v % PATCH_GRIDS) * patchScale) - patchOffset;
float vy = ((float) Math.floor(((float) v) / PATCH_GRIDS))
* patchScale - patchOffset;
if (!moveTo) {
patch.lineTo(vx, vy);
} else {
moveTo = false;
patch.moveTo(vx, vy);
}
}
patch.closePath();
this.patchShapes[i] = patch;
}
}
public Color getBackgroundColor() {
return backgroundColor;
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
/**
* Returns rendered identicon image for given identicon code.
*
*
* Size of the returned identicon image is determined by patchSize set using
* {@link setPatchSize}. Since a 9-block identicon consists of 3x3 patches,
* width and height will be 3 times the patch size.
*
*
* @param code identicon code
* @param size image size
* @return identicon image
*/
public BufferedImage render(int code, int size) {
return renderQuilt(code, size);
}
protected BufferedImage renderQuilt(int code, int size) {
// -------------------------------------------------
// PREPARE
//
// decode the code into parts
// bit 0-1: middle patch type
// bit 2: middle invert
// bit 3-6: corner patch type
// bit 7: corner invert
// bit 8-9: corner turns
// bit 10-13: side patch type
// bit 14: side invert
// bit 15: corner turns
// bit 16-20: blue color component
// bit 21-26: green color component
// bit 27-31: red color component
int middleType = centerPatchTypes[code & 0x3];
boolean middleInvert = ((code >> 2) & 0x1) != 0;
int cornerType = (code >> 3) & 0x0f;
boolean cornerInvert = ((code >> 7) & 0x1) != 0;
int cornerTurn = (code >> 8) & 0x3;
int sideType = (code >> 10) & 0x0f;
boolean sideInvert = ((code >> 14) & 0x1) != 0;
int sideTurn = (code >> 15) & 0x3;
int blue = (code >> 16) & 0x01f;
int green = (code >> 21) & 0x01f;
int red = (code >> 27) & 0x01f;
// color components are used at top of the range for color difference
// use white background for now.
// TODO: support transparency.
Color fillColor = new Color(red << 3, green << 3, blue << 3);
// outline shapes with a noticeable color (complementary will do) if
// shape color and background color are too similar (measured by color
// distance).
Color strokeColor = null;
if (AvatarUtils.getColorDistance(fillColor, backgroundColor) < 32.0f) {
strokeColor = AvatarUtils.getComplementaryColor(fillColor);
}
// -------------------------------------------------
// RENDER
//
BufferedImage targetImage = new BufferedImage(size, size,
BufferedImage.TYPE_INT_RGB);
Graphics2D g = targetImage.createGraphics();
AvatarUtils.activeAntialiasing(g);
g.setBackground(backgroundColor);
g.clearRect(0, 0, size, size);
float blockSize = size / 3.0f;
float blockSize2 = blockSize * 2.0f;
// middle patch
drawPatch(g, blockSize, blockSize, blockSize, middleType, 0,
middleInvert, fillColor, strokeColor);
// side patchs, starting from top and moving clock-wise
drawPatch(g, blockSize, 0, blockSize, sideType, sideTurn++, sideInvert,
fillColor, strokeColor);
drawPatch(g, blockSize2, blockSize, blockSize, sideType, sideTurn++,
sideInvert, fillColor, strokeColor);
drawPatch(g, blockSize, blockSize2, blockSize, sideType, sideTurn++,
sideInvert, fillColor, strokeColor);
drawPatch(g, 0, blockSize, blockSize, sideType, sideTurn++, sideInvert,
fillColor, strokeColor);
// corner patchs, starting from top left and moving clock-wise
drawPatch(g, 0, 0, blockSize, cornerType, cornerTurn++, cornerInvert,
fillColor, strokeColor);
drawPatch(g, blockSize2, 0, blockSize, cornerType, cornerTurn++,
cornerInvert, fillColor, strokeColor);
drawPatch(g, blockSize2, blockSize2, blockSize, cornerType,
cornerTurn++, cornerInvert, fillColor, strokeColor);
drawPatch(g, 0, blockSize2, blockSize, cornerType, cornerTurn++,
cornerInvert, fillColor, strokeColor);
g.dispose();
return targetImage;
}
private 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 %= patchTypes.length;
turn %= 4;
if ((patchFlags[patch] & PATCH_INVERTED) != 0) {
invert = !invert;
}
Shape shape = patchShapes[patch];
double scale = ((double) size) / ((double) patchSize);
float offset = size / 2.0f;
// paint background
g.setColor(invert ? fillColor : 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 ? backgroundColor : fillColor);
g.fill(shape);
g.setTransform(savet);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy