tp/tools/libbti/libbti.py

930 lines
28 KiB
Python

import struct
import sys
import os
from PIL import Image
from io import BytesIO
from math import ceil
from dataclasses import asdict, dataclass
from enum import Enum
import json
imageFormatToInt = {
"I4": 0,
"I8": 1,
"IA4": 2,
"IA8": 3,
"RGB565": 4,
"RGB5A3": 5,
"RGBA32": 6,
"C4": 8,
"C8": 9,
"C14X2": 0xA,
"CMPR": 0xE,
}
colorFormatToInt = {"IA8": 0, "RGB565": 1, "RGB5A3": 2}
wrapModeToInt = {"CLAMP": 0, "REPEAT": 1, "MIRROR": 2, "MAX_TEXWRAP_MODE": 3}
texFilterToInt = {
"NEAR": 0,
"LINEAR": 1,
"NEAR_MIP_NEAR": 2,
"LINEAR_MIP_NEAR": 3,
"NEAR_MIP_LINEAR": 4,
"LINEAR_MIP_LINEAR": 5,
}
def invertDict(dict):
return {v: i for i, v in dict.items()}
@dataclass
class BTI_Header:
format: int
alphaEnabled: int
width: int
height: int
wrapS: int
wrapT: int
indexTexture: int
colorFormat: int
numColors: int
paletteOffset: int
mipmapEnabled: int
doEdgeLOD: int
biasClamp: int
maxAnisotropy: int
minFilter: int
magFilter: int
minLOD: int
maxLOD: int
mipmapCount: int
unk0: int
LODBias: int
imageOffset: int
@classmethod
def fromBytes(cls, header_bytes):
assert len(header_bytes) == 0x20
unpacked = struct.unpack(">BBHHBBBBHIBBBBBBbbBBhI", header_bytes)
return cls(*unpacked)
def getBytes(self):
return struct.pack(
">BBHHBBBBHIBBBBBBbbBBhI",
self.format,
self.alphaEnabled,
self.width,
self.height,
self.wrapS,
self.wrapT,
self.indexTexture,
self.colorFormat,
self.numColors,
self.paletteOffset,
self.mipmapEnabled,
self.doEdgeLOD,
self.biasClamp,
self.maxAnisotropy,
self.minFilter,
self.magFilter,
self.minLOD,
self.maxLOD,
self.mipmapCount,
self.unk0,
self.LODBias,
self.imageOffset,
)
def decodeIA8(bytes):
intensity = bytes[1]
return intensity, intensity, intensity, bytes[0]
def encodeIA8(color):
return ((color[3] << 8) & 0xFF00) | (((color[0] + color[1] + color[2]) // 3) & 0xFF)
def decodeRGB565(bytes):
full = (bytes[0] << 8) | bytes[1]
return (full >> 11) * 8, ((full >> 5) & 0b111111) * 4, (full & 0b11111) * 8, 0xFF
def encodeRGB565(color):
return (
(((color[0] // 8) & 0b11111) << 11)
| (((color[1] // 4) & 0b111111) << 5)
| ((color[2] // 8) & 0b11111)
)
def decodeRGB5A3(bytes):
full = (bytes[0] << 8) | bytes[1]
isAlpha = not (full & 0x8000) > 0
if isAlpha:
return (
((full >> 8) & 0xF) * 0x11,
((full >> 4) & 0xF) * 0x11,
(full & 0xF) * 0x11,
((full >> 12) & 0b111) * 0x20,
)
else:
return (
((full >> 10) & 0b11111) * 8,
((full >> 5) & 0b11111) * 8,
(full & 0b11111) * 8,
0xFF,
)
def encodeRGB5A3(color):
isAlpha = color[3] < 0xFF
if isAlpha:
return (
(((color[3] // 0x20) & 0b111) << 12)
| (((color[0] // 0x11) & 0xF) << 8)
| (((color[1] // 0x11) & 0xF) << 4)
| ((color[2] // 0x11) & 0xF)
)
else:
return (
(1 << 15)
| (((color[0] // 8) & 0b11111) << 10)
| (((color[1] // 8) & 0b11111) << 5)
| ((color[2] // 8) & 0b11111)
)
class ImageBase:
def __init__(self, header, data=None, imageBuffer=None):
self.header = header
self.data = data
self.paletteColors = []
if self.data == None:
self.data = bytearray()
self.imageBuffer = (
imageBuffer
if imageBuffer != None
else [(0, 0, 0, 0xFF)] * (header.width * header.height)
)
def decodePaletteColors(self):
decodeFunc = None
match self.header.colorFormat:
case 0:
decodeFunc = decodeIA8
case 1:
decodeFunc = decodeRGB565
case 2:
decodeFunc = decodeRGB5A3
colors = []
offset = self.header.paletteOffset
for i in range(self.header.numColors):
colors.append(decodeFunc(self.data[offset : offset + 2]))
offset += 2
self.paletteColors = colors
def encodePaletteColors(self):
encodeFunc = None
match self.header.colorFormat:
case 0:
encodeFunc = encodeIA8
case 1:
encodeFunc = encodeRGB565
case 2:
encodeFunc = encodeRGB5A3
for color in self.paletteColors:
self.data += struct.pack(">H", encodeFunc(color))
def writePixel(self, x, y, pixelValue):
if x >= self.header.width or y >= self.header.height:
return
self.imageBuffer[(y * self.header.width) + x] = pixelValue
def readPixel(self, x, y):
if x >= self.header.width or y >= self.header.height:
return 0xFF, 0xFF, 0xFF, 0xFF
return self.imageBuffer[(y * self.header.width) + x]
def decodeLoop(self, blockWidth, blockHeight, blockStride):
widthInBlocks = ceil(self.header.width / blockWidth)
heightInBlocks = ceil(self.header.height / blockHeight)
offset = self.header.imageOffset
for blockY in range(heightInBlocks):
for blockX in range(widthInBlocks):
self.decodeBlock(
blockX * blockWidth,
blockY * blockHeight,
self.data[offset : offset + blockStride],
)
offset += blockStride
def decode(self):
pass
def decodeBlock(self, x, y, blockBytes):
pass
def encodeLoop(self, blockWidth, blockHeight):
widthInBlocks = ceil(self.header.width / blockWidth)
heightInBlocks = ceil(self.header.height / blockHeight)
for blockY in range(heightInBlocks):
for blockX in range(widthInBlocks):
self.encodeBlock(blockX * blockWidth, blockY * blockHeight)
def encode(self):
pass
def encodeBlock(self, x, y):
pass
class I4Image(ImageBase):
def decode(self):
self.decodeLoop(8, 8, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(8):
for inX in range(4):
val = ((blockBytes[offset] & 0xF0) >> 4) * 0x11
self.writePixel((x + (inX * 2)), y + inY, (val, val, val, 0xFF))
val = (blockBytes[offset] & 0xF) * 0x11
self.writePixel((x + (inX * 2)) + 1, y + inY, (val, val, val, 0xFF))
offset += 1
def encode(self):
self.encodeLoop(8, 8)
self.header.numColors = len(self.paletteColors)
self.header.paletteOffset = len(self.data) + 0x20
self.encodePaletteColors()
def encodeBlock(self, x, y):
for inY in range(8):
for inX in range(4):
color1 = self.readPixel(x + (inX * 2), y + inY)
intensity1 = ((color1[0] + color1[1] + color1[2]) // 3) // 0x11
color2 = self.readPixel(x + (inX * 2) + 1, y + inY)
intensity2 = ((color2[0] + color2[1] + color2[2]) // 3) // 0x11
self.data.append((intensity1 << 4) | intensity2)
class I8Image(ImageBase):
def decode(self):
self.decodeLoop(8, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(8):
intensity = blockBytes[offset]
self.writePixel(
x + inX, y + inY, (intensity, intensity, intensity, 0xFF)
)
offset += 1
def encode(self):
self.encodeLoop(8, 4)
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(8):
color = self.readPixel(x + inX, y + inY)
intensity = (color[0] + color[1] + color[2]) // 3
self.data.append(intensity)
class IA4Image(ImageBase):
def decode(self):
self.decodeLoop(8, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(8):
byte = blockBytes[offset]
intensity = (byte & 0b1111) * 0x11
self.writePixel(
x + inX,
y + inY,
(intensity, intensity, intensity, (byte >> 4) * 0x11),
)
offset += 1
def encode(self):
self.encodeLoop(8, 4)
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(8):
color = self.readPixel(x + inX, y + inY)
intensity = (color[0] + color[1] + color[2]) // 3
self.data.append(((color[3] // 0x11) << 4) | (intensity // 0x11))
class IA8Image(ImageBase):
def decode(self):
self.decodeLoop(4, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(4):
self.writePixel(
x + inX, y + inY, decodeIA8(blockBytes[offset : offset + 2])
)
offset += 2
def encode(self):
self.encodeLoop(4, 4)
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(4):
color = self.readPixel(x + inX, y + inY)
self.data += struct.pack(">H", encodeIA8(color))
class RGB565Image(ImageBase):
def decode(self):
self.decodeLoop(4, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(4):
self.writePixel(
x + inX, y + inY, decodeRGB565(blockBytes[offset : offset + 2])
)
offset += 2
def encode(self):
self.encodeLoop(4, 4)
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(4):
color = self.readPixel(x + inX, y + inY)
self.data += struct.pack(">H", encodeRGB565(color))
class RGB5A3Image(ImageBase):
def decode(self):
self.decodeLoop(4, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(4):
self.writePixel(
x + inX, y + inY, decodeRGB5A3(blockBytes[offset : offset + 2])
)
offset += 2
def encode(self):
self.encodeLoop(4, 4)
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(4):
color = self.readPixel(x + inX, y + inY)
self.data += struct.pack(">H", encodeRGB5A3(color))
class RGBA32Image(ImageBase):
def decode(self):
self.decodeLoop(4, 4, 64)
def decodeBlock(self, x, y, blockBytes):
aOffset = 0
rOffset = 1
gOffset = 32
bOffset = 33
for inY in range(4):
for inX in range(4):
self.writePixel(
x + inX,
y + inY,
(
blockBytes[rOffset],
blockBytes[gOffset],
blockBytes[bOffset],
blockBytes[aOffset],
),
)
aOffset += 2
rOffset += 2
gOffset += 2
bOffset += 2
def encode(self):
self.encodeLoop(4, 4)
def encodeBlock(self, x, y):
aOffset = 0
rOffset = 1
gOffset = 32
bOffset = 33
buffer = bytearray(64)
for inY in range(4):
for inX in range(4):
color = self.readPixel(x + inX, y + inY)
buffer[rOffset] = color[0]
buffer[gOffset] = color[1]
buffer[bOffset] = color[2]
buffer[aOffset] = color[3]
aOffset += 2
rOffset += 2
gOffset += 2
bOffset += 2
class C4Image(ImageBase):
def decode(self):
self.decodePaletteColors()
self.decodeLoop(8, 8, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(8):
for inX in range(4):
index = (blockBytes[offset] & 0xF0) >> 4
if index >= self.header.numColors:
offset += 1
continue # Out of bounds, likely due to the block going out of the image
self.writePixel((x + (inX * 2)), y + inY, self.paletteColors[index])
index = blockBytes[offset] & 0xF
if index >= self.header.numColors:
offset += 1
continue # Out of bounds, likely due to the block going out of the image
self.writePixel((x + (inX * 2)) + 1, y + inY, self.paletteColors[index])
offset += 1
def encode(self):
# Mipmaps are currently not allowed for paletted textures
assert (
self.header.mipmapEnabled == 0 and self.header.mipmapCount == 1
), "Mipmaps are not supported on Paletted textures!"
self.encodeLoop(8, 8)
self.header.numColors = len(self.paletteColors)
self.header.paletteOffset = len(self.data) + 0x20
self.encodePaletteColors()
def encodeBlock(self, x, y):
for inY in range(8):
for inX in range(4):
pixelValue = self.readPixel(x + (inX * 2), y + inY)
if not pixelValue in self.paletteColors:
self.paletteColors.append(pixelValue)
index = self.paletteColors.index(pixelValue)
pixelValue = self.readPixel(x + (inX * 2) + 1, y + inY)
if not pixelValue in self.paletteColors:
self.paletteColors.append(pixelValue)
index2 = self.paletteColors.index(pixelValue)
self.data.append((index << 4) | index2)
class C8Image(ImageBase):
def decode(self):
self.decodePaletteColors()
self.decodeLoop(8, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(8):
# print(self.header.numColors)
if blockBytes[offset] >= self.header.numColors:
offset += 1
continue # Out of bounds, likely due to the block going out of the image
self.writePixel(
x + inX, y + inY, self.paletteColors[blockBytes[offset]]
)
offset += 1
def encode(self):
# Mipmaps are currently not allowed for paletted textures
assert (
self.header.mipmapEnabled == 0 and self.header.mipmapCount == 1
), "Mipmaps are not supported on Paletted textures!"
self.encodeLoop(8, 4)
self.header.numColors = len(self.paletteColors)
self.header.paletteOffset = len(self.data) + 0x20
self.encodePaletteColors()
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(8):
pixelValue = self.readPixel(x + inX, y + inY)
if not pixelValue in self.paletteColors:
self.paletteColors.append(pixelValue)
index = self.paletteColors.index(pixelValue)
self.data.append(index)
class C14X2Image(ImageBase):
def decode(self):
self.decodePaletteColors()
self.decodeLoop(4, 4, 32)
def decodeBlock(self, x, y, blockBytes):
offset = 0
for inY in range(4):
for inX in range(8):
index = struct.unpack(">H", blockBytes[offset : offset + 2])[0] & 0x3FFF
if index < self.header.numColors:
offset += 2
continue # Out of bounds, likely due to the block going out of the image
self.writePixel(x + inX, y + inY, self.paletteColors[index])
offset += 2
def encode(self):
# Mipmaps are currently not allowed for paletted textures
assert (
self.header.mipmapEnabled == 0 and self.header.mipmapCount == 1
), "Mipmaps are not supported on Paletted textures!"
self.encodeLoop(4, 4)
self.header.numColors = len(self.paletteColors)
self.header.paletteOffset = len(self.data) + 0x20
self.encodePaletteColors()
def encodeBlock(self, x, y):
for inY in range(4):
for inX in range(4):
pixelValue = self.readPixel(x + inX, y + inY)
if not pixelValue in self.paletteColors:
self.paletteColors.append(pixelValue)
index = self.paletteColors.index(pixelValue)
self.data += struct.pack(">H", index)
class CMPRImage(ImageBase):
def decode(self):
self.decodeLoop(8, 8, 32)
def interpolateColor(self, color1, color2, factor):
return tuple(int(c1 + ((c2 - c1) * factor)) for c1, c2 in zip(color1, color2))
def decodeBlock(self, x, y, blockBytes):
offset = 0
for subY in range(2):
for subX in range(2):
colors = [
decodeRGB565(blockBytes[offset + 0 : offset + 2]),
decodeRGB565(blockBytes[offset + 2 : offset + 4]),
]
realVal1, realVal2 = struct.unpack(
">HH", blockBytes[offset + 0 : offset + 4]
)
if realVal1 > realVal2:
colors.append(self.interpolateColor(colors[0], colors[1], (1 / 3)))
colors.append(self.interpolateColor(colors[0], colors[1], (2 / 3)))
else:
colors.append(self.interpolateColor(colors[0], colors[1], (1 / 2)))
colors.append((0, 0, 0, 0)) # transparent
# print(colors)
val = struct.unpack(">I", blockBytes[offset + 4 : offset + 8])[0]
for inY in range(4):
for inX in range(4):
self.writePixel(
x + (subX * 4) + inX,
y + (subY * 4) + inY,
colors[val >> 30],
)
val = (val << 2) & 0xFFFFFFFF
offset += 8
def encode(self):
self.encodeLoop(8, 8)
def color_distance(self, color1, color2):
return (
((color1[0] - color2[0]) ** 2)
+ ((color1[1] - color2[1]) ** 2)
+ ((color1[2] - color2[2]) ** 2)
)
def encodeBlock(self, x, y):
for subY in range(2):
for subX in range(2):
block = [
self.readPixel(x + (subX * 4) + inX, y + (subY * 4) + inY)
for inY in range(4)
for inX in range(4)
]
# print(f"{x} {y}")
# print(block)
isTransparent = False
for color in block:
if color[3] < 200:
isTransparent = True
break
colors = [min(block), max(block)]
realColor1 = encodeRGB565(colors[0])
realColor2 = encodeRGB565(colors[1])
if realColor1 < realColor2:
colors[0], colors[1] = colors[1], colors[0]
realColor1, realColor2 = realColor2, realColor1
if isTransparent and realColor1 > realColor2:
colors[0], colors[1] = colors[1], colors[0]
realColor1, realColor2 = realColor2, realColor1
# print(colors[0])
if not isTransparent:
colors.append(self.interpolateColor(colors[0], colors[1], (1 / 3)))
colors.append(self.interpolateColor(colors[0], colors[1], (2 / 3)))
else:
colors.append(self.interpolateColor(colors[0], colors[1], (1 / 2)))
# colors.append((0,0,0,0))
indices = []
for color in block:
if color[3] < 200:
indices.append(3)
continue
distances = [
self.color_distance(color, testColor) for testColor in colors
]
indices.append(distances.index(min(distances)))
indicesInt = 0
for i in range(16):
indicesInt = (indicesInt << 2) | (
indices[i] & 0b11
) # (indicesInt >> 2) | ((indices[i]&0b11)<<30)
# print(realColor1)
# print(realColor2)
self.data += struct.pack(">HHI", realColor1, realColor2, indicesInt)
def bti_to_png_cli(inFile, outFile, data, writeFunc):
bti_to_png(os.path.splitext(outFile)[0] + ".bti", data, writeFunc)
def bti_to_png(name, data, writefunc):
outName = os.path.splitext(name)[0] + ".png"
print(f"Converting {name} to {outName}")
header = BTI_Header.fromBytes(data[0:0x20])
# print(header.getBytes())
image = None
match header.format:
case 0:
image = I4Image(header, data)
case 1:
image = I8Image(header, data)
case 2:
image = IA4Image(header, data)
case 3:
image = IA8Image(header, data)
case 4:
image = RGB565Image(header, data)
case 5:
image = RGB5A3Image(header, data)
case 6:
image = RGBA32Image(header, data)
case 8:
image = C4Image(header, data)
case 9:
image = C8Image(header, data)
case 10:
image = C14X2Image(header, data)
case 0xE:
image = CMPRImage(header, data)
case _:
print("Invalid format!")
image.decode()
pilImage = Image.new("RGBA", (header.width, header.height))
pilImage.putdata(image.imageBuffer)
with BytesIO() as output:
pilImage.save(output, format="PNG")
writefunc(outName, output.getvalue())
header_dict = asdict(header)
# print(header_dict)
header_dict["format"] = invertDict(imageFormatToInt)[header.format]
if header.format >= 8 and header.format <= 10:
header_dict["colorFormat"] = invertDict(colorFormatToInt)[header.colorFormat]
else:
del header_dict["colorFormat"]
header_dict["wrapS"] = invertDict(wrapModeToInt)[header.wrapS]
header_dict["wrapT"] = invertDict(wrapModeToInt)[header.wrapT]
header_dict["minFilter"] = invertDict(texFilterToInt)[header.minFilter]
header_dict["magFilter"] = invertDict(texFilterToInt)[header.magFilter]
del header_dict["width"]
del header_dict["height"]
del header_dict["numColors"]
del header_dict["paletteOffset"]
del header_dict["unk0"]
del header_dict["imageOffset"]
# print(header_dict)
splitext = os.path.splitext(name)
writefunc(
splitext[0] + ".bti.json", bytes(json.dumps(header_dict, indent=4), "ascii")
)
return outName
def namedValueFromDict(value, dict):
if not value in dict:
print(
f"Format {value} is not a valid type. Valid types are {', '.join(dict.keys())}"
)
return None
return dict[value]
def png_to_bti(name, convertFunc):
# print(name)
splitext = os.path.splitext(name)
header_dict = json.load(open(splitext[0] + ".bti.json", "r"))
format = namedValueFromDict(header_dict["format"], imageFormatToInt)
colorFormat = 0
if format >= 8 and format <= 10:
colorFormat = namedValueFromDict(header_dict["colorFormat"], colorFormatToInt)
wrapS = namedValueFromDict(header_dict["wrapS"], wrapModeToInt)
wrapT = namedValueFromDict(header_dict["wrapT"], wrapModeToInt)
minFilter = namedValueFromDict(header_dict["minFilter"], texFilterToInt)
magFilter = namedValueFromDict(header_dict["magFilter"], texFilterToInt)
pilImage = Image.open(name)
# Reduce the amount of colors to go within the palette bounds, if needed
if format >= 8 and format <= 10:
unique_pixels = len(set(list(pilImage.getdata())))
match format:
case 8: # C4
if unique_pixels > 16:
pilImage = pilImage.quantize(15)
case 9: # C8
if unique_pixels > 256:
pilImage = pilImage.quantize(255)
case 10: # C14X2
if unique_pixels > 16384:
pilImage = pilImage.quantize(16383)
pilImage = pilImage.convert("RGBA")
width = pilImage.width
height = pilImage.height
imageBuffer = list(pilImage.getdata())
pilImage.close()
# Fill header with default values
header = BTI_Header(
format,
header_dict["alphaEnabled"],
width,
height,
wrapS,
wrapT,
header_dict["indexTexture"],
colorFormat,
0,
0,
header_dict["mipmapEnabled"],
header_dict["doEdgeLOD"],
header_dict["biasClamp"],
header_dict["maxAnisotropy"],
minFilter,
magFilter,
header_dict["minLOD"],
header_dict["maxLOD"],
header_dict["mipmapCount"],
0,
header_dict["LODBias"],
32,
)
match header.format:
case 0:
image = I4Image(header, None, imageBuffer)
case 1:
image = I8Image(header, None, imageBuffer)
case 2:
image = IA4Image(header, None, imageBuffer)
case 3:
image = IA8Image(header, None, imageBuffer)
case 4:
image = RGB565Image(header, None, imageBuffer)
case 5:
image = RGB5A3Image(header, None, imageBuffer)
case 6:
image = RGBA32Image(header, None, imageBuffer)
case 8:
image = C4Image(header, None, imageBuffer)
case 9:
image = C8Image(header, None, imageBuffer)
case 10:
image = C14X2Image(header, None, imageBuffer)
case 0xE:
image = CMPRImage(header, None, imageBuffer)
if header.mipmapEnabled == 0 or header.mipmapCount == 1:
image.encode()
else:
# Handle mipmaps
for i in range(header.mipmapCount):
image.encode()
header.width = header.width // 2
header.height = header.height // 2
newPixels = [(0, 0, 0, 0xFF)] * (header.width * header.height)
for y in range(header.height):
for x in range(header.width):
p1 = image.imageBuffer[(y * header.width * 4) + (x * 2)]
p2 = image.imageBuffer[(y * header.width * 4) + (x * 2) + 1]
p3 = image.imageBuffer[
(header.width * 2) + (y * header.width * 4) + (x * 2)
]
p4 = image.imageBuffer[
(header.width * 2) + (y * header.width * 4) + (x * 2) + 1
]
newPixels[(y * header.width) + x] = (
(p1[0] + p2[0] + p3[0] + p4[0]) // 4,
(p1[1] + p2[1] + p3[1] + p4[1]) // 4,
(p1[2] + p2[2] + p3[2] + p4[2]) // 4,
(p1[3] + p2[3] + p3[3] + p4[3]) // 4,
)
image.imageBuffer = newPixels
# reset image info
header.width = width
header.height = height
# The encoding function should've modified the header for any changes
return header.getBytes() + image.data
def testWriteFunc(name, data):
with open(name, "wb") as f:
f.write(data)
def main():
if len(sys.argv) != 3:
print(
f"Usage: {sys.argv[0]} input.bti output.png OR {sys.argv[0]} input.png output.bti"
)
exit(1)
inputString = sys.argv[1]
outputString = sys.argv[2]
if inputString.rfind(".png") != -1 and outputString.rfind(".bti") != -1:
bti_bytes = png_to_bti(inputString, None)
testWriteFunc(outputString, bti_bytes)
elif inputString.rfind(".bti") != -1 and outputString.rfind(".png") != -1:
bti_file = open(inputString, "rb")
bti_bytes = bti_file.read()
bti_file.close()
name = bti_to_png_cli(inputString, outputString, bti_bytes, testWriteFunc)
else:
print("Error: One of the provided arguments is not valid!")
exit(1)
if __name__ == "__main__":
main()