from direct.directnotify.DirectNotifyGlobal import directNotify from panda3d.core import PNMImage, Filename, PNMFileTypeRegistry, StringStream import struct class Icon: """ This class is used to create an icon for various platforms. """ notify = directNotify.newCategory("Icon") def __init__(self): self.images = {} def addImage(self, image): """ Adds an image to the icon. Returns False on failure, True on success. Only one image per size can be loaded, and the image size must be square. """ if not isinstance(image, PNMImage): fn = image if not isinstance(fn, Filename): fn = Filename.fromOsSpecific(fn) image = PNMImage() if not image.read(fn): Icon.notify.warning("Image '%s' could not be read" % fn.getBasename()) return False if image.getXSize() != image.getYSize(): Icon.notify.warning("Ignoring image without square size") return False self.images[image.getXSize()] = image return True def generateMissingImages(self): """ Generates image sizes that should be present but aren't by scaling from the next higher size. """ for required_size in (256, 128, 48, 32, 16): if required_size in self.images: continue sizes = sorted(self.images.keys()) if required_size * 2 in sizes: from_size = required_size * 2 else: from_size = 0 for from_size in sizes: if from_size > required_size: break if from_size > required_size: Icon.notify.warning("Generating %dx%d icon by scaling down %dx%d image" % (required_size, required_size, from_size, from_size)) image = PNMImage(required_size, required_size) image.setColorType(self.images[from_size].getColorType()) image.quickFilterFrom(self.images[from_size]) self.images[required_size] = image else: Icon.notify.warning("Cannot generate %dx%d icon; no higher resolution image available" % (required_size, required_size)) def _write_bitmap(self, fp, image, size, bpp): """ Writes the bitmap header and data of an .ico file. """ fp.write(struct.pack('> 3) & 3) for y in range(size): mask = 0 num_bits = 7 for x in range(size): a = image.get_alpha_val(x, size - y - 1) if a <= 1: mask |= (1 << num_bits) num_bits -= 1 if num_bits < 0: fp.write(struct.pack('> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) fp.write(b'\x00' * (andsize * size)) def makeICO(self, fn): """ Writes the images to a Windows ICO file. Returns True on success. """ if not isinstance(fn, Filename): fn = Filename.fromOsSpecific(fn) fn.setBinary() # ICO files only support resolutions up to 256x256. count = 0 for size in self.images: if size < 256: count += 1 if size <= 256: count += 1 dataoffs = 6 + count * 16 ico = open(fn, 'wb') ico.write(struct.pack('= 256: continue ico.write(struct.pack('> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) datasize = 40 + 256 * 4 + (xorsize + andsize) * size ico.write(struct.pack(' 256: continue elif size == 256: ico.write(b'\0\0') else: ico.write(struct.pack('> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) datasize = 40 + (xorsize + andsize) * size ico.write(struct.pack('I', len(pngdata))) icns.write(pngdata) elif size in icon_types: # If it has an alpha channel, we write out a mask too. if image.hasAlpha(): icns.write(mask_types[size]) icns.write(struct.pack('>I', size * size + 8)) for y in range(size): for x in range(size): icns.write(struct.pack('I', size * size * 4 + 8)) for y in range(size): for x in range(size): r, g, b = image.getXel(x, y) icns.write(struct.pack('>BBBB', 0, int(r * 255), int(g * 255), int(b * 255))) length = icns.tell() icns.seek(4) icns.write(struct.pack('>I', length)) icns.close() return True