def __init__(self, size=(256, 256), scheme='classic', dotsize=50, pointgrid_size=1, padding=0):
     if pointgrid_size < 1 or pointgrid_size >= min(size):
         raise ValueError("pointgrid_size must be >= 1 and <= {0}!".format(min(size)))
     self.size = size
     self.padsize = (size[0] + padding * 2, size[1] + padding * 2)
     self.padding = padding
     self.pointgrid_size = pointgrid_size
     self.colors = ColorScheme(scheme).get()
     self.img = Image.new('RGBA', size=self.padsize, color=(0, 0, 0, 0))
     self.points = []
     self.dot = Dot(size=dotsize)
     self.dots = {}
     self.dot_offset = float(self.dot.size) / 2
class HeatMap():
    def __init__(self, size=(256, 256), scheme='classic', dotsize=50, pointgrid_size=1, padding=0):
        if pointgrid_size < 1 or pointgrid_size >= min(size):
            raise ValueError("pointgrid_size must be >= 1 and <= {0}!".format(min(size)))
        self.size = size
        self.padsize = (size[0] + padding * 2, size[1] + padding * 2)
        self.padding = padding
        self.pointgrid_size = pointgrid_size
        self.colors = ColorScheme(scheme).get()
        self.img = Image.new('RGBA', size=self.padsize, color=(0, 0, 0, 0))
        self.points = []
        self.dot = Dot(size=dotsize)
        self.dots = {}
        self.dot_offset = float(self.dot.size) / 2

    def add_points(self, points, data_bounds=None):
        if not data_bounds:
            _points = points

            _points = map(
                lambda p: self._snap_to_grid(p),
                _points
            )
        else:
            ((xmin, ymin), (xmax, ymax)) = data_bounds
            rangex, rangey = xmax - xmin, ymax - ymin
            xmod, ymod = float(self.size[0]) / float(rangex), float(self.size[1]) / float(rangey)
            _points = []
            for point in points:
                p = ((point[0] - xmin) * xmod, (point[1] - ymin) * ymod)
                _points.append(self._snap_to_grid(p))
        _points = map(lambda p: (p[0] - self.dot_offset, p[1] - self.dot_offset), _points)
        self._normalize_points(_points)

    def _snap_to_grid(self, point):
        if self.pointgrid_size == 1:
            return point
        s = self.pointgrid_size
        x, y = point
        x = x - x % s + round(x % s / float(s)) * s
        y = y - y % s + round(y % s / float(s)) * s
        return x, y

    def _normalize_points(self, points):
        self.dots = {}
        values = {}
        self.points = []
        max_value = 1
        for point in points:
            x, y = round(point[0]), round(point[1])
            newpoint = (int(x - x % self.pointgrid_size), int(y - y % self.pointgrid_size))

            if not newpoint in self.points:
                self.points.append(newpoint)
                values[newpoint] = 1
            else:
                values[newpoint] += 1
                max_value = max(max_value, values[newpoint])

        for key, value in values.iteritems():
            percent = value * 100.0 / float(max_value)
            self.dots[key] = self.dot.get_image(percent)

    def _colorize(self):
        _computed_opacities = {}
        pix = self.img.load()
        for x in range(self.padsize[0]):
            for y in range(self.padsize[1]):
                # Get color for this intensity
                # ============================
                # is a value

                val = self.colors[0, pix[x, y][3]]
                try:
                    pix_alpha = val[3]  # the color image has transparency
                except IndexError:
                    pix_alpha = 255  # it doesn't


                # Blend the opacities
                # ===================

                conf, pixel = 255, pix_alpha
                if (conf, pixel) not in _computed_opacities:
                    opacity = int(( (conf/255.0)    # from configuration
                                    * (pixel/255.0)   # from per-pixel alpha
                                  ) * 255)
                    _computed_opacities[(conf, pixel)] = opacity

                if pix[x, y][3] > 0:
                    pix[x, y] = val[:3] + (_computed_opacities[(conf, pixel)],)

    def generate(self):
        for point, dot in self.dots.iteritems():
            self.img.paste(dot, (point[0] + self.padding, point[1] + self.padding), dot)
        self._colorize()

    def save(self, out, outformat=None):
        if outformat is None:
            self.img.save(out)
        else:
            self.img.save(out, outformat)