PHP Classes

File: src/python/SimpleCaptcha.py

Recommend this page to a friend!
  Classes of Nikos M.  >  Simple PHP Captcha Library  >  src/python/SimpleCaptcha.py  >  Download  
File: src/python/SimpleCaptcha.py
Role: Auxiliary data
Content type: text/plain
Description: Auxiliary data
Class: Simple PHP Captcha Library
Show images to verify humans with math expressions
Author: By
Last change: v.2.2.0

* support scale distortion vs position distortion
* add option(distortion_type)
* update tests
Date: 1 month ago
Size: 91,246 bytes
 

Contents

Class file image Download
##
#   SimpleCaptcha
#   Simple image-based macthematical captcha
#
#   @version 2.2.0
#   https://github.com/foo123/simple-captcha
#
##
import math, random, base64, hmac, hashlib, zlib, struct

def rand(m, M):
    return random.randrange(m, M+1)

def split(s):
    return [c for c in str(s)]

def createHash(key, data):
    return str(hmac.new(bytes(str(key), 'utf-8'), msg=bytes(str(data), 'utf-8'), digestmod=hashlib.sha256).hexdigest())

def imagepng(img, width, height, metaData=dict()):
    return 'data:image/png;base64,' + base64.b64encode(PNGPacker(metaData).toPNG(img, width, height)).decode("ascii")


class SimpleCaptcha:
    """
    SimpleCaptcha
    https://github.com/foo123/simple-captcha
    """
    VERSION = '2.2.0'

    def __init__(self):
        self.captcha = None
        self.hmac = None
        self.opts = {}
        self.option('secret_key', 'SECRET_KEY')
        self.option('secret_salt', 'SECRET_SALT_')
        self.option('difficulty', 1) # 0 (very easy) to 3 (more difficult)
        self.option('distortion_type', 1) # distortion type: 1: position distortion, 2: scale distortion
        self.option('distortion', None) # distortion amplitudes by difficulty
        self.option('num_terms', 2) # default
        self.option('max_num_terms', -1) # default, same as num_terms
        self.option('min_term', 1) # default
        self.option('max_term', 20) # default
        self.option('has_multiplication', True) # default
        self.option('has_division', True) # default
        self.option('has_equal_sign', True) # default
        self.option('color', 0x121212) # text color
        self.option('background', 0xffffff) # background color

    def option(self, *args):
        nargs = len(args)
        if 1 == nargs:
            key = str(args[0])
            return self.opts[key] if key in self.opts else None
        elif 1 < nargs:
            key = str(args[0])
            val = args[1]
            self.opts[key] = val
        return self

    def getCaptcha(self):
        if not self.captcha: self.generate()
        return self.captcha

    def getHash(self):
        if not self.captcha: self.generate()
        return self.hmac

    def reset(self):
        self.captcha = None
        self.hmac = None
        return self

    def validate(self, answer = None, hash = None):
        if (answer is None) or (hash is None): return False
        hasha = createHash(str(self.option('secret_key')), str(self.option('secret_salt') if this.option('secret_salt') else '') + str(answer))
        return hasha == hash

    def generate(self):
        difficulty = min(3, max(0, int(self.option('difficulty'))))
        distortion_type = min(2, max(0, self.option('distortion_type')))
        distortion = self.option('distortion')
        num_terms = max(1, int(self.option('num_terms')))
        max_num_terms = int(self.option('max_num_terms'))
        min_term = max(0, int(self.option('min_term')))
        max_term = max(0, int(self.option('max_term')))
        has_mult = bool(self.option('has_multiplication'))
        has_div = bool(self.option('has_division'))
        has_equal = bool(self.option('has_equal_sign'))
        color = int(self.option('color'))
        background = int(self.option('background'))

        if max_num_terms > num_terms:
            num_terms = rand(num_terms, max_num_terms)

        # generate mathematical formula
        formula, result = self.formula(num_terms, min_term, max_term, has_mult, has_div, has_equal, difficulty)

        # compute hmac of result
        self.hmac = createHash(str(self.option('secret_key')), str(self.option('secret_salt') if self.option('secret_salt') else '') + str(result))

        # create image captcha with formula depending on difficulty
        captcha, width, height = self.image(formula, color, background, difficulty, distortion_type, distortion)

        # output image
        self.captcha = imagepng(captcha, width, height)

        return self

    def formula(self, terms, min, max, has_mult, has_div, has_equal, difficulty):
        # generate mathematical formula
        formula = []
        result = 0
        factor = 0
        divider = 0
        for i in range(terms):
            x = rand(min, max)

            if (result > x) and rand(0, 1):
                # randomly use plus or minus operator
                x = -x
            elif has_mult and (x <= 10) and rand(0, 1):
                # randomly use multiplication factor
                factor = rand(2, 3)
            elif has_div and (0 == x % 2) and rand(0, 1):
                # randomly use division factor
                divider = rand(2, 3) if 0 == x % 3 else 2

            if 0 < factor:
                result += x * factor
                if 0 > x:
                    formula.append('-')
                    formula.extend(split(abs(x)))
                    formula.append('')
                    formula.extend(split(factor))
                else:
                    if 0 < i: formula.append('+')
                    formula.extend(split(x))
                    formula.append('')
                    formula.extend(split(factor))

            elif 0 < divider:
                result += math.floor(x / divider)
                if 0 > x:
                    formula.append('-')
                    formula.extend(split(abs(x)))
                    formula.append('')
                    formula.extend(split(divider))
                else:
                    if 0 < i: formula.append('+')
                    formula.extend(split(x))
                    formula.append('')
                    formula.extend(split(divider))

            else:
                result += x
                if 0 > x:
                    formula.append('-')
                    formula.extend(split(abs(x)))
                else:
                    if 0 < i: formula.append('+')
                    formula.extend(split(x))

            factor = 0
            divider = 0

        if has_equal:
            formula.append('=')
            formula.append('?')

        return (formula, result)

    def image(self, chars, color, background, difficulty, distortion_type, distortion):
        bitmaps = _chars()

        cw = bitmaps['width']
        ch = bitmaps['height']
        n = len(chars)
        space = 1
        x0 = 10
        y0 = 10
        w = n * cw + (n-1) * space + 2 * x0
        h = ch + 2 * y0
        wh = w*h

        r0 = clamp((background >> 16) & 255)
        g0 = clamp((background >> 8) & 255)
        b0 = clamp(background & 255)
        r = clamp((color >> 16) & 255)
        g = clamp((color >> 8) & 255)
        b = clamp(color & 255)

        # img bitmap
        imgbmp = [((r0 << 16) | (g0 << 8) | (b0)) & 0xffffffff] * wh

        # render chars
        for c in chars:
            charbmp = bitmaps['chars'][c]['bitmap']
            for x in range(cw):
                for y in range(ch):
                    alpha = charbmp[x + cw*y]
                    if 0 < alpha:
                        alpha = float(alpha) / 255.0
                        imgbmp[x0+x + w*(y0+y)] = ((clamp(r0*(1-alpha) + alpha*r) << 16) | (clamp(g0*(1-alpha) + alpha*g) << 8) | (clamp(b0*(1-alpha) + alpha*b))) & 0xffffffff

            x0 += cw + space

        img = [0] * (wh << 2)
        if (0 < difficulty) and (0 < distortion_type):
            if 2 == distortion_type:
                # create scale-distorted image data based on difficulty level
                for j in range(wh):
                    i = j << 2
                    img[i  ] = r0
                    img[i+1] = g0
                    img[i+2] = b0
                    img[i+3] = 255

                phase = float(rand(0, 2)) * 3.14 / 2.0
                amplitude = float(distortion[str(difficulty)]) if isinstance(distortion, dict) and (str(difficulty) in distortion) else (0.5 if 3 == difficulty else (0.25 if 2 == difficulty else 0.15))
                x0 = max(0, round((w - n*(1.0+amplitude)*cw - (n-1)*space) / 2))
                for k in range(n):
                    scale = (1.0 + amplitude * math.sin(phase + 6.28 * 2 * k / n))
                    sw = min(w, round(scale * cw))
                    sh = min(h, round(scale * ch))
                    y0 = max(0, round((h - sh) / 2))
                    for ys in range(sh):
                        y = max(0, min(h-1, round(10 + ys / scale)))
                        for xs in range(sw):
                            x = max(0, min(w-1, round(10 + k*(cw+space) + xs / scale)))
                            c = imgbmp[x + y*w]
                            j = ((x0+xs + (y0+ys)*w) << 2)
                            img[j  ] = clamp((c >> 16) & 255)
                            img[j+1] = clamp((c >> 8) & 255)
                            img[j+2] = clamp(c & 255)
                    x0 += space + sw
            else:
                # create position-distorted image data based on difficulty level
                phase = float(rand(0, 2)) * 3.14 / 2.0
                amplitude = float(distortion[str(difficulty)]) if isinstance(distortion, dict) and (str(difficulty) in distortion) else (5.0 if 3 == difficulty else (3.0 if 2 == difficulty else 1.5))
                yw = 0
                for y in range(h):
                    y0 = y
                    for x in range(w):
                        x0 = x
                        y0 = max(0, min(h-1, round(y + amplitude * math.sin(phase + 6.28 * 2.0 * x / w))))
                        c = imgbmp[x0 + y0*w]
                        i = ((x + yw) << 2)
                        img[i  ] = clamp((c >> 16) & 255)
                        img[i+1] = clamp((c >> 8) & 255)
                        img[i+2] = clamp(c & 255)
                        img[i+3] = 255
                    yw += w
        else:
            # create non-distorted image data
            yw = 0
            for y in range(h):
                for x in range(w):
                    i = x + yw
                    c = imgbmp[i]
                    i = (i << 2)
                    img[i  ] = clamp((c >> 16) & 255)
                    img[i+1] = clamp((c >> 8) & 255)
                    img[i+2] = clamp(c & 255)
                    img[i+3] = 255
                yw += w

        # free memory
        bitmaps = None
        imgbmp = None

        return (img, w, h)



def _chars():
    return {
        "fontSize": 20,
        "width": 12,
        "height": 15,
        "chars": {
            "0": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    0,
                    0,
                    0,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    0,
                    0,
                    0,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "1": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "2": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    222,
                    255,
                    139,
                    0,
                    0,
                    0,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    94,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0
                ]
            },
            "3": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "4": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    182,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    48,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    139,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    222,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    48,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    48,
                    255,
                    139,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    48,
                    0,
                    139,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0
                ]
            },
            "5": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    94,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    222,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    94,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "6": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    139,
                    0,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    94,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    48,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    139,
                    255,
                    222,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "7": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    48,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "8": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    255,
                    255,
                    255,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    139,
                    0,
                    0,
                    0,
                    94,
                    255,
                    182,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    182,
                    255,
                    139,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "9": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    222,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    222,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    182,
                    255,
                    182,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    48,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    222,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    0,
                    182,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    222,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    48,
                    255,
                    182,
                    0,
                    0,
                    0,
                    139,
                    255,
                    222,
                    0,
                    0,
                    0,
                    222,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "+": {
                "width": 12,
                "height": 10,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "-": {
                "width": 7,
                "height": 2,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "": {
                "width": 12,
                "height": 9,
                "bitmap": [
                    0,
                    0,
                    222,
                    48,
                    0,
                    0,
                    0,
                    0,
                    182,
                    94,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    48,
                    0,
                    0,
                    139,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    94,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    255,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    48,
                    0,
                    0,
                    139,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    222,
                    48,
                    0,
                    0,
                    0,
                    0,
                    182,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "": {
                "width": 11,
                "height": 8,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    182,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "=": {
                "width": 12,
                "height": 6,
                "bitmap": [
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    139,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            },
            "?": {
                "width": 12,
                "height": 15,
                "bitmap": [
                    0,
                    0,
                    0,
                    222,
                    255,
                    255,
                    255,
                    255,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    94,
                    0,
                    0,
                    0,
                    182,
                    255,
                    182,
                    0,
                    0,
                    0,
                    139,
                    255,
                    255,
                    0,
                    0,
                    0,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    48,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    182,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    182,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    48,
                    255,
                    222,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    0,
                    139,
                    255,
                    94,
                    0,
                    0,
                    0,
                    0,
                    0
                ]
            }
        }
    }



# PNG utilities
PNG_SIGNATURE = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a"

# color-type bits
COLORTYPE_GRAYSCALE = 0
COLORTYPE_PALETTE = 1
COLORTYPE_COLOR = 2
COLORTYPE_ALPHA = 4 # e.g. grayscale and alpha

# color-type combinations
COLORTYPE_PALETTE_COLOR = 3
COLORTYPE_COLOR_ALPHA = 6

COLORTYPE_TO_BPP_MAP = {
    '0': 1,
    '2': 3,
    '3': 1,
    '4': 2,
    '6': 4
}

GAMMA_DIVISION = 100000

def clamp(value):
    return max(0, min(255, round(value)))

def paethPredictor(left, above, upLeft):
    paeth = left + above - upLeft
    pLeft = abs(paeth - left)
    pAbove = abs(paeth - above)
    pUpLeft = abs(paeth - upLeft)

    if pLeft <= pAbove and pLeft <= pUpLeft: return left
    if pAbove <= pUpLeft: return above
    return upLeft

def filterNone(pxData, pxPos, byteWidth, rawData, rawPos, bpp):
    rawData[rawPos:rawPos+byteWidth] = pxData[pxPos:pxPos+byteWidth]

def filterSumNone(pxData, pxPos, byteWidth, bpp):
    sum = 0
    for i in range(pxPos, pxPos + byteWidth):
        sum += abs(pxData[i])
    return sum

def filterSub(pxData, pxPos, byteWidth, rawData, rawPos, bpp):
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        val = pxData[pxPos + x] - left
        rawData[rawPos + x] = ubyte(val)

def filterSumSub(pxData, pxPos, byteWidth, bpp):
    sum = 0
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        val = pxData[pxPos + x] - left
        sum += abs(val)
    return sum

def filterUp(pxData, pxPos, byteWidth, rawData, rawPos, bpp):
    for x in range(byteWidth):
        up = pxData[pxPos + x - byteWidth] if pxPos > 0 else 0
        val = pxData[pxPos + x] - up
        rawData[rawPos + x] = ubyte(val)

def filterSumUp(pxData, pxPos, byteWidth, bpp):
    sum = 0
    for x in range(pxPos, pxPos + byteWidth):
        up = pxData[x - byteWidth] if pxPos > 0 else 0
        val = pxData[x] - up
        sum += abs(val)
    return sum

def filterAvg(pxData, pxPos, byteWidth, rawData, rawPos, bpp):
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        up = pxData[pxPos + x - byteWidth] if pxPos > 0 else 0
        val = pxData[pxPos + x] - ((left + up) >> 1)
        rawData[rawPos + x] = ubyte(val)

def filterSumAvg(pxData, pxPos, byteWidth, bpp):
    sum = 0
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        up = pxData[pxPos + x - byteWidth] if pxPos > 0 else 0
        val = pxData[pxPos + x] - ((left + up) >> 1)
        sum += abs(val)
    return sum

def filterPaeth(pxData, pxPos, byteWidth, rawData, rawPos, bpp):
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        up = pxData[pxPos + x - byteWidth] if pxPos > 0 else 0
        upleft = pxData[pxPos + x - (byteWidth + bpp)] if pxPos > 0 and x >= bpp else 0
        val = pxData[pxPos + x] - paethPredictor(left, up, upleft)
        rawData[rawPos + x] = ubyte(val)

def filterSumPaeth(pxData, pxPos, byteWidth, bpp):
    sum = 0
    for x in range(byteWidth):
        left = pxData[pxPos + x - bpp] if x >= bpp else 0
        up = pxData[pxPos + x - byteWidth] if pxPos > 0 else 0
        upleft = pxData[pxPos + x - (byteWidth + bpp)] if pxPos > 0 and x >= bpp else 0
        val = pxData[pxPos + x] - paethPredictor(left, up, upleft)
        sum += abs(val)
    return sum


def deflate(data, compressionLevel=-1, chunkSize=None):
    #chunkSize = 16*1024 if chunkSize is None else chunkSize
    compressor = zlib.compressobj(level=compressionLevel)
    zdata = compressor.compress(data)
    zdata += compressor.flush()
    return zdata

def crc32(data):
    return zlib.crc32(data)

def ubyte(value):
    return value & 255

def I1(value):
    return struct.pack('!B', value & 255)

def I4(value):
    return struct.pack('!I', value & 0xffffffff)

def i4(value):
    return struct.pack('!i', value)

class PNGPacker:
    def __init__(self, options=dict()):
        options['deflateChunkSize'] = max(1024, int(options['deflateChunkSize'] if ('deflateChunkSize' in options) else 32 * 1024))
        options['deflateLevel'] = min(9, max(0, int(options['deflateLevel'] if ('deflateLevel' in options) else 9)))
        options['deflateStrategy'] = min(3, max(0, int(options['deflateStrategy'] if ('deflateStrategy' in options) else 3)))
        options['inputHasAlpha'] = bool(options['inputHasAlpha'] if ('inputHasAlpha' in options) else True)
        options['bitDepth'] = 8 #int(options['bitDepth'] if 'bitDepth' in options else 8)
        options['colorType'] = min(6, max(0, int(options['colorType'] if ('colorType' in options) else COLORTYPE_COLOR_ALPHA)))

        if (options['colorType'] != COLORTYPE_COLOR) and (options['colorType'] != COLORTYPE_COLOR_ALPHA):
            raise Exception('option color type:' + str(options['colorType']) + ' is not supported at present')

       #if options['bitDepth'] != 8:
       #    raise Exception('option bit depth:' + str(options['bitDepth']) + ' is not supported at present')
        self._options = options

    def toPNG(self, data, width, height):
        # Signature
        png = PNG_SIGNATURE

        # Header
        png += self.packIHDR(width, height)

        # gAMA
        if 'gamma' in self._options:
            png += self.packGAMA(self._options['gamma'])

        # filter data
        filteredData = self.filterData(data, width, height)

        # compress data
        deflateOpts = self.getDeflateOptions()
        compressedData = deflate(bytes(filteredData), deflateOpts['level'], deflateOpts['chunkSize'])
        filteredData = None

        # Data
        png += self.packIDAT(compressedData)
        compressedData = None

        # End
        png += self.packIEND()

        return png

    def getDeflateOptions(self):
        return {
            'chunkSize': self._options['deflateChunkSize'],
            'level': self._options['deflateLevel'],
            'strategy': self._options['deflateStrategy']
        }

    def filterData(self, data, width, height):
        # convert to correct format for filtering (e.g. right bpp and bit depth)
        # and filter pixel data
        return self._filter(self._bitPack(data, width, height), width, height)

    def packIHDR(self, width, height):
        IHDR = I4(width) + I4(height)
        IHDR += I1(self._options['bitDepth']) # bit depth
        IHDR += I1(self._options['colorType']) # color type
        IHDR += I1(0) # compression
        IHDR += I1(0) # filter
        IHDR += I1(0) # interlace
        return self._packChunk('IHDR', IHDR)

    def packGAMA(self, gamma):
        return self._packChunk('gAMA', I4(math.floor(float(gamma) * GAMMA_DIVISION)))

    def packIDAT(self, data):
        return self._packChunk('IDAT', data)

    def packIEND(self):
        return self._packChunk('IEND', None)

    def _bitPack(self, data, width, height):
        outHasAlpha = ('colorType' in self._options) and self._options['colorType'] == COLORTYPE_COLOR_ALPHA
        inputHasAlpha = ('inputHasAlpha' in self._options) and bool(self._options['inputHasAlpha'])

        if inputHasAlpha and outHasAlpha: return data
        if (not inputHasAlpha) and (not outHasAlpha): return data

        outBpp = 4 if outHasAlpha else 3
        outData = [0] * (width * height * outBpp)
        inBpp = 4 if inputHasAlpha else 3
        inIndex = 0
        outIndex = 0

        bgColor = self._options['bgColor'] if 'bgColor' in self._options else {}
        bgRed = clamp(bgColor['red'] if 'red' in bgColor else 255)
        bgGreen = clamp(bgColor['green'] if 'green' in bgColor else 255)
        bgBlue = clamp(bgColor['blue'] if 'blue' in bgColor else 255)

        for y in range(height):
            for x in range(width):
                red = data[inIndex]
                green = data[inIndex + 1]
                blue = data[inIndex + 2]

                if inputHasAlpha:
                    alpha = data[inIndex + 3]
                    if not outHasAlpha:
                        alpha = float(alpha) / 255.0
                        red = (1 - alpha) * bgRed + alpha * red
                        green = (1 - alpha) * bgGreen + alpha * green
                        blue = (1 - alpha) * bgBlue + alpha * blue
                else:
                    alpha = 255

                outData[outIndex] = clamp(red)
                outData[outIndex + 1] = clamp(green)
                outData[outIndex + 2] = clamp(blue)
                if outHasAlpha: outData[outIndex + 3] = clamp(alpha)

                inIndex += inBpp
                outIndex += outBpp

        return outData

    def _filter(self, pxData, width, height):
        filters = [
          filterNone,
          filterSub,
          filterUp,
          filterAvg,
          filterPaeth
        ]

        filterSums = [
          filterSumNone,
          filterSumSub,
          filterSumUp,
          filterSumAvg,
          filterSumPaeth
        ]

        filterTypes = [0] # make it default

        #if (not 'filterType' in self._options) or (self._options['filterType'] == -1):
        #    filterTypes = [0, 1, 2, 3, 4]
        #elif int(self._options['filterType']) == self._options['filterType']:
        #    filterTypes = [self._options['filterType']]
        #else:
        #    raise Exception('unrecognised filter types')

        bpp = COLORTYPE_TO_BPP_MAP[str(self._options['colorType'])]
        byteWidth = width * bpp
        rawPos = 0
        pxPos = 0
        rawData = [0] * ((byteWidth + 1) * height)
        sel = filterTypes[0]
        n = len(filterTypes)

        for y in range(height):
            if n > 1:
                # find best filter for this line (with lowest sum of values)
                min = math.inf
                for i in range(n):
                    sum = filterSums[filterTypes[i]](pxData, pxPos, byteWidth, bpp)
                    if sum < min:
                        sel = filterTypes[i]
                        min = sum

            rawData[rawPos] = sel
            rawPos += 1
            filters[sel](pxData, pxPos, byteWidth, rawData, rawPos, bpp)
            rawPos += byteWidth
            pxPos += byteWidth
        return rawData

    def _packChunk(self, type, data = None):
        block = str(type).encode('ascii')
        length = 0
        if data is not None:
            if isinstance(data, list): data = bytes(data)
            length = len(data)
            block += data
        return I4(length) + block + I4(crc32(block))


__all__ = ['SimpleCaptcha']
For more information send a message to info at phpclasses dot org.