def optimizeTextures(mesh):
    
    previous_images = []

    for cimg in mesh.images:
        previous_images.append(cimg.path)
        
        pilimg = cimg.pilimage
        
        #PIL doesn't support DDS, so if loading failed, try and load it as a DDS with panda3d
        if pilimg is None:
            imgdata = cimg.data
            
            #if we can't even load the image's data, can't convert
            if imgdata is None:
                print("Couldn't load image data", file=sys.stderr)
                continue
            
            try:
                from panda3d.core import Texture
                from panda3d.core import StringStream
                from panda3d.core import PNMImage
            except ImportError:
                #if panda3d isn't installed and PIL failed, can't convert
                print('Tried loading image with PIL and DDS and both failed', file=sys.stderr)
                continue
            
            t = Texture()
            success = t.readDds(StringStream(imgdata))
            if success == 0:
                #failed to load as DDS, so let's give up
                print('Tried loading image as DDS and failed', file=sys.stderr)
                continue

            #convert DDS to PNG
            outdata = t.getRamImageAs('RGB').getData()
            try:
                im = Image.fromstring('RGB', (t.getXSize(), t.getYSize()), outdata)
                im.load()
            except IOError:
                #Any problem with panda3d might generate an invalid image buffer, so don't convert this
                print('Problem loading DDS file with PIL', file=sys.stderr)
                continue
            
            pilimg = im
        
        if pilimg.format == 'JPEG':
            #PIL image is already in JPG format so don't convert
            continue
        
        if 'A' in pilimg.getbands():
            alpha = numpy.array(pilimg.split()[-1].getdata())
            if not numpy.any(alpha < 255):
                alpha = None
                #this means that none of the pixels are using alpha, so convert to RGB
                pilimg = pilimg.convert('RGB') 
        
        if 'A' in pilimg.getbands():
            #save textures with an alpha channel in PNG
            output_format = 'PNG'
            output_extension = '.png'
            output_options = {'optimize':True}
        else:
            if pilimg.format != 'RGB':
                pilimg = pilimg.convert("RGB")
            #otherwise save as JPEG since it gets 
            output_format = 'JPEG'
            output_extension = '.jpg'
            output_options = {'quality':95, 'optimize':True}
        
        if cimg.path.lower()[-len(output_extension):] != output_extension:
            dot = cimg.path.rfind('.')
            before_ext = cimg.path[0:dot] if dot != -1 else cimg.path
            while before_ext + output_extension in previous_images:
                before_ext = before_ext + '-x'
            cimg.path = before_ext + output_extension
            previous_images.append(cimg.path)
        
        outbuf = StringIO()
               
        try:
            pilimg.save(outbuf, output_format, **output_options)
        except IOError as ex:
            print(ex)

        cimg.data = outbuf.getvalue()
def getMipMaps(mesh):

    mipmaps = {}
    for effect in mesh.effects:
        for prop in effect.supported:
            propval = getattr(effect, prop)
            if isinstance(propval, collada.material.Map):
                image_name = propval.sampler.surface.image.path
                image_data = propval.sampler.surface.image.data

                try:
                    im = Image.open(StringIO(image_data))
                    im.load()
                except IOError:
                    from panda3d.core import Texture
                    from panda3d.core import StringStream
                    from panda3d.core import PNMImage

                    #PIL failed, so lets try DDS reader with panda3d
                    t = Texture(image_name)
                    success = t.readDds(StringStream(image_data))
                    if success == 0:
                        raise FilterException("Failed to read image file %s" %
                                              image_name)

                    #convert DDS to PNG
                    outdata = t.getRamImageAs('RGBA').getData()
                    try:
                        im = Image.fromstring('RGBA',
                                              (t.getXSize(), t.getYSize()),
                                              outdata)
                        im.load()
                    except IOError:
                        raise FilterException("Failed to read image file %s" %
                                              image_name)

                #Keep JPG in same format since JPG->PNG is pretty bad
                if im.format == 'JPEG':
                    output_format = 'JPEG'
                    output_extension = 'jpg'
                    output_options = {'quality': 95, 'optimize': True}
                else:
                    output_format = 'PNG'
                    output_extension = 'png'
                    output_options = {'optimize': True}

                #store a copy to the original image so we can resize from it directly each time
                orig_im = im

                width, height = im.size

                #round down to power of 2
                width = int(math.pow(2, int(math.log(width, 2))))
                height = int(math.pow(2, int(math.log(height, 2))))

                pil_images = []

                while True:
                    im = orig_im.resize((width, height), Image.ANTIALIAS)
                    pil_images.insert(0, im)
                    if width == 1 and height == 1:
                        break
                    width = max(width / 2, 1)
                    height = max(height / 2, 1)

                tar_buf = StringIO()
                tar = tarfile.TarFile(fileobj=tar_buf, mode='w')

                cur_offset = 0
                byte_ranges = []
                for i, pil_img in enumerate(pil_images):
                    buf = StringIO()
                    pil_img.save(buf, output_format, **output_options)
                    file_len = buf.tell()
                    cur_name = '%dx%d.%s' % (pil_img.size[0], pil_img.size[1],
                                             output_extension)
                    tar_info = tarfile.TarInfo(name=cur_name)
                    tar_info.size = file_len
                    buf.seek(0)
                    tar.addfile(tarinfo=tar_info, fileobj=buf)

                    #tar files have a 512 byte header
                    cur_offset += 512
                    file_start = cur_offset

                    byte_ranges.append({
                        'offset': file_start,
                        'length': file_len,
                        'width': pil_img.size[0],
                        'height': pil_img.size[1]
                    })

                    #file lengths are rounded up to nearest 512 multiple
                    file_len = 512 * ((file_len + 512 - 1) / 512)
                    cur_offset += file_len

                tar.close()

                mipmaps[propval.sampler.surface.image.path] = (
                    tar_buf.getvalue(), byte_ranges)
    return mipmaps
Exemple #3
0
class Panda3dCameraSensor(object):
    def __init__(self,
                 base,
                 color=True,
                 depth=False,
                 size=None,
                 near_far=None,
                 hfov=None,
                 title=None):
        if size is None:
            size = (640, 480)
        if near_far is None:
            near_far = (0.01, 10000.0)
        if hfov is None:
            hfov = 60
        winprops = WindowProperties.size(*size)
        winprops.setTitle(title or 'Camera Sensor')
        fbprops = FrameBufferProperties()
        # Request 8 RGB bits, 8 alpha bits, and a depth buffer.
        fbprops.setRgbColor(True)
        fbprops.setRgbaBits(8, 8, 8, 8)
        fbprops.setDepthBits(24)
        self.graphics_engine = GraphicsEngine(base.pipe)

        window_type = base.config.GetString('window-type', 'onscreen')
        flags = GraphicsPipe.BFFbPropsOptional
        if window_type == 'onscreen':
            flags = flags | GraphicsPipe.BFRequireWindow
        elif window_type == 'offscreen':
            flags = flags | GraphicsPipe.BFRefuseWindow

        self.buffer = self.graphics_engine.makeOutput(base.pipe,
                                                      "camera sensor buffer",
                                                      -100, fbprops, winprops,
                                                      flags)

        if not color and not depth:
            raise ValueError("At least one of color or depth should be True")
        if color:
            self.color_tex = Texture("color_texture")
            self.buffer.addRenderTexture(self.color_tex,
                                         GraphicsOutput.RTMCopyRam,
                                         GraphicsOutput.RTPColor)
        else:
            self.color_tex = None
        if depth:
            self.depth_tex = Texture("depth_texture")
            self.buffer.addRenderTexture(self.depth_tex,
                                         GraphicsOutput.RTMCopyRam,
                                         GraphicsOutput.RTPDepth)
        else:
            self.depth_tex = None

        self.cam = base.makeCamera(self.buffer,
                                   scene=base.render,
                                   camName='camera_sensor')
        self.lens = self.cam.node().getLens()
        self.lens.setFov(hfov)
        self.lens.setFilmSize(
            *size)  # this also defines the units of the focal length
        self.lens.setNearFar(*near_far)

    def observe(self):
        for _ in range(self.graphics_engine.getNumWindows()):
            self.graphics_engine.renderFrame()
        self.graphics_engine.syncFrame()

        images = []

        if self.color_tex:
            data = self.color_tex.getRamImageAs('RGBA')
            if sys.version_info < (3, 0):
                data = data.get_data()
            image = np.frombuffer(data, np.uint8)
            image.shape = (self.color_tex.getYSize(),
                           self.color_tex.getXSize(),
                           self.color_tex.getNumComponents())
            image = np.flipud(image)
            image = image[
                ..., :
                -1]  # remove alpha channel; if alpha values are needed, set alpha bits to 8
            images.append(image)

        if self.depth_tex:
            depth_data = self.depth_tex.getRamImage()
            if sys.version_info < (3, 0):
                depth_data = depth_data.get_data()
            depth_image_size = self.depth_tex.getYSize(
            ) * self.depth_tex.getXSize() * self.depth_tex.getNumComponents()
            if len(depth_data) == 2 * depth_image_size:
                dtype = np.float16
            elif len(depth_data) == 3 * depth_image_size:
                dtype = np.float24
            elif len(depth_data) == 4 * depth_image_size:
                dtype = np.float32
            else:
                raise ValueError(
                    "Depth data has %d bytes but the size of the depth image is %d"
                    % (len(depth_data), depth_image_size))
            depth_image = np.frombuffer(depth_data, dtype)
            depth_image.shape = (self.depth_tex.getYSize(),
                                 self.depth_tex.getXSize(),
                                 self.depth_tex.getNumComponents())
            depth_image = np.flipud(depth_image)
            depth_image = depth_image.astype(
                np.float32, copy=False)  # copy only if necessary
            images.append(depth_image)

        return tuple(images)
def getMipMaps(mesh):
    
    mipmaps = {}
    for effect in mesh.effects:
        for prop in effect.supported:
            propval = getattr(effect, prop)
            if isinstance(propval, collada.material.Map):
                image_name = propval.sampler.surface.image.path
                image_data = propval.sampler.surface.image.data

                try:
                    im = Image.open(StringIO(image_data))
                    im.load()
                except IOError:
                    from panda3d.core import Texture
                    from panda3d.core import StringStream
                    from panda3d.core import PNMImage
                    
                    #PIL failed, so lets try DDS reader with panda3d
                    t = Texture(image_name)
                    success = t.readDds(StringStream(image_data))
                    if success == 0:
                        raise FilterException("Failed to read image file %s" % image_name)
        
                    #convert DDS to PNG
                    outdata = t.getRamImageAs('RGBA').getData()
                    try:
                        im = Image.fromstring('RGBA', (t.getXSize(), t.getYSize()), outdata)
                        im.load()
                    except IOError:
                        raise FilterException("Failed to read image file %s" % image_name)
                    
                #Keep JPG in same format since JPG->PNG is pretty bad
                if im.format == 'JPEG':
                    output_format = 'JPEG'
                    output_extension = 'jpg'
                    output_options = {'quality': 95, 'optimize':True}
                else:
                    output_format = 'PNG'
                    output_extension = 'png'
                    output_options = {'optimize':True}
                    
                #store a copy to the original image so we can resize from it directly each time
                orig_im = im
                
                width, height = im.size
                
                #round down to power of 2
                width = int(math.pow(2, int(math.log(width, 2))))
                height = int(math.pow(2, int(math.log(height, 2))))

                pil_images = []

                while True:
                    im = orig_im.resize((width, height), Image.ANTIALIAS)
                    pil_images.insert(0, im)
                    if width == 1 and height == 1:
                        break
                    width = max(width / 2, 1)
                    height = max(height / 2, 1)

                tar_buf = StringIO()
                tar = tarfile.TarFile(fileobj=tar_buf, mode='w')
              
                cur_offset = 0
                byte_ranges = []
                for i, pil_img in enumerate(pil_images):
                    buf = StringIO()
                    pil_img.save(buf, output_format, **output_options)
                    file_len = buf.tell()
                    cur_name = '%dx%d.%s' % (pil_img.size[0], pil_img.size[1], output_extension)
                    tar_info = tarfile.TarInfo(name=cur_name)
                    tar_info.size=file_len
                    buf.seek(0)
                    tar.addfile(tarinfo=tar_info, fileobj=buf)
                    
                    #tar files have a 512 byte header
                    cur_offset += 512
                    file_start = cur_offset
                    
                    byte_ranges.append({'offset':file_start,
                                        'length':file_len,
                                        'width':pil_img.size[0],
                                        'height':pil_img.size[1]})
                    
                    #file lengths are rounded up to nearest 512 multiple
                    file_len = 512 * ((file_len + 512 - 1) / 512)
                    cur_offset += file_len
                
                tar.close()
                
                mipmaps[propval.sampler.surface.image.path] = (tar_buf.getvalue(), byte_ranges)
    return mipmaps