A non-power-of-2, pixel centric texture loader for directGui

This is a small class to load textures for directGui objects that uses textures that are non-power-of-2. (If you use the default loader for textures that are non-powers-of-2 ie., 53x53, it will upsample/downsample your image before loading, making it blurry.) It also scales the frame such that the original pixel sizes are preserved on screen for pixel centric people like me.


a = clZCard("/c/pandastuff/bear.jpg", booAspectCorrect=True)
tf = DirectFrame(geom = a.GetNPCard())
from pandac.PandaModules import CardMaker, NodePath, PNMImage, PNMImageHeader, Texture, Point2, TextureStage
from math import log, ceil

class clZCard:
	def __init__(self, filename, booAspectCorrect=False):
		self.node = NodePath("CardElement")
		origImage = PNMImage()
		assert origImage.read(filename)
		self.oldsize_x = origImage.getXSize()
		self.oldsize_y = origImage.getYSize()
		self.newsize_x = self.ScaleToNextPower2(self.oldsize_x)
		self.newsize_y = self.ScaleToNextPower2(self.oldsize_y)
		newImage = PNMImage(self.newsize_x, self.newsize_y)
		newImage.copySubImage(origImage, 0, 0, 0, 0)
		size =1.0
		card = CardMaker('testname')
		self.frameSizeX = 2.*self.oldsize_x/self.GetScreenResX()
		self.frameSizeY = 2.*self.oldsize_y/self.GetScreenResY()
		if booAspectCorrect:
			self.frameSizeX = self.frameSizeX*(self.GetScreenResX()/self.GetScreenResY())
		card.setFrame(self.frameSizeX/2.,  -self.frameSizeX/2., -self.frameSizeY/2., self.frameSizeY/2.)
		self.fU = 1.*self.oldsize_x/self.newsize_x
		self.fV = 1.*self.oldsize_y/self.newsize_y
		card.setUvRange(Point2(self.fU, 0), Point2(0, self.fV))
		self.npCard = self.node.attachNewNode(card.generate())
		self.npCard.setTexOffset(TextureStage.getDefault(), 0, 1-self.fV)

	def __str__(self):
		astr1 =  "Img original size: "  +str(self.oldsize_x)  + ' ' +str(self.oldsize_y) + '\n'
		astr2 = "New size: " + str(self.newsize_x) + ' ' + str(self.newsize_y) + '\n'
		astr3 = "Framesize: %.3f  %.3f" % (self.frameSizeX, self.frameSizeY)
		return astr1+astr2+astr3
	def GetNPCard(self):
		return self.npCard
	def GetScreenResX(self):
		return 1.*base.win.getXSize()
	def GetScreenResY(self):
		return 1*base.win.getYSize()
	def ScaleToNextPower2(self, aInt):
		return 2**int(ceil(log(aInt)/log(2.)))

Thanks for this code snippet! In case anyone’s interested, I modified it to generate cards appropriate for use in pixel2d, added support for alpha channels, and fixed some bugs:

mybutton = DirectButton(geom = makeGeom('button_ready.png'))
# Python modules
from math import log, ceil

# Panda3D modules
from panda3d.core import CardMaker
from panda3d.core import NodePath
from panda3d.core import PNMImage
from panda3d.core import Texture
from panda3d.core import Point2
from panda3d.core import TextureStage
from panda3d.core import TransparencyAttrib

cardMaker = CardMaker('CardMaker')

def nextPowOf2(n):
    return 2**int(ceil(log(n, 2)))

def makeGeom(filename, booAspectCorrect=False):
    origImage = PNMImage()
    assert origImage.read(filename)
    oldWidth = origImage.getXSize()
    oldHeight = origImage.getYSize()
    newWidth = nextPowOf2(oldWidth)
    newHeight = nextPowOf2(oldHeight)

    newImage = PNMImage(newWidth, newHeight)
    if origImage.hasAlpha():
    newImage.copySubImage(origImage, 0, 0, 0, 0)
    tex = Texture()
    cardMaker.setFrame(0, oldWidth, 0, oldHeight)

    fU = float(oldWidth)/newWidth
    fV = float(oldHeight)/newHeight

    # cardMaker.setHasUvs(True)
    cardMaker.setUvRange(Point2(0, 0), Point2(fU, fV))

    npCard = NodePath(cardMaker.generate())
    npCard.setTexOffset(TextureStage.getDefault(), 0, 1-fV)
    if origImage.hasAlpha():
    return npCard