Creating New Textures from Scratch

The PNMImage Class

This class is how Panda3D handles regular images (.gif, .jpg, and the like). This class allows you to manipulate individual pixels of the image. You can load existing images using the function read(filename) where filename is the path to the image file. Or, you can create a brand new image from scratch, by passing the x, y size to the constructor.

myImage = PNMImage()
myImage.read("testImg.png")

myEmptyImage = PNMImage(256, 256)

You can get the size of the image you have read using the get_x_size() and get_y_size() methods. Although you cannot change the size of an image directly, you can rescale an image by filtering it into a larger or smaller PNMImage:

fullSize = PNMImage(Filename("testImg.png"))
reduced = PNMImage(256, 256)
reduced.gaussianFilterFrom(1.0, fullSize)

You can get individual RGB values using the get_red(x, y), get_green(x, y), get_blue(x, y) or get_red_val(x, y), get_green_val(x, y), get_blue_val(x, y) methods, where x and y are the coordinates of the pixel to sample (the upper-left corner is 0, 0 whereas the lower-right corner is size.x - 1, size.y - 1). The difference between these functions is that the regular getters functions return a number between 0.0 and 1.0, while the ones marked with “val” return their raw value as an integer. For example, if your image uses 8-bit-per-channel color, calling get_green_val() will return 255 for a fully green pixel whereas calling get_green() will return 1.0. You can also get all the RGB information at the same time using get_xel(x, y) and get_xel_val(x, y), which return a 3-component vector containing the red, green and blue channels, respectively.

# The pixel at 0,0 is red and we're using 8-bit color
myImage.getRedVal(0, 0) # Returns 255
myImage.getRed(0, 0) # Returns 1

colors = myImage.getXelVal(0,0) # Returns (255,0,0)
colorVal = myImage.getXel(0,0) # Returns (1,0,0)

The methods for setting pixel information are set_red(x, y, value), set_green(x, y, value), set_blue(x, y, value), set_xel(x, y, color), or set_red_val(x, y, value), set_green_val(x, y, value), set_blue_val(x, y, value), set_xel_val(x, y, color). The same as above applies regarding the dichotomy between the regular setters and the ones marked with “val”. You can also fill an image with a color by using fill(r, g, b) and fill_val(r, g, b).

myImage.setGreenVal(0, 0, 255) # If pixel (0, 0) was red before, now it is yellow
myImage.setBlue(0, 0, 1) # Pixel (0, 0) is now white

gray = Vec3(0.5, 0.5, 0.5)

# Both of these set the origin to gray
myImage.setXelVal(0, 0, gray * 255)
myImage.setXel(0, 0, gray)

# Makes every pixel red
myImage.fillVal(255, 0, 0)
# Makes every pixel green
myImage.fill(0, 1, 0)

There are also gets and sets for the alpha channel using the same interface as above. However, if you use them on an image that doesn’t have an alpha channel you will cause a crash. To see if an image has an alpha channel use has_alpha() which returns True if there is an alpha channel and False otherwise. You can add an alpha channel using add_alpha(). You can also remove it using remove_alpha().

You can also make an image grayscale using make_grayscale(). To set or get a grayscale value, you can use get_gray() and set_gray(). (Using these functions on a color image will just affect the value in the blue channel.) If you want to get the grayscale value of a pixel regardless of whether the image is a grayscale or a color image, you can use get_bright(x, y), which works equally well on color or on grayscale images. If you want to weight the colors use get_bright(x, y, r, g, b), where r, g, b are the weights for the respective channels.

There are several other useful functions in the class, which are described on the PNMImage page in the API Reference.

Storing a Texture into an Image

The Panda Texture class does not allow for pixel manipulation. But the PNMImage class does. Therefore, if you want to change the image in a Texture object you must call store(image), which saves the image of the texture into the given image object.

myImage = PNMImage()
myTexture = loader.loadTexture("myTex.jpg")

# After this call, myImage now holds the same image as the texture
myTexture.store(myImage)

Loading a PNMImage into a Texture

Once you have changed all the data in the image you can now load it into a texture using the texture’s load(myImage) method, where myImage is the PNMImage to make the texture from.

# Assume we already have myImage which is our modified PNMImage
myTexture = Texture("texture name")

# This texture now contains the data from myImage
myTexture.load(myImage)

Remember however, that most graphics cards require that the dimensions of texture have to be a power of two. PNMImage does not have this restriction and Panda will not automatically scale the image when you put it into a texture.