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