Skip to content

Commit

Permalink
BTI: Implement mipmap support
Browse files Browse the repository at this point in the history
  • Loading branch information
LagoLunatic committed Nov 7, 2023
1 parent 96406ff commit e5a913c
Show file tree
Hide file tree
Showing 5 changed files with 169 additions and 37 deletions.
43 changes: 20 additions & 23 deletions asset_dumper.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def dump_all_textures_in_gcm(self, gcm: GCM, out_dir):
for _ in self.dump_all_textures_in_rarc(rarc, out_path, display_path_prefix=file_path):
continue
elif file_ext == ".bti":
out_path = os.path.join(out_dir, rel_dir, base_name + ".png")
out_path = os.path.join(out_dir, rel_dir, base_name)
bti = BTI(gcm.get_changed_file_data(file_path))
self.dump_texture(bti, out_path)
elif file_ext in [".bmd", ".bdl", ".bmt"]:
Expand Down Expand Up @@ -85,7 +85,7 @@ def dump_all_textures_in_rarc(self, rarc: RARC, out_dir, display_path_prefix=Non

try:
if file_ext == ".bti":
out_path = os.path.join(out_dir, rel_dir, base_name + ".png")
out_path = os.path.join(out_dir, rel_dir, base_name)
bti = rarc.get_file(file_entry.name, BTI)
self.dump_texture(bti, out_path)
elif file_ext in [".bmd", ".bdl", ".bmt"]:
Expand All @@ -112,7 +112,7 @@ def dump_all_textures_in_tex1(self, tex1: TEX1, out_dir):
if len(textures) == 0:
continue

images: list[Image.Image] = []
unique_image_count = 0
for i, texture in enumerate(textures):
is_duplicate = False
for prev_texture in textures[:i]:
Expand All @@ -122,34 +122,31 @@ def dump_all_textures_in_tex1(self, tex1: TEX1, out_dir):
break
if is_duplicate:
continue

image = texture.render()

if image in images:
# A duplicate not detected by is_visually_equal_to().
# For example, if one version uses C8 format with RGB565 palette and the other uses RGB565 format, they can be identical, but is_visually_equal_to() will not detect this.
continue

images.append(image)
unique_image_count += 1

has_different_duplicates = len(images) > 1
for i, image in enumerate(images):
has_different_duplicates = unique_image_count > 1
for i, texture in enumerate(textures):
dupe_tex_name = texture_name
if has_different_duplicates:
# If there's more than one unique version of this texture, append _dupe0 _dupe1 etc to all the images.
out_path = os.path.join(out_dir, texture_name + "_dupe%d.png" % i)
else:
out_path = os.path.join(out_dir, texture_name + ".png")
dupe_tex_name += ".dupe%d" % i

image.save(out_path)
out_path = os.path.join(out_dir, dupe_tex_name)

self.succeeded_file_count += 1
self.dump_texture(texture, out_path)

def dump_texture(self, bti: BTI, out_path):
out_dir = os.path.dirname(out_path)
if not os.path.isdir(out_dir):
os.makedirs(out_dir)
os.makedirs(out_dir, exist_ok=True)

image = bti.render()
image.save(out_path)
for i in range(bti.mipmap_count):
mip_out_path = out_path
if (bti.mipmap_count) > 1:
mip_out_path += ".mip%d" % i
mip_out_path += ".png"
# if os.path.isfile(mip_out_path):
# continue
image = bti.render_mipmap(i)
image.save(mip_out_path)

self.succeeded_file_count += 1
65 changes: 60 additions & 5 deletions gcft_ui/bti_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
("min_lod", 1),
("max_lod", 1),
("lod_bias", 2),
("mipmap_count", 1),
]

class BTITab(QWidget):
Expand All @@ -41,6 +42,8 @@ def __init__(self):

self.ui.export_bti.setDisabled(True)
self.ui.export_bti_image.setDisabled(True)
self.ui.bti_curr_mipmap.setDisabled(True)
self.ui.replace_bti_mipmap.setDisabled(True)

self.ui.bti_file_size.setText("")
self.ui.bti_resolution.setText("")
Expand All @@ -57,6 +60,8 @@ def __init__(self):
self.ui.import_bti_image.clicked.connect(self.import_bti_image)
self.ui.export_bti_image.clicked.connect(self.export_bti_image)
self.ui.import_bti_from_bnr.clicked.connect(self.import_bti_from_bnr)
self.ui.bti_curr_mipmap.currentIndexChanged.connect(self.reload_bti_image)
self.ui.replace_bti_mipmap.clicked.connect(self.replace_bti_mipmap_image)

for field_name, field_enum in BTI_ENUM_FIELDS:
widget_name = "bti_" + field_name
Expand Down Expand Up @@ -116,6 +121,13 @@ def import_bti_from_bnr(self):
file_type="BNR", file_filter="All files (*.*)"
)

def replace_bti_mipmap_image(self):
self.window().generic_do_gui_file_operation(
op_callback=self.replace_bti_mipmap_image_by_path,
is_opening=True, is_saving=False, is_folder=False,
file_type="image", file_filter="PNG Files (*.png)"
)


def import_bti_by_path(self, bti_path):
with open(bti_path, "rb") as f:
Expand All @@ -126,6 +138,8 @@ def import_bti_by_path(self, bti_path):
self.import_bti_by_data(data, bti_name)

def import_bti_by_data(self, data, bti_name):
self.ui.bti_curr_mipmap.setCurrentIndex(0)

prev_bti = self.bti
self.bti = BTI(data)

Expand Down Expand Up @@ -187,9 +201,15 @@ def import_bti_by_data(self, data, bti_name):

self.ui.export_bti.setDisabled(False)
self.ui.export_bti_image.setDisabled(False)
self.ui.replace_bti_mipmap.setDisabled(False)
self.ui.bti_curr_mipmap.setDisabled(False)

def reload_bti_image(self):
self.bti_image = self.bti.render()
selected_mipmap_index = self.ui.bti_curr_mipmap.currentIndex()
selected_mipmap_index = max(0, selected_mipmap_index)
selected_mipmap_index = min(self.bti.mipmap_count-1, selected_mipmap_index)

self.bti_image = self.bti.render_mipmap(selected_mipmap_index)

image_bytes = self.bti_image.tobytes('raw', 'BGRA')
qimage = QImage(image_bytes, self.bti_image.width, self.bti_image.height, QImage.Format_ARGB32)
Expand All @@ -210,6 +230,14 @@ def reload_bti_image(self):
self.ui.bti_image_label.setFixedWidth(self.bti_image.width)
self.ui.bti_image_label.setFixedHeight(self.bti_image.height)
self.ui.bti_image_label.show()

self.ui.bti_curr_mipmap.blockSignals(True)
self.ui.bti_curr_mipmap.clear()
for i in range(self.bti.mipmap_count):
_, _, mipmap_width, mipmap_height = self.bti.get_mipmap_offset_and_size(i)
self.ui.bti_curr_mipmap.addItem(f"{i}: {mipmap_width}x{mipmap_height}")
self.ui.bti_curr_mipmap.setCurrentIndex(selected_mipmap_index)
self.ui.bti_curr_mipmap.blockSignals(False)

def export_bti_by_path(self, bti_path):
self.bti.save_changes()
Expand Down Expand Up @@ -290,6 +318,26 @@ def import_bti_from_bnr_by_data(self, data, bti_name):

self.import_bti_by_data(data, bti_name)

def replace_bti_mipmap_image_by_path(self, image_path):
_, _, width, height = self.bti.get_mipmap_offset_and_size(self.ui.bti_curr_mipmap.currentIndex())
image = Image.open(image_path)
if image.width != width or image.height != height:
QMessageBox.warning(self,
"Invalid mipmap size",
"The image you selected has the wrong size for this mipmap.\n" +
"Each mipmap must be exactly half the size of the previous one.\n" +
f"Expected size: {width}x{height}\n" +
f"Instead got: {image.width}x{image.height}\n\n" +
"If you want to completely replace all mipmaps with an image of a different size, select \"Import Image\" instead."
)

self.bti.replace_mipmap(self.ui.bti_curr_mipmap.currentIndex(), image)

self.bti.save_changes()
print(self.bti.num_colors)

self.reload_bti_image()

def bti_header_field_changed(self):
for field_name, field_enum in BTI_ENUM_FIELDS:
widget_name = "bti_" + field_name
Expand Down Expand Up @@ -324,13 +372,20 @@ def bti_header_field_changed(self):
QMessageBox.warning(self, "Invalid value", "\"%s\" is not a valid decimal number." % new_str_value)
new_value = old_value

if new_value < 0:
QMessageBox.warning(self, "Invalid value", "Value cannot be negative.")
if field_name == "mipmap_count":
min_value = 1
max_value = self.bti.get_max_valid_mipmap_count()
else:
min_value = 0
max_value = max_value = (2**(byte_size*8)) - 1

if new_value < min_value:
QMessageBox.warning(self, "Invalid value", f"Value is too small (minimum value: 0x{min_value:X})")
new_value = old_value
if new_value >= 2**(byte_size*8):
if new_value > max_value:
QMessageBox.warning(
self, "Invalid value",
"Value is too large to fit in field %s (maximum value: 0x%X)" % (field_name, (2**(byte_size*8))-1)
"Value is too large to fit in field %s (maximum value: 0x%X)" % (field_name, max_value)
)
new_value = old_value

Expand Down
54 changes: 49 additions & 5 deletions gcft_ui/bti_tab.ui
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,17 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="replace_bti_mipmap">
<property name="text">
<string>Replace Mipmap</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="import_bti_from_bnr">
<property name="text">
<string>Import From GameCube Banner (.bnr)</string>
<string>Import From Banner (.bnr)</string>
</property>
</widget>
</item>
Expand All @@ -58,14 +65,17 @@
<item>
<widget class="QWidget" name="widget" native="true"/>
</item>
<item>
<widget class="QWidget" name="widget_2" native="true"/>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_8">
<layout class="QHBoxLayout" name="horizontalLayout_8" stretch="1,0">
<item>
<widget class="QWidget" name="bti_image_container" native="true">
<layout class="QVBoxLayout" name="verticalLayout_12">
<property name="margin">
<property name="margin" stdset="0">
<number>0</number>
</property>
<item>
Expand Down Expand Up @@ -145,8 +155,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>591</width>
<height>433</height>
<width>570</width>
<height>431</height>
</rect>
</property>
<layout class="QGridLayout" name="gridLayout">
Expand Down Expand Up @@ -393,6 +403,40 @@
</property>
</widget>
</item>
<item row="14" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Num Mipmaps</string>
</property>
</widget>
</item>
<item row="14" column="1">
<widget class="QLineEdit" name="bti_mipmap_count">
<property name="maximumSize">
<size>
<width>35</width>
<height>16777215</height>
</size>
</property>
</widget>
</item>
<item row="15" column="0">
<widget class="QLabel" name="label_16">
<property name="text">
<string>Selected Mipmap</string>
</property>
</widget>
</item>
<item row="15" column="1">
<widget class="QComboBox" name="bti_curr_mipmap">
<property name="minimumSize">
<size>
<width>80</width>
<height>0</height>
</size>
</property>
</widget>
</item>
</layout>
</item>
</layout>
Expand Down
42 changes: 39 additions & 3 deletions gcft_ui/uic/ui_bti_tab.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
################################################################################
## Form generated from reading UI file 'bti_tab.ui'
##
## Created by: Qt User Interface Compiler version 6.4.0
## Created by: Qt User Interface Compiler version 6.5.2
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################
Expand Down Expand Up @@ -39,6 +39,11 @@ def setupUi(self, BTITab):

self.horizontalLayout_5.addWidget(self.import_bti_image)

self.replace_bti_mipmap = QPushButton(BTITab)
self.replace_bti_mipmap.setObjectName(u"replace_bti_mipmap")

self.horizontalLayout_5.addWidget(self.replace_bti_mipmap)

self.import_bti_from_bnr = QPushButton(BTITab)
self.import_bti_from_bnr.setObjectName(u"import_bti_from_bnr")

Expand All @@ -64,6 +69,11 @@ def setupUi(self, BTITab):

self.horizontalLayout.addWidget(self.widget)

self.widget_2 = QWidget(BTITab)
self.widget_2.setObjectName(u"widget_2")

self.horizontalLayout.addWidget(self.widget_2)


self.verticalLayout.addLayout(self.horizontalLayout)

Expand Down Expand Up @@ -92,7 +102,7 @@ def setupUi(self, BTITab):
self.bti_image_scroll_area.setWidgetResizable(True)
self.scrollAreaWidgetContents = QWidget()
self.scrollAreaWidgetContents.setObjectName(u"scrollAreaWidgetContents")
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 591, 433))
self.scrollAreaWidgetContents.setGeometry(QRect(0, 0, 570, 431))
self.gridLayout = QGridLayout(self.scrollAreaWidgetContents)
self.gridLayout.setObjectName(u"gridLayout")
self.bti_image_label = QLabel(self.scrollAreaWidgetContents)
Expand Down Expand Up @@ -272,9 +282,32 @@ def setupUi(self, BTITab):

self.formLayout.setWidget(13, QFormLayout.FieldRole, self.bti_lod_bias)

self.label_14 = QLabel(BTITab)
self.label_14.setObjectName(u"label_14")

self.formLayout.setWidget(14, QFormLayout.LabelRole, self.label_14)

self.bti_mipmap_count = QLineEdit(BTITab)
self.bti_mipmap_count.setObjectName(u"bti_mipmap_count")
self.bti_mipmap_count.setMaximumSize(QSize(35, 16777215))

self.formLayout.setWidget(14, QFormLayout.FieldRole, self.bti_mipmap_count)

self.label_16 = QLabel(BTITab)
self.label_16.setObjectName(u"label_16")

self.formLayout.setWidget(15, QFormLayout.LabelRole, self.label_16)

self.bti_curr_mipmap = QComboBox(BTITab)
self.bti_curr_mipmap.setObjectName(u"bti_curr_mipmap")
self.bti_curr_mipmap.setMinimumSize(QSize(80, 0))

self.formLayout.setWidget(15, QFormLayout.FieldRole, self.bti_curr_mipmap)


self.horizontalLayout_8.addLayout(self.formLayout)

self.horizontalLayout_8.setStretch(0, 1)

self.verticalLayout.addLayout(self.horizontalLayout_8)

Expand All @@ -288,7 +321,8 @@ def retranslateUi(self, BTITab):
BTITab.setWindowTitle(QCoreApplication.translate("BTITab", u"Form", None))
self.import_bti.setText(QCoreApplication.translate("BTITab", u"Import BTI", None))
self.import_bti_image.setText(QCoreApplication.translate("BTITab", u"Import Image", None))
self.import_bti_from_bnr.setText(QCoreApplication.translate("BTITab", u"Import From GameCube Banner (.bnr)", None))
self.replace_bti_mipmap.setText(QCoreApplication.translate("BTITab", u"Replace Mipmap", None))
self.import_bti_from_bnr.setText(QCoreApplication.translate("BTITab", u"Import From Banner (.bnr)", None))
self.export_bti.setText(QCoreApplication.translate("BTITab", u"Export BTI", None))
self.export_bti_image.setText(QCoreApplication.translate("BTITab", u"Export Image", None))
self.bti_image_label.setText("")
Expand All @@ -310,5 +344,7 @@ def retranslateUi(self, BTITab):
self.label_8.setText(QCoreApplication.translate("BTITab", u"Min LOD", None))
self.label_9.setText(QCoreApplication.translate("BTITab", u"Max LOD", None))
self.label_10.setText(QCoreApplication.translate("BTITab", u"LOD Bias", None))
self.label_14.setText(QCoreApplication.translate("BTITab", u"Num Mipmaps", None))
self.label_16.setText(QCoreApplication.translate("BTITab", u"Selected Mipmap", None))
# retranslateUi

2 changes: 1 addition & 1 deletion gclib

0 comments on commit e5a913c

Please sign in to comment.