| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.apache.ofbiz.common.qrcode; |
| |
| import java.awt.Graphics2D; |
| import java.awt.geom.AffineTransform; |
| import java.awt.image.AffineTransformOp; |
| import java.awt.image.BufferedImage; |
| import java.util.Arrays; |
| import java.util.EnumMap; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.io.IOException; |
| import java.lang.Integer; |
| |
| import org.apache.ofbiz.base.util.Debug; |
| import org.apache.ofbiz.base.util.FileUtil; |
| import org.apache.ofbiz.base.util.UtilProperties; |
| import org.apache.ofbiz.base.util.UtilValidate; |
| import org.apache.ofbiz.common.image.ImageTransform; |
| import org.apache.ofbiz.service.DispatchContext; |
| import org.apache.ofbiz.service.ServiceUtil; |
| |
| import com.google.zxing.BarcodeFormat; |
| import com.google.zxing.ChecksumException; |
| import com.google.zxing.DecodeHintType; |
| import com.google.zxing.EncodeHintType; |
| import com.google.zxing.FormatException; |
| import com.google.zxing.MultiFormatWriter; |
| import com.google.zxing.NotFoundException; |
| import com.google.zxing.WriterException; |
| import com.google.zxing.common.BitMatrix; |
| import com.google.zxing.common.DecoderResult; |
| import com.google.zxing.common.DetectorResult; |
| import com.google.zxing.qrcode.decoder.Decoder; |
| import com.google.zxing.qrcode.detector.Detector; |
| |
| import freemarker.template.utility.StringUtil; |
| |
| /** |
| * Services for QRCode. |
| */ |
| public class QRCodeServices { |
| |
| public static final String module = QRCodeServices.class.getName(); |
| |
| public static final String QRCODE_DEFAULT_WIDTH = UtilProperties.getPropertyValue("qrcode", "qrcode.default.width", "200"); |
| |
| public static final String QRCODE_DEFAULT_HEIGHT = UtilProperties.getPropertyValue("qrcode", "qrcode.default.height", "200"); |
| |
| public static final String QRCODE_DEFAULT_FORMAT = UtilProperties.getPropertyValue("qrcode", "qrcode.default.format", "jpg"); |
| |
| public static final String QRCODE_FORMAT_SUPPORTED = UtilProperties.getPropertyValue("qrcode", "qrcode.format.supported", "jpg|png|bmp"); |
| |
| public static final String QRCODE_DEFAULT_LOGOIMAGE = UtilProperties.getPropertyValue("qrcode", "qrcode.default.logoimage"); |
| |
| public static BufferedImage defaultLogoImage; |
| |
| public static final String[] FORMAT_NAMES = StringUtil.split(QRCODE_FORMAT_SUPPORTED, '|'); |
| |
| public static final List<String> FORMATS_SUPPORTED = Arrays.asList(FORMAT_NAMES); |
| |
| public static final int MIN_SIZE = 20; |
| |
| public static final int MAX_SIZE = 500; |
| |
| private static final int BLACK = 0xFF000000; |
| |
| private static final int WHITE = 0xFFFFFFFF; |
| |
| static { |
| if (UtilValidate.isNotEmpty(QRCODE_DEFAULT_LOGOIMAGE)) { |
| try { |
| Map<String, Object> logoImageResult = ImageTransform.getBufferedImage(FileUtil.getFile(QRCODE_DEFAULT_LOGOIMAGE).getAbsolutePath(), Locale.getDefault()); |
| defaultLogoImage = (BufferedImage) logoImageResult.get("bufferedImage"); |
| if (UtilValidate.isEmpty(defaultLogoImage)) { |
| Debug.logError("Your logo image file(" + QRCODE_DEFAULT_LOGOIMAGE + ") cannot be read by javax.imageio.ImageIO. Please use png, jpeg formats instead of ico and etc.", module); |
| } |
| } catch (IllegalArgumentException e) { |
| defaultLogoImage = null; |
| } catch (IOException e) { |
| defaultLogoImage = null; |
| } |
| } |
| } |
| |
| /** Streams QR Code to the result. */ |
| public static Map<String, Object> generateQRCodeImage(DispatchContext ctx,Map<String, Object> context) { |
| Locale locale = (Locale) context.get("locale"); |
| String message = (String) context.get("message"); |
| Integer width = (Integer) context.get("width"); |
| Integer height = (Integer) context.get("height"); |
| String format = (String) context.get("format"); |
| String encoding = (String) context.get("encoding"); |
| Boolean verifyOutput = (Boolean) context.get("verifyOutput"); |
| String logoImage = (String) context.get("logoImage"); |
| Integer logoImageMaxWidth = (Integer) context.get("logoImageMaxWidth"); |
| Integer logoImageMaxHeight = (Integer) context.get("logoImageMaxHeight"); |
| |
| if (UtilValidate.isEmpty(message)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ParameterCannotEmpty", new Object[] { "message" }, locale)); |
| } |
| if (width == null) { |
| width = Integer.parseInt(QRCODE_DEFAULT_WIDTH); |
| } |
| if (width.intValue() < MIN_SIZE || width.intValue() > MAX_SIZE) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "SizeOutOfBorderError", new Object[] {"width", String.valueOf(width), String.valueOf(MIN_SIZE), String.valueOf(MAX_SIZE)}, locale)); |
| } |
| if (height == null) { |
| height = Integer.parseInt(QRCODE_DEFAULT_HEIGHT); |
| } |
| if (height.intValue() < MIN_SIZE || height.intValue() > MAX_SIZE) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "SizeOutOfBorderError", |
| new Object[] { "height", String.valueOf(height), String.valueOf(MIN_SIZE), String.valueOf(MAX_SIZE) }, locale)); |
| } |
| if (UtilValidate.isEmpty(format)) { |
| format = QRCODE_DEFAULT_FORMAT; |
| } |
| if (!FORMATS_SUPPORTED.contains(format)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ErrorFormatNotSupported", new Object[] { format }, locale)); |
| } |
| Map<EncodeHintType, Object> encodeHints = null; |
| if (UtilValidate.isNotEmpty(encoding)) { |
| encodeHints = new EnumMap<>(EncodeHintType.class); |
| encodeHints.put(EncodeHintType.CHARACTER_SET, encoding); |
| } |
| |
| try { |
| BitMatrix bitMatrix = new MultiFormatWriter().encode(message, BarcodeFormat.QR_CODE, width, height, encodeHints); |
| BufferedImage bufferedImage = toBufferedImage(bitMatrix, format); |
| BufferedImage logoBufferedImage = null; |
| if (UtilValidate.isNotEmpty(logoImage)) { |
| Map<String, Object> logoImageResult; |
| try { |
| logoImageResult = ImageTransform.getBufferedImage(FileUtil.getFile(logoImage).getAbsolutePath(), locale); |
| logoBufferedImage = (BufferedImage) logoImageResult.get("bufferedImage"); |
| } catch (IllegalArgumentException e) { |
| // do nothing |
| } catch (IOException e) { |
| // do nothing |
| } |
| } |
| if (UtilValidate.isEmpty(logoBufferedImage)) { |
| logoBufferedImage = defaultLogoImage; |
| } |
| |
| BufferedImage newBufferedImage = null; |
| if (UtilValidate.isNotEmpty(logoBufferedImage)) { |
| if (UtilValidate.isNotEmpty(logoImageMaxWidth) && UtilValidate.isNotEmpty(logoImageMaxHeight) && (logoBufferedImage.getWidth() > logoImageMaxWidth.intValue() || logoBufferedImage.getHeight() > logoImageMaxHeight.intValue())) { |
| Map<String, String> typeMap = new HashMap<String, String>(); |
| typeMap.put("width", logoImageMaxWidth.toString()); |
| typeMap.put("height", logoImageMaxHeight.toString()); |
| Map<String, Map<String, String>> dimensionMap = new HashMap<String, Map<String, String>>(); |
| dimensionMap.put("QRCode", typeMap); |
| Map<String, Object> logoImageResult = ImageTransform.scaleImage(logoBufferedImage, (double) logoBufferedImage.getWidth(), (double) logoBufferedImage.getHeight(), dimensionMap, "QRCode", locale); |
| logoBufferedImage = (BufferedImage) logoImageResult.get("bufferedImage"); |
| } |
| BitMatrix newBitMatrix = bitMatrix.clone(); |
| newBufferedImage = toBufferedImage(newBitMatrix, format); |
| Graphics2D graphics = newBufferedImage.createGraphics(); |
| graphics.drawImage(logoBufferedImage, new AffineTransformOp(AffineTransform.getTranslateInstance(1, 1), null), (newBufferedImage.getWidth() - logoBufferedImage.getWidth())/2, (newBufferedImage.getHeight() - logoBufferedImage.getHeight())/2); |
| graphics.dispose(); |
| } |
| |
| if (UtilValidate.isNotEmpty(verifyOutput) && verifyOutput.booleanValue()) { |
| Decoder decoder = new Decoder(); |
| Map<DecodeHintType, Object> decodeHints = new EnumMap<>(DecodeHintType.class); |
| decodeHints.put(DecodeHintType.TRY_HARDER, Boolean.TRUE); |
| if (UtilValidate.isNotEmpty(encoding)) { |
| decodeHints.put(DecodeHintType.CHARACTER_SET, encoding); |
| } |
| DetectorResult detectorResult = null; |
| if (UtilValidate.isNotEmpty(newBufferedImage)) { |
| BitMatrix newBitMatrix = createMatrixFromImage(newBufferedImage); |
| DecoderResult result = null; |
| try { |
| detectorResult = new Detector(newBitMatrix).detect(decodeHints); |
| result = decoder.decode(detectorResult.getBits(), decodeHints); |
| } catch (ChecksumException e) { |
| // do nothing |
| } catch (FormatException e) { |
| // do nothing |
| } catch (NotFoundException e) { |
| // do nothing |
| } |
| if (UtilValidate.isNotEmpty(result) && !result.getText().equals(message)) { |
| detectorResult = new Detector(bitMatrix).detect(decodeHints); |
| result = decoder.decode(detectorResult.getBits(), decodeHints); |
| if (!result.getText().equals(message)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "GeneratedTextNotMatchOriginal", new Object[]{result.getText(), message}, locale)); |
| } |
| } else { |
| bufferedImage = newBufferedImage; |
| } |
| } else { |
| detectorResult = new Detector(bitMatrix).detect(decodeHints); |
| DecoderResult result = decoder.decode(detectorResult.getBits(), decodeHints); |
| if (!result.getText().equals(message)) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "GeneratedTextNotMatchOriginal", new Object[]{result.getText(), message}, locale)); |
| } |
| } |
| } else if (UtilValidate.isNotEmpty(newBufferedImage)) { |
| bufferedImage = newBufferedImage; |
| } |
| |
| Map<String, Object> result = ServiceUtil.returnSuccess(); |
| result.put("bufferedImage", bufferedImage); |
| return result; |
| } catch (WriterException e) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ErrorGenerateQRCode", new Object[] { e.toString() }, locale)); |
| } catch (ChecksumException e) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ErrorVerifyQRCode", new Object[] { e.toString() }, locale)); |
| } catch (FormatException e) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ErrorVerifyQRCode", new Object[] { e.toString() }, locale)); |
| } catch (NotFoundException e) { |
| return ServiceUtil.returnError(UtilProperties.getMessage("QRCodeUiLabels", "ErrorVerifyQRCode", new Object[] { e.toString() }, locale)); |
| } |
| } |
| |
| /** |
| * Renders a {@link BitMatrix} as an image, where "false" bits are rendered |
| * as white, and "true" bits are rendered as black. |
| * |
| * This is to replace MatrixToImageWriter.toBufferedImage(bitMatrix) if you |
| * find the output image is not right, you can change BufferedImage image = |
| * new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); to |
| * BufferedImage image = new BufferedImage(width, height, |
| * BufferedImage.TYPE_INT_RGB); or others to make it work correctly. |
| */ |
| private static BufferedImage toBufferedImage(BitMatrix matrix, String format) { |
| int width = matrix.getWidth(); |
| int height = matrix.getHeight(); |
| BufferedImage image = null; |
| String osName = System.getProperty("os.name").toLowerCase(); |
| image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); |
| if (osName.startsWith("mac os") && format.equals("png")) { |
| image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); |
| } |
| for (int x = 0; x < width; x++) { |
| for (int y = 0; y < height; y++) { |
| image.setRGB(x, y, matrix.get(x, y) ? BLACK : WHITE); |
| } |
| } |
| return image; |
| } |
| |
| private static BitMatrix createMatrixFromImage(BufferedImage image) { |
| int width = image.getWidth(); |
| int height = image.getHeight(); |
| int[] pixels = new int[width * height]; |
| image.getRGB(0, 0, width, height, pixels, 0, width); |
| |
| BitMatrix matrix = new BitMatrix(width, height); |
| for (int y = 0; y < height; y++) { |
| for (int x = 0; x < width; x++) { |
| int pixel = pixels[y * width + x]; |
| int luminance = (306 * ((pixel >> 16) & 0xFF) + |
| 601 * ((pixel >> 8) & 0xFF) + |
| 117 * (pixel & 0xFF)) >> 10; |
| if (luminance <= 0x7F) { |
| matrix.set(x, y); |
| } |
| } |
| } |
| return matrix; |
| } |
| } |