Некоторое время назад мне потребовалось генерировать несколько QR-кодов для каждого пользователя системы. А чтобы было интересно сканировать этот код, было решено добавить в него логотип.
Как это сделать читайте дальше.
Предисловие
QR-коды можно встретить везде, но как их отличить друг от друга? QR-коды всё время завоёвывают популярность, и нет-нет, да и встретятся несколько штук рядом. Зрелище это не из приятных — какой сканировать первым? И вообще, зачем сканировать то, от чего начинает рябить в глазах?
Решением такой задачи может служить персонализирование QR-кода: нестандартные цвета, логотип, или пояснительная надпись немного ниже самого кода, по которой можно понять — интересно ли зрителю это или нет.
Навеняка многие видели красивые QR-коды (а кто не видел, может посмотреть), но мне стоит оговориться — чтобы создать такой, нужно вложить либо большие ресурсы в алгоритм генерации картинки, либо нарисовать такой код в фотошопе, но это будет единичный экземпляр, и для большинства из нас он не годится (если, конечно, есть вообще необходимость генерировать их самому).
Как это реализовано?
Создатели QR-кодов не расчитывали, что мы будем вставлять свои картинки в закодированные сообщения, коими являются сами коды, но они предусмотрели возможность высокого объёма восстановительной информации — код может содержать до 30% последней. Чем её больше — тем гуще картинка, но больше шансов, что пользователь раскодирует испорченный код. А портить мы его будем логотипом.
Для генерации кода использовалась библиотека ZXing — это open source библиотека для обработки различных 1D/2D штрихкодов, которая, кроме Java, имеет порты на другие языки.
Особенностью этой библиотеки является то, что она разбита на модули и распространяется в исходных кодах, которые необходимо компилировать. Но, к счастью, она есть в мавен репозитории — модуль core использовался для генерации, и модуль java se использовался для валидации кодов.
Для работы с графикой были использованы стандартные классы из пакета java.awt (JavaSE).
За дело!
Для экспериментов была сделана небольшую консольная программа, которую можно найти на гитхабе — репозиторий опытного образца, которую я и разберу в этом разделе.
Тот, кому просто нужен QR-код, может написать следующее:
BitMatrix matrix = new MultiFormatWriter().encode("text to encode", BarcodeFormat.QR_CODE, width, height);
MatrixToImageWriter.writeToFile(matrix, filename.substring(filename.lastIndexOf('.')+1), new File(filename));
В противном случае так делать не стоит — по умолчанию библиотека добавит мало восстановительной информации, и даже если после вставки логотипа картинка расшифруется у нас на компьютере, то с фотокамеры она уже может считаться неправильно. По-этому будет хорошим тоном добавить максимум восстановительной информации, а раз мы будем изменять цвета и катинку, то не нужно спешить сохранять результат:
Hashtable<EncodeHintType, ErrorCorrectionLevel> hintMap = new Hashtable<EncodeHintType, ErrorCorrectionLevel>();
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
QRCodeWriter qrCodeWriter = new QRCodeWriter();
BitMatrix bitMatrix = qrCodeWriter.encode(content, BarcodeFormat.QR_CODE, qrCodeSize, qrCodeSize, hintMap);
Создание картинки из матрицы кода делается в цикле — создаём картинку соответствующего размера и, проходя матрицу кода, отображаем наличие бита в матрице на картинку как информативный пиксель. Во время этого действа можно задать цвет фона и цвет кода:
int matrixWidth = bitMatrix.getWidth();
BufferedImage image = new BufferedImage(matrixWidth, matrixWidth, BufferedImage.TYPE_INT_RGB);
image.createGraphics();
Graphics2D graphics = (Graphics2D) image.getGraphics();
graphics.setColor(Color.white);
graphics.fillRect(0, 0, matrixWidth, matrixWidth);
Color mainColor = new Color(51, 102, 153);
graphics.setColor(mainColor);
//Write Bit Matrix as image
for (int i = 0; i < matrixWidth; i++) {
for (int j = 0; j < matrixWidth; j++) {
if (bitMatrix.get(i, j)) {
graphics.fillRect(i, j, 1, 1);
}
}
}
Ну вот, теперь, когда мы оперируем картинкой, а не матицей единиц и нулей, нам очень даже удобно и логотип в центр поместить, предварительно поправив его разрешение, чтобы не перекрывать весь код в случае слишком большого размера:
BufferedImage logo = ImageIO.read( this.getLogoFile());
double scale = calcScaleRate(image, logo);
logo = getScaledImage( logo,
(int)( logo.getWidth() * scale),
(int)( logo.getHeight() * scale) );
graphics.drawImage( logo,
image.getWidth()/2 - logo.getWidth()/2,
image.getHeight()/2 - logo.getHeight()/2,
image.getWidth()/2 + logo.getWidth()/2,
image.getHeight()/2 + logo.getHeight()/2,
0, 0, logo.getWidth(), logo.getHeight(), null);
private BufferedImage getScaledImage(BufferedImage image, int width, int height) throws IOException {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
double scaleX = (double)width/imageWidth;
double scaleY = (double)height/imageHeight;
AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp bilinearScaleOp = new AffineTransformOp( scaleTransform, AffineTransformOp.TYPE_BILINEAR);
return bilinearScaleOp.filter( image, new BufferedImage(width, height, image.getType()));
}
После нашего надругательства над кодом, обязательно стоит его проверить на правильность — хватит ли восстановительной информации для идеальной фотокамеры? И если хватит, то пора сохранить катинку и отдать её пользователю:
if ( isQRCodeCorrect(content, image)) {
ImageIO.write(image, imageFormat, this.getGeneratedFileStream());
}
private boolean isQRCodeCorrect(String content, BufferedImage image){
boolean result = false;
Result qrResult = decode(image);
if (qrResult != null && content != null && content.equals(qrResult.getText())){
result = true;
}
return result;
}
private Result decode(BufferedImage image){
if (image == null) {
return null;
}
try {
LuminanceSource source = new BufferedImageLuminanceSource(image);
BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
Result result = new MultiFormatReader().decode(bitmap, Collections.EMPTY_MAP);
return result;
} catch (NotFoundException nfe) {
return null;
}
}
Поставленная цель достигнута — QR код сгенерирован. Спасибо за внимание!
Автор: valkiriy