docs: def write_png(matrix, version, out, scale=1, border=None, color='#000',
                background='#fff', compresslevel=9, dpi=None, addad=True):
      """\
      Serializes the QR Code as PNG image.
  
      By default, the generated PNG will be a greyscale image with a bitdepth
      of 1. If different colors are provided, an indexed-color image with
      the same bitdepth is generated.
  
      :param matrix: The matrix to serialize.
      :param int version: The (Micro) QR code version
      :param out: Filename or a file-like object supporting to write bytes.
      :param scale: Indicates the size of a single module (default: 1 which
              corresponds to 1 x 1 pixel per module).
      :param int border: Integer indicating the size of the quiet zone.
              If set to ``None`` (default), the recommended border size
              will be used (``4`` for QR Codes, ``2`` for a Micro QR Codes).
      :param color: Color of the modules (default: black). The
              color can be provided as ``(R, G, B)`` tuple, as web color name
              (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``).
      :param background: Optional background color (default: white).
              See `color` for valid values. In addition, ``None`` is
              accepted which indicates a transparent background.
      :param int dpi: Optional DPI setting. By default (``None``), the PNG won't
              have any DPI information. Note that the DPI value is converted into
              meters since PNG does not support any DPI information.
      :param int compresslevel: Integer indicating the compression level
              (default: 9). 1 is fastest and produces the least
              compression, 9 is slowest and produces the most.
              0 is no compression.
      """
  
      def png_color(clr):
          return colors.color_to_rgb_or_rgba(clr, alpha_float=False)
  
      def chunk(name, data):
          """\
          Returns a PNG chunk with checksum.
          """
          chunk_head = name + data
          # See <https://docs.python.org/2/library/zlib.html#zlib.crc32>
          # why crc32() & 0xFFFFFFFF is necessary
          return pack(b'>I', len(data)) + chunk_head \
                 + pack(b'>I', zlib.crc32(chunk_head) & 0xFFFFFFFF)
  
      def scale_row_x_axis(row):
          """\
          Returns each pixel `scale` times.
          """
          for b in row:
              for i in range(scale):
                  yield b
  
      def scanline(row, filter_type=b'\0'):
          """\
          Returns a single scanline.
          """
          return bytearray(chain(filter_type, _pack_bits_into_byte(row)))
  
      def invert_row_bits(row):
          """\
          Inverts the row bits 0 -> 1, 1 -> 0
          """
          return (b ^ 0x1 for b in row)
  
      scale = int(scale)
      check_valid_scale(scale)
      check_valid_border(border)
      # Background color index
      bg_color_idx = 0
      trans_color = 1  # white
      stroke_is_transparent, bg_is_transparent = color is None, background is None
      stroke_color = png_color(color) if not stroke_is_transparent else None
      bg_color = png_color(background) if not bg_is_transparent else None
      if stroke_color == bg_color:
          raise ValueError('The stroke color and background color must not be the same')
      stroke_is_black, stroke_is_white = False, False
      bg_is_white, bg_is_black = False, False
      if not stroke_is_transparent:
          stroke_is_black = colors.color_is_black(stroke_color)
          if not stroke_is_black:
              stroke_is_white = colors.color_is_white(stroke_color)
      if not bg_is_transparent:
          bg_is_white = colors.color_is_white(bg_color)
          if not bg_is_white:
              bg_is_black = colors.color_is_black(bg_color)
      transparency = stroke_is_transparent or bg_is_transparent
      is_greyscale = False
      invert_row = False
      if bg_is_white:
          is_greyscale = stroke_is_black or stroke_is_transparent
          invert_row = is_greyscale
          trans_color = int(not is_greyscale)
      elif bg_is_black:
          is_greyscale = stroke_is_transparent or stroke_is_white
      elif bg_is_transparent:
          is_greyscale = stroke_is_black or stroke_is_white
          invert_row = is_greyscale
      palette = None
      if not is_greyscale:
          # PLTE image
          if bg_is_transparent:
              bg_color = colors.invert_color(stroke_color[:3])
              if len(stroke_color) == 4:
                  bg_color += (0,)
          elif stroke_is_transparent:
              stroke_color = colors.invert_color(bg_color[:3])
              if len(bg_color) == 4:
                  stroke_color += (0,)
          palette = sorted([bg_color, stroke_color], key=len, reverse=True)
          bg_color_idx = palette.index(bg_color)
          # Usually, the background color is the first entry in the PLTE so
          # no bit inverting should be necessary
          invert_row = bg_color_idx > 0
      border = get_border(version, border)
      width, height = get_symbol_size(version, scale, border)
      if dpi:
          dpi = int(dpi)
          if dpi < 0:
              raise ValueError('DPI value must not be negative')
          dpi = int(dpi // 0.0254)
      with writable(out, 'wb') as f:
          write = f.write
          # PNG writing by "hand" since this lib should not rely on PIL/Pillow
          # and PyPNG does not support PNG filters which leads to a suboptimal
          # performance (and file size) if the PNG image should be scaled.
          # I.e. a (unrealistic) scaling factor of 600 would require a 17400 x 17400
          # matrix as input for PyPNG for a 21 x 21 QR Code (+ border = 4) while
          # this algorithm works upon the primary 21 x 21 matrix.
          write(b'\211PNG\r\n\032\n')  # Magic number
          colortype = 3 if not is_greyscale else 0
          if is_greyscale:
              bg_color_idx = int(invert_row)
          # Header:
          # width, height, bitdepth, colortype, compression meth., filter, interlance
          write(chunk(b'IHDR', pack(b'>2I5B', width, height, 1, colortype, 0, 0, 0)))
          if dpi:
              write(chunk(b'pHYs', pack(b'>LLB', dpi, dpi, 1)))
          if colortype == 3:  # Palette
              write(chunk(b'PLTE', b''.join(pack(b'>3B', *clr[:3]) for clr in palette)))
              # <https://www.w3.org/TR/PNG/#11tRNS>
              if len(palette[0]) > 3:  # Color with alpha is the first in the palette
                  f.write(chunk(b'tRNS', b''.join(pack(b'>B', clr[3]) for clr in palette if len(clr) > 3)))
              elif transparency:
                  f.write(chunk(b'tRNS', pack(b'>B', bg_color_idx)))
          elif is_greyscale and transparency:  # Greyscale with alpha
              # Greyscale with alpha channel
              # <https://www.w3.org/TR/PNG/#11tRNS>
              # 2 bytes for color type == 0 (greyscale)
              write(chunk(b'tRNS', pack(b'>1H', trans_color)))
          # <https://www.w3.org/TR/PNG/#9Filters>
          # This variable holds the "Up" filter which indicates that this scanline
          # is equal to the above scanline (since it is filled with null bytes)
          same_as_above = b''
          row_filters = []
          if invert_row:
              row_filters.append(invert_row_bits)
          if scale > 1:
              # 2 == PNG Filter "Up"  <https://www.w3.org/TR/PNG/#9-table91>
              same_as_above = scanline([0] * width, filter_type=b'\2') * (scale - 1)
              row_filters.append(scale_row_x_axis)
          horizontal_border, vertical_border = b'', b''
          if border > 0:
              # Calculate horizontal and vertical border
              horizontal_border = scanline([bg_color_idx] * width) * border * scale
              vertical_border = [bg_color_idx] * border * scale
          res = bytearray(horizontal_border)
          for row in matrix:
              row = reduce(lambda r, fn: fn(r), row_filters, row)
              # Chain precalculated left border with row and right border
              res += scanline(chain(vertical_border, row, vertical_border))
              res += same_as_above  # This is b'' if no scaling factor was provided
          res += horizontal_border
          if _PY2:
              res = bytes(res)
          write(chunk(b'IDAT', zlib.compress(res, compresslevel)))
          if addad:
              write(chunk(b'tEXt', b'Software\x00' + CREATOR.encode('ascii')))
          write(chunk(b'IEND', b''))