Beispiel #1
0
def serialize_rle_stream(parent, buffer, **kwargs):
    '''
    Returns a buffer of pixel data from the supplied buffer.
    If the tag says the pixel data is rle compressed, this
    function will compress the buffer before returning it.
    '''
    assert parent is not None, "Cannot write tga pixels without without parent"

    header = parent.parent.header

    if header.image_type.rle_compressed:
        bpp = (header.bpp + 7) // 8  # +7 to round up to nearest byte

        buffer.seek(0)

        # start the compressed pixels buffer out as the same size as the
        # uncompressed pixels to minimize the number of times python has to
        # reallocate space every time the comp_pixels buffer is written to
        comp_pixels = BytearrayBuffer([0] * len(buffer))

        # get the first pixel to compress
        curr_pixel = buffer.read(bpp)
        next_pixel = buffer.peek(bpp)

        # keep running as long as there are pixels
        while curr_pixel:
            if curr_pixel == next_pixel:
                # this packet can be compressed with RLE
                rle_len = 1
                packet = curr_pixel

                # DO NOT REVERSE THESE CONDITIONS. If you do, read
                # wont be called and the read position will be wrong
                while curr_pixel == buffer.read(bpp) and rle_len < 128:
                    # see how many repeated pixels we can find(128 at most)
                    rle_len += 1

                # seek backward and read the last pixel
                buffer.seek(-bpp, 1)
                curr_pixel = buffer.read(bpp)
                next_pixel = buffer.peek(bpp)

                # write the header and the packet to comp_pixels
                comp_pixels.write(bytes([127 + rle_len]) + packet)

                # if the next read returns nothing, there are not more pixels
                if len(next_pixel) != bpp:
                    break
            else:
                # this should be a raw packet
                packet = b''

                while curr_pixel != next_pixel and len(packet) // bpp < 128:
                    # see how many non-repeated pixels we can find(128 at most)
                    packet += curr_pixel
                    curr_pixel = next_pixel
                    next_pixel = buffer.read(bpp)

                    # if next_pixel is the start of a repeated
                    # sequence of pixels then just break here
                    if curr_pixel == next_pixel or len(next_pixel) != bpp:
                        break

                # write the header and the packet to comp_pixels
                comp_pixels.write(bytes([len(packet) // bpp - 1]) + packet)

                # if the next read returns nothing, there are not more pixels
                if len(curr_pixel) != bpp:
                    break

        # slice the compressed pixels off at when the last write was
        comp_pixels = comp_pixels[:comp_pixels.tell()]
    else:
        comp_pixels = buffer

    return comp_pixels
Beispiel #2
0
    def serialize(self, **kwargs):
        '''
        This function will serialize this Block to the provided
        filepath/buffer. The name of the Block will be used as the
        extension. This function is used ONLY for writing a piece
        of a tag to a file/buffer, not the entire tag. DO NOT CALL
        this function when writing a whole tag at once.
        '''

        buffer = kwargs.pop('buffer', kwargs.pop('writebuffer', None))
        filepath = kwargs.pop('filepath', None)
        temp = kwargs.pop('temp', False)
        clone = kwargs.pop('clone', True)
        zero_fill = kwargs.pop('zero_fill', True)

        attr_index = kwargs.pop('attr_index', None)
        root_offset = kwargs.pop('root_offset', 0)
        offset = kwargs.pop('offset', 0)

        kwargs.pop('parent', None)

        mode = 'buffer'
        parent = None
        block = self
        desc = self.desc
        if buffer is None:
            mode = 'file'

        if 'tag' in kwargs:
            parent_tag = kwargs.pop("tag")
        else:
            parent_tag = self.get_root()

        if "calc_pointers" in kwargs:
            calc_pointers = kwargs["calc_pointers"]
        if isinstance(parent_tag, supyr_struct.tag.Tag):
            calc_pointers = parent_tag.calc_pointers
        else:
            calc_pointers = True
            parent_tag = None

        # convert string attr_indexes to ints
        if isinstance(attr_index, str) and attr_index not in desc:
            attr_index = desc['NAME_MAP'][attr_index]

        # if we are serializing an attribute, change some stuff
        if attr_index is not None:
            parent = self
            block = self[attr_index]
            desc = desc[attr_index]

        calc_pointers = bool(kwargs.pop("calc_pointers", calc_pointers))

        if filepath is None and buffer is None:
            # neither a filepath nor a buffer were
            # given, so make a BytearrayBuffer to write to.
            buffer = BytearrayBuffer()
            mode = 'buffer'
        elif filepath is not None and buffer is not None:
            raise IOError("Provide either a buffer or a filepath, not both.")

        if mode == 'file':
            filepath = str(Path(filepath))
            folderpath = os.path.dirname(filepath)

            if os.path.exists(filepath) and not os.path.isfile(filepath):
                raise IOError('filepath must be a valid path ' +
                              'to a file, not a folder.')

            # if the path doesnt exist, create it
            if not os.path.exists(folderpath):
                os.makedirs(folderpath)

            if temp:
                filepath += ".temp"
            try:
                # to avoid 'open' failing if windows files are hidden, we
                # open in 'r+b' mode and truncate if the file exists.
                if os.path.isfile(filepath):
                    buffer = open(filepath, 'r+b')
                    buffer.truncate(0)
                else:
                    buffer = open(filepath, 'w+b')
            except Exception:
                raise IOError('Output filepath for serializing Block ' +
                              'was invalid or the file could not ' +
                              'be created.\n    %s' % filepath)

        # make sure the buffer has a valid write and seek routine
        if not (hasattr(buffer, 'write') and hasattr(buffer, 'seek')):
            raise TypeError('Cannot serialize a Block without either' +
                            ' an output path or a writable buffer')

        cloned = False
        # try to write the Block to the buffer
        try:
            # if we need to calculate the pointers, do so
            if calc_pointers:
                # Make a copy of this Block so any changes
                # to pointers dont affect the entire Tag
                try:
                    if clone:
                        block = block.__deepcopy__({})
                        cloned = True
                        # remove the parent so any pointers
                        # higher in the tree are unaffected
                        block.parent = None
                    block.set_pointers(offset)
                except (NotImplementedError, AttributeError):
                    pass

            # make the buffer as large as the Block is calculated to fill
            if zero_fill:
                try:
                    blocksize = block.binsize
                    buffer.seek(0, 2)
                    if buffer.tell() < blocksize:
                        buffer.seek(blocksize - 1)
                        buffer.write(b'\x00')
                except AttributeError:
                    pass

            # commence the writing process
            desc[TYPE].serializer(block,
                                  parent=parent,
                                  attr_index=attr_index,
                                  writebuffer=buffer,
                                  root_offset=root_offset,
                                  offset=offset,
                                  **kwargs)

            # if a copy of the Block was made, delete the copy
            if cloned:
                del block
                cloned = False

            # return the filepath or the buffer in case
            # the caller wants to do anything with it
            if mode == 'file':
                try:
                    buffer.close()
                except Exception:
                    pass
                return str(filepath)
            return buffer
        except Exception as e:
            if mode == 'file':
                try:
                    buffer.close()
                except Exception:
                    pass
            try:
                os.remove(str(filepath))
            except Exception:
                pass
            # if a copy of the Block was made, delete the copy
            if cloned:
                del block
            e.args += ("Error occurred while attempting to serialize the "
                       "%s to:\"%s\"" % (type(self), str(filepath)), )
            raise