• Hallo liebe Userinnen und User,

    nach bereits längeren Planungen und Vorbereitungen sind wir nun von vBulletin auf Xenforo umgestiegen. Die Umstellung musste leider aufgrund der Serverprobleme der letzten Tage notgedrungen vorverlegt werden. Das neue Forum ist soweit voll funktionsfähig, allerdings sind noch nicht alle der gewohnten Funktionen vorhanden. Nach Möglichkeit werden wir sie in den nächsten Wochen nachrüsten. Dafür sollte es nun einige der Probleme lösen, die wir in den letzten Tagen, Wochen und Monaten hatten. Auch der Server ist nun potenter als bei unserem alten Hoster, wodurch wir nun langfristig den Tank mit Bytes vollgetankt haben.

    Anfangs mag die neue Boardsoftware etwas ungewohnt sein, aber man findet sich recht schnell ein. Wir wissen, dass ihr alle Gewohnheitstiere seid, aber gebt dem neuen Board eine Chance.
    Sollte etwas der neuen oder auch gewohnten Funktionen unklar sein, könnt ihr den "Wo issn da der Button zu"-Thread im Feedback nutzen. Bugs meldet ihr bitte im Bugtracker, es wird sicher welche geben die uns noch nicht aufgefallen sind. Ich werde das dann versuchen, halbwegs im Startbeitrag übersichtlich zu halten, was an Arbeit noch aussteht.

    Neu ist, dass die Boardsoftware deutlich besser für Mobiltelefone und diverse Endgeräte geeignet ist und nun auch im mobilen Style alle Funktionen verfügbar sind. Am Desktop findet ihr oben rechts sowohl den Umschalter zwischen hellem und dunklem Style. Am Handy ist der Hell-/Dunkelschalter am Ende der Seite. Damit sollte zukünftig jeder sein Board so konfigurieren können, wie es ihm am liebsten ist.


    Die restlichen Funktionen sollten eigentlich soweit wie gewohnt funktionieren. Einfach mal ein wenig damit spielen oder bei Unklarheiten im Thread nachfragen. Viel Spaß im ngb 2.0.

Dateiformat .des von Texcelle umwandeln

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 :D 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.
 
Zuletzt bearbeitet:

WoodstockimWeb

Danke für den Fisch

Registriert
12 Juli 2013
Beiträge
2.020
Ort
Münsterland
  • Thread Starter Thread Starter
  • #22
Ja, der Hammer, genau das sollte rauskommen.
Da es sich nur um Indizierte Grafiken handelt, sind wir schon durch.

Ich würde den Code gerne "klauen" oder magst mir was basteln, entweder mit Eingabemaske oder CLI Command.

Ich sage mal Vielen lieben Dank und kein Problem für das Schänden.

Gruß
Woodstock
 

Dr. M.

Weltmaschinenfahrer

Registriert
30 Juli 2018
Beiträge
188
Klar, kein Problem. Ich hab den Code oben mal eben so angepasst dass der Dateiname per Argument übergeben werden kann. Der Output landet dann in einer png-Datei mit gleichem Namen. Unter Windows kannst du die des einfach per Drag&Drop auf das Script ziehen.

Falls du kein Python installiert hast, hier mit PyInstaller in eine exe gepackt: http://xup.in/dl,13064183
 

WoodstockimWeb

Danke für den Fisch

Registriert
12 Juli 2013
Beiträge
2.020
Ort
Münsterland
  • Thread Starter Thread Starter
  • #24
Jo, was soll ich dazu sagen, promte Lieferung.

Eine kleine Kleinigkeit haben wir noch, nun ja eigentlich zwei:

1. Die ausgegebene PNG Datei ist spiegelverkehrt.
2. ich hätte die Ausgabedatei gern als TIF.

Hier mal ne Handvoll .des Dateien zum spielen: Link

P.S. die "Probleme" kann ich lösen, als Exe wäre es jedoch perfekt.

Gruß
Woodstock
 

Dr. M.

Weltmaschinenfahrer

Registriert
30 Juli 2018
Beiträge
188
So, nachdem ich den Code gestern Abend angepasst hatte, bin ich nun endlich auch dazu gekommen, auf einer Windows-Kiste eine neue exe zu bauen: http://xup.in/dl,12142994
Aber Vorsicht, die TIFFs werden mitunter sehr gross, weil unkomprimiert (~200 MB bei deiner ersten DES).

Wenn du eine DES-Datei hast, die nicht (mehr oder weniger) symmetrisch ist, wärs einfacher zu sehen ob nun die Ausrichtung stimmt. Blöde Bildformate, die Pixel nicht von links oben nach rechts unten lesen :)
 

theSplit

1998
Veteran Barkeeper

Registriert
3 Aug. 2014
Beiträge
28.557
@Dr. M.: Super dass du das Thema mit deiner Lösung aufgegriffen hast. Aber mal eine Anmerkung zum Code - ich würde einen Parameter mitgeben ob "left to right" oder "right to left" (respektiv top bottom, bottom top) gelesen werden soll bzw. eher ob das "Image Transpose" aufgerufen werden soll oder nicht. Der Parameter macht das etwas flexibler.

Im weiteren würde ich die sys.argv testen ob wirklich ein Parameter angegeben wurde und wenn ein Dateiname übergeben wurde, die Datei existiert. (os exists). Oder eben das aktuelle Workdir komplett gelesen und vearbeitet werden soll in dem das Skript aufgerufen wird.

Und zu guter letzt - Output nach entweder "PNG" oder "TIFF" - auch über einen möglichen Parameter gesteuert, so das Default Tiff wäre, aber PNG auch möglich ist.
 

Dr. M.

Weltmaschinenfahrer

Registriert
30 Juli 2018
Beiträge
188
In Bezug auf das Errorhandling hast du natürlich Recht, die Fehlermeldungen könnten informativer sein, wenn du kein Argument übergibst kommt aktuell ein IndexError raus, wenn die Datei nicht existiert ein FileNotFoundError. Ob man das vorab überprüft, oder erst annimmt das alles passt und ggf. die Exceptions behandelt ist eine eher philosophische Frage, wobei Python letzteres empfiehlt: https://docs.python.org/3/glossary.html#term-eafp und https://docs.quantifiedcode.com/pyt...d_of_forgiveness_when_working_with_files.html
Auf jedem Fall sollte ich die Exceptions aber eigentlich abfangen und schönere Fehlermeldungen produzieren. Konsequenterweise insbesondere auch dann, wenn die Baumstruktur im File nicht so ist wie ich sie erwarte, also gewisse Chunks fehlen, die Bildgrösse im Header nicht mit der Grösse der Daten übereinstimmt, das RLE-Decoding fehlschlägt, etc. Momentan fliegt da im Zweifel eine eher nichts sagende Exception (Assertions sind ja schon da). Und das Parsing würde eigentlich in eine Library gehören und sollte vom Ezeugen des Outputs unabhängig sein. War halt nur mal schnell hingehackt :)

Wegen der Argumente, ich habe das absichtlich so gemacht dass genau ein Argument übergeben wird, damit die Windows-User unter uns DES-Dateien per Drag&Drop auf das Script ziehen können und eben nicht jedes Mal explizit die Kommandozeile bemühen müssen. In diesem Fall übergibt Windows dem Skript halt genau ein Argument. Ansonsten hätte ich einen weiteren Parameter für die Ausgabedatei gemacht. Gut, könnte man natürlich optional machen, wenn da verwenden, wenn nein aus dem Eingabenamen ableiten.
Die Reihenfolge der Pixel ist andererseits aber durch das DES-Format gegeben, da sehe ich eher weniger Nutzen für einen Parameter. Das sollte man einmal richtig hinkriegen und gut. Ich bin mir nur nicht sicher, ob das jetzt passt, weil Woodstocks Dateien alle sehr symmetrisch ausschauen. Da ist dann schwer zu beurteilen, ob die gerade spiegelverkehrt oder 180° gedreht sind...
 
Oben