Exemple #1
0
def pilFromData(image_data):
    try:
        im = Image.open(StringIO(image_data))
        im.load()
    except IOError:
        #PIL couldn't open, so try to read with panda3d which supports DDS:
        im = None
        tex = textureFromData(image_data)
        if tex is not None:
            outdata = tex.getRamImageAs('RGB').getData()
            try:
                im = Image.fromstring('RGB', (tex.getXSize(), tex.getYSize()), outdata)
                im.load()
            except IOError:
                #Any problem with panda3d might generate an invalid image buffer, so don't convert this
                im = None
    return im
def pilFromData(image_data):
    try:
        im = Image.open(StringIO(image_data))
        im.load()
    except IOError:
        #PIL couldn't open, so try to read with panda3d which supports DDS:
        im = None
        tex = textureFromData(image_data)
        if tex is not None:
            outdata = tex.getRamImageAs('RGB').getData()
            try:
                im = Image.fromstring('RGB', (tex.getXSize(), tex.getYSize()), outdata)
                im.load()
            except IOError:
                #Any problem with panda3d might generate an invalid image buffer, so don't convert this
                im = None
    return im
Exemple #3
0
def getTexture(color=None, alpha=None, texture_cache=None, diffuseinit=None):

    unique_id = ""
    if color:
        unique_id += str(id(color.sampler.surface.image))
    if alpha:
        unique_id += '_' + str(id(alpha.sampler.surface.image))

    if texture_cache is not None:
        if unique_id in texture_cache:
            return texture_cache[unique_id]

    image_file = ""
    if alpha:
        im = pilFromData(alpha.sampler.surface.image.data)

        gray = None
        for i, band in enumerate(im.getbands()):
            if band == 'A':
                gray = im.split()[i]
        if gray is None:
            gray = ImageOps.grayscale(im)

        if color:
            newim = pilFromData(color.sampler.surface.image.data)
            if 'A' not in newim.getbands():
                newim = newim.convert('RGBA')
        else:
            if diffuseinit is None:
                diffuseinit = (0, 0, 0, 1)
            else:
                diffuseinit = tuple(int(v * 255) for v in diffuseinit)
            newim = Image.new('RGBA', im.size, diffuseinit)
        newim.putalpha(gray)
        newbuf = StringIO()
        newim.save(newbuf, 'PNG')
        image_data = newbuf.getvalue()
        image_file = 'whatever.png'
    else:
        image_file = posixpath.basename(color.sampler.surface.image.path)
        im = pilFromData(color.sampler.surface.image.data)
        if im:
            if 'A' in im.getbands():
                im = im.convert('RGBA')
            else:
                im = im.convert('RGB')
            newbuf = StringIO()
            im.save(newbuf, 'PNG')
            image_data = newbuf.getvalue()
        else:
            image_data = color.sampler.surface.image.data

    tex = textureFromData(image_data, image_file)

    if texture_cache is not None:
        texture_cache[unique_id] = tex
    return tex
def getTexture(color=None, alpha=None, texture_cache=None, diffuseinit=None):

    unique_id = ""
    if color:
        unique_id += str(id(color.sampler.surface.image))
    if alpha:
        unique_id += '_' + str(id(alpha.sampler.surface.image))

    if texture_cache is not None:
        if unique_id in texture_cache:
            return texture_cache[unique_id]
    
    image_file = ""
    if alpha:
        im = pilFromData(alpha.sampler.surface.image.data)
        
        gray = None
        for i, band in enumerate(im.getbands()):
            if band == 'A':
                gray = im.split()[i]
        if gray is None:
            gray = ImageOps.grayscale(im)
            
        if color:
            newim = pilFromData(color.sampler.surface.image.data)
            if 'A' not in newim.getbands():
                newim = newim.convert('RGBA')
        else:
            if diffuseinit is None:
                diffuseinit = (0,0,0,1)
            else:
                diffuseinit = tuple(int(v*255) for v in diffuseinit)
            newim = Image.new('RGBA', im.size, diffuseinit)
        newim.putalpha(gray)
        newbuf = StringIO()
        newim.save(newbuf, 'PNG')
        image_data = newbuf.getvalue()
        image_file = 'whatever.png'
    else:
        image_file = posixpath.basename(color.sampler.surface.image.path)
        im = pilFromData(color.sampler.surface.image.data)
        if im:
            if 'A' in im.getbands():
                im = im.convert('RGBA')
            else:
                im = im.convert('RGB')
            newbuf = StringIO()
            im.save(newbuf, 'PNG')
            image_data = newbuf.getvalue()
        else:
            image_data = color.sampler.surface.image.data
    
    tex = textureFromData(image_data, image_file)

    if texture_cache is not None:
        texture_cache[unique_id] = tex
    return tex
Exemple #5
0
def getScreenshot(p3dApp):

    p3dApp.taskMgr.step()
    p3dApp.taskMgr.step()
    pnmss = PNMImage()
    p3dApp.win.getScreenshot(pnmss)
    resulting_ss = StringStream()
    pnmss.write(resulting_ss, "screenshot.png")
    screenshot_buffer = resulting_ss.getData()
    pilimage = Image.open(StringIO(screenshot_buffer))
    pilimage.load()

    #pnmimage will sometimes output as palette mode for 8-bit png so convert
    pilimage = pilimage.convert('RGBA')
    return pilimage
def getScreenshot(p3dApp):

    p3dApp.taskMgr.step()
    p3dApp.taskMgr.step()
    pnmss = PNMImage()
    p3dApp.win.getScreenshot(pnmss)
    resulting_ss = StringStream()
    pnmss.write(resulting_ss, "screenshot.png")
    screenshot_buffer = resulting_ss.getData()
    pilimage = Image.open(StringIO(screenshot_buffer))
    pilimage.load()
    
    #pnmimage will sometimes output as palette mode for 8-bit png so convert
    pilimage = pilimage.convert('RGBA')
    return pilimage
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 makeAtlases(mesh):
    # get a mapping from path to actual image, since theoretically you could have
    # the same image file in multiple image nodes
    unique_images = {}
    image_scales = {}
    for cimg in mesh.images:
        path = cimg.path
        if path not in unique_images:
            unique_images[path] = cimg.pilimage
            image_scales[path] = (1,1)
    
    # get a mapping from texture coordinates to all of the images they get bound to
    tex2img = getTexcoordToImgMapping(mesh)
    
    # don't consider any texcoords that are used more than once with different images
    # could probably have an algorithm that takes this into account, but it would
    # require some complex groupings. any images referenced have to also not be
    # considered for atlasing, as well as any tex coords that reference them
    # also filter out any images that are >= 1024 in either dimension
    texs_to_delete = []
    imgs_to_delete = []
    for texset, imgpaths in tex2img.iteritems():
        
        valid_range = False
        if len(imgpaths) == 1:
            texarray = mesh.geometries[texset.geom_id] \
                        .primitives[texset.prim_index] \
                        .texcoordset[texset.texcoordset_index]
            
            width, height = unique_images[imgpaths[0]].size
            tile_x = int(numpy.ceil(numpy.max(texarray[:,0])))
            tile_y = int(numpy.ceil(numpy.max(texarray[:,1])))
            stretched_width = tile_x * width
            stretched_height = tile_y * height
            
            #allow tiling of texcoords if the final tiled image is <= MAX_TILING_DIMENSION
            if numpy.min(texarray) < 0.0:
                valid_range = False
            elif stretched_width > MAX_TILING_DIMENSION or stretched_height > MAX_TILING_DIMENSION:
                valid_range = False
            else:
                valid_range = True
        
            if valid_range:
                scale_x, scale_y = image_scales[imgpaths[0]]
                scale_x = max(scale_x, tile_x)
                scale_y = max(scale_y, tile_y)
                image_scales[imgpaths[0]] = (scale_x, scale_y)
        
        if len(imgpaths) > 1 or not valid_range:
            for imgpath in imgpaths:
                if imgpath not in imgs_to_delete:
                    imgs_to_delete.append(imgpath)
            texs_to_delete.append(texset)
    for imgpath, pilimg in unique_images.iteritems():
        if max(pilimg.size) > MAX_IMAGE_DIMENSION and imgpath not in imgs_to_delete:
            imgs_to_delete.append(imgpath)
    for imgpath in imgs_to_delete:
        for texset, imgpaths in tex2img.iteritems():
            if imgpaths[0] == imgpath and texset not in texs_to_delete:
                texs_to_delete.append(texset)
        del unique_images[imgpath]
    for texset in texs_to_delete:
        del tex2img[texset]
    
    # now make a mapping between images and the tex coords that have to be
    # updated if it is atlased
    img2texs = {}
    for imgpath in unique_images:
        img2texs[imgpath] = []
        for texset, imgpaths in tex2img.iteritems():
            if imgpaths[0] == imgpath:
                img2texs[imgpath].append(texset)
    
    for path, pilimg in unique_images.iteritems():
        tile_x, tile_y = image_scales[path]
        width, height = pilimg.size
        if tile_x > 1 or tile_y > 1:
            if 'A' in pilimg.getbands():
                imgformat = 'RGBA'
                initval = (0,0,0,255)
            else:
                imgformat = 'RGB'
                initval = (0,0,0)
            tiled_img = Image.new(imgformat, (width*tile_x, height*tile_y), initval)
            for x in range(tile_x):
                for y in range(tile_y):
                    tiled_img.paste(pilimg, (x*width,y*height))
            pilimg = tiled_img
            width, height = pilimg.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))))
        if (width, height) != pilimg.size:
            pilimg = pilimg.resize((width, height), Image.ANTIALIAS)
        
        unique_images[path] = pilimg
    
    group1, group2 = splitAlphas(unique_images)
    to_del = combinePacks(packImages(mesh, img2texs, group1, image_scales),
                packImages(mesh, img2texs, group2, image_scales))
    if to_del is not None:
        for geom, primindices in to_del.iteritems():
            for i in sorted(primindices, reverse=True):
                del geom.primitives[i]
def packImages(mesh, img2texs, unique_images, image_scales):
    #if there aren't at least two images left, nothing to do
    if len(unique_images) < 2:
        return
    
    #okay, now we can start packing!
    rp = RectPack(MAX_IMAGE_DIMENSION, MAX_IMAGE_DIMENSION)
    for path, pilimg in unique_images.iteritems():
        width, height = pilimg.size
        rp.addRectangle(path, width, height)
    success = rp.pack()

    if not success:
        if len(rp.rejects) == len(unique_images):
            #this means that nothing could be packed into the max size
            # if not a single image can be packed into the max size
            # then there's no point in continuing
            return
                
        group1 = dict(( (path, pilimg) for path, pilimg in unique_images.iteritems() if path in rp.rejects ))
        group2 = dict(( (path, pilimg) for path, pilimg in unique_images.iteritems() if path not in rp.rejects ))
        
        return combinePacks(packImages(mesh, img2texs, group1, image_scales),
                    packImages(mesh, img2texs, group2, image_scales))
    
    width = rp.width
    height = rp.height
    
    print "actually making atlas of size %dx%d with %d subimages referenced by %d texcoords" % \
        (width, height, len(unique_images), sum([len(img2texs[imgpath]) for imgpath in unique_images]))
    atlasimg = Image.new('RGBA', (width, height), (0,0,0,255))
    
    to_del = {}
    for path, pilimg in unique_images.iteritems():
        x,y,w,h = rp.getPlacement(path)
        atlasimg.paste(pilimg, (x,y,x+w,y+h))
        
        x,y,w,h,width,height = (float(i) for i in (x,y,w,h,width,height))
        
        for texset in img2texs[path]:
            geom = mesh.geometries[texset.geom_id]
            prim = geom.primitives[texset.prim_index]
            texarray = numpy.copy(prim.texcoordset[texset.texcoordset_index])
            tile_x, tile_y = (float(i) for i in image_scales[path])
            
            #this shrinks the texcoords to 0,1 range for a tiled image
            if tile_x > 1.0:
                texarray[:,0] = texarray[:,0] / tile_x
            if tile_y > 1.0:
                texarray[:,1] = texarray[:,1] / tile_y
            
            #this computes the coordinates of the lowest and highest texel
            # if the texcoords go outside that range, rescale so they are inside
            # suggestion by nvidia texture atlasing white paper
            minx, maxx = numpy.min(texarray[:,0]), numpy.max(texarray[:,0])
            miny, maxy = numpy.min(texarray[:,1]), numpy.max(texarray[:,1])
            lowest_x = 0.5 / w
            lowest_y = 0.5 / h
            highest_x = 1.0 - lowest_x
            highest_y = 1.0 - lowest_y
            if minx < lowest_x or maxx > highest_x:
                texarray[:,0] = texarray[:,0] * (highest_x - lowest_x) + lowest_x
            if miny < lowest_y or maxy > highest_y:
                texarray[:,1] = texarray[:,1] * (highest_y - lowest_y) + lowest_y

            #this rescales the texcoords to map to the new atlas location
            texarray[:,0] = texarray[:,0] * (w / width) + (x / (width-1))
            texarray[:,1] = texarray[:,1] * (h / height) + (1.0 - (y+h)/height)
            
            oldsources = prim.getInputList().getList()
            newsources = collada.source.InputList()
            for (offset, semantic, source, setid) in oldsources:
                if semantic == 'TEXCOORD' and (setid is None or int(setid) == texset.texcoordset_index):
                    orig_source = source
                    i=0
                    while source[1:] in geom.sourceById:
                        source = orig_source + '-atlas-' + str(i)
                        i += 1
                    new_tex_src = collada.source.FloatSource(source[1:], texarray, ('S', 'T'))
                    geom.sourceById[source[1:]] = new_tex_src
                newsources.addInput(offset, semantic, source, setid)
            
            if geom not in to_del:
                to_del[geom] = []
            to_del[geom].append(texset.prim_index)
            
            if type(prim) is collada.triangleset.TriangleSet:
                prim.index.shape = -1
                newprim = geom.createTriangleSet(prim.index, newsources, prim.material)
            elif type(prim) is collada.polylist.Polylist:
                prim.index.shape = -1
                prim.vcounts.shape = -1
                newprim = geom.createPolylist(prim.index, prim.vcounts, newsources, prim.material)
            elif type(prim) is collada.polygons.Polygons:
                prim.index.shape = -1
                newprim = geom.createPolygons(prim.index, newsources, prim.material)
            elif type(prim) is collada.lineset.LineSet:
                prim.index.shape = -1
                newprim = geom.createLineSet(prim.index, newsources, prim.material)
            else:
                raise Exception("Unknown primitive type")
            
            geom.primitives.append(newprim)
        
    imgs_deleted = [cimg for cimg in mesh.images if cimg.path in unique_images]
    mesh.images = [cimg for cimg in mesh.images if cimg.path not in unique_images]
    
    baseimgid = imgs_deleted[0].id + '-atlas'
    baseimgpath = './atlas'
    newimgid = baseimgid
    newimgpath = baseimgpath
    ct = 0
    while newimgid in mesh.images or newimgpath + '.png' in [cimg.path for cimg in mesh.images]:
        newimgid = baseimgid + '-' + str(ct)
        newimgpath = baseimgpath + '-' + str(ct)
        ct += 1

    newimgpath = newimgpath + '.png'
    newcimage = collada.material.CImage(newimgid, newimgpath, mesh)
    
    strbuf = StringIO()
    atlasimg.save(strbuf, 'PNG', optimize=True)
    newcimage._data = strbuf.getvalue()
    mesh.images.append(newcimage)
    
    for effect in mesh.effects:
        for param in effect.params:
            if type(param) is collada.material.Surface:
                if param.image in imgs_deleted:
                    param.image = newcimage
                    
    return to_del
Exemple #10
0
def makeAtlases(mesh):
    # get a mapping from path to actual image, since theoretically you could have
    # the same image file in multiple image nodes
    unique_images = {}
    image_scales = {}
    for cimg in mesh.images:
        path = cimg.path
        if path not in unique_images:
            unique_images[path] = cimg.pilimage
            image_scales[path] = (1, 1)

    # get a mapping from texture coordinates to all of the images they get bound to
    tex2img = getTexcoordToImgMapping(mesh)

    # don't consider any texcoords that are used more than once with different images
    # could probably have an algorithm that takes this into account, but it would
    # require some complex groupings. any images referenced have to also not be
    # considered for atlasing, as well as any tex coords that reference them
    # also filter out any images that are >= 1024 in either dimension
    texs_to_delete = []
    imgs_to_delete = []
    for texset, imgpaths in tex2img.iteritems():

        valid_range = False
        if len(imgpaths) == 1:
            texarray = mesh.geometries[texset.geom_id] \
                        .primitives[texset.prim_index] \
                        .texcoordset[texset.texcoordset_index]

            width, height = unique_images[imgpaths[0]].size
            tile_x = int(numpy.ceil(numpy.max(texarray[:, 0])))
            tile_y = int(numpy.ceil(numpy.max(texarray[:, 1])))
            stretched_width = tile_x * width
            stretched_height = tile_y * height

            #allow tiling of texcoords if the final tiled image is <= MAX_TILING_DIMENSION
            if numpy.min(texarray) < 0.0:
                valid_range = False
            elif stretched_width > MAX_TILING_DIMENSION or stretched_height > MAX_TILING_DIMENSION:
                valid_range = False
            else:
                valid_range = True

            if valid_range:
                scale_x, scale_y = image_scales[imgpaths[0]]
                scale_x = max(scale_x, tile_x)
                scale_y = max(scale_y, tile_y)
                image_scales[imgpaths[0]] = (scale_x, scale_y)

        if len(imgpaths) > 1 or not valid_range:
            for imgpath in imgpaths:
                if imgpath not in imgs_to_delete:
                    imgs_to_delete.append(imgpath)
            texs_to_delete.append(texset)
    for imgpath, pilimg in unique_images.iteritems():
        if max(pilimg.size
               ) > MAX_IMAGE_DIMENSION and imgpath not in imgs_to_delete:
            imgs_to_delete.append(imgpath)
    for imgpath in imgs_to_delete:
        for texset, imgpaths in tex2img.iteritems():
            if imgpaths[0] == imgpath and texset not in texs_to_delete:
                texs_to_delete.append(texset)
        del unique_images[imgpath]
    for texset in texs_to_delete:
        del tex2img[texset]

    # now make a mapping between images and the tex coords that have to be
    # updated if it is atlased
    img2texs = {}
    for imgpath in unique_images:
        img2texs[imgpath] = []
        for texset, imgpaths in tex2img.iteritems():
            if imgpaths[0] == imgpath:
                img2texs[imgpath].append(texset)

    for path, pilimg in unique_images.iteritems():
        tile_x, tile_y = image_scales[path]
        width, height = pilimg.size
        if tile_x > 1 or tile_y > 1:
            if 'A' in pilimg.getbands():
                imgformat = 'RGBA'
                initval = (0, 0, 0, 255)
            else:
                imgformat = 'RGB'
                initval = (0, 0, 0)
            tiled_img = Image.new(imgformat, (width * tile_x, height * tile_y),
                                  initval)
            for x in range(tile_x):
                for y in range(tile_y):
                    tiled_img.paste(pilimg, (x * width, y * height))
            pilimg = tiled_img
            width, height = pilimg.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))))
        if (width, height) != pilimg.size:
            pilimg = pilimg.resize((width, height), Image.ANTIALIAS)

        unique_images[path] = pilimg

    group1, group2 = splitAlphas(unique_images)
    to_del = combinePacks(packImages(mesh, img2texs, group1, image_scales),
                          packImages(mesh, img2texs, group2, image_scales))
    if to_del is not None:
        for geom, primindices in to_del.iteritems():
            for i in sorted(primindices, reverse=True):
                del geom.primitives[i]
Exemple #11
0
def packImages(mesh, img2texs, unique_images, image_scales):
    #if there aren't at least two images left, nothing to do
    if len(unique_images) < 2:
        return

    #okay, now we can start packing!
    rp = RectPack(MAX_IMAGE_DIMENSION, MAX_IMAGE_DIMENSION)
    for path, pilimg in unique_images.iteritems():
        width, height = pilimg.size
        rp.addRectangle(path, width, height)
    success = rp.pack()

    if not success:
        if len(rp.rejects) == len(unique_images):
            #this means that nothing could be packed into the max size
            # if not a single image can be packed into the max size
            # then there's no point in continuing
            return

        group1 = dict(((path, pilimg)
                       for path, pilimg in unique_images.iteritems()
                       if path in rp.rejects))
        group2 = dict(((path, pilimg)
                       for path, pilimg in unique_images.iteritems()
                       if path not in rp.rejects))

        return combinePacks(packImages(mesh, img2texs, group1, image_scales),
                            packImages(mesh, img2texs, group2, image_scales))

    width = rp.width
    height = rp.height

    print "actually making atlas of size %dx%d with %d subimages referenced by %d texcoords" % \
        (width, height, len(unique_images), sum([len(img2texs[imgpath]) for imgpath in unique_images]))
    atlasimg = Image.new('RGBA', (width, height), (0, 0, 0, 255))

    to_del = {}
    for path, pilimg in unique_images.iteritems():
        x, y, w, h = rp.getPlacement(path)
        atlasimg.paste(pilimg, (x, y, x + w, y + h))

        x, y, w, h, width, height = (float(i)
                                     for i in (x, y, w, h, width, height))

        for texset in img2texs[path]:
            geom = mesh.geometries[texset.geom_id]
            prim = geom.primitives[texset.prim_index]
            texarray = numpy.copy(prim.texcoordset[texset.texcoordset_index])
            tile_x, tile_y = (float(i) for i in image_scales[path])

            #this shrinks the texcoords to 0,1 range for a tiled image
            if tile_x > 1.0:
                texarray[:, 0] = texarray[:, 0] / tile_x
            if tile_y > 1.0:
                texarray[:, 1] = texarray[:, 1] / tile_y

            #this computes the coordinates of the lowest and highest texel
            # if the texcoords go outside that range, rescale so they are inside
            # suggestion by nvidia texture atlasing white paper
            minx, maxx = numpy.min(texarray[:, 0]), numpy.max(texarray[:, 0])
            miny, maxy = numpy.min(texarray[:, 1]), numpy.max(texarray[:, 1])
            lowest_x = 0.5 / w
            lowest_y = 0.5 / h
            highest_x = 1.0 - lowest_x
            highest_y = 1.0 - lowest_y
            if minx < lowest_x or maxx > highest_x:
                texarray[:,
                         0] = texarray[:,
                                       0] * (highest_x - lowest_x) + lowest_x
            if miny < lowest_y or maxy > highest_y:
                texarray[:,
                         1] = texarray[:,
                                       1] * (highest_y - lowest_y) + lowest_y

            #this rescales the texcoords to map to the new atlas location
            texarray[:, 0] = texarray[:, 0] * (w / width) + (x / (width - 1))
            texarray[:, 1] = texarray[:, 1] * (h / height) + (1.0 -
                                                              (y + h) / height)

            oldsources = prim.getInputList().getList()
            newsources = collada.source.InputList()
            for (offset, semantic, source, setid) in oldsources:
                if semantic == 'TEXCOORD' and (setid is None or int(setid)
                                               == texset.texcoordset_index):
                    orig_source = source
                    i = 0
                    while source[1:] in geom.sourceById:
                        source = orig_source + '-atlas-' + str(i)
                        i += 1
                    new_tex_src = collada.source.FloatSource(
                        source[1:], texarray, ('S', 'T'))
                    geom.sourceById[source[1:]] = new_tex_src
                newsources.addInput(offset, semantic, source, setid)

            if geom not in to_del:
                to_del[geom] = []
            to_del[geom].append(texset.prim_index)

            if type(prim) is collada.triangleset.TriangleSet:
                prim.index.shape = -1
                newprim = geom.createTriangleSet(prim.index, newsources,
                                                 prim.material)
            elif type(prim) is collada.polylist.Polylist:
                prim.index.shape = -1
                prim.vcounts.shape = -1
                newprim = geom.createPolylist(prim.index, prim.vcounts,
                                              newsources, prim.material)
            elif type(prim) is collada.polygons.Polygons:
                prim.index.shape = -1
                newprim = geom.createPolygons(prim.index, newsources,
                                              prim.material)
            elif type(prim) is collada.lineset.LineSet:
                prim.index.shape = -1
                newprim = geom.createLineSet(prim.index, newsources,
                                             prim.material)
            else:
                raise Exception("Unknown primitive type")

            geom.primitives.append(newprim)

    imgs_deleted = [cimg for cimg in mesh.images if cimg.path in unique_images]
    mesh.images = [
        cimg for cimg in mesh.images if cimg.path not in unique_images
    ]

    baseimgid = imgs_deleted[0].id + '-atlas'
    baseimgpath = './atlas'
    newimgid = baseimgid
    newimgpath = baseimgpath
    ct = 0
    while newimgid in mesh.images or newimgpath + '.png' in [
            cimg.path for cimg in mesh.images
    ]:
        newimgid = baseimgid + '-' + str(ct)
        newimgpath = baseimgpath + '-' + str(ct)
        ct += 1

    newimgpath = newimgpath + '.png'
    newcimage = collada.material.CImage(newimgid, newimgpath, mesh)

    strbuf = StringIO()
    atlasimg.save(strbuf, 'PNG', optimize=True)
    newcimage._data = strbuf.getvalue()
    mesh.images.append(newcimage)

    for effect in mesh.effects:
        for param in effect.params:
            if type(param) is collada.material.Surface:
                if param.image in imgs_deleted:
                    param.image = newcimage

    return to_del
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
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