Source code for libqrencode.qrencode

#!/usr/bin/env python
"""Python ctypes binding to libqrencode.

This module when ran as a script attempts to mimic the c sample program 
qrenc.c otherwise known as qrencode


This file is part of libqrencode python ctypes bindings.

Copyright (C) 2012 Matthew Baker <mu.beta.06@gmail.com>

This is free software: you can redistribute it and/or modify
it under the terms of the LGNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the LGNU Lesser General Public License
along with this software.  If not, see <http://www.gnu.org/licenses/>."""

import ctypes
import errno
import optparse
import os
import sys

from _qrencode import *


[docs]class Error(Exception): """This is a user-defined exception for errors raised by this module.""" pass
[docs]def call(func, *args): """Convenience routine to call function func, check for NULL pointer and subsequently check errno to report useful error message.""" p = func(*args) if p: return p else: raise Error('NULL pointer returned when calling %s, errno %s' % (str(func), errno.errorcode[ctypes.get_errno()]))
[docs]class QREncode(object): """Base class for representing QREncode c structures. Allows for struct member read access as class attributes.""" def __init__(self, p): """Initialise an DmtxStructure object with pointer p.""" self._p = p def __getattribute__(self, name): """Overloaded method so each field of c structure can be assessed as read only attributes.""" try: return object.__getattribute__(self, name) except AttributeError: p = object.__getattribute__(self, '_p') return getattr(p.contents, name)
[docs]class QRcode(QREncode): """Python representation of QRcode c structure""" def __init__(self, p, cleanup=True): if p: super(QRcode, self).__init__(p) self.cleanup = cleanup #boolean to perform QRcode cleanup or not else: raise ValueError('Invalid pointer to QRcode c structure required') def __del__(self): """Clean up QRcode c structure.""" if self._p and self.cleanup: QRcode_free(self._p)
[docs]class QRcode_List(QREncode, list): """Python representation of QRcode_List c structure.""" def __init__(self, p): if p: super(QRcode_List, self).__init__(p) while p: self.append(QRcode(p.contents.code, cleanup=False)) p = p.contents.next else: raise ValueError('Invalid pointer to QRcode_List c structure') def __del__(self): """Clean up QRcode_List c structure.""" if self._p: QRcode_List_free(self._p)
[docs]class QREncoder(object): """Base class for representing QREncoder. Largely used to maintain the state that the globals in qrenc.c represent. """ def __init__(self): """Initialise object.""" self._casesensitive = True self._code = None #Represents encoded result i.e. a QRcode c structure self._eightbit = False self._hint = QR_MODE_8 self._level = QR_ECLEVEL_L self._micro = False self._version = 0
[docs] def encode(self, data): """Encode input string represented by data""" cdata = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte if self.eightbit else ctypes.c_char)) length = len(data) if self.micro: if self.version == 0: e = 'Version must be specified to encode a Micro QR Code symbol' raise Error(e) elif self.version > MQRSPEC_VERSION_MAX: e = 'Version should be less or equal to %d.' raise Error(e % MQRSPEC_VERSION_MAX) elif self.eightbit: self.code = call(QRcode_encodeDataMQR, length, cdata, self.version, self.level) else: self.code = call(QRcode_encodeStringMQR, cdata, self.version, self.level, self.hint, self.casesensitive) else: if self.eightbit: self.code = call(QRcode_encodeData, length, cdata, self.version, self.level) else: self.code = call(QRcode_encodeString, cdata, self.version, self.level, self.hint, self.casesensitive)
[docs] def as2dlist(self): """Return a list of lists describing the encoded data.""" l = list() offset = 0 for y in range(self.code.width): row = list() for x in range(self.code.width): row.append(bool(self.code.data[offset] & 1)) offset += 1 l.append(row) return l
[docs] def asciipreview(self): """Print an ascii representation of the Encoder output to stdout""" for row in self.as2dlist(): for col in row: sys.stdout.write('XX' if col else ' ') sys.stdout.write('\n')
[docs] def asps(self): """Return a PostScript representation of the encoded QRCode. Paints the "black" symbol in the current colour on the assumption that the background has already been painted and appropriate colour. Symbols are assumed to be 1 user coordinate square. """ ps = """ %QR Code postscript output %Generated by qrencodes.py """ # Get the datamatrix symbols in bottom-to-top & left-to-right order. rows = self.as2dlist() nrow = len(rows) ps += '%d array\n' % nrow for i in range(nrow): ps += 'dup %d [' % (nrow - 1 - i) for sym in rows[i]: ps += '1 ' if sym else '0 ' ps += '] put\n' ps += """ gsave 2 dict begin /x 0 def /y 0 def { { 1 eq {x y 1 1 rectfill} if /x x 1 add store } forall /y y 1 add store /x 0 store } forall end grestore """ return ps
def _get_casesensitive(self): """Return the QREncoder's casesensitive property.""" return int(self._casesensitive) def _set_casesensitive(self, casesensitive): if isinstance(casesensitive, (bool)): self._casesensitive = casesensitive else: raise ValueError('boolean required') def _get_code(self): """Return the QREncode object""" return self._code def _set_code(self, code): self._code = QRcode(code) def _get_eightbit(self): """Return the QREncoder's eightbit property.""" return self._eightbit def _set_eightbit(self, eightbit): if isinstance(eightbit, (bool)): self._eightbit = eightbit else: raise ValueError('boolean required') def _get_hint(self): """Return the QREncoder's hint.""" return self._hint def _set_hint(self, hint): if hint in [QR_MODE_8, QR_MODE_KANJI]: self._hint = hint else: raise ValueError('invalid hint %s' % str(hint)) def _get_level(self): """Return the QREncoder's level.""" return self._level def _set_level(self, level): if level in [QR_ECLEVEL_L, QR_ECLEVEL_M, QR_ECLEVEL_Q, QR_ECLEVEL_H]: self._level = level else: raise ValueError('invalid level %s' % str(level)) def _get_micro(self): """Return the QREncoder's micro property.""" return self._micro def _set_micro(self, micro): if isinstance(micro, (bool)): self._micro = micro else: raise ValueError('boolean required') def _get_version(self): """Return the QREncoder's symbol version property.""" return self._version def _set_version(self, version): if isinstance(version, (int)) and 0<= version <= QRSPEC_VERSION_MAX: self._version = version else: raise ValueError('version must be 0<=int<=%d' % QRSPEC_VERSION_MAX) #properties casesensitive = property(_get_casesensitive, _set_casesensitive, None) code = property(_get_code, _set_code, None) eightbit = property(_get_eightbit, _set_eightbit, None) hint = property(_get_hint, _set_hint, None) level = property(_get_level, _set_level, None) micro = property(_get_micro, _set_micro, None) version = property(_get_version, _set_version, None)
[docs]class StructuredQREncoder(QREncoder): """Subclassing of QREncoder for structured QR encoding.""" def __init__(self): """Initialise object.""" super(StructuredQREncoder, self).__init__() self._code_list = [] # encoded result i.e. a QRcode_List c structure
[docs] def encode(self, data): """Encode input string represented by data""" if self.version == 0: e = 'Version must be specified to encode structured symbols.' raise Error(e) elif self.micro: e = 'Micro QR Code does not support structured symbols.' raise Error(e) else: cdata = ctypes.cast(data, ctypes.POINTER(ctypes.c_ubyte if self.eightbit else ctypes.c_char)) length = len(data) if self.eightbit: self.code_list = call(QRcode_encodeDataStructured, length, cdata, self.version, self.level) else: self.code_list = call(QRcode_encodeStringStructured, cdata, self.version, self.level, self.hint, self.casesensitive)
[docs] def as2dlists(self): """Return a list of list of lists describing the encoded data.""" l = list() for code in self.code_list: self._code = code l.append(super(StructuredQREncoder, self).as2dlist()) return l
[docs] def asciipreview(self): """Print an ascii representation of the Encoder output to stdout""" for code in self.code_list: self._code = code super(StructuredQREncoder, self).asciipreview() sys.stdout.write('\n')
[docs] def asps(self): """Return a list of PostScript strings representing the encoded QRCode's of the StructuredQREncoder.""" l = list() for code in self.code_list: self._code = code l.append(super(StructuredQREncoder, self).asps()) return l
def _get_code_list(self): """Return StructuredQREncoder's code_list.""" return self._code_list def _set_code_list(self, code_list): self._code_list = QRcode_List(code_list) code_list = property(_get_code_list, _set_code_list, None)
[docs]def main(argv): import libqrencode version = libqrencode.version desc = """libqrencode ctypes python binding version %s, """ % version desc += """libqrencode version %s""" % QRcode_APIVersionString() usage = 'Usage: qrencode [OPTION]... [STRING]' p = optparse.OptionParser(description=desc, usage=usage) p.add_option('-o', '--filename', default=None, type='string', action='store', help='Encoded image filename (.png, .pdf, .jpg, .tif...)') p.add_option('-s', '--size', default=3, type='int', action='store', help='Specify module size in dots (pixels). (default=3)') p.add_option('-l', '--level', default='L', type='choice', action='store', choices=['L', 'M', 'Q', 'H'], help="""specify error correction level from L (lowest) to H (highest). (default=L)""") p.add_option('-v', '--symversion', default=0, type='int', action='store', help='specify the version of the symbol. (default=auto)') p.add_option('-m', '--margin', default=None, type='int', action='store', help="""specify the width of the margins. (default=4) (2 for Micro)))""") p.add_option('-d', '--dpi', default=72, type='int', action='store', help='specify the DPI of the generated PNG. (default=72)') p.add_option('-S', '--structured', default=False, action='store_true', help="""make structured symbols. Version must be specified.""") p.add_option('-k', '--kanji', default=False, action='store_true', help="""assume that the input text contains kanji (shift-jis).""") p.add_option('-c', '--casesensitive', default=True, action='store_false', help="""encode lower-case alphabet characters in 8-bit mode. (default)""") p.add_option('-8', '--eightbit', default=False, action='store_true', help="""encode entire data in 8-bit mode. -k and -c will be ignored.""") p.add_option('-M', '--micro', default=False, action='store_true', help="""encode in a Micro QR Code. (experimental).""") options, args = p.parse_args(argv[1:]) #set defaults and do error checking if options.margin is None: if options.micro: options.margin = 2 else: options.margin = 4 elif options.margin < 0: raise Error('Invalid margin: %d' % options.margin) if options.size <= 0: raise Error('Invalid size: %d' % options.size) if options.symversion < 0: raise Error('Invalid version: %d' % options.symversion) if options.level == 'L': options.level = QR_ECLEVEL_L elif options.level == 'M': options.level = QR_ECLEVEL_M elif options.level == 'Q': options.level = QR_ECLEVEL_Q elif options.level == 'H': options.level = QR_ECLEVEL_H if options.dpi < 0: raise Error('Invalid dpi: %d' % options.dpi) if options.kanji: options.kanji = QR_MODE_KANJI else: options.kanji = QR_MODE_8 if args == []: string = sys.stdin.read() #read from stdin into string else: string = args[0] if not options.structured: encoder = QREncoder() else: encoder = StructuredQREncoder() #set properties of encoder encoder.casesensitive = options.casesensitive encoder.eightbit = options.eightbit encoder.hint = options.kanji encoder.level = options.level encoder.micro = options.micro encoder.version = options.symversion #encode encoder.encode(string) #next write output if required. if options.filename is None: encoder.asciipreview() #output to stdout else: try: from PIL import Image except Exception, e: e = 'Cannot write image %s, %s' % (options.filename, str(e)) print >> sys.stderr, e def saveimage(filename, qr): qrim = Image.new('1', (len(qr),)*2) qrim.putdata([0 if symbol else 1 for row in qr for symbol in row]) qrim = qrim.resize((len(qr)*options.size,)*2, Image.NEAREST) im = Image.new('1', tuple(s + 2*options.margin for s in qrim.size), color=1) im.paste(qrim, (options.margin,)*2) im = im.convert('RGB') im.save(filename, dpi=(options.dpi,)*2) if isinstance(encoder, (StructuredQREncoder)): i = 1 for qr in encoder.as2dlists(): basename, ext = os.path.splitext(options.filename) saveimage(basename + str(i) + ext, qr) i += 1 else: qr = encoder.as2dlist() saveimage(options.filename, qr)
if __name__ == '__main__': main(sys.argv)