Skip to content

Instantly share code, notes, and snippets.

@darktohka
Last active December 25, 2020 21:09
Show Gist options
  • Select an option

  • Save darktohka/a8f01d45d07dc9e050aad944a8b18238 to your computer and use it in GitHub Desktop.

Select an option

Save darktohka/a8f01d45d07dc9e050aad944a8b18238 to your computer and use it in GitHub Desktop.

ONLY INSTALL THE TEXTURE POOL FILTER IF A JPG CONTENT PACK HAS BEEN DETECTED! This is why the "legacy-contenet-packs" config variable exists - it is NOT a config option, but a runtime variable that has to be set if a JPG file is ever loaded by the content pack system!

Panda3D filter support must be compiled in! Any Panda3D builds before 2020-07-25 will not work! This is the date filter support was mainlined. (https://github.com/panda3d/panda3d/commit/c4af56620b144627fa7c83491a6a9d484e27c7e1 must be applied otherwise.)

from panda3d.core import get_model_path, Filename, LoaderOptions, PNMImage, TexturePool, Texture, VirtualFileSystem
import os
class ContentPackFilter(object):
# The alpha map keeps track of whether our PNG files have an alpha channel or not.
alpha_map = {}
@staticmethod
def load_img(vfs, model_path, path):
"""
Tries to load an image from the VFS.
Returns a PNMImage if the image has been read, but None otherwise.
"""
filename = Filename(path)
# Resolve the filename using the VFS
if not vfs.resolve_filename(filename, model_path):
# The file could not be found within the VFS.
return None
# Open a read stream using the VFS
stream = vfs.open_read_file(filename, False)
if stream is None:
# The file could not be read.
return None
# Read the image using the stream!
img = PNMImage()
img.read(stream)
# Close the stream before we return the image.
vfs.close_read_file(stream)
if img.is_valid():
# A valid image has been read!
return img
@staticmethod
def resize_img_to(img, source):
"""
Resizes an image to the same size of the source image.
"""
if img.get_size() == source.get_size():
# The two images are already the same size
return img
# Resize image to new dimensions
resized = PNMImage(source.get_x_size(), source.get_y_size(), img.get_num_channels(), img.get_maxval(), img.get_type())
resized.gaussian_filter_from(1.0, img)
# Clear old image data
img.clear()
return resized
@staticmethod
def load_alpha_mix(vfs, model_path, jpg_path, png_path):
"""
This method combines a JPG's image data and a PNG's alpha data.
This is necessary because some content packs do not ship the RGB files.
Reading only the JPG files in this case would result in a texture without an alpha channel.
Since we do not ship RGB files anymore, we must extract the alpha channel manually from the PNG file.
"""
png = None
if png_path not in ContentPackFilter.alpha_map:
# Looks like our PNG has not been read before.
# Does it have an alpha channel?
png = ContentPackFilter.load_img(vfs, model_path, png_path)
ContentPackFilter.alpha_map[png_path] = png is not None and png.has_alpha()
if png is not None and not png.has_alpha():
# Our PNG has been read, but does not have an alpha channel.
# We won't need this PNG anymore, so let's clear it from memory.
png.clear()
if not ContentPackFilter.alpha_map[png_path]:
# This PNG does not have an alpha channel, so we can't load a mixed image.
return None
if png is None:
# Load our PNG file!
png = ContentPackFilter.load_img(vfs, model_path, png_path)
if png is None:
# Our PNG could not be read, we can't load a mixed image.
return None
# Load our JPG file!
jpg = ContentPackFilter.load_img(vfs, model_path, jpg_path)
if jpg is None:
# Our JPG file could not be read, we can't load a mixed image.
return None
# Convert the JPG to an alpha channel image
jpg.set_num_channels(4)
# Copy the alpha channel from the PNG to the JPG
png = ContentPackFilter.resize_img_to(png, jpg)
jpg.copy_channel(png, png.get_num_channels() - 1, jpg.get_num_channels() - 1)
# Clear the PNG data, as we don't need it anymore
png.clear()
# Create our mixed Texture, containing image data from the JPG and alpha data from the PNG
tex = Texture(os.path.basename(png_path))
tex.load(jpg)
# Clear the JPG data, as it has already been loaded into a texture.
jpg.clear()
return tex
@staticmethod
def pre_load(orig_filename, orig_alpha_filename,
primary_file_num_channels, alpha_file_channel,
read_mipmaps, options):
if orig_filename.get_extension().lower() != 'png':
# Not a PNG, we don't have to look for older JPG textures
return None
path = orig_filename.get_fullpath_wo_extension()
vfs = VirtualFileSystem.get_global_ptr()
model_path = get_model_path().get_value()
reg_path = None
# Older format textures can be both JPG or RGB, let's find the texture.
for ext in ['.jpg', '.rgb']:
ext_path = path + ext
if vfs.resolve_filename(ext_path, model_path):
# We've found the texture from the content pack!
reg_path = ext_path
break
if reg_path is None:
# This texture could not be found in any content packs.
# Leave it to the game engine to load the PNG texture.
return None
rgb_path = path + '_a.rgb'
tex = None
# Important options:
# We remove LF_report_errors, as we don't want the console to be spammed if we can't find a texture
# We add TF_no_filters, as we don't want to get caught in a filter deadlock
load_options = LoaderOptions(
options.get_flags() & (~LoaderOptions.LF_report_errors),
options.get_texture_flags() | (LoaderOptions.TF_no_filters)
)
if vfs.resolve_filename(rgb_path, model_path):
# We've got an RGB file too, let's load it as well.
tex = TexturePool.load_texture(
reg_path, rgb_path, primary_file_num_channels,
alpha_file_channel, read_mipmaps, load_options
)
if tex is None:
# We couldn't load the RGB file.
# Let's see if we can extract an alpha image somehow...
tex = ContentPackFilter.load_alpha_mix(vfs, model_path, reg_path, orig_filename.get_fullpath())
if tex is None:
# Let's just load the JPG itself.
tex = TexturePool.load_texture(
reg_path, primary_file_num_channels,
read_mipmaps, load_options
)
return tex
if ConfigVariableBool('legacy-content-packs', False):
from panda3d.core import TexturePool
from toontown.toonbase.ContentPackFilter import ContentPackFilter
notify.info('Enabling legacy content pack support...')
TexturePool.getGlobalPtr().registerFilter(ContentPackFilter)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment