"""
The :class:`GBTile` class represents a single GameBoy tile (8x8 pixels, 16
Bytes).
Creating a tile from scratch::
from img2gb import GBTile
tile = GBTile()
tile.put_pixel(0, 0, 3) # Put a black pixel at (0, 0)
tile.data # -> [128, 128, 0, 0, 0, ...]
tile.to_hex_string() # -> "80 80 00 00 00 ..."
Creating a tile from a PIL image::
from img2gb import GBTile
from PIL import Image
image = Image.open("./my_tile.png")
tile = GBTile.from_image(image)
"""
from PIL import Image
from .helpers import to_pil_rgb_image, rgba_brightness, brightness_to_color_id
[docs]
class GBTile(object):
"""Stores and manipulates data of a single GameBoy tile (8x8 pixels)."""
[docs]
@classmethod
def from_image(Cls, pil_image, tile_x=0, tile_y=0, alternative_palette=False):
"""Create a new GBTile from the given image.
:param PIL.Image.Image pil_image: The input PIL (or Pillow) image.
:param int tile_x: The x location of the tile in the image (default =
``0``).
:param int tile_y: The y location of the tile in the image (default =
``0``).
:param bool alternative_palette: Use the sprite's alternative palette
(inverted colors, default = ``False``).
:rtype: GBTile
"""
image = to_pil_rgb_image(pil_image)
tile = Cls()
for y in range(8):
for x in range(8):
pix_rgb = image.getpixel((tile_x + x, tile_y + y))
pix_brightness = rgba_brightness(*pix_rgb)
color_id = brightness_to_color_id(
pix_brightness, invert=alternative_palette
)
tile.put_pixel(x, y, color_id)
return tile
def __init__(self):
self._data = [0x00] * 16
@property
def data(self):
"""Raw data of the tile.
:type: list of int
"""
return self._data
[docs]
def put_pixel(self, x, y, color_id):
"""Set the color of one of the tile's pixels.
:param int x: The x coordinate of the pixel to change (0-7).
:param int y: The y coordinate of the pixel to change (0-7).
:param int color_id: the color of the pixel (0-3).
"""
mask = 0b00000001 << (7 - x)
mask1 = mask if color_id & 0b01 else 0b00000000
mask2 = mask if color_id & 0b10 else 0b00000000
# Clear changing bits
self._data[y * 2 + 0] &= ~mask
self._data[y * 2 + 1] &= ~mask
# Set bits
self._data[y * 2 + 0] |= mask1
self._data[y * 2 + 1] |= mask2
[docs]
def get_pixel(self, x, y):
"""Returns the color id of a pixel of the tile.
:param int x: The x coordinate of the pixel (0-7).
:param int y: The y coordinate of the pixel (0-7).
:rtype: int
"""
mask = 0b00000001 << (7 - x)
lbit = (self._data[y * 2 + 0] & mask) >> (7 - x)
hbit = (self._data[y * 2 + 1] & mask) >> (7 - x)
return hbit * 0b10 + lbit
[docs]
def to_hex_string(self):
"""Returns the tile as an hexadecimal-encoded string.
:rtype: str
e.g.::
"04 04 04 04 0A 0A 12 12 66 00 99 77 99 77 66 66"
"""
return " ".join(["%02X" % b for b in self._data])
[docs]
def to_image(self):
"""Generates a PIL image from the tile. The generated image is an
indexed image with a 4 shades of gray palette.
:rtype: PIL.Image.Image
"""
image = Image.new("P", (8, 8), 0)
image.putpalette(
[
# fmt: off
0xFF, 0xFF, 0xFF,
0xBB, 0xBB, 0xBB,
0x55, 0x55, 0x55,
0x00, 0x00, 0x00,
# fmt: on
]
)
for y in range(8):
for x in range(8):
image.putpixel((x, y), self.get_pixel(x, y))
return image
def __eq__(self, other):
if not isinstance(other, GBTile):
return False
return self.to_hex_string() == other.to_hex_string()