Skip to content

Commit

Permalink
Update pvr2image.py
Browse files Browse the repository at this point in the history
  • Loading branch information
VincentNLOBJ authored Jul 22, 2024
1 parent b2d89ed commit 61830c6
Showing 1 changed file with 170 additions and 85 deletions.
255 changes: 170 additions & 85 deletions naomiLib_importer/pvr2image.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'''
MIT License
Copyright (c) 2023 VincentNL
Copyright (c) 2024 VincentNL
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand All @@ -24,19 +24,85 @@

import os
import math
import io
import struct
import numpy as np
import zlib

class decode:

debug=False

def __init__(self, files_lst,fmt,out_dir, flip):
def __init__(self, files_lst=None, fmt=None, out_dir=None, args_str=None):
self.files_lst = files_lst
self.out_dir = out_dir
self.fmt = fmt
self.flip = flip
self.flip = "" # Default value for flip
self.log = False # Default value for log flag
self.silent = False # Default value for silent flag
self.debug = False # Default value for debug flag

if len(files_lst)==0 or files_lst == '':
print('No file specified!')
return

if fmt is None:
self.fmt = "png"

if out_dir is None:
self.out_dir = os.path.abspath(os.path.dirname(files_lst[0]))
else:
self.out_dir = out_dir

# Determine out_dir
if args_str:
args = args_str.split() # Split the string into individual arguments

# Iterate through the arguments to find flip, log, and debug options
for arg in args:
if arg.startswith('-flip'):
# If -flip is found, extract the flip value
self.flip = arg[len('-flip'):]
elif arg == '-log':
self.log = True
elif arg == '-dbg':
self.debug = True
elif arg == '-silent':
self.silent = True


self.px_modes = {
0: 'ARGB1555',
1: 'RGB565',
2: 'ARGB4444',
3: 'YUV422',
4: 'BUMP',
5: 'RGB555',
6: 'YUV420',
7: 'ARGB8888',
8: 'PAL-4',
9: 'PAL-8',
10: 'AUTO',
}

self.tex_modes = {
1: 'Twiddled',
2: 'Twiddled + Mips',
3: 'Twiddled VQ',
4: 'Twiddled VQ + Mips',
5: 'Twiddled Pal4 (16-col)',
6: 'Twiddled Pal4 + Mips (16-col)',
7: 'Twiddled Pal8 (256-col)',
8: 'Twiddled Pal8 + Mips (256-col)',
9: 'Rectangle',
10: 'Rectangle + Mips',
11: 'Stride',
12: 'Stride + Mips',
13: 'Twiddled Rectangle',
14: 'BMP',
15: 'BMP + Mips',
16: 'Twiddled SmallVQ',
17: 'Twiddled SmallVQ + Mips',
18: 'Twiddled Alias + Mips',
}

# remove companion .PVP/.PVR, filter the list
new_list = []
Expand All @@ -52,6 +118,11 @@ def __init__(self, files_lst,fmt,out_dir, flip):
# create Extracted\ACT folders
if self.debug: print(out_dir + '\ACT')

# create log file
if self.log:
with open(f'{out_dir}/pvr_log.txt', 'w') as l:
l.write('')

while current_file < selected_files:
if not files_lst: # If no files are selected
break
Expand All @@ -77,49 +148,48 @@ def __init__(self, files_lst,fmt,out_dir, flip):

current_file += 1


def read_col(self,px_format, color):

if px_format == 0: # ARGB1555
a = 0xff if ((color >> 15) & 1) else 0
r = (color >> (10 - 3)) & 0xf8
g = (color >> (5 - 3)) & 0xf8
b = (color << 3) & 0xf8
a = ((color >> 15) & 0x1) * 0xff
r = int(((color >> 10) & 0x1f) * 0xff / 0x1f)
g = int(((color >> 5) & 0x1f) * 0xff / 0x1f)
b = int((color & 0x1f) * 0xff / 0x1f)
return (r, g, b, a)

elif px_format == 1: # RGB565
a = 0xff
r = (color >> (11 - 3)) & (0x1f << 3)
g = (color >> (5 - 2)) & (0x3f << 2)
b = (color << 3) & (0x1f << 3)
r = int(((color >> 11) & 0x1f) * 0xff / 0x1f)
g = int(((color >> 5) & 0x3f) * 0xff / 0x3f)
b = int((color & 0x1f) * 0xff / 0x1f)
return (r, g, b, a)

elif px_format == 2: # ARGB4444
a = (color >> (12 - 4)) & 0xf0
r = (color >> (8 - 4)) & 0xf0
g = (color >> (4 - 4)) & 0xf0
b = (color << 4) & 0xf0
a = ((color >> 12) & 0xf)*0x11
r = ((color >> 8) & 0xf)*0x11
g = ((color >> 4) & 0xf)*0x11
b = (color & 0xf)*0x11
return (r, g, b, a)

elif px_format == 5: # RGB555
a = 0xFF
r = (color >> (10 - 3)) & 0xf8
g = (color >> (5 - 3)) & 0xf8
b = (color << 3) & 0xf8
r = int(((color >> 10) & 0x1f) * 0xff / 0x1f)
g = int(((color >> 5) & 0x1f) * 0xff / 0x1f)
b = int((color & 0x1f) * 0xff / 0x1f)
return (r, g, b, a)

elif px_format in [7]: # ARGB8888
a = (color >> 24) & 0xFF
r = (color >> 16) & 0xFF
g = (color >> 8) & 0xFF
b = (color >> 0) & 0xFF
b = color & 0xFF
return (r, g, b, a)

elif px_format in [14]: # RGBA8888
r = (color >> 24) & 0xFF
g = (color >> 16) & 0xFF
b = (color >> 8) & 0xFF
a = (color >> 0) & 0xFF
a = color & 0xFF
return (r, g, b, a)

elif px_format == 3:
Expand Down Expand Up @@ -217,10 +287,10 @@ def image_flip(self, data, w, h,cmode):
else:
pixels_len = 1

if 'v' in self.flip:
if self.flip and'v' in self.flip:
data = (np.flipud((np.array(data)).reshape(h, w, -1)).flatten()).reshape(-1, pixels_len).tolist()

if 'h' in self.flip:
if self.flip and 'h' in self.flip:
data = (np.fliplr((np.array(data)).reshape(h, w, -1)).flatten()).reshape(-1, pixels_len).tolist()

return data
Expand All @@ -237,7 +307,29 @@ def save_image(self,file_name,data,bits,w,h,cmode,palette):
elif self.fmt == 'tga':
self.save_tga(file_name,data,bits,w,h,cmode,palette )

print(fr"{self.out_dir}\{file_name[:-4]}.{self.fmt}")
if not self.silent:print(fr"{self.out_dir}\{file_name[:-4]}.{self.fmt}")

# Incomplete! Not supporting palettized images!
def save_tga(self, file_name,data,bits,w,h,cmode,palette=None):
# Define TGA header
tga_header = bytearray([0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, w & 255, (w >> 8) & 255,
h & 255, (h >> 8) & 255, 32, 0])

# TGA is not reversed by default
pixel_data = bytearray()

# Iterate over the flattened array and append the pixel data
for pixel in data:
# Assuming pixel is in BGRA format
pixel_data.extend([pixel[2], pixel[1], pixel[0], pixel[3]])

# Combine the header and pixel data
tga_data = tga_header + pixel_data

# Save the TGA file
with open(fr'{self.out_dir}\{file_name[:-4]}.tga', "wb") as tga_file:
tga_file.write(tga_data)


def save_bmp(self, file_name, data, bits, w, h, cmode, palette=None):
# Define BMP file header
Expand Down Expand Up @@ -328,8 +420,7 @@ def encode_data(image_data: list[list[Pixel]]) -> list[int]:
ret = []

for row in image_data:
ret.append(0) # Filter type None for non-indexed color
ret.extend(pixel for color in row for pixel in color)
ret.extend([0] + [pixel for color in row for pixel in color])

return ret

Expand Down Expand Up @@ -386,7 +477,7 @@ def palette_to_bytearray(palette):


# Compress image data using zlib with compression level 0
compressed_data = zlib.compress(image_data)
compressed_data = zlib.compress(image_data,level=1)

# Write PNG signature
signature = b'\x89PNG\r\n\x1a\n'
Expand Down Expand Up @@ -820,64 +911,56 @@ def decode_pvr(self, f, file_name, w, h, offset=None, px_format=None, tex_format
# save the image
self.save_image(file_name,data,8,w,h,cmode,palette)

def load_pvr(self,PVR_file, apply_palette, act_buffer, file_name):
if self.debug: px_modes = {
0: 'ARGB1555',
1: 'RGB565',
2: 'ARGB4444',
3: 'YUV422',
4: 'BUMP',
5: 'RGB555',
6: 'YUV420',
7: 'ARGB8888',
8: 'PAL-4',
9: 'PAL-8',
10: 'AUTO',
}
# Tex format
if self.debug: tex_modes = {
1: 'Twiddled',
2: 'Twiddled Mips',
3: 'Twiddled VQ',
4: 'Twiddled VQ Mips',
5: 'Twiddled Pal4 (16-col)',
6: 'Twiddled Pal4 + Mips (16-col)',
7: 'Twiddled Pal8 (256-col)',
8: 'Twiddled Pal8 + Mips (256-col)',
9: 'Rectangle',
10: 'Rectangle + Mips',
11: 'Stride',
12: 'Stride + Mips',
13: 'Twiddled Rectangle',
14: 'BMP',
15: 'BMP + Mips',
16: 'Twiddled SmallVQ',
17: 'Twiddled SmallVQ + Mips',
18: 'Twiddled Alias + Mips',
}
def load_pvr(self, PVR_file, apply_palette, act_buffer, file_name):
px_modes = self.px_modes
tex_modes = self.tex_modes

with open(PVR_file, 'rb') as f:
header_data = f.read()
# Wrap file content in a BytesIO object
f_buffer = io.BytesIO(f.read())

header_data = f_buffer.getvalue()
gbix_offset = header_data.find(b"GBIX")

if gbix_offset != -1:
f_buffer.seek(gbix_offset + 0x4)
gbix_size = int.from_bytes(f_buffer.read(4), byteorder='little')
if gbix_size == 0x8:
gbix_val1 = int.from_bytes(f_buffer.read(4), byteorder='little')
gbix_val2 = int.from_bytes(f_buffer.read(4), byteorder='little')
if self.debug:
print(hex(gbix_val1), hex(gbix_val2))
elif gbix_size == 0x4:
gbix_val1 = int.from_bytes(f_buffer.read(4), byteorder='little')
gbix_val2 = ''
else:
print('invalid or unsupported GBIX size:', gbix_size, file_name)
else:
if self.debug:
print('GBIX found at:', hex(gbix_offset))
gbix_val1 = ''
gbix_val2 = ''

offset = header_data.find(b"PVRT")
if offset != -1 or len(header_data) < 0x10:
f.seek(offset + 0x8)
f_buffer.seek(offset + 0x8)

# Pixel format
px_format = int.from_bytes(f.read(1), byteorder='little')
tex_format = int.from_bytes(f.read(1), byteorder='little')
px_format = int.from_bytes(f_buffer.read(1), byteorder='little')
tex_format = int.from_bytes(f_buffer.read(1), byteorder='little')

f.seek(f.tell() + 2)
f_buffer.seek(f_buffer.tell() + 2)

# Image size
w = int.from_bytes(f.read(2), byteorder='little')
h = int.from_bytes(f.read(2), byteorder='little')
offset = f.tell()
w = int.from_bytes(f_buffer.read(2), byteorder='little')
h = int.from_bytes(f_buffer.read(2), byteorder='little')
offset = f_buffer.tell()

if self.debug: print(PVR_file.split('/')[-1], 'size:', w, 'x', h, 'format:',
f'[{tex_format}] {tex_modes[tex_format]}', f'[{px_format}] {px_modes[px_format]}')
if self.debug:
print(PVR_file.split('/')[-1], 'size:', w, 'x', h, 'format:',
f'[{tex_format}] {tex_modes[tex_format]}', f'[{px_format}] {px_modes[px_format]}')

if tex_format in [2, 4, 6, 8, 10, 12, 15, 17, 18]:
# print('mip-maps!')

if tex_format in [2, 6, 8, 10, 15, 18]:
# Mips skip
pvr_dim = [4, 8, 16, 32, 64, 128, 256, 512, 1024]
Expand All @@ -891,23 +974,25 @@ def load_pvr(self,PVR_file, apply_palette, act_buffer, file_name):
mip_index = i - 1
break

# Skip mips for image data offset
mip_sum = (sum(mip_size[:mip_index]) * size_adjust[tex_format]) + (extra_mip[tex_format])

offset += mip_sum
# print(hex(offset))

self.decode_pvr(f, file_name, w, h, offset, px_format, tex_format, apply_palette, act_buffer)
self.decode_pvr(f_buffer, file_name, w, h, offset, px_format, tex_format, apply_palette, act_buffer)

if self.log:
log_content = (
f"Filename: {PVR_file}, size: {w}x{h}, format: {tex_modes[tex_format]}, "
f"mode: {px_modes[px_format]}"
f"{f', GBIX: {gbix_val1}' if gbix_val1 != '' else ', GBIX1: ---'}"
f"{f', GBIX2: {gbix_val2}' if gbix_val2 != '' else ', GBIX2: ---'}\n"
)

with open(f'{self.out_dir}/pvr_log.txt', 'a') as l:
l.write(log_content)
else:
print("'PVRT' header not found!")

#try:


#except:
# print(f'PVR data error! {PVR_file}')

def load_pvp(self,PVP_file, act_buffer, file_name):
try:
with open(PVP_file, 'rb') as f:
Expand Down Expand Up @@ -1008,4 +1093,4 @@ def yuv420_to_rgb(self,f, w, h, data):
g = int(max(0, min(255, round(y - 0.344136 * (u - 128) - 0.714136 * (v - 128)))))
b = int(max(0, min(255, round(y + 1.772 * (u - 128)))))
data.append((r, g, b))
return data
return data

0 comments on commit 61830c6

Please sign in to comment.