Source code for pyffi.formats.tga
"""
:mod:`pyffi.formats.tga` --- Targa (.tga)
=========================================
Implementation
--------------
.. autoclass:: TgaFormat
:show-inheritance:
:members:
Regression tests
----------------
Read a TGA file
^^^^^^^^^^^^^^^
>>> # check and read tga file
>>> import os
>>> from os.path import dirname
>>> dirpath = __file__
>>> for i in range(4): #recurse up to root repo dir
... dirpath = dirname(dirpath)
>>> repo_root = dirpath
>>> format_root = os.path.join(repo_root, 'tests', 'formats', 'tga')
>>> file = os.path.join(format_root, 'test.tga').replace("\\\\", "/")
>>> stream = open(file, 'rb')
>>> data = TgaFormat.Data()
>>> data.inspect(stream)
>>> data.read(stream)
>>> stream.close()
>>> data.header.width
60
>>> data.header.height
20
Parse all TGA files in a directory tree
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> for stream, data in TgaFormat.walkData(format_root):
... try:
... # the replace call makes the doctest also pass on windows
... os_path = stream.name
... split = (os_path.split(os.sep))[-4:]
... rejoin = os.path.join(*split).replace("\\\\", "/")
... print("reading %s" % rejoin)
... except Exception:
... print(
... "Warning: read failed due corrupt file,"
... " corrupt format description, or bug.") # doctest: +REPORT_NDIFF
reading tests/formats/tga/test.tga
reading tests/formats/tga/test_footer.tga
Create a TGA file from scratch and write to file
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
>>> data = TgaFormat.Data()
>>> from tempfile import TemporaryFile
>>> stream = TemporaryFile()
>>> data.write(stream)
>>> stream.close()
"""
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (c) 2007-2012, Python File Format Interface
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above
# copyright notice, this list of conditions and the following
# disclaimer in the documentation and/or other materials provided
# with the distribution.
#
# * Neither the name of the Python File Format Interface
# project nor the names of its contributors may be used to endorse
# or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
# ***** END LICENSE BLOCK *****
import struct, os, re
import pyffi.object_models.xml
import pyffi.object_models.common
import pyffi.object_models.xml.basic
import pyffi.object_models.xml.struct_
import pyffi.object_models
import pyffi.utils.graph
from pyffi.utils.graph import EdgeFilter
[docs]class TgaFormat(pyffi.object_models.xml.FileFormat):
"""This class implements the TGA format."""
xml_file_name = 'tga.xml'
# where to look for tga.xml and in what order:
# TGAXMLPATH env var, or TgaFormat module directory
xml_file_path = [os.getenv('TGAXMLPATH'), os.path.dirname(__file__)]
# filter for recognizing tga files by extension
RE_FILENAME = re.compile(r'^.*\.tga$', re.IGNORECASE)
# basic types
int = pyffi.object_models.common.Int
uint = pyffi.object_models.common.UInt
byte = pyffi.object_models.common.Byte
ubyte = pyffi.object_models.common.UByte
char = pyffi.object_models.common.Char
short = pyffi.object_models.common.Short
ushort = pyffi.object_models.common.UShort
float = pyffi.object_models.common.Float
PixelData = pyffi.object_models.common.UndecodedData
[docs] class Image(pyffi.utils.graph.GlobalNode):
def __init__(self):
# children are either individual pixels, or RLE packets
self.children = []
def read(self, stream, data):
data = data
if data.header.image_type in (TgaFormat.ImageType.INDEXED,
TgaFormat.ImageType.RGB,
TgaFormat.ImageType.GREY):
self.children = [
TgaFormat.Pixel(argument=data.header.pixel_size)
for i in range(data.header.width
* data.header.height)]
for pixel in self.children:
pixel.read(stream, data)
else:
self.children = []
count = 0
while count < data.header.width * data.header.height:
pixel = TgaFormat.RLEPixels(
argument=data.header.pixel_size)
pixel.read(stream, data)
self.children.append(pixel)
count += pixel.header.count + 1
def write(self, stream, data):
data = data
for child in self.children:
child.arg = data.header.pixel_size
child.write(stream, data)
[docs] def get_detail_child_nodes(self, edge_filter=EdgeFilter()):
for child in self.children:
yield child
[docs] def get_detail_child_names(self, edge_filter=EdgeFilter()):
for i in range(len(self.children)):
yield str(i)
[docs] class Data(pyffi.object_models.FileFormat.Data):
def __init__(self):
self.header = TgaFormat.Header()
self.image = TgaFormat.Image()
self.footer = None # TgaFormat.Footer() is optional
[docs] def inspect(self, stream):
"""Quick heuristic check if stream contains Targa data,
by looking at the first 18 bytes.
:param stream: The stream to inspect.
:type stream: file
"""
# XXX todo: set some of the actual fields of the header
pos = stream.tell()
# read header
try:
id_length, colormap_type, image_type, \
colormap_index, colormap_length, colormap_size, \
x_origin, y_origin, width, height, \
pixel_size, flags = struct.unpack("<BBBHHBHHHHBB",
stream.read(18))
except struct.error:
# could not read 18 bytes
# not a TGA file
raise ValueError("Not a Targa file.")
finally:
stream.seek(pos)
# check if tga type is valid
# check pixel size
# check width and height
if not(image_type in (1, 2, 3, 9, 10, 11)
and pixel_size in (8, 24, 32)
and width <= 100000
and height <= 100000):
raise ValueError("Not a Targa file.")
# this looks like a tga file!
[docs] def read(self, stream):
"""Read a tga file.
:param stream: The stream from which to read.
:type stream: ``file``
"""
# read the file
self.inspect(stream) # quick check
# header
self.header.read(stream, self)
# image
self.image.read(stream, self)
# check if we are at the end of the file
if not stream.read(1):
self.footer = None
return
# footer
stream.seek(-26, os.SEEK_END)
self.footer = TgaFormat.Footer()
self.footer.read(stream, self)
[docs] def write(self, stream):
"""Write a tga file.
:param stream: The stream to write to.
:type stream: ``file``
"""
self.header.write(stream, self)
self.image.write(stream, self)
if self.footer:
self.footer.write(stream, self)
[docs] def get_global_child_nodes(self, edge_filter=EdgeFilter()):
yield self.header
yield self.image
if self.footer:
yield self.footer
def get_global_child_names(self, edge_filter=EdgeFilter()):
yield "Header"
yield "Image"
if self.footer:
yield "Footer"
if __name__ == '__main__':
import doctest
doctest.testmod()