Source code for direct.dist.icon

from direct.directnotify.DirectNotifyGlobal import *
from panda3d.core import PNMImage, Filename, PNMFileTypeRegistry, StringStream
import struct


[docs]class Icon: """ This class is used to create an icon for various platforms. """ notify = directNotify.newCategory("Icon")
[docs] def __init__(self): self.images = {}
[docs] 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
[docs] def getLargestSize(self): return max(self.images.keys())
[docs] 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)) from_image = self.images[from_size] image = PNMImage(required_size, required_size) image.setColorType(from_image.getColorType()) if from_image.hasAlpha(): image.addAlpha() image.quickFilterFrom(from_image) 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('<IiiHHIIiiII', 40, size, size * 2, 1, bpp, 0, 0, 0, 0, 0, 0)) # XOR mask if bpp == 24: # Align rows to 4-byte boundary rowalign = b'\0' * (-(size * 3) & 3) for y in range(size): for x in range(size): r, g, b = image.getXel(x, size - y - 1) fp.write(struct.pack('<BBB', int(b * 255), int(g * 255), int(r * 255))) fp.write(rowalign) elif bpp == 32: for y in range(size): for x in range(size): r, g, b, a = image.getXelA(x, size - y - 1) fp.write(struct.pack('<BBBB', int(b * 255), int(g * 255), int(r * 255), int(a * 255))) elif bpp == 8: # We'll have to generate a palette of 256 colors. hist = PNMImage.Histogram() image2 = PNMImage(image) if image2.hasAlpha(): image2.premultiplyAlpha() image2.removeAlpha() image2.quantize(256) image2.make_histogram(hist) colors = list(hist.get_pixels()) assert len(colors) <= 256 # Write the palette. i = 0 while i < 256 and i < len(colors): r, g, b, a = colors[i] fp.write(struct.pack('<BBBB', b, g, r, 0)) i += 1 if i < 256: # Fill the rest with zeroes. fp.write(b'\x00' * (4 * (256 - i))) # Write indices. Align rows to 4-byte boundary. rowalign = b'\0' * (-size & 3) for y in range(size): for x in range(size): pixel = image2.get_pixel(x, size - y - 1) index = colors.index(pixel) if index >= 256: # Find closest pixel instead. index = closest_indices[index - 256] fp.write(struct.pack('<B', index)) fp.write(rowalign) else: raise ValueError("Invalid bpp %d" % (bpp)) # Create an AND mask, aligned to 4-byte boundary if image.hasAlpha() and bpp <= 8: rowalign = b'\0' * (-((size + 7) >> 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('<B', mask)) mask = 0 num_bits = 7 if num_bits < 7: fp.write(struct.pack('<B', mask)) fp.write(rowalign) else: andsize = (size + 7) >> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) fp.write(b'\x00' * (andsize * size))
[docs] 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('<HHH', 0, 1, count)) # Write 8-bpp image headers for sizes under 256x256. for size, image in self.images.items(): if size >= 256: continue ico.write(struct.pack('<BB', size, size)) # Calculate row sizes xorsize = size if xorsize % 4 != 0: xorsize += 4 - (xorsize % 4) andsize = (size + 7) >> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) datasize = 40 + 256 * 4 + (xorsize + andsize) * size ico.write(struct.pack('<BBHHII', 0, 0, 1, 8, datasize, dataoffs)) dataoffs += datasize # Write 24/32-bpp image headers. for size, image in self.images.items(): if size > 256: continue elif size == 256: ico.write(b'\0\0') else: ico.write(struct.pack('<BB', size, size)) # Calculate the size so we can write the offset within the file. if image.hasAlpha(): bpp = 32 xorsize = size * 4 else: bpp = 24 xorsize = size * 3 + (-(size * 3) & 3) andsize = (size + 7) >> 3 if andsize % 4 != 0: andsize += 4 - (andsize % 4) datasize = 40 + (xorsize + andsize) * size ico.write(struct.pack('<BBHHII', 0, 0, 1, bpp, datasize, dataoffs)) dataoffs += datasize # Now write the actual icon bitmap data. for size, image in self.images.items(): if size < 256: self._write_bitmap(ico, image, size, 8) for size, image in self.images.items(): if size <= 256: bpp = 32 if image.hasAlpha() else 24 self._write_bitmap(ico, image, size, bpp) assert ico.tell() == dataoffs ico.close() return True
[docs] def makeICNS(self, fn): """ Writes the images to an Apple ICNS file. Returns True on success. """ if not isinstance(fn, Filename): fn = Filename.fromOsSpecific(fn) fn.setBinary() icns = open(fn, 'wb') icns.write(b'icns\0\0\0\0') icon_types = {16: b'is32', 32: b'il32', 48: b'ih32', 128: b'it32'} mask_types = {16: b's8mk', 32: b'l8mk', 48: b'h8mk', 128: b't8mk'} png_types = {256: b'ic08', 512: b'ic09', 1024: b'ic10'} pngtype = PNMFileTypeRegistry.getGlobalPtr().getTypeFromExtension("png") for size, image in sorted(self.images.items(), key=lambda item:item[0]): if size in png_types and pngtype is not None: stream = StringStream() image.write(stream, "", pngtype) pngdata = stream.data icns.write(png_types[size]) icns.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('<B', int(image.getAlpha(x, y) * 255))) icns.write(icon_types[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
[docs] def writeSize(self, required_size, fn): if not isinstance(fn, Filename): fn = Filename.fromOsSpecific(fn) fn.setBinary() fn.makeDir() if required_size in self.images: image = self.images[required_size] else: # Find the next size up. 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)) else: Icon.notify.warning("Generating %dx%d icon by scaling up %dx%d image" % (required_size, required_size, from_size, from_size)) from_image = self.images[from_size] image = PNMImage(required_size, required_size) image.setColorType(from_image.getColorType()) image.quickFilterFrom(from_image) if not image.write(fn): Icon.notify.error("Failed to write %dx%d to %s" % (required_size, required_size, fn))