~dpm/qreator/snap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# -*- Mode: Python; coding: utf-8; indent-tabs-mode: nil; tab-width: 4 -*-
### BEGIN LICENSE
# Copyright (C) 2012 David Planella <david.planella@ubuntu.com>
# This program is free software: you can redistribute it and/or modify it 
# under the terms of the GNU General Public License version 3, as published 
# by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful, but 
# WITHOUT ANY WARRANTY; without even the implied warranties of 
# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR 
# PURPOSE.  See the GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along 
# with this program.  If not, see <http://www.gnu.org/licenses/>.
### END LICENSE

import sys
try:
    import qrencode
except ImportError:
    print "You need to install the python-qrencode package"
    sys.exit(1)
import Image
import ImageOps
import cairo
import array


class QRCodeSize(object):
    SMALL = 120
    MEDIUM = 230
    LARGE = 350


class QRCodeOutput(object):
    PIL_IMAGE = 0
    CAIRO_SURFACE = 1


class QRCode(object):

    def __init__(self, output_size=QRCodeSize.MEDIUM):
        self.output_size = output_size
        self.qrcode_size = 0
        self.pixel_size = 1
        self.image = None
        self.cr_surface = None

    def encode(self, text, current_color_fg=None, previous_color_fg=None,
               current_color_bg=None, previous_color_bg=None, border=True,
               output_type=QRCodeOutput.PIL_IMAGE):
        '''Encodes the given text and returns a QR code in the given format'''

        if output_type == QRCodeOutput.PIL_IMAGE:
            qr_code = self._encode_to_pil(text, current_color_fg,
                previous_color_fg, current_color_bg, previous_color_bg,
                border)
        elif output_type == QRCodeOutput.CAIRO_SURFACE:
            qr_code = self._encode_to_cairo(text, current_color_fg,
                previous_color_fg, current_color_bg, previous_color_bg,
                border)

        return qr_code

    def _encode_to_cairo(self, text, current_color_fg=None,
                         previous_color_fg=None, current_color_bg=None,
                         previous_color_bg=None, border=True):
        '''Encodes the given text and returns a Cairo surface'''
        self._encode_to_pil(text, current_color_fg, previous_color_fg,
                            current_color_bg, previous_color_bg, border)

        # Convert the Python PIL image to a Cairo surface.
        # Notice the conversion to BGRA that Cairo expects
        # See http://cairographics.org/pythoncairopil/ and
        # http://mail.gnome.org/archives/gtkmm-list/2007-May/msg00111.html
        bytearr = array.array('B', self.image.tobytes("raw", "BGRA", 0, 1))
        height, width = self.image.size
        #print self.image.size

        self.cr_surface = cairo.ImageSurface.create_for_data(bytearr,
                                                     cairo.FORMAT_ARGB32,
                                                     width, height,
                                                     width * 4)

        return self.cr_surface

    def _encode_to_pil(self, text, current_color_fg=None,
                         previous_color_fg=None, current_color_bg=None,
                         previous_color_bg=None, border=True):
        '''Encodes the given text and returns a Python PIL Image,
        changing colors if necessary'''

        # Returns an 8-bit pixel, black and white image (mode L)
        version, self.qrcode_size, self.image = qrencode.encode(text)

        if (current_color_fg != previous_color_fg) or \
           (current_color_bg != previous_color_bg):
            self.image = ImageOps.colorize(self.image,
                                           (current_color_fg[0],
                                           current_color_fg[1],
                                           current_color_fg[2]),
                                           (current_color_bg[0],
                                           current_color_bg[1],
                                           current_color_bg[2]))

        # Returns a 4x8-bit pixel, true colour with transparency mask image
        # (mode RGBA)
        self.image = self.image.convert('RGBA')
        
        self._resize()
        if border:
            self._add_border(current_color_bg)

        return self.image

    def _resize(self):
        self._set_pixel_size(self.qrcode_size)
        #print self.pixel_size, self.qrcode_size
        self.image = self.image.resize((self.pixel_size * self.qrcode_size,
                                        self.pixel_size * self.qrcode_size),
                                       Image.NEAREST)

    def _set_pixel_size(self, barcode_orig_size):
        pixel_size = 1

        #print pixel_size * barcode_orig_size, self.output_size
        while (pixel_size * barcode_orig_size < self.output_size):
            pixel_size += 1

        while ((pixel_size * barcode_orig_size > self.output_size) or \
                ((pixel_size * barcode_orig_size) % 2 != 0) or \
                (pixel_size * barcode_orig_size + 2 * pixel_size > \
                    self.output_size)):
            pixel_size -= 1

            # FIXME: it needs to automatically go up to the next size,
            # rather than hardcoding it as now
            if pixel_size == 0:
                self.output_size = QRCodeSize.MEDIUM

        self.pixel_size = pixel_size

    def _add_border(self, current_color_bg=None):
        '''Adds a border to the QR code'''
        if current_color_bg:
            fill = (current_color_bg[0], current_color_bg[1],
                    current_color_bg[2], 255)
        else:
            fill = 'white'
        # Add a border
        border_size = (self.output_size - self.image.size[0]) / 2
        self.image = ImageOps.expand(self.image, border=border_size,
                                     fill=fill)