/** * Convert the ByteMatrix to BitMatrix. * * @param matrix The input matrix. * @return The output matrix. */ private static BitMatrix convertByteMatrixToBitMatrix(ByteMatrix matrix) { int matrixWidgth = matrix.getWidth(); int matrixHeight = matrix.getHeight(); BitMatrix output = new BitMatrix(matrixWidgth, matrixHeight); output.clear(); for (int i = 0; i < matrixWidgth; i++) { for (int j = 0; j < matrixHeight; j++) { // Zero is white in the bytematrix if (matrix.get(i, j) == 1) { output.set(i, j); } } } return output; }
// Note that the input matrix uses 0 == white, 1 == black, while the output matrix uses // 0 == black, 255 == white (i.e. an 8 bit greyscale bitmap). private static BitMatrix renderResult(QRCode code, int width, int height, int quietZone) { ByteMatrix input = code.getMatrix(); if (input == null) { throw new IllegalStateException(); } int inputWidth = input.getWidth(); int inputHeight = input.getHeight(); int qrWidth = inputWidth + (quietZone << 1); int qrHeight = inputHeight + (quietZone << 1); int outputWidth = Math.max(width, qrWidth); int outputHeight = Math.max(height, qrHeight); int multiple = Math.min(outputWidth / qrWidth, outputHeight / qrHeight); // Padding includes both the quiet zone and the extra white pixels to accommodate the requested // dimensions. For example, if input is 25x25 the QR will be 33x33 including the quiet zone. // If the requested size is 200x160, the multiple will be 4, for a QR of 132x132. These will // handle all the padding from 100x100 (the actual QR) up to 200x160. int leftPadding = (outputWidth - (inputWidth * multiple)) / 2; int topPadding = (outputHeight - (inputHeight * multiple)) / 2; BitMatrix output = new BitMatrix(outputWidth, outputHeight); for (int inputY = 0, outputY = topPadding; inputY < inputHeight; inputY++, outputY += multiple) { // Write the contents of this row of the barcode for (int inputX = 0, outputX = leftPadding; inputX < inputWidth; inputX++, outputX += multiple) { if (input.get(inputX, inputY) == 1) { output.setRegion(outputX, outputY, multiple, multiple); } } } return output; }
/** * Encode the given symbol info to a bit matrix. * * @param placement The DataMatrix placement. * @param symbolInfo The symbol info to encode. * @return The bit matrix generated. */ private static BitMatrix encodeLowLevel(DefaultPlacement placement, SymbolInfo symbolInfo) { int symbolWidth = symbolInfo.getSymbolDataWidth(); int symbolHeight = symbolInfo.getSymbolDataHeight(); ByteMatrix matrix = new ByteMatrix(symbolInfo.getSymbolWidth(), symbolInfo.getSymbolHeight()); int matrixY = 0; for (int y = 0; y < symbolHeight; y++) { // Fill the top edge with alternate 0 / 1 int matrixX; if ((y % symbolInfo.matrixHeight) == 0) { matrixX = 0; for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { matrix.set(matrixX, matrixY, (x % 2) == 0); matrixX++; } matrixY++; } matrixX = 0; for (int x = 0; x < symbolWidth; x++) { // Fill the right edge with full 1 if ((x % symbolInfo.matrixWidth) == 0) { matrix.set(matrixX, matrixY, true); matrixX++; } matrix.set(matrixX, matrixY, placement.getBit(x, y)); matrixX++; // Fill the right edge with alternate 0 / 1 if ((x % symbolInfo.matrixWidth) == symbolInfo.matrixWidth - 1) { matrix.set(matrixX, matrixY, (y % 2) == 0); matrixX++; } } matrixY++; // Fill the bottom edge with full 1 if ((y % symbolInfo.matrixHeight) == symbolInfo.matrixHeight - 1) { matrixX = 0; for (int x = 0; x < symbolInfo.getSymbolWidth(); x++) { matrix.set(matrixX, matrixY, true); matrixX++; } matrixY++; } } return convertByteMatrixToBitMatrix(matrix); }
public Renderable createImage( JasperReportsContext jasperReportsContext, JRComponentElement componentElement, QRCodeBean qrCodeBean, String message) { Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>(); hints.put(EncodeHintType.CHARACTER_SET, QRCodeComponent.PROPERTY_DEFAULT_ENCODING); ErrorCorrectionLevel errorCorrectionLevel = qrCodeBean.getErrorCorrectionLevel().getErrorCorrectionLevel(); hints.put(EncodeHintType.ERROR_CORRECTION, errorCorrectionLevel); ByteMatrix matrix = null; SVGCanvasProvider provider = null; try { QRCode qrCode = Encoder.encode(message, errorCorrectionLevel, hints); matrix = qrCode.getMatrix(); provider = new SVGCanvasProvider(false, OrientationEnum.UP.getValue()); } catch (WriterException e) { throw new JRRuntimeException(e); } catch (BarcodeCanvasSetupException e) { throw new JRRuntimeException(e); } Document svgDoc = provider.getDOM(); Element svg = svgDoc.getDocumentElement(); int codeWidth = matrix.getWidth(); int codeHeight = matrix.getHeight(); JRStyle elementStyle = componentElement.getStyle(); int elementWidth = componentElement.getWidth() - (elementStyle == null ? 0 : (elementStyle.getLineBox().getLeftPadding() + elementStyle.getLineBox().getRightPadding())); int elementHeight = componentElement.getHeight() - (elementStyle == null ? 0 : (elementStyle.getLineBox().getTopPadding() + elementStyle.getLineBox().getBottomPadding())); int margin = qrCodeBean.getMargin() == null ? DEFAULT_MARGIN : qrCodeBean.getMargin(); int matrixWidth = codeWidth + 2 * margin; int matrixHeight = codeHeight + 2 * margin; // scaling to match the image size as closely as possible so that it looks good in html. // the resolution is taken into account because the html exporter rasterizes to a png // that has the same size as the svg. int resolution = JRPropertiesUtil.getInstance(jasperReportsContext) .getIntegerProperty( componentElement, BarcodeRasterizedImageProducer.PROPERTY_RESOLUTION, 300); int imageWidth = (int) Math.ceil(elementWidth * (resolution / 72d)); int imageHeight = (int) Math.ceil(elementHeight * (resolution / 72d)); double horizontalScale = ((double) imageWidth) / matrixWidth; double verticalScale = ((double) imageHeight) / matrixHeight; // we are scaling with integer units, not considering fractional coordinates for now int scale = Math.max(1, (int) Math.min(Math.ceil(horizontalScale), Math.ceil(verticalScale))); int qrWidth = scale * matrixWidth; int qrHeight = scale * matrixHeight; // scaling again because of the integer units double qrScale = Math.max(1d, Math.max(((double) qrWidth) / imageWidth, ((double) qrHeight) / imageHeight)); int svgWidth = (int) Math.ceil(qrScale * imageWidth); int svgHeight = (int) Math.ceil(qrScale * imageHeight); svg.setAttribute("width", String.valueOf(svgWidth)); svg.setAttribute("height", String.valueOf(svgHeight)); svg.setAttribute("viewBox", "0 0 " + svgWidth + " " + svgHeight); int xOffset = Math.max(0, (svgWidth - qrWidth) / 2); int yOffset = Math.max(0, (svgHeight - qrHeight) / 2); Color color = componentElement.getForecolor(); String fill = "#" + JRColorUtil.getColorHexa(color); String fillOpacity = color.getAlpha() < 255 ? Float.toString(((float) color.getAlpha()) / 255) : null; String rectangleSize = Integer.toString(scale); for (int x = 0; x < codeWidth; x++) { for (int y = 0; y < codeHeight; y++) { if (matrix.get(x, y) == 1) { Element element = svgDoc.createElementNS(svg.getNamespaceURI(), "rect"); element.setAttribute("x", String.valueOf(xOffset + scale * (x + margin))); element.setAttribute("y", String.valueOf(yOffset + scale * (y + margin))); element.setAttribute("width", rectangleSize); element.setAttribute("height", rectangleSize); element.setAttribute("fill", fill); if (fillOpacity != null) { element.setAttribute("fill-opacity", fillOpacity); } svgDoc.getFirstChild().appendChild(element); } } } Source source = new DOMSource(svgDoc); StringWriter outWriter = new StringWriter(); Result output = new StreamResult(outWriter); try { Transformer transformer = TransformerFactory.newInstance().newTransformer(); transformer.transform(source, output); } catch (TransformerException e) { throw new JRRuntimeException(e); } String svgString = outWriter.toString(); return new BatikRenderer(svgString, null); }