Dr. M.
Weltmaschinenfahrer
- Registriert
- 30 Juli 2018
- Beiträge
- 188
Ich spiele mal Totengräber, weil das ja vielleicht irgendwann für irgendwen interessant sein könnte ...
[src=python]import sys, struct
from collections import namedtuple
from PIL import Image
class DesDataChunk:
__slots__ = ('tag', 'payload')
def __init__(self, tag, payload):
self.tag = tag
self.payload = payload
class DesTreeChunk:
__slots__ = ('tag', 'nodes')
def __init__(self, tag, nodes):
self.tag = tag
self.nodes = nodes
def __getitem__(self, tag):
nodes = [c for c in self.nodes if c.tag == tag]
if len(nodes) == 0:
return None
elif len(nodes) == 1:
return nodes[0]
else:
return tuple(nodes)
DesPathElement = namedtuple('DesPathElement', 'chunk end_pos')
file_name = sys.argv[1]
with open(file_name, 'rb') as desfile:
desdata = desfile.read()
des = DesTreeChunk('', [])
despath = [ DesPathElement(des, len(desdata)) ]
fp = 0
while fp < len(desdata):
tag = desdata[fp:fp+4].decode('ascii')
fp += 4
flags = struct.unpack('BBBB', desdata[fp:fp+4])
fp += 4
payload_len = struct.unpack('<I', desdata[fp:fp+4])[0]
fp += 4
indent = "\t" * (len(despath)-1)
if flags[0] == 0:
chunk = DesTreeChunk(tag, [])
despath[-1].chunk.nodes.append(chunk)
despath.append(DesPathElement(chunk, fp+payload_len))
elif flags[0] == 1:
payload = desdata[fp:fp+payload_len]
fp += payload_len
chunk = DesDataChunk(tag, payload)
despath[-1].chunk.nodes.append(chunk)
else:
raise ValueError('parse error, expected flag[0] to be 0 or 1, got '+flags[0])
print(f'{indent}chunk: {tag} - flags={flags} ==> {type(chunk)} ({payload_len} bytes)')
while despath and fp >= despath[-1].end_pos:
despath.pop()
assert despath == []
print('Structure OK, reading image data ...')
dhdr = des['CPAT']['CDIB']['DHDR'].payload
(img_width, img_height, img_depth, img_resx, img_resy) = struct.unpack('<IIHII', dhdr)
assert img_depth == 8
img_palette = []
dpal = des['CPAT']['CDIB']['DPAL'].payload
for i in range(0, 256):
# color order = b, g, r
img_palette.extend(dpal[4+i*4:4+i*4+3][::-1])
ddat = des['CPAT']['CDIB']['DDAT'].payload
img_data = bytearray()
fp = 4
while fp < len(ddat):
rle_compressed_len = struct.unpack('<I', ddat[fp:fp+4])[0]
fp += 4
rle_uncompressed_len = struct.unpack('<I', ddat[fp:fp+4])[0]
fp += 4
rle_data = ddat[fp:fp+rle_compressed_len]
fp += rle_compressed_len
uncompressed_data = bytearray()
cp = 0
while cp < rle_compressed_len:
if rle_data[cp] == 0xFF:
run_len = rle_data[cp+1]
if run_len == 0:
run_len = 256
run_byte = rle_data[cp+2]
cp += 3
uncompressed_data += bytearray((run_byte,)) * run_len
else:
uncompressed_data += bytearray((rle_data[cp],))
cp += 1
assert len(uncompressed_data) == rle_uncompressed_len
#print(f'RLE; compressed={rle_compressed_len}, uncompressed={rle_uncompressed_len}')
img_data += uncompressed_data
if img_width % 4 > 0:
img_width += 4 - img_width % 4
print('Saving TIFF ...')
img = Image.frombytes('P', (img_width, img_height), bytes(img_data)).transpose(Image.FLIP_LEFT_RIGHT)
img.putpalette(img_palette)
img.save(file_name.replace('.des', '.tiff'))
[/src]
Das Ergebnis ist so was:
Das Format ist nicht so schrecklich kompliziert, die Grundstruktur ist ein Baum von Chunks/Blöcken. Jeder Block hat einen Namen (4 Zeichen) und enthält entweder Daten oder Unterblöcke. Die eigentlichen Bilddaten stehen im Block DDAT und sind per RLE komprimiert.
Was hilft, sobald man die Grundstruktur der Blöcke erkannt hat: Blocknamen googeln. Dann findet man sowas: https://www.freelancer.com/projects/c-programming-windows/create-windows-shell-extension-thumbnail/ (Backup im Spoiler, falls das irgendwann verschwindet). Damit ist der Rest ein Spaziergang Der Python-Code behandelt aber nur die Fälle, die in Woodstocks Beispiel auch verwendet werden - also eine unkomprimierte Datei mit 256 Farben und Farbpalette. Das Ganze gibt's nach der Beschreibung aber auch noch in RGB und in komprimiert ...
[src=python]import sys, struct
from collections import namedtuple
from PIL import Image
class DesDataChunk:
__slots__ = ('tag', 'payload')
def __init__(self, tag, payload):
self.tag = tag
self.payload = payload
class DesTreeChunk:
__slots__ = ('tag', 'nodes')
def __init__(self, tag, nodes):
self.tag = tag
self.nodes = nodes
def __getitem__(self, tag):
nodes = [c for c in self.nodes if c.tag == tag]
if len(nodes) == 0:
return None
elif len(nodes) == 1:
return nodes[0]
else:
return tuple(nodes)
DesPathElement = namedtuple('DesPathElement', 'chunk end_pos')
file_name = sys.argv[1]
with open(file_name, 'rb') as desfile:
desdata = desfile.read()
des = DesTreeChunk('', [])
despath = [ DesPathElement(des, len(desdata)) ]
fp = 0
while fp < len(desdata):
tag = desdata[fp:fp+4].decode('ascii')
fp += 4
flags = struct.unpack('BBBB', desdata[fp:fp+4])
fp += 4
payload_len = struct.unpack('<I', desdata[fp:fp+4])[0]
fp += 4
indent = "\t" * (len(despath)-1)
if flags[0] == 0:
chunk = DesTreeChunk(tag, [])
despath[-1].chunk.nodes.append(chunk)
despath.append(DesPathElement(chunk, fp+payload_len))
elif flags[0] == 1:
payload = desdata[fp:fp+payload_len]
fp += payload_len
chunk = DesDataChunk(tag, payload)
despath[-1].chunk.nodes.append(chunk)
else:
raise ValueError('parse error, expected flag[0] to be 0 or 1, got '+flags[0])
print(f'{indent}chunk: {tag} - flags={flags} ==> {type(chunk)} ({payload_len} bytes)')
while despath and fp >= despath[-1].end_pos:
despath.pop()
assert despath == []
print('Structure OK, reading image data ...')
dhdr = des['CPAT']['CDIB']['DHDR'].payload
(img_width, img_height, img_depth, img_resx, img_resy) = struct.unpack('<IIHII', dhdr)
assert img_depth == 8
img_palette = []
dpal = des['CPAT']['CDIB']['DPAL'].payload
for i in range(0, 256):
# color order = b, g, r
img_palette.extend(dpal[4+i*4:4+i*4+3][::-1])
ddat = des['CPAT']['CDIB']['DDAT'].payload
img_data = bytearray()
fp = 4
while fp < len(ddat):
rle_compressed_len = struct.unpack('<I', ddat[fp:fp+4])[0]
fp += 4
rle_uncompressed_len = struct.unpack('<I', ddat[fp:fp+4])[0]
fp += 4
rle_data = ddat[fp:fp+rle_compressed_len]
fp += rle_compressed_len
uncompressed_data = bytearray()
cp = 0
while cp < rle_compressed_len:
if rle_data[cp] == 0xFF:
run_len = rle_data[cp+1]
if run_len == 0:
run_len = 256
run_byte = rle_data[cp+2]
cp += 3
uncompressed_data += bytearray((run_byte,)) * run_len
else:
uncompressed_data += bytearray((rle_data[cp],))
cp += 1
assert len(uncompressed_data) == rle_uncompressed_len
#print(f'RLE; compressed={rle_compressed_len}, uncompressed={rle_uncompressed_len}')
img_data += uncompressed_data
if img_width % 4 > 0:
img_width += 4 - img_width % 4
print('Saving TIFF ...')
img = Image.frombytes('P', (img_width, img_height), bytes(img_data)).transpose(Image.FLIP_LEFT_RIGHT)
img.putpalette(img_palette)
img.save(file_name.replace('.des', '.tiff'))
[/src]
Das Ergebnis ist so was:
Das Format ist nicht so schrecklich kompliziert, die Grundstruktur ist ein Baum von Chunks/Blöcken. Jeder Block hat einen Namen (4 Zeichen) und enthält entweder Daten oder Unterblöcke. Die eigentlichen Bilddaten stehen im Block DDAT und sind per RLE komprimiert.
Was hilft, sobald man die Grundstruktur der Blöcke erkannt hat: Blocknamen googeln. Dann findet man sowas: https://www.freelancer.com/projects/c-programming-windows/create-windows-shell-extension-thumbnail/ (Backup im Spoiler, falls das irgendwann verschwindet). Damit ist der Rest ein Spaziergang Der Python-Code behandelt aber nur die Fälle, die in Woodstocks Beispiel auch verwendet werden - also eine unkomprimierte Datei mit 256 Farben und Farbpalette. Das Ganze gibt's nach der Beschreibung aber auch noch in RGB und in komprimiert ...
The graphic file format contains a data block which stores a thumbnail image of the image stored in the file, so the entire file does not have to be read. The image data in the thumbnail is stored in either 8-bit indexed-color or 24-bit true-color modes and a data block in the file will specify the bit depth. If the file contains an 8-bit indexed-color image, a data block in the file will contain a color palette of 256 entries. The indexed-color image is stored run-length encoded and the true-color image is stored in BGR byte order. The pixels of the image have an aspect-ratio. A data block will give the resolution in the X and the resolution in the Y of the image as pixels per unit. If the aspect ratio is not square, the thumbnail image created will have to be interpolated using Supersampling to display an image with the correct aspect ratio. The file can be either compressed or uncompressed. A data block at the beginning of the file will determine if compression if used. If the file is compressed, the next data block will contain a Zip compressed version of the uncompressed file.
The file consists of data blocks stored in Little Endian encoding. The first 12 bytes of the block are a header that describe the block and give an offset to the end of the block. The first 4 bytes of the header are ASCII characters that name the block. The next 4 bytes are flags. If the first byte flag if a 1, then data follows the header. If the first flag is 0, nested blocks follow the header. The last 4 bytes in the header are an unsigned integer offset, in bytes, to the end of the block.
The first block in the file is the NIFF block; it specifies if the file is compressed or uncompressed. If the data in the block is ZNIF, the file is compressed. If the data is DSGN, the file is uncompressed. If the file is compressed, the second block in the file will be the ZNIF block; It contains 2 blocks. The second nested block is the ZCHK block. It contains Zip compressed data of the uncompressed version of the file.
The second data block in the uncompressed file is the STMP block. It stored a thumbnail of the image in the file that will be used by the shell extension. The first block inside of the STMP block is the CDIB block that contains the image data of the thumbnail. The first block inside if the CDIB block is the DHDR block which contains header information for the thumbnail.
The data in the DHDR block is as follows. The first 4 bytes are the width of the thumbnail image in pixels. The next 4 bytes are the height of the thumbnail image in pixels. The next 2 bytes are the bit-depth of the image, 8 for indexed-color and 24 for true-color. The next 4 blocks are the resolution of the image in X, and the last 4 bytes are the resolution of the image in Y. The resolution of the image is the number of pixels per square unit and will be used to determine the pixel-aspect-ratio of the image. When creating the image for the thumbnail, if the image is not in square aspect-ratio, the resulting thumbnail will have to either be scaled down alond one of the axes using Supersampling, or scaled up along the other axis using linear interpolation to produce an image that is visually correct.
The next data block inside of the CDIB block, if the image is 8-bit indexed-color, is the DPAL block that contains the palette for the image. The first 4 bytes are flags followed by 256 4-byte records containing bytes for the color values blue, green, then red. The last byte in the record is 0. If the image is 24-bit true-color, the DPAL block will be missing.
The next block in the CDIB block is the DDAT block that contains the image data. The DDAT block begins with 4 bytes of flags and contains the image data for either the 8-bit indexed color pixels, or the 24-bit true-color pixels. The pixels are stores in rows which are read from left to right, and the rows are read from the bottom up. Each row is padded to be divisible by 4 bytes, so if the image is 13 pixels wide, in indexed-color it would be 16 bytes in stride, and in true-color would be 40 bytes in stride. If the image is 24-bit true-color, the pixels are stored as 3 bytes in BGR order. If the image is 8-bit indexed-color, the pixels are stored in run-length-encoded blocks. Each block begins with an 8 byte header of which the first 4 are the length of the block in bytes and the second 4 are the number of bytes in the uncompressed block. After the header, each block contains the bytes of the color indexes, or run-length-encoded sections that begin with FF followed by the length of the run, followed by the byte for the color index. If the length of the run is 0, the run is 256.
The file consists of data blocks stored in Little Endian encoding. The first 12 bytes of the block are a header that describe the block and give an offset to the end of the block. The first 4 bytes of the header are ASCII characters that name the block. The next 4 bytes are flags. If the first byte flag if a 1, then data follows the header. If the first flag is 0, nested blocks follow the header. The last 4 bytes in the header are an unsigned integer offset, in bytes, to the end of the block.
The first block in the file is the NIFF block; it specifies if the file is compressed or uncompressed. If the data in the block is ZNIF, the file is compressed. If the data is DSGN, the file is uncompressed. If the file is compressed, the second block in the file will be the ZNIF block; It contains 2 blocks. The second nested block is the ZCHK block. It contains Zip compressed data of the uncompressed version of the file.
The second data block in the uncompressed file is the STMP block. It stored a thumbnail of the image in the file that will be used by the shell extension. The first block inside of the STMP block is the CDIB block that contains the image data of the thumbnail. The first block inside if the CDIB block is the DHDR block which contains header information for the thumbnail.
The data in the DHDR block is as follows. The first 4 bytes are the width of the thumbnail image in pixels. The next 4 bytes are the height of the thumbnail image in pixels. The next 2 bytes are the bit-depth of the image, 8 for indexed-color and 24 for true-color. The next 4 blocks are the resolution of the image in X, and the last 4 bytes are the resolution of the image in Y. The resolution of the image is the number of pixels per square unit and will be used to determine the pixel-aspect-ratio of the image. When creating the image for the thumbnail, if the image is not in square aspect-ratio, the resulting thumbnail will have to either be scaled down alond one of the axes using Supersampling, or scaled up along the other axis using linear interpolation to produce an image that is visually correct.
The next data block inside of the CDIB block, if the image is 8-bit indexed-color, is the DPAL block that contains the palette for the image. The first 4 bytes are flags followed by 256 4-byte records containing bytes for the color values blue, green, then red. The last byte in the record is 0. If the image is 24-bit true-color, the DPAL block will be missing.
The next block in the CDIB block is the DDAT block that contains the image data. The DDAT block begins with 4 bytes of flags and contains the image data for either the 8-bit indexed color pixels, or the 24-bit true-color pixels. The pixels are stores in rows which are read from left to right, and the rows are read from the bottom up. Each row is padded to be divisible by 4 bytes, so if the image is 13 pixels wide, in indexed-color it would be 16 bytes in stride, and in true-color would be 40 bytes in stride. If the image is 24-bit true-color, the pixels are stored as 3 bytes in BGR order. If the image is 8-bit indexed-color, the pixels are stored in run-length-encoded blocks. Each block begins with an 8 byte header of which the first 4 are the length of the block in bytes and the second 4 are the number of bytes in the uncompressed block. After the header, each block contains the bytes of the color indexes, or run-length-encoded sections that begin with FF followed by the length of the run, followed by the byte for the color index. If the length of the run is 0, the run is 256.
Zuletzt bearbeitet: