smeargle.py/porygon.py

288 lines
6.6 KiB
Python
Executable File

#!/usr/bin/env python3
# Copyright 2018 Kiyoshi Aman
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
# SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
# IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
import sys
import os.path as op
from PyQt5.QtGui import QImage, QPixmap, QPainter, QGuiApplication
def linear1(tile, *args):
data = bytearray()
for y in range(8):
byte = 0
for x in range(8):
if tile.pixelIndex(x, y) > 0:
byte += 2**(8 - x)
data.append(byte)
return bytes(data)
def linear2(tile, palette):
data = bytearray()
for y in range(8):
bp1 = 0
bp2 = 0
for x in range(8):
pixel = tile.pixelIndex(x, y)
if palette is not None:
pixel = palette[pixel]
a = pixel & 0x1
b = pixel & 0x2
if a:
bp1 += 2**(7 - x)
if b:
bp2 += 2**(7 - x)
data.extend((bp1, bp2))
return bytes(data)
def planar2(tile, palette):
data = bytearray()
for y in range(8):
byte = 0
for x in range(4):
pixel = tile.pixelIndex(x, y)
if palette is not None:
pixel = palette[pixel]
pixel &= 0x3
byte = byte + (pixel << x * 2)
data.append(byte)
byte = 0
for x in range(4, 8):
pixel = tile.pixelIndex(x, y)
pixel &= 0x3
byte = byte + (pixel << x * 2)
data.append(byte)
return bytes(data)
def linear4(tile, palette):
data = bytearray()
for y in range(8):
bp1 = 0
bp2 = 0
for x in range(8):
pixel = tile.pixelIndex(x, y)
if palette is not None:
pixel = palette[pixel]
a = pixel & 0x1
b = pixel & 0x2
if a:
bp1 += 2**(7 - x)
if b:
bp2 += 2**(7 - x)
data.extend((bp1, bp2))
for y in range(8):
bp3 = 0
bp4 = 0
for x in range(8):
pixel = tile.pixelIndex(x, y)
a = pixel & 0x4
b = pixel & 0x8
if a:
bp3 += 2**(7-x)
if b:
bp4 += 2**(7-x)
data.extend((bp3, bp4))
return bytes(data)
def padded4_2(tile, palette):
data = bytearray()
for y in range(8):
bp1 = 0
bp2 = 0
for x in range(8):
pixel = tile.pixelIndex(x, y)
if palette is not None:
pixel = palette[pixel]
a = pixel & 0x1
b = pixel & 0x2
if a:
bp1 += 2**(7 - x)
if b:
bp2 += 2**(7 - x)
data.extend((bp1, bp2))
for y in range(8):
bp3 = 0
bp4 = 0
data.extend((bp3, bp4))
return bytes(data)
def planar4(tile, palette):
data = bytearray()
for y in range(8):
byte = 0
for x in range(2):
pixel = tile.pixelIndex(x, y)
if palette is not None:
pixel = palette[pixel]
pixel &= 0x7
byte = byte + (pixel << x * 4)
data.append(byte)
byte = 0
for x in range(2, 4):
pixel = tile.pixelIndex(x, y)
pixel &= 0x7
byte = byte + (pixel << x * 4)
data.append(byte)
byte = 0
for x in range(4, 6):
pixel = tile.pixelIndex(x, y)
pixel &= 0x7
byte = byte + (pixel << x * 4)
data.append(byte)
byte = 0
for x in range(6, 8):
pixel = tile.pixelIndex(x, y)
pixel &= 0x7
byte = byte + (pixel << x * 4)
data.append(byte)
return bytes(data)
# Add new formats to this dict as they are implemented.
formats = {
'linear1': linear1,
'linear2': linear2,
'planar2': planar2,
'nes2': planar2,
'gb2': linear2,
'snes2': linear2,
'gbc2': linear2,
'linear4': linear4,
'planar4': planar4,
'snes4': linear4,
'pce4': linear4,
'padded4_2': padded4_2
}
def main():
app = QGuiApplication(sys.argv)
if len(sys.argv) < 3 or sys.argv[2] not in formats.keys():
print(
'''Usage: porygon.py image format
image is an image file in PNG. The base filename is also used to find a
mapper in order to force palette modifications.
format is one of the supported formats:'''
)
for fmt in formats.keys():
print('* {}'.format(fmt))
sys.exit(1)
(image, fmt) = sys.argv[1:3]
(image_base, ext) = op.splitext(image)
output = '{}.bin'.format(image_base)
mapper = image_base + '.txt'
if not op.exists('output/'):
import os
os.mkdir('output')
if not output.startswith('output/'):
output = 'output/{}'.format(output)
bpp = int(fmt[-1])
print('Loading image...')
data = QImage(image)
data = data.convertToFormat(QImage.Format_Indexed8)
if data.colorCount() > 2**bpp:
raise ValueError('image has too many colors')
width = data.width()
height = data.height()
rows = int(height / 8)
columns = int(width / 8)
palette = {}
if op.exists(mapper):
print('Palette found. Loading...')
with open(mapper, mode='r') as f:
text = f.read().split('\n')
for line in text:
if line == '':
continue
a = line.split('=')
key = int(a[0])
value = int(a[1])
palette[key] = value
else:
print('No palette found.')
palette = None
print('Converting to {}'.format(fmt))
counter = 0
with open(output, mode='wb') as f:
for row in range(rows):
for column in range(columns):
tile = data.copy(column * 8, row * 8, 8, 8)
f.write(formats[fmt](tile, palette))
counter += 1
if __name__ == '__main__':
main()