Source code for pybot.minitel.image

# -*- coding: utf-8 -*-

""" This modules provides tools for loading and converting images to Videotex format.

Based on phooky's Minitel code (https://github.com/phooky/Minitel)

Warning:
    It depends on the availability of PIL. If not installed, the class will be
    replaced by a fake one.
"""
__author__ = 'Eric Pascual'

import logging

log = logging.getLogger('minitel').getChild('image')

try:
    import PIL.Image as Image
    import PIL.ImageOps as ImageOps

except ImportError:
    log.error('PIL is not available. VideotexImage class replaced by a dummy.')
    Image = ImageOps = None

# ordering of sub-pixels (x,y) in a block
_sub_pixels = [(0, 0), (1, 0), (0, 1), (1, 1), (0, 2), (1, 2)]

[docs]class VideotexImage(object): """ Image converter class. """ def __init__(self, image): """ Parameters: image (:py:class:`PIL.image`): the image to be converted """ if not Image: return if not image: raise ValueError('image parameter is mandatory and must be a PIL image') self._image = image self.last_dark = self.last_light = None
[docs] def to_videotex(self, w=80, h=72): """ Returns a list of strings, one for each line of characters in the converted image. The width and height are specified in sub-pixels, and are always rounded up to an even number (for x) or a multiple of 3 (for y). Parameters: w (int): target image width (in sub-pixels) h (int): target image height (in sub-pixels) Returns: list[str]: the Videotex sequence reproducing the image """ if not Image: return [] # tweak w and h to proper values w = ((w + 1) / 2) * 2 h = ((h + 2) / 3) * 3 # convert image to gray scale and resize im = self._image.convert("L") im = im.resize((w, h), Image.ANTIALIAS) # normalize contrast im = ImageOps.autocontrast(im) # down to 3-bit im = ImageOps.posterize(im, 3) # color hack each 6-cell for i in range(w / 2): for j in range(h / 3): self._color_hack(im, i * 2, j * 3) # generate codes for videotex self.last_dark = self.last_light = -255 codes = [] for j in range(h / 3): code_line = '' for i in range(w / 2): code_line += self._generate_code(im, i * 2, j * 3) codes.append(code_line) return codes
def _generate_code(self, image, x, y): """ Generates display codes for 2x3 block corresponding to a given image pixel. """ dark, light = self._find_dark_light(image, x, y) v = 0 for i, j in _sub_pixels: v = (v >> 1) if image.getpixel((x + i, y + j)) == light: v |= (1 << 5) c = '' if light != self.last_light: c += '\x1b' + chr(0x40 + self._convert_color(light)) if dark != self.last_dark: c += '\x1b' + chr(0x50 + self._convert_color(dark)) c += chr(32 + v) self.last_light, self.last_dark = light, dark return c @staticmethod def _find_dark_light(image, x, y): """Finds the darkest and lightest values for a 2x3 block""" c = [] for i, j in _sub_pixels: c.append(image.getpixel((x + i, y + j))) # select the lightest and darkest pixels c.sort() return c[0], c[-1] @staticmethod def _color_hack(image, x, y): """ Quantizes a 2x3 block to two colors. """ dark, light = VideotexImage._find_dark_light(image, x, y) for i, j in _sub_pixels: v = image.getpixel((x + i, y + j)) if v - dark < light - v: v = dark else: v = light image.putpixel((x + i, y + j), v) @staticmethod def _convert_color(value): """ Converts a 256-level gray tone to a 3b minitel 1 tone. """ # take the high three bits, and move the lsb to msb return (value >> 6) | ((value >> 3) & (1 << 2))