code: from io import BytesIO
  from math import ceil
  
  import numpy as np
  from PIL import Image
  
  from .exception import ParsingError
  
  _BLOCK_SIZE = 16
  _BLOCK_PIXEL = _BLOCK_SIZE * _BLOCK_SIZE
  
  PALETTE_OFFSETS = [
      0xB_EC68,
      0xB_EDA8,
      0xB_EEE8,
      0xB_F028,
      0xB_F168,
  ]
  
  
  def bytes_to_tilemap(data, palette=None, bpp=8, width=256):
      """
      Parameters
      ----------
      palette : bytes
         320 long RGBA (80 colors). Alpha is ignored.
  
      Returns
      -------
      PIL.Image
          Rendered RGB image.
      """
  
      # assert bpp in [4, 8]
  
      if bpp < 8:
          nibbles = bytearray()
          # offset = 0x0
          for b in data:
              shift = 8 - bpp
              while shift >= 0:
                  nibbles.append(b >> shift & (2 ** bpp - 1))
                  shift -= bpp
              # nibbles.append((b >> 4) | (offset << 4))
              # nibbles.append((b & 0xF) | (offset << 4))
          data = bytes(nibbles)
          del nibbles
  
      # Assemble bytes into an index-image
      h, w = int(ceil(len(data) / width / _BLOCK_SIZE) * _BLOCK_SIZE), width
      canvas = np.zeros((h, w), dtype=np.uint8)
      i_sprite = 0
      for i in range(0, len(data), _BLOCK_PIXEL):
          sprite = data[i : i + _BLOCK_PIXEL]
  
          x = i_sprite * _BLOCK_SIZE % w
          y = _BLOCK_SIZE * (i_sprite * _BLOCK_SIZE // w)
          view = canvas[y : y + _BLOCK_SIZE, x : x + _BLOCK_SIZE]
          sprite_block = np.frombuffer(sprite, dtype=np.uint8).reshape(
              _BLOCK_SIZE, _BLOCK_SIZE
          )
          view[:] = sprite_block
  
          i_sprite += 1
  
      if palette is None:
          return Image.fromarray(canvas, "L")
  
      # Apply palette to index-image
      p = np.frombuffer(palette, dtype=np.uint8).reshape((80, 4))
      p = p[:, :3]
      p = np.fliplr(p)  # BGR->RGB
  
      im = Image.fromarray(canvas, "P")
      im.putpalette(p)
  
      return im
  
  
  def rgb_to_index(tilemap, palette):
      if isinstance(tilemap, Image.Image):
          tilemap = tilemap.convert("RGB")
          tilemap = np.array(tilemap)
      elif isinstance(tilemap, np.ndarray):
          pass
      else:
          raise TypeError(f"Don't know how to handle tilemap type {type(tilemap)}")
  
      # Convert rgb tilemap to index image
      p = np.frombuffer(palette, dtype=np.uint8).reshape((80, 4))
      p = p[:, :3]
      p = np.fliplr(p)  # BGR->RGB
      p = p[None, None].transpose(0, 1, 3, 2)  # (1, 1, 3, 80)
  
      # Find closest color
      diff = tilemap[..., None] - p
      dist = np.linalg.norm(diff, axis=2)
      tilemap = np.argmin(dist, axis=-1).astype(np.uint8)
  
      return tilemap
  
  
  def tilemap_to_bytes(tilemap, palette=None, bpp=8):
      """
      Parameters
      ----------
      tilemap : PIL.Image.Image or numpy.ndarray
          RGB data
      palette : bytes
         320 long RGBA (80 colors). Alpha is ignored.
  
      Returns
      -------
      bytes
          Bytes representation of index image
      """
  
      if isinstance(tilemap, Image.Image):
          tilemap = tilemap.convert("RGB")
          tilemap = np.array(tilemap)
      elif isinstance(tilemap, np.ndarray):
          pass
      else:
          raise TypeError(f"Don't know how to handle tilemap type {type(tilemap)}")
  
      if palette is not None:
          tilemap = rgb_to_index(tilemap, palette)
  
      # Need to undo the tiling now.
      out = []
      for i in range(0, tilemap.shape[0], _BLOCK_SIZE):
          for j in range(0, tilemap.shape[1], _BLOCK_SIZE):
              sprite = tilemap[i : i + _BLOCK_SIZE, j : j + _BLOCK_SIZE]
              sprite_bytes = sprite.tobytes()
              out.append(sprite_bytes)
      out = b"".join(out)
  
      if bpp == 4:
          out_packed = bytearray()
          assert len(out) % 2 == 0
          for i in range(0, len(out), 2):
              b1, b2 = out[i], out[i + 1]
              b1 &= 0xF
              b2 &= 0xF
              out_packed.append((b1 << 4) | b2)
          out = bytes(out_packed)
  
      return out
  
  
  def decode_backdrop(data):
      """Convert easter egg images to GIF
  
      Based on:
          https://gist.github.com/GMMan/c1f0b516afdbb71769752ee06adbbd9a
  
      Returns
      -------
      PIL.Image.Image
          Decoded image
      int
          Number of bytes consumed to create image.
      """
  
      def rgb565_to_rgba32(pix):
          r = int(((pix >> 11) * 255 + 15) / 31)
          g = int((((pix >> 5) & 0x3F) * 255 + 31) / 63)
          b = int(((pix & 0x1F) * 255 + 15) / 31)
          return r, g, b
  
      idx = 0
      out = []
  
      # Header
      out.append(b"GIF89a")
  
      width = int.from_bytes(data[idx : idx + 2], "little")
      idx += 2
  
      height = int.from_bytes(data[idx : idx + 2], "little")
      idx += 2
  
      palette_size = data[idx]
      idx += 1
      idx += 1  # padding
  
      palette = []
      for _ in range(palette_size):
          palette.append(int.from_bytes(data[idx : idx + 2], "little"))
          idx += 2
  
      gct_size = 0
      calc_gct_size = 2
      while calc_gct_size < palette_size:
          gct_size += 1
          calc_gct_size <<= 1
  
      # Logical screen descriptor
      out.append(width.to_bytes(2, "little"))
      out.append(height.to_bytes(2, "little"))
      out.append(((1 << 7) | gct_size).to_bytes(1, "little"))
      out.append(b"\x00")
      out.append(b"\x00")
  
      # Global Color Table
      for i in range(calc_gct_size):
          if i < len(palette):
              r, g, b = rgb565_to_rgba32(palette[i])
              out.append(r.to_bytes(1, "little"))
              out.append(g.to_bytes(1, "little"))
              out.append(b.to_bytes(1, "little"))
          else:
              out.append(b"\x00")
              out.append(b"\x00")
              out.append(b"\x00")
  
      # Image descriptor
      out.append(b"\x2c")
      out.append(b"\x00\x00")  # x
      out.append(b"\x00\x00")  # y
      out.append(width.to_bytes(2, "little"))
      out.append(height.to_bytes(2, "little"))
      out.append(b"\x00")
  
      # Frame
      min_code_size = data[idx]
      idx += 1
      out.append(min_code_size.to_bytes(1, "little"))
  
      while True:
          block_size = data[idx]
          idx += 1
          out.append(block_size.to_bytes(1, "little"))
          if block_size == 0:
              break
          out.append(data[idx : idx + block_size])
          idx += block_size
  
      trailer = data[idx]
      idx += 1
  
      if trailer != 0x3B:
          raise ParsingError("Invalid GIF Trailer")
      out.append(trailer.to_bytes(1, "little"))
      out = b"".join(out)
  
      im = Image.open(BytesIO(out))
  
      return im, idx
  
apis: numpy.zeros numpy.argmin numpy.fliplr numpy.array numpy.linalg.norm numpy.frombuffer