/* (c) 2016 Open Source Geospatial Foundation - all rights reserved
* This code is licensed under the GPL 2.0 license, available at the root
* application directory.
*/
package org.geoserver.wms.legendgraphic;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.geoserver.wms.GetLegendGraphicRequest;
import org.geoserver.wms.legendgraphic.LegendUtils.LegendLayout;
import org.geoserver.wms.map.ImageUtils;
import org.geotools.styling.Rule;
/**
*
* Utility class containing static methods for merging single legend elements into a final output,
* following a set of given layout options / constraints.
*
* @author mauro.bartolomeoli@geo-solutions.it
*
*/
public class LegendMerger {
/**
* Set of options for legend merging.
* Used to set all needed options for merging a set of icons in a single place
*
* @author mauro.bartolomeoli@geo-solutions.it
*
*/
public static class MergeOptions {
List<RenderedImage> imageStack;
int dx;
int dy;
int margin;
int labelMargin;
Color backgroundColor;
boolean transparent;
boolean antialias;
LegendLayout layout;
int rowWidth;
int rows;
int columnHeight;
int columns;
Font labelFont;
boolean forceLabelsOn;
boolean forceLabelsOff;
/**
* Build a new set of options, specifying each option.
*
* @param imageStack images representing the icons to merge
* @param dx horizontal dimension for raster icons
* @param dy vertical dimension for raster icons
* @param margin margin between icons
* @param labelMargin margin between icon and label
* @param backgroundColor background color for the merged image
* @param transparent using a transparent background
* @param antialias enable antialiasing of fonts in labels
* @param layout layout to be used (vertical, horizontal)
* @param rowWidth rowwidth parameter (for horizontal layout)
* @param rows # of rows (for horizontal layout)
* @param columnHeight columnheight parameter (for vertical layout)
* @param columns # of columns (for vertical layout)
* @param labelFont font to be used for labels
* @param forceLabelsOn force labels to be always rendered
* @param forceLabelsOff force labels to be never rendered
*/
public MergeOptions(List<RenderedImage> imageStack, int dx, int dy, int margin, int labelMargin,
Color backgroundColor, boolean transparent, boolean antialias, LegendLayout layout,
int rowWidth, int rows, int columnHeight, int columns, Font labelFont, boolean forceLabelsOn, boolean forceLabelsOff) {
super();
this.imageStack = imageStack;
this.dx = dx;
this.dy = dy;
this.margin = margin;
this.labelMargin = labelMargin;
this.backgroundColor = backgroundColor;
this.transparent = transparent;
this.antialias = antialias;
this.layout = layout;
this.rowWidth = rowWidth;
this.rows = rows;
this.columnHeight = columnHeight;
this.columns = columns;
this.labelFont = labelFont;
this.forceLabelsOn = forceLabelsOn;
this.forceLabelsOff = forceLabelsOff;
}
/**
* Build a new set of options, getting most of the options from a GetLegendGraphicRequest object.
*
* @param imageStack images representing the icons to merge
* @param dx horizontal dimension for raster icons
* @param dy vertical dimension for raster icons
* @param margin margin between icons
* @param req GetLegendGraphic request descriptor object
* @param forceLabelsOn force labels to be always rendered
* @param forceLabelsOff force labels to be never rendered
*/
public MergeOptions (List<RenderedImage> imageStack, int dx, int dy, int margin, int labelMargin,
GetLegendGraphicRequest req, boolean forceLabelsOn, boolean forceLabelsOff) {
this(imageStack, dx, dy, margin, labelMargin, LegendUtils.getBackgroundColor(req),
req.isTransparent(), LegendUtils.isFontAntiAliasing(req), LegendUtils.getLayout(req),
LegendUtils.getRowWidth(req), LegendUtils.getRows(req), LegendUtils.getColumnHeight(req), LegendUtils.getColumns(req),
LegendUtils.getLabelFont(req), forceLabelsOn, forceLabelsOff);
}
public List<RenderedImage> getImageStack() {
return imageStack;
}
public void setImageStack(List<RenderedImage> imageStack) {
this.imageStack = imageStack;
}
public int getDx() {
return dx;
}
public void setDx(int dx) {
this.dx = dx;
}
public int getDy() {
return dy;
}
public void setDy(int dy) {
this.dy = dy;
}
public int getMargin() {
return margin;
}
public void setMargin(int margin) {
this.margin = margin;
}
public Color getBackgroundColor() {
return backgroundColor;
}
public void setBackgroundColor(Color backgroundColor) {
this.backgroundColor = backgroundColor;
}
public boolean isTransparent() {
return transparent;
}
public void setTransparent(boolean transparent) {
this.transparent = transparent;
}
public boolean isAntialias() {
return antialias;
}
public void setAntialias(boolean antialias) {
this.antialias = antialias;
}
public LegendLayout getLayout() {
return layout;
}
public void setLayout(LegendLayout layout) {
this.layout = layout;
}
public int getRowWidth() {
return rowWidth;
}
public void setRowWidth(int rowWidth) {
this.rowWidth = rowWidth;
}
public int getRows() {
return rows;
}
public void setRows(int rows) {
this.rows = rows;
}
public int getColumnHeight() {
return columnHeight;
}
public void setColumnHeight(int columnHeight) {
this.columnHeight = columnHeight;
}
public int getColumns() {
return columns;
}
public void setColumns(int columns) {
this.columns = columns;
}
public Font getLabelFont() {
return labelFont;
}
public void setLabelFont(Font labelFont) {
this.labelFont = labelFont;
}
public boolean isForceLabelsOn() {
return forceLabelsOn;
}
public void setForceLabelsOn(boolean forceLabelsOn) {
this.forceLabelsOn = forceLabelsOn;
}
public boolean isForceLabelsOff() {
return forceLabelsOff;
}
public void setForceLabelsOff(boolean forceLabelsOff) {
this.forceLabelsOff = forceLabelsOff;
}
public int getLabelMargin() {
return labelMargin;
}
public void setLabelMargin(int labelMargin) {
this.labelMargin = labelMargin;
}
public static MergeOptions createFromRequest(List<RenderedImage> imageStack, int dx, int dy, int margin, int labelMargin,
GetLegendGraphicRequest req, boolean forceLabelsOn, boolean forceLabelsOff) {
return new LegendMerger.MergeOptions(imageStack, dx, dy, margin, labelMargin,
req, forceLabelsOn, forceLabelsOff);
}
}
/**
* Receives a list of <code>BufferedImages</code>, embedded in the mergeOptions object,
* and produces a new one which holds all the images in <code>imageStack</code> one above the
* other.
*
* @param mergeOptions options to be used for merging
* @return the legend image with all the images on the argument list.
*/
public static BufferedImage mergeRasterLegends(MergeOptions mergeOptions) {
List<RenderedImage> imageStack= mergeOptions.getImageStack();
LegendLayout layout = mergeOptions.getLayout();
List<BufferedImage> nodes = new ArrayList<BufferedImage>();
final int imgCount = imageStack.size();
for (int i = 0; i < imgCount; i++) {
nodes.add((BufferedImage) imageStack.get(i));
}
BufferedImage finalLegend = null;
if (layout == LegendLayout.HORIZONTAL) {
Row[] rows = createRows(nodes, mergeOptions.getRowWidth(), mergeOptions.getRows());
finalLegend = buildFinalHLegend(rows, mergeOptions);
}
if (layout == LegendLayout.VERTICAL) {
Column[] columns = createColumns(nodes, mergeOptions.getColumnHeight(), mergeOptions.getColumns());
finalLegend = buildFinalVLegend(columns, mergeOptions);
}
return finalLegend;
}
/**
* Receives a list of <code>BufferedImages</code>, embedded in the mergeOptions object,
* and produces a new one which holds all the images in <code>imageStack</code> one above the
* other, handling labels.
*
* @param rules The applicable rules, one for each image in the stack (if not null it's used to compute labels)
* @param request The request.
* @param mergeOptions options to be used for merging
*
* @return the image with all the images on the argument list.
*
*/
public static BufferedImage mergeLegends(Rule[] rules, GetLegendGraphicRequest req, MergeOptions mergeOptions) {
List<RenderedImage> imageStack= mergeOptions.getImageStack();
// Builds legend nodes (graphics + label)
final int imgCount = imageStack.size();
List<BufferedImage> nodes = new ArrayList<BufferedImage>();
// Single legend, no rules, no force label
if (imgCount == 1 && (!mergeOptions.isForceLabelsOn() || rules == null)) {
return (BufferedImage) imageStack.get(0);
} else {
for (int i = 0; i < imgCount; i++) {
BufferedImage img = (BufferedImage) imageStack.get(i);
if (rules != null && rules[i] != null) {
BufferedImage label = renderLabel(img, rules[i], req, mergeOptions);
if (label != null) {
img = joinBufferedImageHorizzontally(img, label, mergeOptions.getLabelFont(), mergeOptions.isAntialias(),
mergeOptions.isTransparent(), mergeOptions.getBackgroundColor(), mergeOptions.getLabelMargin());
}
nodes.add(img);
} else {
nodes.add(img);
}
}
}
// Sets legend nodes into a matrix according to layout rules
LegendLayout layout = mergeOptions.getLayout();
BufferedImage finalLegend = null;
if (layout == LegendLayout.HORIZONTAL) {
Row[] rows = createRows(nodes, mergeOptions.getRowWidth(), mergeOptions.getRows());
finalLegend = buildFinalHLegend(rows, mergeOptions);
}
if (layout == LegendLayout.VERTICAL) {
Column[] columns = createColumns(nodes, mergeOptions.getColumnHeight(), mergeOptions.getColumns());
finalLegend = buildFinalVLegend(columns, mergeOptions);
}
return finalLegend;
}
/**
* Receives a list of <code>BufferedImages</code>, embedded in the mergeOptions object,
* and produces a new one which holds all the images in <code>imageStack</code> one above the
* other, handling labels.
*
* @param rules The applicable rules, one for each image in the stack (if not null it's used to compute labels)
* @param mergeOptions options to be used for merging
*
* @return the image with all the images on the argument list.
*
*/
public static BufferedImage mergeGroups(Rule[] rules, MergeOptions mergeOptions) {
List<RenderedImage> imageStack= mergeOptions.getImageStack();
final int imgCount = imageStack.size();
if (imgCount == 1 && (!mergeOptions.isForceLabelsOn() || rules == null)) {
return (BufferedImage) imageStack.get(0);
}
List<BufferedImage> nodes = new ArrayList<BufferedImage>(imgCount / 2);
// Single legend, no rules, no force label
for (int i = 0; i < imgCount; i = i + 2) {
BufferedImage lbl = (BufferedImage) imageStack.get(i);
BufferedImage img = (BufferedImage) imageStack.get(i + 1);
img = joinBufferedImageVertically(lbl, img, mergeOptions.getLabelFont(), mergeOptions.isAntialias(), mergeOptions.isTransparent(),
mergeOptions.getBackgroundColor());
nodes.add(img);
}
// Sets legend nodes into a matrix according to layout rules
LegendLayout layout = mergeOptions.getLayout();
BufferedImage finalLegend = null;
if (layout == LegendLayout.HORIZONTAL) {
Row[] rows = createRows(nodes, 0, 0);
finalLegend = buildFinalHLegend(rows, mergeOptions);
}
if (layout == LegendLayout.VERTICAL) {
Column[] columns = createColumns(nodes, 0, 0);
finalLegend = buildFinalVLegend(columns, mergeOptions);
}
return finalLegend;
}
/**
*
* Represents a column of legends images
*
*/
private static class Column {
private int width;
private int height;
private List<BufferedImage> nodes = new ArrayList<BufferedImage>();
public void addNode(BufferedImage img) {
nodes.add(img);
width = Math.max(width, img.getWidth());
height = height + img.getHeight();
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public List<BufferedImage> getNodes() {
return nodes;
}
}
/**
*
* Represents a row of legends images
*
*/
private static class Row {
private int width;
private int height;
private List<BufferedImage> nodes = new ArrayList<BufferedImage>();
public void addNode(BufferedImage img) {
nodes.add(img);
height = Math.max(height, img.getHeight());
width = width + img.getWidth();
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
public List<BufferedImage> getNodes() {
return nodes;
}
}
/**
* Creates legends columns for vertical layout according to max height and max columns limits
*
* @param nodes legend images
* @param maxHeight maximum height of legend
* @param maxColumns maximum number of columns
*
*/
private static Column[] createColumns(List<BufferedImage> nodes, int maxHeight, int maxColumns) {
Column[] legendMatrix = new Column[0];
/*
* Limit max height
*/
if (maxHeight > 0) {
/*
* Limit max column
*/
int cnLimit = maxColumns > 0 ? maxColumns : nodes.size();
legendMatrix = new Column[cnLimit];
legendMatrix[0] = new Column();
int cn = 0;
int columnHeight = 0;
for (int i = 0; i < nodes.size(); i++) {
BufferedImage node = nodes.get(i);
if (columnHeight <= maxHeight) {
// Fill current column
legendMatrix[cn].addNode(node);
columnHeight = columnHeight + node.getHeight();
} else {
// Add current node to next column
i--;
cn++;
// Stop if column limits is reached
if (cn == cnLimit) {
break;
}
// Reset column counter
columnHeight = 0;
// Create new column
legendMatrix[cn] = new Column();
}
}
} else {
/*
* Limit max column, if no limit set it to 1
*/
int colNumber = maxColumns > 0 ? maxColumns : 1;
legendMatrix = new Column[colNumber];
legendMatrix[0] = new Column();
int rowNumber = (int) Math.ceil((float) nodes.size() / colNumber);
int cn = 0;
int rc = 0;
for (int i = 0; i < nodes.size(); i++) {
if (rc < rowNumber) {
legendMatrix[cn].addNode(nodes.get(i));
rc++;
} else {
i--;
cn++;
rc = 0;
legendMatrix[cn] = new Column();
}
}
}
return legendMatrix;
}
/**
* Creates legends rows for horizontal layout according to max width and max rows limits
*
* @param nodes legend images
* @param maxWidth maximum width of legend
* @param maxRows maximum number of rows
*
*/
private static Row[] createRows(List<BufferedImage> nodes, int maxWidth, int maxRows) {
Row[] legendMatrix = new Row[0];
/*
* Limit max height
*/
if (maxWidth > 0) {
/*
* Limit max column
*/
int rnLimit = maxRows > 0 ? maxRows : nodes.size();
legendMatrix = new Row[rnLimit];
legendMatrix[0] = new Row();
int rn = 0;
int rowWidth = 0;
for (int i = 0; i < nodes.size(); i++) {
BufferedImage node = nodes.get(i);
if (rowWidth <= maxWidth) {
// Fill current column
legendMatrix[rn].addNode(node);
rowWidth = rowWidth + node.getWidth();
} else {
// Add current node to next column
i--;
rn++;
// Stop if column limits is reached
if (rn == rnLimit) {
break;
}
// Reset column counter
rowWidth = 0;
// Create new column
legendMatrix[rn] = new Row();
}
}
} else {
/*
* Limit max column, if no limit set it to 1
*/
int rowNumber = maxRows > 0 ? maxRows : 1;
legendMatrix = new Row[rowNumber];
legendMatrix[0] = new Row();
int colNumber = (int) Math.ceil((float) nodes.size() / rowNumber);
int rn = 0;
int cc = 0;
for (int i = 0; i < nodes.size(); i++) {
if (cc < colNumber) {
legendMatrix[rn].addNode(nodes.get(i));
cc++;
} else {
i--;
rn++;
cc = 0;
legendMatrix[rn] = new Row();
}
}
}
return legendMatrix;
}
/**
* Renders legend columns and cut off the node that exceeds the maximum limits
*
* @param columns list of columns to draw
* @param options options to be used for merging
*
* @return BufferedImage of legend
*
*/
private static BufferedImage buildFinalVLegend(Column[] columns, MergeOptions options) {
int totalWidth = 0;
int totalHeight = 0;
for (Column c : columns) {
if (c != null) {
if (totalWidth > 0) {
totalWidth = totalWidth + options.getDx();
}
totalWidth = totalWidth + c.getWidth();
int h = c.getHeight() + (c.nodes.size() - 1) * options.getDy();
totalHeight = Math.max(totalHeight, h);
}
}
totalWidth = totalWidth + options.getMargin() * 2;
totalHeight = totalHeight + options.getMargin() * 2;
// buffer the width a bit
totalWidth += 2;
final BufferedImage finalLegend = ImageUtils.createImage(totalWidth, totalHeight,
(IndexColorModel) null, options.isTransparent());
final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>();
Graphics2D finalGraphics = ImageUtils.prepareTransparency(options.isTransparent(), options.getBackgroundColor(),
finalLegend, hintsMap);
// finalGraphics.setFont(labelFont);
if (options.isAntialias()) {
finalGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
finalGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
int vOffset = options.getMargin();
int hOffset = options.getMargin();
for (Column c : columns) {
if (c != null) {
for (BufferedImage n : c.getNodes()) {
finalGraphics.drawImage(n, hOffset, vOffset, null);
vOffset = vOffset + n.getHeight() + options.getDy();
}
hOffset = hOffset + c.getWidth() + options.getDx();
vOffset = options.getMargin();
}
}
finalGraphics.dispose();
return finalLegend;
}
/**
* Renders legend rows and cut off the node that exceeds the maximum limits
*
* @param rows list of rows to draw
* @param options options to be used for merging
*
* @return BufferedImage of legend
*
*/
private static BufferedImage buildFinalHLegend(Row[] rows, MergeOptions options) {
int totalWidth = 0;
int totalHeight = 0;
for (Row r : rows) {
if (r != null) {
if (totalHeight > 0) {
totalHeight = totalHeight + options.getDy();
}
totalHeight = totalHeight + r.getHeight();
int w = r.getWidth() + (r.nodes.size() - 1) * options.getDx();
totalWidth = Math.max(totalWidth, w);
}
}
totalWidth = totalWidth + options.getMargin() * 2;
totalHeight = totalHeight + options.getMargin() * 2;
// buffer the width a bit
totalWidth += 2;
final BufferedImage finalLegend = ImageUtils.createImage(totalWidth, totalHeight,
(IndexColorModel) null, options.isTransparent());
final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>();
Graphics2D finalGraphics = ImageUtils.prepareTransparency(options.isTransparent(), options.getBackgroundColor(),
finalLegend, hintsMap);
// finalGraphics.setFont(labelFont);
if (options.isAntialias()) {
finalGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
finalGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
int vOffset = options.getMargin();
int hOffset = options.getMargin();
for (Row r : rows) {
if (r != null) {
for (BufferedImage n : r.getNodes()) {
finalGraphics.drawImage(n, hOffset, vOffset, null);
hOffset = hOffset + n.getWidth() + options.getDx();
}
vOffset = vOffset + r.getHeight() + options.getDy();
hOffset = options.getMargin();
}
}
finalGraphics.dispose();
return finalLegend;
}
/**
* Join image and label to create a single legend node image horizzontally
*
* @param img image of legend
* @param label label of legend
* @param labelFont font to use
* @param useAA if true applies anti aliasing
* @param transparent if true make legend transparent
* @param backgroundColor background color of legend
* @return BufferedImage of image and label side by side and vertically center
*/
private static BufferedImage joinBufferedImageHorizzontally(BufferedImage img,
BufferedImage label, Font labelFont, boolean useAA, boolean transparent,
Color backgroundColor, int labelXOffset) {
// do some calculate first
int wid = img.getWidth() + label.getWidth() + labelXOffset;
int height = Math.max(img.getHeight(), label.getHeight());
// create a new buffer and draw two image into the new image
BufferedImage newImage = ImageUtils.createImage(wid, height, (IndexColorModel) null,
transparent);
final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>();
Graphics2D g2 = ImageUtils.prepareTransparency(transparent, backgroundColor, newImage,
hintsMap);
g2.setFont(labelFont);
if (useAA) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
// move the images to the vertical center of the row
int imgOffset = (int) Math.round((height - img.getHeight()) / 2d);
int labelYOffset = (int) Math.round((height - label.getHeight()) / 2d);
g2.drawImage(img, null, 0, imgOffset);
g2.drawImage(label, null, img.getWidth() + labelXOffset, labelYOffset);
g2.dispose();
return newImage;
}
/**
* Join image and label to create a single legend node image vertically
*
* @param img image of legend
* @param label label of legend
* @param labelFont font to use
* @param useAA if true applies anti aliasing
* @param transparent if true make legend transparent
* @param backgroundColor background color of legend
* @return BufferedImage of image and label side by side and vertically center
*/
private static BufferedImage joinBufferedImageVertically(BufferedImage label,
BufferedImage img, Font labelFont, boolean useAA, boolean transparent,
Color backgroundColor) {
// do some calculate first
int offset = 0;
int height = img.getHeight() + label.getHeight() + offset;
int wid = Math.max(img.getWidth(), label.getWidth()) + offset;
// create a new buffer and draw two image into the new image
BufferedImage newImage = ImageUtils.createImage(wid, height, (IndexColorModel) null,
transparent);
final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>();
Graphics2D g2 = ImageUtils.prepareTransparency(transparent, backgroundColor, newImage,
hintsMap);
g2.setFont(labelFont);
if (useAA) {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
// move the images to the vertical center of the row
g2.drawImage(label, null, 0, 0);
g2.drawImage(img, null, 0, label.getHeight());
g2.dispose();
return newImage;
}
/**
* Renders the legend image label
*
* @param img the BufferedImage
* @param rule the applicable rule for img, if rule is not null the label will be rendered
* @param req the request
* @param options options to be used for merging
*
* @return the BufferedImage of label
*
*/
private static BufferedImage renderLabel(RenderedImage img, Rule rule,
GetLegendGraphicRequest req, MergeOptions options) {
BufferedImage labelImg = null;
if (!options.isForceLabelsOff() && rule != null) {
String label = LegendUtils.getRuleLabel(rule, req);
if (label != null && label.length() > 0) {
final BufferedImage renderedLabel = getRenderedLabel((BufferedImage) img, label,
req);
labelImg = renderedLabel;
}
}
return labelImg;
}
/**
* Renders a label on the given image, using parameters from the request for the rendering style.
*
* @param image
* @param label
* @param request
*
*/
protected static BufferedImage getRenderedLabel(BufferedImage image, String label, GetLegendGraphicRequest req) {
final Graphics2D graphics = image.createGraphics();
Font labelFont = LegendUtils.getLabelFont(req);
boolean useAA = LegendUtils.isFontAntiAliasing(req);
graphics.setFont(labelFont);
if (useAA) {
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
} else {
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_OFF);
}
return LegendUtils.renderLabel(label, graphics, req);
}
}