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''))
\
    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.