Skip to content

Instantly share code, notes, and snippets.

@jbergstroem
Created April 28, 2011 10:06
Show Gist options
  • Save jbergstroem/946116 to your computer and use it in GitHub Desktop.
Save jbergstroem/946116 to your computer and use it in GitHub Desktop.
PEP8 fixes
diff --git a/spritecss/__init__.py b/spritecss/__init__.py
index 9cf604a..8e2881a 100644
--- a/spritecss/__init__.py
+++ b/spritecss/__init__.py
@@ -15,6 +15,7 @@ generator
references to sprites that have been mapped.
"""
+
class SpriteMap(list):
def __init__(self, fname, L=[]):
self.fname = fname
@@ -28,6 +29,7 @@ class SpriteMap(list):
return o.fname == self.fname
return NotImplemented
+
class SpriteRef(object):
"""Reference to a sprite, existent or not."""
@@ -49,6 +51,7 @@ class SpriteRef(object):
return o.fname == self.fname
return NotImplemented
+
class MappedSpriteRef(SpriteRef):
def __init__(self, fname, source, pos):
super(MappedSpriteRef, self).__init__(fname, source)
diff --git a/spritecss/config.py b/spritecss/config.py
index 0d5cdda..5075a63 100644
--- a/spritecss/config.py
+++ b/spritecss/config.py
@@ -1,23 +1,27 @@
import shlex
from os import path
-from itertools import imap, ifilter
-from urlparse import urljoin
+
+from urllib.parse import urljoin
from .css import CSSParser, iter_events
+
def parse_config_stmt(line, prefix="spritemapper."):
line = line.strip()
if line.startswith(prefix) and "=" in line:
(key, value) = line.split("=", 1)
return (key[len(prefix):].strip(), value.strip())
+
def iter_config_stmts(data):
- return ifilter(None, imap(parse_config_stmt, data.splitlines()))
+ return filter(None, map(parse_config_stmt, data.splitlines()))
+
def iter_css_config(parser):
for ev in iter_events(parser, lexemes=("comment",)):
for v in iter_config_stmts(ev.comment):
yield v
+
class CSSConfig(object):
def __init__(self, parser=None, base=None, root=None, fname=None):
if fname and root is None:
@@ -29,7 +33,7 @@ class CSSConfig(object):
def __iter__(self):
# this is mostly so you can go CSSConfig(base=CSSConfig(..))
- return self._data.iteritems()
+ return iter(self._data.items())
@classmethod
def from_file(cls, fname):
@@ -59,7 +63,7 @@ class CSSConfig(object):
raise RuntimeError("cannot have sprite_dirs "
"when output_image is set")
sdirs = shlex.split(self._data["sprite_dirs"])
- return map(self.normpath, sdirs)
+ return list(map(self.normpath, sdirs))
@property
def output_image(self):
@@ -106,18 +110,20 @@ class CSSConfig(object):
else:
return path.join(dirn, "sm_" + base)
+
def print_config(fname):
from pprint import pprint
from .css import CSSParser
with open(fname, "rb") as fp:
- print "%s\n%s\n" % (fname, "=" * len(fname))
+ print("%s\n%s\n" % (fname, "=" * len(fname)))
pprint(dict(iter_css_config(CSSParser.read_file(fp))))
- print
+ print()
+
def main():
import sys
- map(print_config, sys.argv[1:])
+ list(map(print_config, sys.argv[1:]))
if __name__ == "__main__":
main()
diff --git a/spritecss/css/__init__.py b/spritecss/css/__init__.py
index bad446f..bf37360 100644
--- a/spritecss/css/__init__.py
+++ b/spritecss/css/__init__.py
@@ -1,22 +1,24 @@
"""Pure-Python CSS parser - no dependencies!"""
-# Copyright held by Yo Studios AB <opensource@yo.se>, 2011
+# Copyright held by Yo Studios AB <opensource@yo.se>, 2011
#
# Part of Spritemapper (https://github.com/yostudios/Spritemapper)
# Released under a MIT/X11 license
from .parser import CSSParser, print_css
-from itertools import ifilter, imap
+
__all__ = ["CSSParser", "iter_events", "split_declaration",
"print_css", "iter_declarations"]
+
def iter_events(parser, lexemes=None, predicate=None):
if lexemes and predicate:
raise TypeError("specify either events or predicate, not both")
elif lexemes:
predicate = lambda e: e.lexeme in lexemes
- return ifilter(predicate, iter(parser))
+ return filter(predicate, iter(parser))
+
def split_declaration(decl):
parts = decl.split(":", 1)
@@ -26,6 +28,7 @@ def split_declaration(decl):
(prop, val) = parts
return (prop, val)
+
def iter_declarations(parser, predicate=None):
evs = iter_events(parser, lexemes=("declaration",))
- return imap(split_declaration, evs)
+ return map(split_declaration, evs)
diff --git a/spritecss/css/parser.py b/spritecss/css/parser.py
index 527ff88..f70ac17 100644
--- a/spritecss/css/parser.py
+++ b/spritecss/css/parser.py
@@ -18,13 +18,15 @@ following lexemes:
"""
import sys
-from itertools import imap
+
from collections import deque
+
def bisect(v, midpoint):
"""Split ordered sequence *v* at the index *midpoint*."""
return (v[:midpoint], v[midpoint:])
+
class EventStream(object):
def __init__(self, events=None):
if events is None:
@@ -32,7 +34,7 @@ class EventStream(object):
self._events = events
self._event = None
- def next(self):
+ def __next__(self):
self._event = event = self._events.popleft()
return event
@@ -47,6 +49,7 @@ class EventStream(object):
def push(self, event):
self._events.append(event)
+
class OutOfTokens(Exception):
"""A lot like StopIteration: signals that the end of the input token stream
has been reached.
@@ -55,11 +58,12 @@ class OutOfTokens(Exception):
to `CSSParser._eval_once`.
"""
+
class Token(object):
"""A token from CSS code. Lexeme definitions:
"char" : multiple
- a character without special meaning (this includes quotes and parentheses)
+ characters without special meaning (this includes quotes and parentheses)
"w" : multiple
some form of whitespace, see str.isspace (note: contiguous whitespace may
@@ -105,17 +109,20 @@ class Token(object):
other.value == self.value
return NotImplemented
+
def _bytestream(chunks):
"""Yield each byte of a set of chunks."""
for chunk in chunks:
for char in chunk:
yield char
+
def _css_token_stream(chars):
for char in chars:
yield Token(value=char)
yield Token(lexeme="eof")
+
def _css_tokenize_comments(toks):
begin = None
expect = None
@@ -153,6 +160,7 @@ def _css_tokenize_comments(toks):
else:
yield tok
+
def _css_tokenize_strings(toks):
escaped = False
expect = ""
@@ -175,6 +183,7 @@ def _css_tokenize_strings(toks):
yield tok
+
def _css_tokenizer_lvl1(chars):
"""Tokenize comment begin/end, block begin/end, colon, semicolon,
whitespace.
@@ -199,6 +208,7 @@ def _css_tokenizer_lvl1(chars):
tok.lexeme = "char"
yield tok
+
def _css_tokenizer_lineno(toks):
"""Tokenize and count line numbers. Yields states."""
col_no = 1
@@ -213,12 +223,15 @@ def _css_tokenizer_lineno(toks):
else:
col_no += 1
+
def css_tokenize(it):
return _css_tokenizer_lineno(_css_tokenizer_lvl1(_bytestream(it)))
+
def css_tokenize_data(css):
return css_tokenize([css])
+
class CSSParseState(object):
"""The state of the CSS parser."""
@@ -279,12 +292,12 @@ class CSSParseState(object):
return self.prev(token=self.token)
def update(self, **kwds):
- for (nam, val) in kwds.iteritems():
+ for (nam, val) in kwds.items():
if nam not in self.__slots__:
raise TypeError(nam)
setattr(self, nam, val)
- def next(self):
+ def __next__(self):
"""Take the next token from the token stream and place it as the
current `token`.
@@ -292,7 +305,7 @@ class CSSParseState(object):
None.
"""
try:
- self.token = tok = self.tokens.next()
+ self.token = tok = next(self.tokens)
except StopIteration:
self.token = None
raise OutOfTokens
@@ -317,7 +330,7 @@ class CSSParseState(object):
tok = self.token
while predicate(tok):
yield tok
- tok = self.next()
+ tok = next(self)
@classmethod
def from_chunks(cls, chunks, **kwds):
@@ -326,6 +339,7 @@ class CSSParseState(object):
"""
return cls(tokens=css_tokenize(chunks), **kwds)
+
# {{{ event defs
class CSSParserEvent(object):
__slots__ = ("state",)
@@ -333,6 +347,7 @@ class CSSParserEvent(object):
def __init__(self, state):
self.state = state
+
class Selector(CSSParserEvent):
lexeme = "selector"
__slots__ = ("selector",)
@@ -341,6 +356,7 @@ class Selector(CSSParserEvent):
self.state = state
self.selector = selector if selector else state.selector
+
class AtRule(CSSParserEvent):
__slots__ = ("at_rule",)
@@ -348,12 +364,15 @@ class AtRule(CSSParserEvent):
self.state = state
self.at_rule = at_rule if at_rule else state.at_rule
+
class AtBlock(AtRule):
lexeme = "at_block"
+
class AtStatement(AtRule):
lexeme = "at_statement"
+
class Comment(CSSParserEvent):
lexeme = "comment"
__slots__ = ("comment",)
@@ -362,6 +381,7 @@ class Comment(CSSParserEvent):
self.state = state
self.comment = comment if comment else state.comment
+
class Declaration(CSSParserEvent):
lexeme = "declaration"
__slots__ = ("declaration",)
@@ -370,9 +390,11 @@ class Declaration(CSSParserEvent):
self.state = state
self.declaration = declaration if declaration else state.declaration
+
class BlockEnd(CSSParserEvent):
lexeme = "block_end"
+
class Whitespace(CSSParserEvent):
lexeme = "whitespace"
__slots__ = ("whitespace")
@@ -382,6 +404,7 @@ class Whitespace(CSSParserEvent):
self.whitespace = whitespace if whitespace else state.whitespace
# }}}
+
class CSSParser(EventStream):
"""An event stream of parser events."""
@@ -415,7 +438,7 @@ class CSSParser(EventStream):
"""Iterator over printable the CSS code."""
evs = self.iter_events()
if converter:
- evs = imap(converter, evs)
+ evs = map(converter, evs)
return iter_print_css(evs)
def evaluate(self, st=None):
@@ -425,7 +448,7 @@ class CSSParser(EventStream):
h = self._handle_any
else:
h = st.handler
- st.next()
+ next(st)
new = h(st)
if new is None:
raise RuntimeError("invalid transition from %r" % (st,))
@@ -521,6 +544,7 @@ class CSSParser(EventStream):
def _handle_eof(self, st):
raise IOError("cannot parse beyond end of file")
+
def iter_print_css(parser):
for event in parser:
if event.lexeme == "comment":
@@ -540,13 +564,16 @@ def iter_print_css(parser):
else:
raise RuntimeError("unknown event %s" % (event,))
+
def print_css(parser, out=sys.stdout):
"""Print an event stream of CSS parser events."""
for data in iter_print_css(parser):
out.write(data)
+
def main():
print_css(CSSParser.read_file(sys.stdin), out=sys.stdout)
+
if __name__ == "__main__":
main()
diff --git a/spritecss/finder.py b/spritecss/finder.py
index e0c5434..dbcf5b6 100644
--- a/spritecss/finder.py
+++ b/spritecss/finder.py
@@ -12,12 +12,18 @@ logger = logging.getLogger(__name__)
bg_url_re = re.compile(r'\s*url\([\'"]?(.*?)[\'"]?\)\s*')
-class NoSpriteFound(Exception): pass
-class PositionedBackground(NoSpriteFound): pass
+
+class NoSpriteFound(Exception):
+ pass
+
+
+class PositionedBackground(NoSpriteFound):
+ pass
_pos_names = ("top", "center", "bottom", "right", "middle", "left")
_pos_units = ("px", "%")
+
def _bg_positioned(val):
# TODO Improve detection of positioned backgrounds
parts = val.split()
@@ -27,23 +33,27 @@ def _bg_positioned(val):
return True
return False
+
def _match_background_url(val):
mo = bg_url_re.match(val)
if not mo:
raise NoSpriteFound(val)
return mo.groups()[0]
+
def get_background_url(val):
if _bg_positioned(val):
raise PositionedBackground(val)
return _match_background_url(val)
+
def find_decl_background_url(decl):
(prop, val) = split_declaration(decl)
if prop not in ("background", "background-image"):
raise NoSpriteFound(decl)
return get_background_url(val)
+
class SpriteEvent(object):
lexeme = "spriteref"
@@ -52,6 +62,7 @@ class SpriteEvent(object):
self.declaration = ev.declaration
self.sprite = sprite
+
def iter_spriterefed(evs, conf=None, source=None, root=None):
if source and not root:
root = path.dirname(source)
@@ -76,22 +87,25 @@ def iter_spriterefed(evs, conf=None, source=None, root=None):
ev = SpriteEvent(ev, sprite)
yield ev
+
def find_sprite_refs(*args, **kwds):
for ev in iter_spriterefed(*args, **kwds):
if ev.lexeme == "spriteref":
yield ev.sprite
+
def main():
import sys
import json
from .css import CSSParser
for fname in sys.argv[1:]:
with open(fname, "rb") as fp:
- print >>sys.stderr, "extracting from", fname
+ print("extracting from", fname, file=sys.stderr)
parser = CSSParser.read_file(fp)
- srefs = map(str, find_sprite_refs(parser, source=fname))
+ srefs = list(map(str, find_sprite_refs(parser, source=fname)))
v = [fname, srefs]
json.dump(v, sys.stdout, indent=2)
+
if __name__ == "__main__":
main()
diff --git a/spritecss/image.py b/spritecss/image.py
index f0a127a..a60a05f 100644
--- a/spritecss/image.py
+++ b/spritecss/image.py
@@ -1,5 +1,6 @@
from . import png
+
# TODO Image class should abstract `pixels`
# TODO Image class shouldn't assume RGBA
class Image(object):
diff --git a/spritecss/main.py b/spritecss/main.py
index ab278d3..0beadf9 100644
--- a/spritecss/main.py
+++ b/spritecss/main.py
@@ -2,7 +2,7 @@ import sys
import logging
import optparse
from os import path, access, R_OK
-from itertools import ifilter
+
from contextlib import contextmanager
from spritecss.css import CSSParser, print_css
@@ -16,6 +16,7 @@ from spritecss.replacer import SpriteReplacer
logger = logging.getLogger(__name__)
+
# TODO CSSFile should probably fit into the bigger picture
class CSSFile(object):
def __init__(self, fname, conf=None):
@@ -43,12 +44,17 @@ class CSSFile(object):
def map_sprites(self):
with self.open_parser() as p:
srefs = find_sprite_refs(p, conf=self.conf, source=self.fname)
+
def test_sref(sref):
if not access(str(sref), R_OK):
- logger.error("%s: not readable", sref); return False
+ logger.error("%s: not readable", sref)
+ return False
else:
- logger.debug("%s passed", sref); return True
- return self.mapper.map_reduced(ifilter(test_sref, srefs))
+ logger.debug("%s passed", sref)
+ return True
+
+ return self.mapper.map_reduced(filter(test_sref, srefs))
+
class InMemoryCSSFile(CSSFile):
def __init__(self, *a, **k):
@@ -61,6 +67,7 @@ class InMemoryCSSFile(CSSFile):
def open_parser(self):
yield self._evs
+
def spritemap(css_fs, conf=None, out=sys.stderr):
w_ln = lambda t: out.write(t + "\n")
@@ -111,6 +118,7 @@ op.add_option("-v", "--verbose", action="store_true",
op.set_default("in_memory", False)
op.set_default("anneal", None)
+
def main():
(opts, args) = op.parse_args()
@@ -127,7 +135,7 @@ def main():
base = {}
if opts.conf:
- from ConfigParser import ConfigParser
+ from configparser import ConfigParser
cp = ConfigParser()
with open(opts.conf) as fp:
cp.readfp(fp)
diff --git a/spritecss/mapper.py b/spritecss/mapper.py
index f692e4f..927e9a4 100644
--- a/spritecss/mapper.py
+++ b/spritecss/mapper.py
@@ -5,6 +5,7 @@ from spritecss.config import CSSConfig
logger = logging.getLogger(__name__)
+
class BaseMapper(object):
def __call__(self, sprite):
try:
@@ -29,6 +30,7 @@ class BaseMapper(object):
smap.append(sref)
return smaps
+
class OutputImageMapper(BaseMapper):
"""Maps all sprites to a single output spritemap."""
@@ -44,6 +46,7 @@ class OutputImageMapper(BaseMapper):
def _map_sprite_ref(self, sref):
return self.fname
+
class SpriteDirsMapper(BaseMapper):
"""Maps sprites to spritemaps by using the sprite directory."""
@@ -75,6 +78,7 @@ class SpriteDirsMapper(BaseMapper):
raise LookupError
+
def mapper_from_conf(conf):
if conf.output_image:
assert not conf.is_mapping_recursive
@@ -83,6 +87,7 @@ def mapper_from_conf(conf):
cls = SpriteDirsMapper
return cls.from_conf(conf)
+
class SpriteMapCollector(object):
"""Collect spritemap listings from sprite references."""
@@ -100,7 +105,7 @@ class SpriteMapCollector(object):
return self.smaps.get(None, SpriteMap(None, []))
def collect(self, smaps):
- for fname, smap in smaps.iteritems():
+ for fname, smap in smaps.items():
if fname in self._maps:
self._maps[fname].extend(smap)
else:
@@ -125,12 +130,14 @@ class SpriteMapCollector(object):
return self.map_sprite_refs(srefs, mapper=mapper)
+
def print_spritemaps(smaps):
for smap in sorted(smaps, key=lambda sm: sm.fname):
- print smap.fname
+ print(smap.fname)
for sref in smap:
- print "-", sref
- print
+ print("-", sref)
+ print()
+
def _map_and_print(fnames):
smaps = SpriteMapCollector()
@@ -138,6 +145,7 @@ def _map_and_print(fnames):
smaps.map_file(fname)
print_spritemaps(smaps)
+
def _map_fnames(fnames):
import sys
import json
@@ -152,9 +160,11 @@ def _map_fnames(fnames):
srefs = [SpriteRef(sref, source=src_fname) for sref in srefs]
smaps.collect(mapper.map_reduced(srefs))
- print >>sys.stderr, "mapped", len(srefs), "sprites in", src_fname
+ print("mapped", len(srefs), "sprites in", src_fname, file=sys.stderr)
+
+ json.dump([(smap.fname, list(map(str, smap))) for smap in smaps],
+ sys.stdout, indent=2)
- json.dump([(smap.fname, map(str, smap)) for smap in smaps], sys.stdout, indent=2)
def main():
import sys
@@ -172,5 +182,6 @@ def main():
example_fn = "ext/Spritemapper/css/example_source.css"
_map_and_print([path.normpath(path.join(src_dir, example_fn))])
+
if __name__ == "__main__":
main()
diff --git a/spritecss/packing/__init__.py b/spritecss/packing/__init__.py
index ee37084..9d33261 100644
--- a/spritecss/packing/__init__.py
+++ b/spritecss/packing/__init__.py
@@ -21,17 +21,22 @@ algorithm, as follows:
import random
from .anneal import Annealer
+
class Rect(object):
def __init__(self, rect=None, x1=None, y1=None, x2=None, y2=None):
# calculate rect
if rect:
if not hasattr(rect, "x1"):
- kwds = dict(zip(("x1", "y1", "x2", "y2"), rect))
+ kwds = dict(list(zip(("x1", "y1", "x2", "y2"), rect)))
rect = Rect(**kwds)
- if x1 is None: x1 = rect.x1
- if y1 is None: y1 = rect.y1
- if x2 is None: x2 = rect.x2
- if y2 is None: y2 = rect.y2
+ if x1 is None:
+ x1 = rect.x1
+ if y1 is None:
+ y1 = rect.y1
+ if x2 is None:
+ x2 = rect.x2
+ if y2 is None:
+ y2 = rect.y2
(self.x1, self.y1, self.x2, self.y2) = (x1, y1, x2, y2)
@@ -68,13 +73,16 @@ class Rect(object):
def from_size(cls, size):
return cls((0, 0, size[0], size[1]))
+
class NoRoom(Exception):
pass
+
class Node(object):
def insert(self, rect):
raise NoRoom
+
class BoxNode(Node, Rect):
def insert(self, rect):
# If we've got sub-nodes, they are responsible for allocating space.
@@ -111,11 +119,11 @@ class BoxNode(Node, Rect):
opaque = OpaqueBoxNode(used, x2=(used.x1 + rect.width + rect.pad_x),
y2=(used.y1 + rect.height + rect.pad_y))
fragments = [opaque]
- if opaque.y2 < used.y2: # vertical remainder
+ if opaque.y2 < used.y2: # vertical remainder
fragments.append(BoxNode(used, y1=opaque.y2, x2=opaque.x2))
- if opaque.x2 < used.x2: # horizontal remainder
+ if opaque.x2 < used.x2: # horizontal remainder
fragments.append(BoxNode(used, x1=opaque.x2, y2=opaque.y2))
- if opaque.y2 < used.y2 and opaque.x2 < used.x2: # diagonal remainder
+ if opaque.y2 < used.y2 and opaque.x2 < used.x2: # diagonal remainder
fragments.append(BoxNode(used, x1=opaque.x2, y1=opaque.y2))
used.children = tuple(f for f in fragments if f.area)
@@ -137,10 +145,12 @@ class BoxNode(Node, Rect):
else:
raise NoRoom("couldn't fit into any child")
+
class OpaqueBoxNode(BoxNode):
def insert(self, rect):
raise NoRoom("opaque box node")
+
class PackingAnnealer(Annealer):
def __init__(self, boxes):
# self.move, self.energy need not be set: the class methods are fine.
@@ -150,7 +160,7 @@ class PackingAnnealer(Annealer):
sum(b.outer_height for b in boxes))
def move(self, state):
- a, b = random.sample(xrange(len(state)), 2)
+ a, b = random.sample(range(len(state)), 2)
state[a], state[b] = state[b], state[a]
def energy(self, state):
@@ -170,9 +180,10 @@ class PackingAnnealer(Annealer):
return w * h
def anneal(self, *a, **k):
- state, e = Annealer.anneal(self, range(len(self.boxes)), *a, **k)
+ state, e = Annealer.anneal(self, list(range(len(self.boxes))), *a, **k)
# Crops nodes to fit entire map exactly
w, h = self._last_size
+
def walk(n):
n.x2 = min(n.x2, w)
n.y2 = min(n.y2, h)
@@ -183,6 +194,7 @@ class PackingAnnealer(Annealer):
walk(self._last_tree)
return self._last_plcs, self._last_size
+
class PackedBoxes(object):
def __init__(self, boxes, pad=(0, 0), anneal_steps=9200):
self.pad = pad
@@ -213,11 +225,13 @@ class PackedBoxes(object):
def unused_amount(self):
return float(self.unused_area) / self.area
+
def print_packed_size(packed, out=None):
args = (packed.size + (packed.unused_amount * 100,))
- print >>out, "Packed size is %dx%d (%.3f%% empty space)" % args
+ print("Packed size is %dx%d (%.3f%% empty space)" % args, file=out)
+
def dump_placements(packed, out=None):
for (pos, box) in packed.placements:
box_desc = ",".join(map(str, box.calc_box(pos)))
- print >>out, "{0},{1}".format(box.fname, box_desc)
+ print("{0},{1}".format(box.fname, box_desc), file=out)
diff --git a/spritecss/packing/anneal.py b/spritecss/packing/anneal.py
index 4a273c2..8208fff 100644
--- a/spritecss/packing/anneal.py
+++ b/spritecss/packing/anneal.py
@@ -1,13 +1,13 @@
#!/usr/bin/env python
# Python module for simulated annealing - anneal.py - v1.0 - 2 Sep 2009
-#
+#
# Copyright (c) 2009, Richard J. Wagner <wagnerr@umich.edu>
-#
+#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
@@ -26,31 +26,37 @@ cities in the United States.
"""
# How to optimize a system with simulated annealing:
-#
+#
# 1) Define a format for describing the state of the system.
-#
+#
# 2) Define a function to calculate the energy of a state.
-#
+#
# 3) Define a function to make a random change to a state.
-#
+#
# 4) Choose a maximum temperature, minimum temperature, and number of steps.
-#
+#
# 5) Set the annealer to work with your state and functions.
-#
+#
# 6) Study the variation in energy with temperature and duration to find a
# productive annealing schedule.
-#
+#
# Or,
-#
+#
# 4) Run the automatic annealer which will attempt to choose reasonable values
# for maximum and minimum temperatures and then anneal for the allotted time.
-import copy, math, random, sys, time
+import copy
+import math
+import random
+import sys
+import time
+
def round_figures(x, n):
"""Returns x rounded to n significant figures."""
return round(x, int(n - math.ceil(math.log10(abs(x)))))
+
def time_string(seconds):
"""Returns time in seconds as a string formatted HHHH:MM:SS."""
s = int(round(seconds)) # round to nearest second
@@ -58,6 +64,7 @@ def time_string(seconds):
m, s = divmod(s, 60) # split remainder into minutes and seconds
return '%4i:%02i:%02i' % (h, m, s)
+
class Annealer:
"""Performs simulated annealing by calling functions to calculate
energy and make moves on a state. The temperature schedule for
@@ -90,30 +97,31 @@ class Annealer:
"""Prints the current temperature, energy, acceptance rate,
improvement rate, elapsed time, and remaining time.
- The acceptance rate indicates the percentage of moves since the last
- update that were accepted by the Metropolis algorithm. It includes
- moves that decreased the energy, moves that left the energy
- unchanged, and moves that increased the energy yet were reached by
- thermal excitation.
+ The acceptance rate indicates the percentage of moves since the
+ last update that were accepted by the Metropolis algorithm.
+ It includes moves that decreased the energy, moves that left the
+ energy unchanged, and moves that increased the energy yet were
+ reached by thermal excitation.
The improvement rate indicates the percentage of moves since the
- last update that strictly decreased the energy. At high
+ last update that strictly decreased the energy. At high
temperatures it will include both moves that improved the overall
state and moves that simply undid previously accepted moves that
increased the energy by thermal excititation. At low temperatures
it will tend toward zero as the moves that can decrease the energy
- are exhausted and moves that would increase the energy are no longer
- thermally accessible."""
+ are exhausted and moves that would increase the energy are no
+ longer thermally accessible."""
elapsed = time.time() - start
if step == 0:
- wln(' Temperature Energy Accept Improve Elapsed Remaining')
- wln('%12.2f %12.2f %s ' %
- (T, E, time_string(elapsed) ))
+ wln(' Temperature Energy Accept Improve'
+ ' Elapsed Remaining')
+ wln('%12.2f %12.2f %s '
+ % (T, E, time_string(elapsed)))
else:
- remain = ( steps - step ) * ( elapsed / step )
- wln('%12.2f %12.2f %7.2f%% %7.2f%% %s %s' %
- (T, E, 100.0*acceptance, 100.0*improvement,
+ remain = (steps - step) * (elapsed / step)
+ wln('%12.2f %12.2f %7.2f%% %7.2f%% %s %s'
+ % (T, E, 100.0 * acceptance, 100.0 * improvement,
time_string(elapsed), time_string(remain)))
# Precompute factor for exponential cooling from Tmax to Tmin
@@ -121,7 +129,7 @@ class Annealer:
raise ValueError('exponential cooling requires a minimum '
'temperature greater than zero')
- Tfactor = -math.log( float(Tmax) / Tmin )
+ Tfactor = -math.log(float(Tmax) / Tmin)
# Note initial state
T = Tmax
@@ -138,12 +146,12 @@ class Annealer:
# Attempt moves to new states
while step < steps:
step += 1
- T = Tmax * math.exp( Tfactor * step / steps )
+ T = Tmax * math.exp(Tfactor * step / steps)
self.move(state)
E = self.energy(state)
dE = E - prevEnergy
trials += 1
- if dE > 0.0 and math.exp(-dE/T) < random.random():
+ if dE > 0.0 and math.exp(-dE / T) < random.random():
# Restore previous state
state = copy.deepcopy(prevState)
E = prevEnergy
@@ -158,8 +166,9 @@ class Annealer:
bestState = copy.deepcopy(state)
bestEnergy = E
if updates > 1:
- if step // updateWavelength > (step-1) // updateWavelength:
- update(T, E, float(accepts)/trials, float(improves)/trials)
+ if step // updateWavelength > (step - 1) // updateWavelength:
+ update(T, E, float(accepts) / trials,
+ float(improves) / trials)
trials, accepts, improves = 0, 0, 0
# Return best state and energy
@@ -189,7 +198,7 @@ class Annealer:
self.move(state)
E = self.energy(state)
dE = E - prevEnergy
- if dE > 0.0 and math.exp(-dE/T) < random.random():
+ if dE > 0.0 and math.exp(-dE / T) < random.random():
state = copy.deepcopy(prevState)
E = prevEnergy
else:
@@ -198,7 +207,7 @@ class Annealer:
improves += 1
prevState = copy.deepcopy(state)
prevEnergy = E
- return state, E, float(accepts)/steps, float(improves)/steps
+ return state, E, float(accepts) / steps, float(improves) / steps
step = 0
start = time.time()
@@ -211,27 +220,29 @@ class Annealer:
while T == 0.0:
step += 1
self.move(state)
- T = abs( self.energy(state) - E )
+ T = abs(self.energy(state) - E)
wln('Exploring temperature landscape:')
wln(' Temperature Energy Accept Improve Elapsed')
+
def update(T, E, acceptance, improvement):
"""Prints the current temperature, energy, acceptance rate,
improvement rate, and elapsed time."""
elapsed = time.time() - start
wln('%12.2f %12.2f %7.2f%% %7.2f%% %s' % \
- (T, E, 100.0*acceptance, 100.0*improvement, time_string(elapsed)))
+ (T, E, 100.0 * acceptance, 100.0 * improvement,
+ time_string(elapsed)))
# Search for Tmax - a temperature that gives 98% acceptance
state, E, acceptance, improvement = run(state, T, steps)
step += steps
while acceptance > 0.98:
- T = round_figures(T/1.5, 2)
+ T = round_figures(T / 1.5, 2)
state, E, acceptance, improvement = run(state, T, steps)
step += steps
update(T, E, acceptance, improvement)
while acceptance < 0.98:
- T = round_figures(T*1.5, 2)
+ T = round_figures(T * 1.5, 2)
state, E, acceptance, improvement = run(state, T, steps)
step += steps
update(T, E, acceptance, improvement)
@@ -239,7 +250,7 @@ class Annealer:
# Search for Tmin - a temperature that gives 0% improvement
while improvement > 0.0:
- T = round_figures(T/1.5, 2)
+ T = round_figures(T / 1.5, 2)
state, E, acceptance, improvement = run(state, T, steps)
step += steps
update(T, E, acceptance, improvement)
@@ -250,66 +261,68 @@ class Annealer:
duration = round_figures(int(60.0 * minutes * step / elapsed), 2)
# Perform anneal
- wln('Annealing from %.2f to %.2f over %i steps:' % (Tmax, Tmin, duration))
+ wln('Annealing from %.2f to %.2f over %i steps:'
+ % (Tmax, Tmin, duration))
return self.anneal(state, Tmax, Tmin, duration, 20)
+
if __name__ == '__main__':
"""Test annealer with a traveling salesman problem."""
# List latitude and longitude (degrees) for the twenty largest U.S. cities
- cities = { 'New York City': (40.72,74.00), 'Los Angeles': (34.05,118.25),
- 'Chicago': (41.88,87.63), 'Houston': (29.77,95.38),
- 'Phoenix': (33.45,112.07), 'Philadelphia': (39.95,75.17),
- 'San Antonio': (29.53,98.47), 'Dallas': (32.78,96.80),
- 'San Diego': (32.78,117.15), 'San Jose': (37.30,121.87),
- 'Detroit': (42.33,83.05), 'San Francisco': (37.78,122.42),
- 'Jacksonville': (30.32,81.70), 'Indianapolis': (39.78,86.15),
- 'Austin': (30.27,97.77), 'Columbus': (39.98,82.98),
- 'Fort Worth': (32.75,97.33), 'Charlotte': (35.23,80.85),
- 'Memphis': (35.12,89.97), 'Baltimore': (39.28,76.62) }
+ cities = {'New York City': (40.72, 74.00), 'Los Angeles': (34.05, 118.25),
+ 'Chicago': (41.88, 87.63), 'Houston': (29.77, 95.38),
+ 'Phoenix': (33.45, 112.07), 'Philadelphia': (39.95, 75.17),
+ 'San Antonio': (29.53, 98.47), 'Dallas': (32.78, 96.80),
+ 'San Diego': (32.78, 117.15), 'San Jose': (37.30, 121.87),
+ 'Detroit': (42.33, 83.05), 'San Francisco': (37.78, 122.42),
+ 'Jacksonville': (30.32, 81.70), 'Indianapolis': (39.78, 86.15),
+ 'Austin': (30.27, 97.77), 'Columbus': (39.98, 82.98),
+ 'Fort Worth': (32.75, 97.33), 'Charlotte': (35.23, 80.85),
+ 'Memphis': (35.12, 89.97), 'Baltimore': (39.28, 76.62)}
def distance(a, b):
"""Calculates distance between two latitude-longitude coordinates."""
R = 3963 # radius of Earth (miles)
lat1, lon1 = math.radians(a[0]), math.radians(a[1])
lat2, lon2 = math.radians(b[0]), math.radians(b[1])
- return math.acos( math.sin(lat1)*math.sin(lat2) +
- math.cos(lat1)*math.cos(lat2)*math.cos(lon1-lon2) ) * R
+ return math.acos(math.sin(lat1) * math.sin(lat2) +
+ math.cos(lat1) * math.cos(lat2) * math.cos(lon1 - lon2)) * R
def route_move(state):
"""Swaps two cities in the route."""
- a = random.randint( 0, len(state)-1 )
- b = random.randint( 0, len(state)-1 )
+ a = random.randint(0, len(state) - 1)
+ b = random.randint(0, len(state) - 1)
state[a], state[b] = state[b], state[a]
def route_energy(state):
"""Calculates the length of the route."""
e = 0
for i in range(len(state)):
- e += distance( cities[state[i-1]], cities[state[i]] )
+ e += distance(cities[state[i - 1]], cities[state[i]])
return e
# Start with the cities listed in random order
- state = cities.keys()
+ state = list(cities.keys())
random.shuffle(state)
# Minimize the distance to be traveled by simulated annealing with a
# manually chosen temperature schedule
annealer = Annealer(route_energy, route_move)
- state, e = annealer.anneal(state, 10000000, 0.01, 18000*len(state), 9)
+ state, e = annealer.anneal(state, 10000000, 0.01, 18000 * len(state), 9)
while state[0] != 'New York City':
state = state[1:] + state[:1] # rotate NYC to start
- print "%i mile route:" % route_energy(state)
+ print("%i mile route:" % route_energy(state))
for city in state:
- print "\t", city
+ print("\t", city)
# Minimize the distance to be traveled by simulated annealing with an
# automatically chosen temperature schedule
state, e = annealer.auto(state, 4)
while state[0] != 'New York City':
state = state[1:] + state[:1] # rotate NYC to start
- print "%i mile route:" % route_energy(state)
+ print("%i mile route:" % route_energy(state))
for city in state:
- print "\t", city
+ print("\t", city)
sys.exit()
diff --git a/spritecss/packing/sprites.py b/spritecss/packing/sprites.py
index c53dfe0..a66a3fa 100644
--- a/spritecss/packing/sprites.py
+++ b/spritecss/packing/sprites.py
@@ -3,6 +3,7 @@ from contextlib import contextmanager
from ..image import Image
from . import Rect
+
class SpriteNode(Rect):
def __init__(self, im, width, height, fname=None, pad=(0, 0)):
Rect.__init__(self, (0, 0, width, height))
@@ -36,6 +37,7 @@ class SpriteNode(Rect):
fname = fo.name
return cls.from_image(Image.load(fo), fname=fname, pad=pad)
+
@contextmanager
def open_sprites(fnames, **kwds):
fs = [(fn, open(str(fn), "rb")) for fn in fnames]
diff --git a/spritecss/png.py b/spritecss/png.py
index fdbeb73..e21408b 100644
--- a/spritecss/png.py
+++ b/spritecss/png.py
@@ -75,10 +75,10 @@ trivial, but see the ``README.txt`` file (with the source distribution)
for details.
This file can also be used as a command-line utility to convert
-`Netpbm <http://netpbm.sourceforge.net/>`_ PNM files to PNG, and the reverse conversion from PNG to
-PNM. The interface is similar to that of the ``pnmtopng`` program from
-Netpbm. Type ``python png.py --help`` at the shell prompt
-for usage and a list of options.
+`Netpbm <http://netpbm.sourceforge.net/>`_ PNM files to PNG, and the reverse
+conversion from PNG to PNM. The interface is similar to that of the
+``pnmtopng`` program from Netpbm. Type ``python png.py --help`` at the
+shell prompt for usage and a list of options.
A note on spelling and terminology
----------------------------------
@@ -163,12 +163,13 @@ And now, my famous members
"""
# http://www.python.org/doc/2.2.3/whatsnew/node5.html
-from __future__ import generators
+
__version__ = "$URL$ $Rev$"
from array import array
-try: # See :pyver:old
+from functools import reduce
+try: # See :pyver:old
import itertools
except:
pass
@@ -196,10 +197,11 @@ _adam7 = ((0, 0, 8, 8),
(1, 0, 2, 2),
(0, 1, 1, 2))
+
def group(s, n):
- # See
- # http://www.python.org/doc/2.6/library/functions.html#zip
- return zip(*[iter(s)]*n)
+ # See http://www.python.org/doc/2.6/library/functions.html#zip
+ return list(zip(*[iter(s)] * n))
+
def isarray(x):
"""Same as ``isinstance(x, array)`` except on Python 2.2, where it
@@ -243,9 +245,10 @@ def interleave_planes(ipixels, apixels, ipsize, apsize):
for i in range(ipsize):
out[i:newtotal:newpsize] = ipixels[i:itotal:ipsize]
for i in range(apsize):
- out[i+ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
+ out[i + ipsize:newtotal:newpsize] = apixels[i:atotal:apsize]
return out
+
def check_palette(palette):
"""Check a palette argument (to the :class:`Writer` class) for validity.
Returns the palette as a list if okay; raises an exception otherwise.
@@ -258,9 +261,10 @@ def check_palette(palette):
p = list(palette)
if not (0 < len(p) <= 256):
raise ValueError("a palette must have between 1 and 256 entries")
+
seen_triple = False
- for i,t in enumerate(p):
- if len(t) not in (3,4):
+ for i, t in enumerate(p):
+ if len(t) not in (3, 4):
raise ValueError(
"palette entry %d: entries must be 3- or 4-tuples." % i)
if len(t) == 3:
@@ -271,14 +275,18 @@ def check_palette(palette):
for x in t:
if int(x) != x or not(0 <= x <= 255):
raise ValueError(
- "palette entry %d: values must be integer: 0 <= x <= 255" % i)
+ "palette entry %d: values must be integer: "
+ "0 <= x <= 255" % i)
return p
+
class Error(Exception):
prefix = 'Error'
+
def __str__(self):
return self.prefix + ': ' + ' '.join(self.args)
+
class FormatError(Error):
"""Problem with input file format. In other words, PNG file does
not conform to the specification in some way and is invalid.
@@ -286,6 +294,7 @@ class FormatError(Error):
prefix = 'FormatError'
+
class ChunkError(FormatError):
prefix = 'ChunkError'
@@ -306,11 +315,11 @@ class Writer:
gamma=None,
compression=None,
interlace=False,
- bytes_per_sample=None, # deprecated
+ bytes_per_sample=None, # deprecated
planes=None,
colormap=None,
maxval=None,
- chunk_limit=2**20):
+ chunk_limit=2 ** 20):
"""
Create a PNG encoder object.
@@ -417,9 +426,9 @@ class Writer:
connexions interlaced images can be partially decoded by the
browser to give a rough view of the image that is successively
refined as more image data appears.
-
+
.. note ::
-
+
Enabling the `interlace` option requires the entire image
to be processed in working memory.
@@ -479,13 +488,13 @@ class Writer:
"size argument should be a pair (width, height)")
if width is not None and width != size[0]:
raise ValueError(
- "size[0] (%r) and width (%r) should match when both are used."
- % (size[0], width))
+ "size[0] (%r) and width (%r) should match "
+ "when both are used." % (size[0], width))
if height is not None and height != size[1]:
raise ValueError(
- "size[1] (%r) and height (%r) should match when both are used."
- % (size[1], height))
- width,height = size
+ "size[1] (%r) and height (%r) should match "
+ "when both are used." % (size[1], height))
+ width, height = size
del size
if width <= 0 or height <= 0:
@@ -493,8 +502,8 @@ class Writer:
if not isinteger(width) or not isinteger(height):
raise ValueError("width and height must be integers")
# http://www.w3.org/TR/PNG/#7Integers-and-byte-order
- if width > 2**32-1 or height > 2**32-1:
- raise ValueError("width and height cannot exceed 2**32-1")
+ if width > 2 ** 32 - 1 or height > 2 ** 32 - 1:
+ raise ValueError("width and height cannot exceed 2 ** 32 - 1")
if alpha and transparent is not None:
raise ValueError(
@@ -506,7 +515,7 @@ class Writer:
if bytes_per_sample not in (0.125, 0.25, 0.5, 1, 2):
raise ValueError(
"bytes per sample must be .125, .25, .5, 1, or 2")
- bitdepth = int(8*bytes_per_sample)
+ bitdepth = int(8 * bytes_per_sample)
del bytes_per_sample
if not isinteger(bitdepth) or bitdepth < 1 or 16 < bitdepth:
raise ValueError("bitdepth (%r) must be a postive integer <= 16" %
@@ -514,8 +523,8 @@ class Writer:
self.rescale = None
if palette:
- if bitdepth not in (1,2,4,8):
- raise ValueError("with palette, bitdepth must be 1, 2, 4, or 8")
+ if bitdepth not in (1, 2, 4, 8):
+ raise ValueError("with palette, bitdepth must be 1, 2, 4 or 8")
if transparent is not None:
raise ValueError("transparent and palette not compatible")
if alpha:
@@ -525,21 +534,21 @@ class Writer:
else:
# No palette, check for sBIT chunk generation.
if alpha or not greyscale:
- if bitdepth not in (8,16):
- targetbitdepth = (8,16)[bitdepth > 8]
+ if bitdepth not in (8, 16):
+ targetbitdepth = (8, 16)[bitdepth > 8]
self.rescale = (bitdepth, targetbitdepth)
bitdepth = targetbitdepth
del targetbitdepth
else:
assert greyscale
assert not alpha
- if bitdepth not in (1,2,4,8,16):
+ if bitdepth not in (1, 2, 4, 8, 16):
if bitdepth > 8:
targetbitdepth = 16
elif bitdepth == 3:
targetbitdepth = 4
else:
- assert bitdepth in (5,6,7)
+ assert bitdepth in (5, 6, 7)
targetbitdepth = 8
self.rescale = (bitdepth, targetbitdepth)
bitdepth = targetbitdepth
@@ -572,13 +581,14 @@ class Writer:
self.interlace = bool(interlace)
self.palette = check_palette(palette)
- self.color_type = 4*self.alpha + 2*(not greyscale) + 1*self.colormap
- assert self.color_type in (0,2,3,4,6)
+ self.color_type = 4 * self.alpha + 2 * (not greyscale) \
+ + 1 * self.colormap
+ assert self.color_type in (0, 2, 3, 4, 6)
- self.color_planes = (3,1)[self.greyscale or self.colormap]
+ self.color_planes = (3, 1)[self.greyscale or self.colormap]
self.planes = self.color_planes + self.alpha
# :todo: fix for bitdepth < 8
- self.psize = (self.bitdepth/8) * self.planes
+ self.psize = (self.bitdepth / 8) * self.planes
def make_palette(self):
"""Create the byte sequences for a ``PLTE`` and if necessary a
@@ -596,8 +606,8 @@ class Writer:
p = tostring(p)
t = tostring(t)
if t:
- return p,t
- return p,None
+ return p, t
+ return p, None
def write(self, outfile, rows):
"""Write a PNG image to the output file. `rows` should be
@@ -607,7 +617,7 @@ class Writer:
If `interlace` is specified (when creating the instance), then
an interlaced PNG file will be written. Supply the rows in the
normal image order; the interlacing is carried out internally.
-
+
.. note ::
Interlacing will require the entire image to be in working memory.
@@ -630,7 +640,7 @@ class Writer:
Most users are expected to find the :meth:`write` or
:meth:`write_array` method more convenient.
-
+
The rows should be given to this method in the order that
they appear in the output file. For straightlaced images,
this is the usual top to bottom ordering, but for interlaced
@@ -641,7 +651,6 @@ class Writer:
`packed` is ``False`` the rows should be in boxed row flat pixel
format; when `packed` is ``True`` each row should be a packed
sequence of bytes.
-
"""
# http://www.w3.org/TR/PNG/#5PNG-file-signature
@@ -657,21 +666,21 @@ class Writer:
# http://www.w3.org/TR/PNG/#11gAMA
if self.gamma is not None:
write_chunk(outfile, 'gAMA',
- struct.pack("!L", int(round(self.gamma*1e5))))
+ struct.pack("!L", int(round(self.gamma * 1e5))))
# See :chunk:order
# http://www.w3.org/TR/PNG/#11sBIT
if self.rescale:
write_chunk(outfile, 'sBIT',
struct.pack('%dB' % self.planes,
- *[self.rescale[0]]*self.planes))
-
+ * [self.rescale[0]] * self.planes))
+
# :chunk:order: Without a palette (PLTE chunk), ordering is
# relatively relaxed. With one, gAMA chunk must precede PLTE
# chunk which must precede tRNS and bKGD.
# See http://www.w3.org/TR/PNG/#5ChunkOrdering
if self.palette:
- p,t = self.make_palette()
+ p, t = self.make_palette()
write_chunk(outfile, 'PLTE', p)
if t:
# tRNS chunk is optional. Only needed if palette entries
@@ -717,25 +726,27 @@ class Writer:
# Pack into bytes
assert self.bitdepth < 8
# samples per byte
- spb = int(8/self.bitdepth)
+ spb = int(8 / self.bitdepth)
+
def extend(sl):
a = array('B', sl)
# Adding padding bytes so we can group into a whole
# number of spb-tuples.
l = float(len(a))
- extra = math.ceil(l / float(spb))*spb - l
- a.extend([0]*int(extra))
+ extra = math.ceil(l / float(spb)) * spb - l
+ a.extend([0] * int(extra))
# Pack into bytes
l = group(a, spb)
- l = map(lambda e: reduce(lambda x,y:
- (x << self.bitdepth) + y, e), l)
+ l = [reduce(lambda x, y: (x << self.bitdepth)
+ + y, e) for e in l]
data.extend(l)
if self.rescale:
oldextend = extend
factor = \
- float(2**self.rescale[1]-1) / float(2**self.rescale[0]-1)
+ float(2 ** self.rescale[1] - 1) / float(2 ** self.rescale[0] - 1)
+
def extend(sl):
- oldextend(map(lambda x: int(round(factor*x)), sl))
+ oldextend([int(round(factor * x)) for x in sl])
# Build the first row, testing mostly to see if we need to
# changed the extend function to cope with NumPy integer types
@@ -750,7 +761,7 @@ class Writer:
# :todo: Certain exceptions in the call to ``.next()`` or the
# following try would indicate no row data supplied.
# Should catch.
- i,row = enumrows.next()
+ i, row = next(enumrows)
try:
# If this fails...
extend(row)
@@ -760,12 +771,12 @@ class Writer:
# types, there are probably lots of other, unknown, "nearly"
# int types it works for.
def wrapmapint(f):
- return lambda sl: f(map(int, sl))
+ return lambda sl: f(list(map(int, sl)))
extend = wrapmapint(extend)
del wrapmapint
extend(row)
- for i,row in enumrows:
+ for i, row in enumrows:
# Add "None" filter type. Currently, it's essential that
# this filter type be used for every scanline as we do not
# mark the first row of a reduced pass image; that means we
@@ -793,7 +804,7 @@ class Writer:
write_chunk(outfile, 'IDAT', compressed + flushed)
# http://www.w3.org/TR/PNG/#11IEND
write_chunk(outfile, 'IEND')
- return i+1
+ return i + 1
def write_array(self, outfile, pixels):
"""
@@ -836,7 +847,7 @@ class Writer:
if self.interlace:
pixels = array('B')
pixels.fromfile(infile,
- (self.bitdepth/8) * self.color_planes *
+ (self.bitdepth / 8) * self.color_planes *
self.width * self.height)
self.write_passes(outfile, self.array_scanlines_interlace(pixels))
else:
@@ -849,15 +860,15 @@ class Writer:
"""
pixels = array('B')
pixels.fromfile(ppmfile,
- (self.bitdepth/8) * self.color_planes *
+ (self.bitdepth / 8) * self.color_planes *
self.width * self.height)
apixels = array('B')
apixels.fromfile(pgmfile,
- (self.bitdepth/8) *
+ (self.bitdepth / 8) *
self.width * self.height)
pixels = interleave_planes(pixels, apixels,
- (self.bitdepth/8) * self.color_planes,
- (self.bitdepth/8))
+ (self.bitdepth / 8) * self.color_planes,
+ (self.bitdepth / 8))
if self.interlace:
self.write_passes(outfile, self.array_scanlines_interlace(pixels))
else:
@@ -880,6 +891,7 @@ class Writer:
assert self.bitdepth == 16
row_bytes *= 2
fmt = '>%dH' % vpr
+
def line():
return array('H', struct.unpack(fmt, infile.read(row_bytes)))
else:
@@ -920,25 +932,26 @@ class Writer:
if xstart >= self.width:
continue
# Pixels per row (of reduced image)
- ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+ ppr = int(math.ceil((self.width - xstart) / float(xstep)))
# number of values in reduced image row.
- row_len = ppr*self.planes
+ row_len = ppr * self.planes
for y in range(ystart, self.height, ystep):
if xstep == 1:
offset = y * vpr
- yield pixels[offset:offset+vpr]
+ yield pixels[offset:offset + vpr]
else:
row = array(fmt)
# There's no easier way to set the length of an array
row.extend(pixels[0:row_len])
offset = y * vpr + xstart * self.planes
- end_offset = (y+1) * vpr
+ end_offset = (y + 1) * vpr
skip = self.planes * xstep
for i in range(self.planes):
row[i::self.planes] = \
- pixels[offset+i:end_offset:skip]
+ pixels[offset + i:end_offset:skip]
yield row
+
def write_chunk(outfile, tag, data=''):
"""
Write a PNG chunk to the output file, including length and
@@ -953,6 +966,7 @@ def write_chunk(outfile, tag, data=''):
checksum = zlib.crc32(data, checksum)
outfile.write(struct.pack("!i", checksum))
+
def write_chunks(out, chunks):
"""Create a PNG file by writing out the chunks."""
@@ -960,6 +974,7 @@ def write_chunks(out, chunks):
for chunk in chunks:
write_chunk(out, *chunk)
+
def filter_scanline(type, line, fo, prev=None):
"""Apply a scanline filter to a scanline. `type` specifies the
filter type (0 to 4); `line` specifies the current (unfiltered)
@@ -983,23 +998,26 @@ def filter_scanline(type, line, fo, prev=None):
x = (x - line[ai]) & 0xff
out.append(x)
ai += 1
+
def up():
- for i,x in enumerate(line):
+ for i, x in enumerate(line):
x = (x - prev[i]) & 0xff
out.append(x)
+
def average():
ai = -fo
- for i,x in enumerate(line):
+ for i, x in enumerate(line):
if ai >= 0:
x = (x - ((line[ai] + prev[i]) >> 1)) & 0xff
else:
x = (x - (prev[i] >> 1)) & 0xff
out.append(x)
ai += 1
+
def paeth():
# http://www.w3.org/TR/PNG/#9Filter-type-4-Paeth
- ai = -fo # also used for ci
- for i,x in enumerate(line):
+ ai = -fo # also used for ci
+ for i, x in enumerate(line):
a = 0
b = prev[i]
c = 0
@@ -1011,9 +1029,12 @@ def filter_scanline(type, line, fo, prev=None):
pa = abs(p - a)
pb = abs(p - b)
pc = abs(p - c)
- if pa <= pb and pa <= pc: Pr = a
- elif pb <= pc: Pr = b
- else: Pr = c
+ if pa <= pb and pa <= pc:
+ Pr = a
+ elif pb <= pc:
+ Pr = b
+ else:
+ Pr = c
x = (x - Pr) & 0xff
out.append(x)
@@ -1025,11 +1046,11 @@ def filter_scanline(type, line, fo, prev=None):
# of the image simpler. "up" becomes "none"; "paeth" becomes
# "left" (non-trivial, but true). "average" needs to be handled
# specially.
- if type == 2: # "up"
- return line # type = 0
+ if type == 2: # "up"
+ return line # type = 0
elif type == 3:
- prev = [0]*len(line)
- elif type == 4: # "paeth"
+ prev = [0] * len(line)
+ elif type == 4: # "paeth"
type = 1
if type == 0:
out.extend(line)
@@ -1039,7 +1060,7 @@ def filter_scanline(type, line, fo, prev=None):
up()
elif type == 3:
average()
- else: # type == 4
+ else: # type == 4
paeth()
return out
@@ -1055,7 +1076,7 @@ def from_array(a, mode=None, info={}):
only. It doesn't actually work. Please bear with us. Meanwhile
enjoy the complimentary snacks (on request) and please use a
2-dimensional array.
-
+
Unless they are specified using the *info* parameter, the PNG's
height and width are taken from the array size. For a 3 dimensional
array the first axis is the height; the second axis is the width;
@@ -1110,7 +1131,7 @@ def from_array(a, mode=None, info={}):
metadata (in the same style as the arguments to the
:class:``png.Writer`` class). For this function the keys that are
useful are:
-
+
height
overrides the height derived from the array dimensions and allows
*a* to be an iterable.
@@ -1136,7 +1157,7 @@ def from_array(a, mode=None, info={}):
bitdepth = None
try:
mode = mode.split(';')
- if len(mode) not in (1,2):
+ if len(mode) not in (1, 2):
raise Error()
if mode[0] not in ('L', 'LA', 'RGB', 'RGBA'):
raise Error()
@@ -1160,13 +1181,13 @@ def from_array(a, mode=None, info={}):
# Dimensions.
if 'size' in info:
# Check width, height, size all match where used.
- for dimension,axis in [('width', 0), ('height', 1)]:
+ for dimension, axis in [('width', 0), ('height', 1)]:
if dimension in info:
if info[dimension] != info['size'][axis]:
raise Error(
"info[%r] shhould match info['size'][%r]." %
(dimension, axis))
- info['width'],info['height'] = info['size']
+ info['width'], info['height'] = info['size']
if 'height' not in info:
try:
l = len(a)
@@ -1192,8 +1213,8 @@ def from_array(a, mode=None, info={}):
# In order to work out whether we the array is 2D or 3D we need its
# first row, which requires that we take a copy of its iterator.
# We may also need the first row to derive width and bitdepth.
- a,t = itertools.tee(a)
- row = t.next()
+ a, t = itertools.tee(a)
+ row = next(t)
del t
try:
row[0][0]
@@ -1240,6 +1261,7 @@ def from_array(a, mode=None, info={}):
# So that refugee's from PIL feel more at home. Not documented.
fromarray = from_array
+
class Image:
"""A PNG image.
You can create an :class:`Image` object from an array of pixels by calling
@@ -1248,10 +1270,10 @@ class Image:
def __init__(self, rows, info):
"""
.. note ::
-
+
The constructor is not public. Please do not call it.
"""
-
+
self.rows = rows
self.info = info
@@ -1270,16 +1292,21 @@ class Image:
try:
file.write
- def close(): pass
+
+ def close():
+ pass
+
except:
file = open(file, 'wb')
- def close(): file.close()
+ def close():
+ file.close()
try:
w.write(file, self.rows)
finally:
close()
+
class _readable:
"""
A simple file-like interface for strings and arrays.
@@ -1290,7 +1317,7 @@ class _readable:
self.offset = 0
def read(self, n):
- r = self.buf[self.offset:self.offset+n]
+ r = self.buf[self.offset:self.offset + n]
if isarray(r):
r = r.tostring()
self.offset += n
@@ -1368,7 +1395,7 @@ class Reader:
# http://www.w3.org/TR/PNG/#5Chunk-layout
if not self.atchunk:
self.atchunk = self.chunklentype()
- length,type = self.atchunk
+ length, type = self.atchunk
self.atchunk = None
data = self.file.read(length)
if len(data) != length:
@@ -1386,7 +1413,7 @@ class Reader:
# http://bugs.python.org/issue1202 .
# We coerce it to be positive here (in a way which works on
# Python 2.3 and older).
- verify &= 2**32 - 1
+ verify &= 2 ** 32 - 1
verify = struct.pack('!I', verify)
if checksum != verify:
# print repr(checksum)
@@ -1403,8 +1430,8 @@ class Reader:
"""
while True:
- t,v = self.chunk()
- yield t,v
+ t, v = self.chunk()
+ yield t, v
if t == 'IEND':
break
@@ -1437,7 +1464,7 @@ class Reader:
# above.
return result
- if filter_type not in (1,2,3,4):
+ if filter_type not in (1, 2, 3, 4):
raise FormatError('Invalid PNG Filter Type.'
' See http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters .')
@@ -1452,7 +1479,7 @@ class Reader:
# first line 'up' is the same as 'null', 'paeth' is the same
# as 'sub', with only 'average' requiring any special case.
if not previous:
- previous = array('B', [0]*len(scanline))
+ previous = array('B', [0] * len(scanline))
def sub():
"""Undo sub filter."""
@@ -1535,7 +1562,7 @@ class Reader:
# writes to the output array randomly (well, not quite), so the
# entire output array must be in memory.
fmt = 'BH'[self.bitdepth > 8]
- a = array(fmt, [0]*vpr*self.height)
+ a = array(fmt, [0] * vpr * self.height)
source_offset = 0
for xstart, ystart, xstep, ystep in _adam7:
@@ -1548,13 +1575,13 @@ class Reader:
# line.
recon = None
# Pixels per row (reduced pass image)
- ppr = int(math.ceil((self.width-xstart)/float(xstep)))
+ ppr = int(math.ceil((self.width - xstart) / float(xstep)))
# Row size in bytes for this pass.
row_size = int(math.ceil(self.psize * ppr))
for y in range(ystart, self.height, ystep):
filter_type = raw[source_offset]
source_offset += 1
- scanline = raw[source_offset:source_offset+row_size]
+ scanline = raw[source_offset:source_offset + row_size]
source_offset += row_size
recon = self.undo_filter(filter_type, scanline, recon)
# Convert so that there is one element per pixel value
@@ -1562,13 +1589,13 @@ class Reader:
if xstep == 1:
assert xstart == 0
offset = y * vpr
- a[offset:offset+vpr] = flat
+ a[offset:offset + vpr] = flat
else:
offset = y * vpr + xstart * self.planes
- end_offset = (y+1) * vpr
+ end_offset = (y + 1) * vpr
skip = self.planes * xstep
for i in range(self.planes):
- a[offset+i:end_offset:skip] = \
+ a[offset + i:end_offset:skip] = \
flat[i::self.planes]
return a
@@ -1586,19 +1613,20 @@ class Reader:
return raw
if self.bitdepth == 16:
raw = tostring(raw)
- return array('H', struct.unpack('!%dH' % (len(raw)//2), raw))
+ return array('H', struct.unpack('!%dH' % (len(raw) // 2), raw))
assert self.bitdepth < 8
width = self.width
# Samples per byte
- spb = 8//self.bitdepth
+ spb = 8 // self.bitdepth
out = array('B')
- mask = 2**self.bitdepth - 1
- shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
+ mask = 2 ** self.bitdepth - 1
+ shifts = list(map(self.bitdepth.__mul__,
+ reversed(list(range(spb)))))
for o in raw:
- out.extend(map(lambda i: mask&(o>>i), shifts))
+ out.extend([mask & (o >> i) for i in shifts])
return out[:width]
- return itertools.imap(asvalues, rows)
+ return map(asvalues, rows)
def serialtoflat(self, bytes, width=None):
"""Convert serial format (byte stream) pixel data to flat row
@@ -1610,18 +1638,18 @@ class Reader:
if self.bitdepth == 16:
bytes = tostring(bytes)
return array('H',
- struct.unpack('!%dH' % (len(bytes)//2), bytes))
+ struct.unpack('!%dH' % (len(bytes) // 2), bytes))
assert self.bitdepth < 8
if width is None:
width = self.width
# Samples per byte
- spb = 8//self.bitdepth
+ spb = 8 // self.bitdepth
out = array('B')
- mask = 2**self.bitdepth - 1
- shifts = map(self.bitdepth.__mul__, reversed(range(spb)))
+ mask = 2 ** self.bitdepth - 1
+ shifts = list(map(self.bitdepth.__mul__, reversed(list(range(spb)))))
l = width
for o in bytes:
- out.extend(map(lambda i: mask&(o>>i), shifts)[:l])
+ out.extend(map(lambda i: mask & (o >> i), shifts)[:l])
l -= spb
if l <= 0:
l = width
@@ -1643,8 +1671,8 @@ class Reader:
a.extend(some)
while len(a) >= rb + 1:
filter_type = a[0]
- scanline = a[1:rb+1]
- del a[:rb+1]
+ scanline = a[1:rb + 1]
+ del a[:rb + 1]
recon = self.undo_filter(filter_type, scanline, recon)
yield recon
if len(a) != 0:
@@ -1699,10 +1727,10 @@ class Reader:
if len(x) != 8:
raise FormatError(
'End of file whilst reading chunk length and type.')
- length,type = struct.unpack('!I4s', x)
- if length > 2**31-1:
- raise FormatError('Chunk %s is too large: %d.' % (type,length))
- return length,type
+ length, type = struct.unpack('!I4s', x)
+ if length > 2 ** 31 - 1:
+ raise FormatError('Chunk %s is too large: %d.' % (type, length))
+ return length, type
def process_chunk(self):
"""Process the next chunk and its data. This only processes the
@@ -1720,15 +1748,15 @@ class Reader:
self.interlace) = struct.unpack("!2I5B", data)
# Check that the header specifies only valid combinations.
- if self.bitdepth not in (1,2,4,8,16):
+ if self.bitdepth not in (1, 2, 4, 8, 16):
raise Error("invalid bit depth %d" % self.bitdepth)
- if self.color_type not in (0,2,3,4,6):
+ if self.color_type not in (0, 2, 3, 4, 6):
raise Error("invalid colour type %d" % self.color_type)
# Check indexed (palettized) images have 8 or fewer bits
# per pixel; check only indexed or greyscale images have
# fewer than 8 bits per pixel.
if ((self.color_type & 1 and self.bitdepth > 8) or
- (self.bitdepth < 8 and self.color_type not in (0,3))):
+ (self.bitdepth < 8 and self.color_type not in (0, 3))):
raise FormatError("Illegal combination of bit depth (%d)"
" and colour type (%d)."
" See http://www.w3.org/TR/2003/REC-PNG-20031110/#table111 ."
@@ -1739,17 +1767,17 @@ class Reader:
raise FormatError("Unknown filter method %d,"
" see http://www.w3.org/TR/2003/REC-PNG-20031110/#9Filters ."
% self.filter)
- if self.interlace not in (0,1):
+ if self.interlace not in (0, 1):
raise FormatError("Unknown interlace method %d,"
" see http://www.w3.org/TR/2003/REC-PNG-20031110/#8InterlaceMethods ."
% self.interlace)
# Derived values
# http://www.w3.org/TR/PNG/#6Colour-values
- colormap = bool(self.color_type & 1)
+ colormap = bool(self.color_type & 1)
greyscale = not (self.color_type & 2)
alpha = bool(self.color_type & 4)
- color_planes = (3,1)[greyscale or colormap]
+ color_planes = (3, 1)[greyscale or colormap]
planes = color_planes + alpha
self.colormap = colormap
@@ -1757,7 +1785,7 @@ class Reader:
self.alpha = alpha
self.color_planes = color_planes
self.planes = planes
- self.psize = float(self.bitdepth)/float(8) * planes
+ self.psize = float(self.bitdepth) / float(8) * planes
if int(self.psize) == self.psize:
self.psize = int(self.psize)
self.row_bytes = int(math.ceil(self.width * self.psize))
@@ -1777,7 +1805,7 @@ class Reader:
if len(data) % 3 != 0:
raise FormatError(
"PLTE chunk's length should be a multiple of 3.")
- if len(data) > (2**self.bitdepth)*3:
+ if len(data) > (2 ** self.bitdepth) * 3:
raise FormatError("PLTE chunk is too long.")
if len(data) == 0:
raise FormatError("Empty PLTE is not allowed.")
@@ -1800,7 +1828,7 @@ class Reader:
if not self.plte:
warnings.warn("PLTE chunk is required before tRNS chunk.")
else:
- if len(data) > len(self.plte)/3:
+ if len(data) > len(self.plte) / 3:
# Was warning, but promoted to Error as it
# would otherwise cause pain later on.
raise FormatError("tRNS chunk is too long.")
@@ -1840,7 +1868,7 @@ class Reader:
while True:
try:
type, data = self.chunk()
- except ValueError, e:
+ except ValueError as e:
raise ChunkError(e.args[0])
if type == 'IEND':
# http://www.w3.org/TR/PNG/#11IEND
@@ -1878,12 +1906,13 @@ class Reader:
raw = iterdecomp(iteridat())
if self.interlace:
- raw = array('B', itertools.chain(*raw))
- arraycode = 'BH'[self.bitdepth>8]
+ raw = array('B', itertools.chain(* raw))
+ arraycode = 'BH'[self.bitdepth > 8]
# Like :meth:`group` but producing an array.array object for
# each row.
- pixels = itertools.imap(lambda *row: array(arraycode, row),
- *[iter(self.deinterlace(raw))]*self.width*self.planes)
+ pixels = map(lambda *row: array(arraycode, row),
+ * [iter(self.deinterlace(raw))]
+ * self.width * self.planes)
else:
pixels = self.iterboxed(self.iterstraight(raw))
meta = dict()
@@ -1896,7 +1925,6 @@ class Reader:
meta[attr] = a
return self.width, self.height, pixels, meta
-
def read_flat(self):
"""
Read a PNG file and decode it into flat row flat pixel format.
@@ -1911,8 +1939,8 @@ class Reader:
"""
x, y, pixel, meta = self.read()
- arraycode = 'BH'[meta['bitdepth']>8]
- pixel = array(arraycode, itertools.chain(*pixel))
+ arraycode = 'BH'[meta['bitdepth'] > 8]
+ pixel = array(arraycode, itertools.chain(* pixel))
return x, y, pixel, meta
def palette(self, alpha='natural'):
@@ -1934,8 +1962,8 @@ class Reader:
plte = group(array('B', self.plte), 3)
if self.trns or alpha == 'force':
trns = array('B', self.trns or '')
- trns.extend([255]*(len(plte)-len(trns)))
- plte = map(operator.add, plte, group(trns, 1))
+ trns.extend([255] * (len(plte) - len(trns)))
+ plte = list(map(operator.add, plte, group(trns, 1)))
return plte
def asDirect(self):
@@ -1982,7 +2010,7 @@ class Reader:
if not self.colormap and not self.trns and not self.sbit:
return self.read()
- x,y,pixels,meta = self.read()
+ x, y, pixels, meta = self.read()
if self.colormap:
meta['colormap'] = False
@@ -1990,10 +2018,11 @@ class Reader:
meta['bitdepth'] = 8
meta['planes'] = 3 + bool(self.trns)
plte = self.palette()
+
def iterpal(pixels):
for row in pixels:
- row = map(plte.__getitem__, row)
- yield array('B', itertools.chain(*row))
+ row = list(map(plte.__getitem__, row))
+ yield array('B', itertools.chain(* row))
pixels = iterpal(pixels)
elif self.trns:
# It would be nice if there was some reasonable way of doing
@@ -2002,12 +2031,14 @@ class Reader:
# clearly much simpler or much faster. (Actually, the L to LA
# conversion could perhaps go faster (all those 1-tuples!), but
# I still wonder whether the code proliferation is worth it)
+
it = self.transparent
- maxval = 2**meta['bitdepth']-1
+ maxval = 2 ** meta['bitdepth'] - 1
planes = meta['planes']
meta['alpha'] = True
meta['planes'] += 1
- typecode = 'BH'[meta['bitdepth']>8]
+ typecode = 'BH'[meta['bitdepth'] > 8]
+
def itertrns(pixels):
for row in pixels:
# For each row we group it into pixels, then form a
@@ -2016,11 +2047,11 @@ class Reader:
# 0/maxval (by multiplication), and add it as the extra
# channel.
row = group(row, planes)
- opa = map(it.__ne__, row)
- opa = map(maxval.__mul__, opa)
- opa = zip(opa) # convert to 1-tuples
+ opa = list(map(it.__ne__, row))
+ opa = list(map(maxval.__mul__, opa))
+ opa = list(zip(opa)) # convert to 1-tuples
yield array(typecode,
- itertools.chain(*map(operator.add, row, opa)))
+ itertools.chain(*list(map(operator.add, row, opa))))
pixels = itertrns(pixels)
targetbitdepth = None
if self.sbit:
@@ -2028,7 +2059,7 @@ class Reader:
targetbitdepth = max(sbit)
if targetbitdepth > meta['bitdepth']:
raise Error('sBIT chunk %r exceeds bitdepth %d' %
- (sbit,self.bitdepth))
+ (sbit, self.bitdepth))
if min(sbit) <= 0:
raise Error('sBIT chunk %r has a 0-entry' % sbit)
if targetbitdepth == meta['bitdepth']:
@@ -2036,11 +2067,12 @@ class Reader:
if targetbitdepth:
shift = meta['bitdepth'] - targetbitdepth
meta['bitdepth'] = targetbitdepth
+
def itershift(pixels):
for row in pixels:
- yield map(shift.__rrshift__, row)
+ yield list(map(shift.__rrshift__, row))
pixels = itershift(pixels)
- return x,y,pixels,meta
+ return x, y, pixels, meta
def asFloat(self, maxval=1.0):
"""Return image pixels as per :meth:`asDirect` method, but scale
@@ -2048,27 +2080,29 @@ class Reader:
*maxval*.
"""
- x,y,pixels,info = self.asDirect()
- sourcemaxval = 2**info['bitdepth']-1
+ x, y, pixels, info = self.asDirect()
+ sourcemaxval = 2 ** info['bitdepth'] - 1
del info['bitdepth']
info['maxval'] = float(maxval)
- factor = float(maxval)/float(sourcemaxval)
+ factor = float(maxval) / float(sourcemaxval)
+
def iterfloat():
for row in pixels:
- yield map(factor.__mul__, row)
- return x,y,iterfloat(),info
+ yield list(map(factor.__mul__, row))
+ return x, y, iterfloat(), info
def _as_rescale(self, get, targetbitdepth):
"""Helper used by :meth:`asRGB8` and :meth:`asRGBA8`."""
- width,height,pixels,meta = get()
- maxval = 2**meta['bitdepth'] - 1
- targetmaxval = 2**targetbitdepth - 1
+ width, height, pixels, meta = get()
+ maxval = 2 ** meta['bitdepth'] - 1
+ targetmaxval = 2 ** targetbitdepth - 1
factor = float(targetmaxval) / float(maxval)
meta['bitdepth'] = targetbitdepth
+
def iterscale():
for row in pixels:
- yield map(lambda x: int(round(x*factor)), row)
+ yield [int(round(x * factor)) for x in row]
return width, height, iterscale(), meta
def asRGB8(self):
@@ -2085,7 +2119,7 @@ class Reader:
This function returns a 4-tuple:
(*width*, *height*, *pixels*, *metadata*).
*width*, *height*, *metadata* are as per the :meth:`read` method.
-
+
*pixels* is the pixel data in boxed row flat pixel format.
"""
@@ -2115,20 +2149,21 @@ class Reader:
``metadata['greyscale']`` will be ``False``.
"""
- width,height,pixels,meta = self.asDirect()
+ width, height, pixels, meta = self.asDirect()
if meta['alpha']:
raise Error("will not convert image with alpha channel to RGB")
if not meta['greyscale']:
- return width,height,pixels,meta
+ return width, height, pixels, meta
meta['greyscale'] = False
typecode = 'BH'[meta['bitdepth'] > 8]
+
def iterrgb():
for row in pixels:
a = array(typecode, [0]) * 3 * width
for i in range(3):
a[i::3] = row
yield a
- return width,height,iterrgb(),meta
+ return width, height, iterrgb(), meta
def asRGBA(self):
"""Return image as RGBA pixels. Greyscales are expanded into
@@ -2140,14 +2175,16 @@ class Reader:
``metadata['alpha']`` will be ``True``.
"""
- width,height,pixels,meta = self.asDirect()
+ width, height, pixels, meta = self.asDirect()
if meta['alpha'] and not meta['greyscale']:
- return width,height,pixels,meta
+ return width, height, pixels, meta
typecode = 'BH'[meta['bitdepth'] > 8]
- maxval = 2**meta['bitdepth'] - 1
+ maxval = 2 ** meta['bitdepth'] - 1
+
def newarray():
return array(typecode, [0]) * 4 * width
if meta['alpha'] and meta['greyscale']:
+
# LA to RGBA
def convert():
for row in pixels:
@@ -2160,6 +2197,7 @@ class Reader:
a[3::4] = row[1::2]
yield a
elif meta['greyscale']:
+
# L to RGBA
def convert():
for row in pixels:
@@ -2170,6 +2208,7 @@ class Reader:
yield a
else:
assert not meta['alpha'] and not meta['greyscale']
+
# RGB to RGBA
def convert():
for row in pixels:
@@ -2180,4 +2219,4 @@ class Reader:
yield a
meta['alpha'] = True
meta['greyscale'] = False
- return width,height,convert(),meta
+ return width, height, convert(), meta
diff --git a/spritecss/replacer.py b/spritecss/replacer.py
index 55602d2..5e34a61 100644
--- a/spritecss/replacer.py
+++ b/spritecss/replacer.py
@@ -8,10 +8,12 @@ from .finder import NoSpriteFound, get_background_url
logger = logging.getLogger(__name__)
+
def _build_pos_map(smap, placements):
"""Build a dict of sprite ref => pos."""
return dict((n.fname, p) for (p, n) in placements)
+
class SpriteReplacer(object):
def __init__(self, spritemaps):
self._smaps = dict((sm.fname, _build_pos_map(sm, plcs))
diff --git a/spritecss/stitch.py b/spritecss/stitch.py
index 98acc03..f132a99 100644
--- a/spritecss/stitch.py
+++ b/spritecss/stitch.py
@@ -1,10 +1,11 @@
"""Builds a spritemap image from a set of sprites."""
from array import array
-from itertools import izip, chain, repeat
+from itertools import chain, repeat
from .image import Image
+
class StitchedSpriteNodes(object):
"""An iterable that yields the image data rows of a tree of sprite
nodes. Suitable for writing to an image.
@@ -25,18 +26,18 @@ class StitchedSpriteNodes(object):
def _pad_trans(self, rows, n):
padded_rows = chain(rows, repeat(self._trans_pixels(n.width)))
- for idx, row in izip(xrange(n.height), padded_rows):
+ for idx, row in zip(range(n.height), padded_rows):
yield row + self._trans_pixels(n.width - (len(row) / self.planes))
def iter_empty_rows(self, n):
- return (self._trans_pixels(n.width) for i in xrange(n.height))
+ return (self._trans_pixels(n.width) for i in range(n.height))
def iter_rows_stitch(self, a, b):
(rows_a, rows_b) = (self.iter_rows(a), self.iter_rows(b))
if a.x1 == b.x1 and a.x2 == b.x2:
return chain(rows_a, rows_b)
elif a.y1 == b.y1 and a.y2 == b.y2:
- return (a + b for (a, b) in izip(rows_a, rows_b))
+ return (a + b for (a, b) in zip(rows_a, rows_b))
else:
raise ValueError("nodes %r and %r are not adjacent" % (a, b))
@@ -48,14 +49,15 @@ class StitchedSpriteNodes(object):
elif num_child == 2:
return self.iter_rows_stitch(*n.children)
else:
- # we can't sow together more than binary boxes because that would
- # entail very complicated algorithms :<
+ # we can't sow together more than 2 binary boxes because
+ # that would entail very complicated algorithms :<
raise ValueError("node %r has too many children" % (n,))
elif hasattr(n, "box"):
return self._pad_trans(n.box.im.pixels, n)
else:
return self.iter_empty_rows(n)
+
def stitch(packed, mode="RGBA", reusable=False):
assert mode == "RGBA" # TODO Support other modes than RGBA
root = packed.tree
@@ -68,23 +70,25 @@ def stitch(packed, mode="RGBA", reusable=False):
pixels = list(pixels)
return Image(root.width, root.height, pixels, meta)
+
def _pack_and_stitch(smap_fn, sprites, conf=None):
import sys
# pack boxes
from .packing import PackedBoxes, dump_placements, print_packed_size
- print >>sys.stderr, "packing sprites for map", smap_fn
+ print("packing sprites for map", smap_fn, file=sys.stderr)
packed = PackedBoxes(sprites)
print_packed_size(packed, out=sys.stderr)
dump_placements(packed)
# write map
img_fname = smap_fn + ".png"
- print >>sys.stderr, "writing spritemap image", img_fname
+ print("writing spritemap image", img_fname, file=sys.stderr)
im = stitch(packed)
with open(img_fname, "wb") as fp:
im.save(fp)
+
def main(fnames=None):
import sys
import json
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment