"""Draws FileIcons.png, the file dialog's folder, file and refresh icons for the Glass testbed. Run it from anywhere: python Modules/Glass/Glass.Testbed/SampleMaterials/make_file_icons.py The icons are 24 pixels square, the height of a line of LiberationSans16 text with its default margins, and sit side by side with no border between them. They are white and grey so a material can tint them. """ import math import os import struct import zlib SIZE = 24 ICON_COUNT = 3 # Each pixel averages SUBSAMPLES squared samples, which is what smooths the curved edges. SUBSAMPLES = 4 OUTLINE_WIDTH = 1.0 def rounded_box_distance(x, y, width, height, radius): """Returns how far (x, y) is outside a rounded box, negative inside it.""" qx = abs(x - width / 2) - (width / 2 - radius) qy = abs(y - height / 2) - (height / 2 - radius) return math.hypot(max(qx, 0.0), max(qy, 0.0)) + min(max(qx, qy), 0.0) - radius def segment_distance(x, y, x0, y0, x1, y1): """Returns how far (x, y) is from the segment between the two end points.""" dx = x1 - x0 dy = y1 - y0 along = max(0.0, min(1.0, ((x - x0) * dx + (y - y0) * dy) / (dx * dx + dy * dy))) return math.hypot(x - (x0 + along * dx), y - (y0 + along * dy)) def outlined(distance, outline, fill): """Returns a shape from a function giving how far (x, y) is outside it, negative inside.""" def shape(x, y): edge = distance(x, y) if edge > 0.0: return None if edge > -OUTLINE_WIDTH: return outline return fill(x, y) return shape def folder_shape(): def distance(x, y): tab = rounded_box_distance(x - 2.0, y - 3.5, 9.0, 6.0, 1.5) body = rounded_box_distance(x - 2.0, y - 6.5, 20.0, 14.5, 2.0) return min(tab, body) def fill(x, y): # A little darker along the lip, so the folder reads as having a front. return 0.8 if 6.5 < y < 8.0 else 0.92 return outlined(distance, 0.35, fill) def file_shape(): cut = 5.0 left, top, width, height = 4.0, 2.0, 16.0, 20.0 fold_x = left + width - cut fold_y = top + cut def distance(x, y): box = rounded_box_distance(x - left, y - top, width, height, 1.5) corner = (x - y - (fold_x - top)) / math.sqrt(2.0) return max(box, corner) def fill(x, y): if x > fold_x and y < fold_y: return 0.7 if left + 3.0 <= x <= left + width - 3.0 and any(abs(y - line) < 0.6 for line in (11.0, 14.0, 17.0)): return 0.6 return 0.97 page = outlined(distance, 0.35, fill) def shape(x, y): value = page(x, y) if value is not None and x >= fold_x - 0.5 and y <= fold_y + 0.5: near_fold = min(segment_distance(x, y, fold_x, top, fold_x, fold_y), segment_distance(x, y, fold_x, fold_y, left + width, fold_y)) if near_fold < 0.5 and distance(x, y) < 0.0: return 0.35 return value return shape def refresh_shape(): center = 12.0 radius = 7.2 half_width = 1.3 gap_start = math.radians(-75.0) gap_end = math.radians(-15.0) head_half_width = 4.0 head_length = math.radians(34.0) def polar(angle, distance_from_center): return center + distance_from_center * math.cos(angle), center + distance_from_center * math.sin(angle) # The ring runs clockwise on screen from gap_end round to gap_start, and the head points on into the gap. head = [polar(gap_start, radius - head_half_width), polar(gap_start, radius + head_half_width), polar(gap_start + head_length, radius)] def side(x, y, first, second): return (second[0] - first[0]) * (y - first[1]) - (second[1] - first[1]) * (x - first[0]) def shape(x, y): signs = [side(x, y, head[i], head[(i + 1) % 3]) for i in range(3)] if all(sign >= 0 for sign in signs) or all(sign <= 0 for sign in signs): return 0.25 if abs(math.hypot(x - center, y - center) - radius) > half_width: return None angle = math.atan2(y - center, x - center) if gap_start < angle < gap_end: return None return 0.25 return shape def render(shapes): """Returns the rows of RGBA pixels for the icons laid side by side.""" pixels = [[(0, 0, 0, 0)] * (SIZE * len(shapes)) for _ in range(SIZE)] for index, shape in enumerate(shapes): for v in range(SIZE): for u in range(SIZE): red_green_blue = 0.0 alpha = 0.0 for sub_y in range(SUBSAMPLES): for sub_x in range(SUBSAMPLES): sample = shape(u + (sub_x + 0.5) / SUBSAMPLES, v + (sub_y + 0.5) / SUBSAMPLES) if sample is not None: red_green_blue += sample alpha += 1.0 if alpha > 0.0: value = int(round(red_green_blue / alpha * 255)) coverage = int(round(alpha / (SUBSAMPLES * SUBSAMPLES) * 255)) pixels[v][index * SIZE + u] = (value, value, value, coverage) return pixels def png_chunk(kind, data): body = kind + data return struct.pack(">I", len(data)) + body + struct.pack(">I", zlib.crc32(body) & 0xFFFFFFFF) def write_png(path, pixels): height = len(pixels) width = len(pixels[0]) rows = b"".join(b"\x00" + b"".join(bytes(pixel) for pixel in row) for row in pixels) header = struct.pack(">IIBBBBB", width, height, 8, 6, 0, 0, 0) with open(path, "wb") as output: output.write(b"\x89PNG\r\n\x1a\n") output.write(png_chunk(b"IHDR", header)) output.write(png_chunk(b"IDAT", zlib.compress(rows, 9))) output.write(png_chunk(b"IEND", b"")) def main(): pixels = render([folder_shape(), file_shape(), refresh_shape()]) write_png(os.path.join(os.path.dirname(os.path.abspath(__file__)), "FileIcons.png"), pixels) if __name__ == "__main__": main()