All Downloads are FREE. Search and download functionalities are using the official Maven repository.

boofcv.alg.fiducial.microqr.MicroQrCodeGenerator Maven / Gradle / Ivy

/*
 * Copyright (c) 2023, Peter Abeles. All Rights Reserved.
 *
 * This file is part of BoofCV (http://boofcv.org).
 *
 * Licensed 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 boofcv.alg.fiducial.microqr;

import boofcv.alg.drawing.FiducialImageEngine;
import boofcv.alg.fiducial.qrcode.PackedBits32;
import boofcv.alg.fiducial.qrcode.QrCodeCodeWordLocations;
import boofcv.alg.fiducial.qrcode.QrGeneratorBase;
import boofcv.struct.image.GrayU8;
import georegression.struct.point.Point2D_I32;

/**
 * Generates an image of a Micro QR Code.
 *
 * @author Peter Abeles
 */
@SuppressWarnings({"NullAway.Init"})
public class MicroQrCodeGenerator extends QrGeneratorBase {

	/** Convenience function for rendering images */
	public static GrayU8 renderImage( int pixelPerModule, int border, MicroQrCode qr ) {
		int numModules = MicroQrCode.totalModules(qr.version);
		var render = new FiducialImageEngine();
		render.configure(pixelPerModule*border, numModules*pixelPerModule);
		new MicroQrCodeGenerator().setMarkerWidth(numModules*pixelPerModule).setRender(render).render(qr);
		return render.getGray();
	}

	public MicroQrCodeGenerator render( MicroQrCode qr ) {
		numModules = MicroQrCode.totalModules(qr.version);
		moduleWidth = markerWidth/numModules;

		render.init();

		positionPattern(0, 0, qr.pp);
		qr.thresholdPP = 127;

		timingPattern(7*moduleWidth, 0, moduleWidth, 0, numModules - 7);
		timingPattern(0, 7*moduleWidth, 0, moduleWidth, numModules - 7);

		formatInformation(qr);

		if (renderData) {
			MicroQrCode.VersionInfo info = MicroQrCode.VERSION_INFO[qr.version];

			// Give an informative error message instead of a random NPE
			if (!info.isErrorSupported(qr.error)) {
				throw new IllegalStateException("ErrorLevel " + qr.error +
						" not supported with version " + qr.version + " marker.");
			}

			MicroQrCode.DataInfo data = info.levels(qr.error);
			int eccWords = info.codewords - data.dataCodewords;
			int dataBits = qr.getMaxDataBits();

			if (qr.rawbits.length != info.codewords)
				throw new RuntimeException("Unexpected length of raw data.");

			// mark which modules can store data
			bitLocations = QrCodeCodeWordLocations.microqr(qr.version).bits;

			int numBytes = bitLocations.size()/8 + (bitLocations.size()%8 == 0 ? 0 : 1);
			if (numBytes != qr.rawbits.length)
				throw new RuntimeException("Egads. unexpected length of qrcode raw data");

			// Render the output data and handle situations where the last data word is only 4-bits
			renderData(qr.mask, qr.rawbits, 0, 0, dataBits);
			renderData(qr.mask, qr.rawbits, data.dataCodewords, dataBits, eccWords*8);
		}

		qr.bounds.set(0, 0, 0);
		qr.bounds.set(1, markerWidth, 0);
		qr.bounds.set(2, markerWidth, markerWidth);
		qr.bounds.set(3, 0, markerWidth);

		return this;
	}

	/** Renders format bits */
	private void formatInformation( MicroQrCode qr ) {
		PackedBits32 bits = formatInformationBits(qr);

		for (int i = 0; i < 15; i++) {
			if (bits.get(i) == 0) {
				continue;
			}
			if (i < 8) {
				square(i + 1, 8);
			} else {
				square(8, 15 - i);
			}
		}
	}

	static PackedBits32 formatInformationBits( MicroQrCode qr ) {
		var bits = new PackedBits32(15);
		bits.data[0] = qr.encodeFormatBits();
		bits.data[0] ^= MicroQrCode.FORMAT_MASK;
		return bits;
	}

	/**
	 * Renders the raw data bit output while applying the selected mask
	 */
	private void renderData( MicroQrCodeMaskPattern mask, byte[] rawbits, int offsetByte, int offsetBits, int lengthBits ) {
		for (int bitIdx = 0; bitIdx < lengthBits; bitIdx += 8) {
			int bits = rawbits[offsetByte + bitIdx/8] & 0xFF;

			int N = Math.min(8, lengthBits - bitIdx);

			for (int i = 0; i < N; i++) {
				Point2D_I32 coor = bitLocations.get(offsetBits + bitIdx + i);
				int value = mask.apply(coor.y, coor.x, ((bits >> i) & 0x01));
				if (value > 0) {
					square(coor.y, coor.x);
				}
			}
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy