Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nativ/fix ascii and tty #6

Merged
merged 1 commit into from
Oct 3, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions qrcode/image/ascii.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Needed on case-insensitive filesystems
from __future__ import absolute_import

from qrcode.image.tty import TTYImage

class ASCIIImage(TTYImage):
def __init__(self, *args, **kwargs):
super(ASCIIImage, self).__init__(*args, **kwargs)
if 'foreground' in kwargs:
self.foreground = kwargs['foreground']
else:
self.foreground = False

def save(self, stream, format=None, **kwargs):
if not stream.isatty():
raise OSError("Not a tty")

import sys
if sys.version_info < (2, 7):
# On Python versions 2.6 and earlier, stdout tries to encode
# strings using ASCII rather than stdout.encoding, so use this
# workaround.
import codecs
out = codecs.getwriter(sys.stdout.encoding)(sys.stdout)
else:
out = sys.stdout

BACKGROUND_COLOR = '48;2;255;255;255'
for i in xrange(self.border):
stream.write('\x1b[' + BACKGROUND_COLOR + 'm' + (self.false_character * ((self.border * 2) + self.width)))
stream.write('\n')
for line in self.data:
stream.write('\x1b[' + BACKGROUND_COLOR + 'm' + (self.false_character * self.border))
for pixel in line:
if None == pixel:
stream.write('\x1b[' + BACKGROUND_COLOR + 'm' + self.false_character)
else:
if self.foreground:
stream.write("\x1b[38;2;%d;%d;%d;" % (pixel) + BACKGROUND_COLOR + 'm' + self.character)
else:
stream.write("\x1b[38;2;255;255;255;48;2;%d;%d;%dm" % (pixel) + self.character)
stream.write('\x1b[' + BACKGROUND_COLOR + 'm '+ (self.false_character * self.border))
stream.write('\n')

39 changes: 39 additions & 0 deletions qrcode/image/tty.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# Needed on case-insensitive filesystems
from __future__ import absolute_import

from qrcode.image.base import BaseImage

class TTYImage(BaseImage):
def __init__(self, *args, **kwargs):
super(TTYImage, self).__init__(*args, **kwargs)
if 'character' in kwargs:
self.character = kwargs['character']
else:
self.character = ' '
if 'false_character' in kwargs:
self.false_character = false_character
else:
self.false_character = ' ' * len(self.character)
self.data = []
for l in xrange(self.width):
self.data.append([0]*self.width)

def save(self, stream, format=None, **kwargs):
if not stream.isatty():
raise OSError("Not a tty")

for i in xrange(self.border):
stream.write('\x1b[30;47m' + (self.false_character * (self.border * 2 + self.width)))
stream.write('\n')
for line in self.data:
stream.write(self.false_character * self.border)
for pixel in line:
if None == pixel or (255, 255, 255) == pixel:
stream.write("\x1b[30;47m" + self.false_character)
else:
stream.write("\x1b[0m" + self.character)
stream.write('\x1b[30;47m' + (self.false_character * self.border))
stream.write('\n')

def drawrect(self, row, col, color):
self.data[row][col] = color
110 changes: 19 additions & 91 deletions qrcode/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ def __init__(self, version=None,
self.image_factory_modifiers = image_factory_modifiers
self.clear()
self.control_colors = defaultdict(lambda :true_color)
self.control_colors[True] = true_color
self.control_colors[False] = false_color
self.control_colors[0] = false_color
self.control_colors[None] = false_color
Expand All @@ -63,6 +64,14 @@ def set_image_factory(self, image_factory):
elif image_factory == 'svg':
from qrcode.image.svg import SvgImage
image_factory = SvgImage
elif image_factory == 'tty':
from qrcode.image.tty import TTYImage
image_factory = TTYImage
elif image_factory == 'ascii':
from qrcode.image.ascii import ASCIIImage
image_factory = ASCIIImage
else:
raise Exception("Unknown Image factory %s" % image_factory)
elif image_factory is None:
# Use PIL by default
from qrcode.image.pil import PilImage
Expand Down Expand Up @@ -132,7 +141,7 @@ def makeImpl(self, test, mask_pattern, use_colors=True):
self.setup_type_info(test, mask_pattern, use_colors=use_colors)

if self.version >= 7:
self.setup_type_number(test)
self.setup_type_number(test, use_colors=use_colors)

if self.data_cache is None:
self.data_cache = util.create_data(
Expand All @@ -154,7 +163,6 @@ def get_false_and_true_colors(self, color_index, use_colors=True):
return (false_color, true_color)

def setup_position_probe_pattern(self, row, col, use_colors=True):

false_color, true_color = self.get_false_and_true_colors('prob', use_colors=use_colors)

for r in range(-1, 8):
Expand Down Expand Up @@ -221,88 +229,6 @@ def best_mask_pattern(self):

return pattern

def print_tty(self, out=None):
"""
Output the QR Code only using TTY colors.

If the data has not been compiled yet, make it first.
"""
if out is None:
import sys
out = sys.stdout

if not out.isatty():
raise OSError("Not a tty")

if self.data_cache is None:
self.make()

modcount = self.modules_count
out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n")
for r in range(modcount):
out.write("\x1b[1;47m \x1b[40m")
for c in range(modcount):
if self.modules[r][c]:
out.write(" ")
else:
out.write("\x1b[1;47m \x1b[40m")
out.write("\x1b[1;47m \x1b[0m\n")
out.write("\x1b[1;47m" + (" " * (modcount * 2 + 4)) + "\x1b[0m\n")
out.flush()

def print_ascii(self, out=None, tty=False, invert=False):
"""
Output the QR Code using ASCII characters.

:param tty: use fixed TTY color codes (forces invert=True)
:param invert: invert the ASCII characters (solid <-> transparent)
"""
if out is None:
import sys
if sys.version_info < (2, 7):
# On Python versions 2.6 and earlier, stdout tries to encode
# strings using ASCII rather than stdout.encoding, so use this
# workaround.
import codecs
out = codecs.getwriter(sys.stdout.encoding)(sys.stdout)
else:
out = sys.stdout

if tty and not out.isatty():
raise OSError("Not a tty")

if self.data_cache is None:
self.make()

modcount = self.modules_count
codes = [six.int2byte(code).decode('cp437')
for code in (255, 223, 220, 219)]
if tty:
invert = True
if invert:
codes.reverse()

def get_module(x, y):
if (invert and self.border and
max(x, y) >= modcount+self.border):
return 1
if min(x, y) < 0 or max(x, y) >= modcount:
return 0
return self.modules[x][y]

for r in range(-self.border, modcount+self.border, 2):
if tty:
if not invert or r < modcount+self.border-1:
out.write('\x1b[48;5;232m') # Background black
out.write('\x1b[38;5;255m') # Foreground white
for c in range(-self.border, modcount+self.border):
pos = get_module(r, c) + (get_module(r+1, c) << 1)
out.write(codes[pos])
if tty:
out.write('\x1b[0m')
out.write('\n')
out.flush()

def make_image(self, **kwargs):
"""
Make an image from the QR Code data.
Expand Down Expand Up @@ -340,7 +266,6 @@ def setup_timing_pattern(self, use_colors=True):
self.modules[6][c] = false_color

def setup_position_adjust_pattern(self, use_colors):

false_color, true_color = self.get_false_and_true_colors('prob', use_colors=use_colors)
pos = util.pattern_position(self.version)

Expand All @@ -360,19 +285,21 @@ def setup_position_adjust_pattern(self, use_colors):
else:
self.modules[row + r][col + c] = false_color

def setup_type_number(self, test):
def setup_type_number(self, test, use_colors=True):
false_color, true_color = self.get_false_and_true_colors('type', use_colors=use_colors)
colors = {False:false_color, True:true_color}
bits = util.BCH_type_number(self.version)

for i in range(18):
mod = self.control_colors[bool(not test and ((bits >> i) & 1) == 1)]
mod = colors[bool(not test and ((bits >> i) & 1) == 1)]
self.modules[i // 3][i % 3 + self.modules_count - 8 - 3] = mod

for i in range(18):
mod = self.control_colors[bool(not test and ((bits >> i) & 1) == 1)]
mod = colors[bool(not test and ((bits >> i) & 1) == 1)]
self.modules[i % 3 + self.modules_count - 8 - 3][i // 3] = mod

def setup_type_info(self, test, mask_pattern, use_colors=True):
false_color, true_color = self.get_false_and_true_colors(True, use_colors=use_colors)
false_color, true_color = self.get_false_and_true_colors('type', use_colors=use_colors)
colors = {False:false_color, True:true_color}
data = (self.error_correction << 3) | mask_pattern
bits = util.BCH_type_info(data)
Expand Down Expand Up @@ -408,6 +335,7 @@ def map_data(self, data, mask_pattern, use_colors=True):
inc = -1
row = self.modules_count - 1
bit_index = 0
false_color, true_color = self.get_false_and_true_colors('padding', use_colors)

mask_func = util.mask_func(mask_pattern)

Expand All @@ -427,7 +355,7 @@ def map_data(self, data, mask_pattern, use_colors=True):
color = bit[1]
else:
value = False
color = self.control_colors[True]
color = true_color

if mask_func(row, c):
value = not value
Expand All @@ -436,7 +364,7 @@ def map_data(self, data, mask_pattern, use_colors=True):
if value:
self.modules[row][c] = color
else:
self.modules[row][c] = self.control_colors[False]
self.modules[row][c] = false_color
else:
self.modules[row][c] = value

Expand Down
7 changes: 4 additions & 3 deletions qrcode/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -451,10 +451,11 @@ def create_data(version, error_correction, data_list, colors, control_colors=Non
if None == control_colors:
control_colors = defaultdict(lambda :True)

last_color = colors[-1]
bit_buf = BitBuffer()
for data in data_list:
bit_buf.put(data.mode, 4, control_colors.get(True))
bit_buf.put(len(data), length_in_bits(data.mode, version), control_colors.get(True))
bit_buf.put(data.mode, 4, colors[0])
bit_buf.put(len(data), length_in_bits(data.mode, version), colors[0])
data.write_to_buffer(bit_buf, colors[:len(data)])
colors = colors[len(data):]

Expand All @@ -471,7 +472,7 @@ def create_data(version, error_correction, data_list, colors, control_colors=Non

# Terminate the bits (add up to four 0s).
for i in range(min(bit_limit - len(bit_buf), 4)):
bit_buf.put_bit(False, control_colors[True])
bit_buf.put_bit(False, last_color)

# Delimit the string into 8-bit words, padding with 0s if necessary.
delimit = len(bit_buf) % 8
Expand Down