0

I am using the following code coming (with very little modifications) from this post. This script simply takes an array of hex color and writes a PNG image out of it. I am trying to adapt it to Py3 but something is going wrong.

import zlib, struct

def png_pack(png_tag, data):
    chunk_head = png_tag + data
    return (struct.pack("!I", len(data)) +
            chunk_head +
            struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)))

def write_png(buf, width, height):

    # reverse the vertical line order and add null bytes at the start
    width_byte_4 = width * 4
    raw_data = b''.join(b'\x00' + buf[span:span + width_byte_4]
                        for span in range((height - 1) * width * 4, -1, - width_byte_4))

    return b''.join([
        b'\x89PNG\r\n\x1a\n',
        png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
        png_pack(b'IDAT', zlib.compress(raw_data, 9)),
        png_pack(b'IEND', b'')])


def saveAsPNG(array, filename):
    import struct
    if any([len(row) != len(array[0]) for row in array]):
        raise ValueError("Array should have elements of equal size")

    #First row becomes top row of image.
    flat = []
    map(flat.extend, reversed(array))
    #Big-endian, unsigned 32-byte integer.
    buf = b''.join([struct.pack('>I', ((0xffFFff & i32)<<8)|(i32>>24) )
                    for i32 in flat])   #Rotate from ARGB to RGBA.
    #print(type(buf))
    data = write_png(buf, len(array[0]), len(array))
    f = open(filename, 'wb')
    f.write(data)
    f.close()


saveAsPNG([[0xffFF0000, 0xffFFFF00],
           [0xff00aa77, 0xff333333]], 'test.png')

It works perfectly with Python 2.7 and run without raising any error on Python 3. However the resulting image is empty... I cannot figure out what's the problem. I tried to substitute map with starmap but nothing changed. I have checked that buf is bytes instead of string, and it is. I really don't know why it doesn't write the file correctly. Any clue?

Community
  • 1
  • 1
alec_djinn
  • 10,104
  • 8
  • 46
  • 71

1 Answers1

2

In Python2, map returns a list. In Python3, map returns a map object:

print(type(map(flat.extend, reversed(array))))
# <class 'map'>

The map object is an iterator. It does not call flat.extend until it is iterated over. Since no variable is used to save the map object, it is discarded, and flat remains an empty list.

So instead you need:

flat = []
for item in reversed(array):
    flat.extend(item)

or a list comprehension:

flat = [item for arr in reversed(array) for item in arr]

or itertools.chain.from_iterable:

import itertools as IT
flat = IT.chain.from_iterable(reversed(array))

map should never be used for its side-effects. It should only be used for generating a list in Python2, or an iterator in Python3.

In Python2 using map for its side-effects was a frowned-upon practice. In Python3, that policy is "enforced" to some degree by making map an iterator.

unutbu
  • 842,883
  • 184
  • 1,785
  • 1,677