not saving texture images on disc before loading in panda?

Hello.
We are using a custom 3d format for the game we are working on and the texture image data is stored in the 3d file itself, not as a separate image file. Right now what I do is I get the data from the 3d file, create a png file, save it to disc and load it in Panda with loadTexture() call.
My question is: can I create a Panda3d texture object from my RGBA data without saving it to a file first? This seems slow to me.

Certainly. What format is the data in already? Is it raw RGBA data or is it formatted PNG data? In either case, you can use the data to populate a PNMImage object, then load it directly to a texture, as illustrated on this page of the manual.

David

The texture section of the 3d format starts with 4 bytes for the x and y resolution (2 bytes each) and is followed with RGBA (4 byte) data for every pixel. So its file.read(xresolution * yresolution * 4) for the image data.
EDIT. Oops, I was wrong, its in BGRA order. is it still possible with the same method?

From what I understand you mean this:

PNMImage.read(data, ".img")

but isn’t ‘.img’ mode for RGB?

Perhaps you want to take advantage of texture->set_ram_image_as(), then. It can accept a buffer of BGRA data, but you’ll want to strip off the size prefix.

David

data = file_object.read(4* width* height)
texture = Texture()
texture.setRamImageAs(data, "BGRA")
texture.setRamImageAs(data_chunk, "BGRA")
AssertionError: image.size() == (size_t)(_component_width * format.size() * imgs
ize) at line 872 of c:\buildslave\dev_sdk_win32\build\panda3d\panda\src\gobj\tex
ture.cxx

I hope I understood what it does. Where do you specify the resolution?

texture = Texture()
texture.setup2dTexture(width, height, Texture.TUnsignedByte, FRgba)
texture.setRamImageAs(data, "BGRA")

OK. Wit this method I got 50x speed boost, that’s nice :slight_smile:

I’m just curious, is loading textures like this a hack? From what I understood the method is used to replace a texture data in the RAM with a new one. What is it usually used for?

Anyway, thank you.

This is a perfectly legitimate way to load a texture. This method can either replace an existing texture data or load the initial texture data.

David

OK

BTW, we are using psd image files for some GUI stuff and I’m using PIL library for that. I could try to use pilimageobject.save() and pass it as StringStream to PNMImage and see if it works, like you said here: [PIL in panda) but save() actually saves it to a file, which is slow and was the reason I changed my model loader above.

PIL.save() says it can accept any Python file-like object, including a StringIO object, which is kind of like a StringStream: it writes to a Python string instead of writing to a file. So there’s not necessarily a need to write to disk.

But this is all academic. That was just a suggested way to extract the data from your PIL image. Since you already have the raw, uncompressed data, you’re good. And avoiding the use of PNMImage is certainly going to be faster.

David

Oh, this is a different case. The model format indeed has the image data stored like that, but the GUI images are stored in PSD files, so i can’t do the same thing here (at least before we write our own PSD loader and not rely on PIL).
Or I didn’t understand what you meant?

EDIT: Hmm…

strio = StringIO()

pilobject.save(strio, 'PNG')
width = pilobject.size[0]
height = pilobject.size[1]

p = PNMImage()
p.read(StringStream(strio))
tex = Texture()
tex.setup2dTexture(width, height, Texture.TUnsignedByte, Texture.FRgba) 
tex.load(p)

Sure, that should work. Does it? For the record, you don’t need the setupTexture() call if you’re calling texture.load(), which infers all of the texture properties from the PNMImage.

You could also use tostring() to retrieve the raw RGBA contents of the image, and stuff that directly in the texture, avoiding PNMImage. Depends on whether speed is important to you or not.

David

Sorry for not being clear. No, it doesn’t work.

import direct.directbase.DirectStart
from pandac.PandaModules import *
import Image
from StringIO import *

pilobject = Image.open('test.psd')

strio = StringIO()

pilobject.save(strio, 'PNG')
width = pilobject.size[0]
height = pilobject.size[1]

p = PNMImage()
p.read(StringStream(strio))
tex = Texture()
#tex.setup2dTexture(width, height, Texture.TUnsignedByte, Texture.FRgba)
tex.load(p)

box = loader.loadModel('box')
box.setTexture(tex, 1)
box.reparentTo(render)

run()
  File "test.py", line 15, in <module>
    p.read(StringStream(strio))
TypeError: StringStream() argument 1 must be string or read-only buffer, not ins
tance

Heres an example file 2shared.com/photo/Z3Sk97m_/test.html

Yes, I need to squeeze every millisecond I can, I will try tostring().

EDIT: Error with this one too.

import direct.directbase.DirectStart
from pandac.PandaModules import *
import Image

pilobject = Image.open('test.psd')

width = pilobject.size[0]
height = pilobject.size[1]

data = pilobject.tostring()

texture = Texture()
texture.setup2dTexture(width, height, Texture.TUnsignedByte, Texture.FRgba) 
texture.setRamImageAs(data, "RGBA")

box = loader.loadModel('box')
box.setTexture(texture, 1)
box.reparentTo(render)

run()
Assertion failed: image.size() == (size_t)(_component_width * format.size() * im
gsize) at line 872 of c:\buildslave\dev_sdk_win32\build\panda3d\panda\src\gobj\t
exture.cxx
Traceback (most recent call last):
  File "421313.py", line 14, in <module>
    texture.setRamImageAs(data, "RGBA")
AssertionError: image.size() == (size_t)(_component_width * format.size() * imgs
ize) at line 872 of c:\buildslave\dev_sdk_win32\build\panda3d\panda\src\gobj\tex
ture.cxx

It means your image is not exactly the same format that you told Texture it is.

p.read(StringStream(strio))
TypeError: StringStream() argument 1 must be string or read-only buffer, not ins
tance

Right, you need to get the string out of the StringIO object first, you can’t pass the StringIO object directly to StringStream. Maybe more like this?

p.read(StringStream(strio.getvalue()))
Assertion failed: image.size() == (size_t)(_component_width * format.size() * imgsize

So this means the string you passed to setRamImageAs() was not the length width * height * 4. Make sure you understand what format tostring() is giving you, and confirm that the length of that string is consistent with what you expect.

David

Yes, this method works now.

Well, it’s returning a 4800 lenght string. The image is 40x40 resolution, so that’s (40*40 * 4) bytes (RGBA), which is 6400. I guessed that there is no alpha channel in this PSD, so I tried the “RGB” mode and it worked. I think there is a method to find out the number of channels in PIL, so I can get that info for Panda.
BTW, PIL couldn’t “identify” any multilayer PSD we had, guess well need to write our own PSD reader.

Anyway, thanks.

There is one last problem though. The images are vertically flipped. I can fix that in PIL, but I think telling Panda to read it in another order would be faster then telling PIL to rearrange the data.

If you’re going to render it, it’s probably more efficient to just flip the UV coordinates or something of the sort.

Yes I could just do

np.setTexScale(TextureStage.getDefault(), -1, 1)

if there is no faster way like loading the data differently from the string.

That would be faster than flipping the image in PIL.

pilobject = Image.open('test.psd')

width = pilobject.size[0]
height = pilobject.size[1]

data = pilobject.tostring()

texture = Texture()
texture.setup2dTexture(width, height, Texture.TUnsignedByte, Texture.FRgba)
texture.setRamImageAs(data, "RGBA") 
    texture.setRamImageAs(data, "RGBA")
AssertionError: image.size() == (size_t)(_component_width * format.size() * imgs
ize) at line 950 of c:\buildslave\dev_sdk_win32\build\panda3d\panda\src\gobj\tex
ture.cxx