image.py (3937B)
1 import os, sys, array, math 2 3 from io import BytesIO 4 5 from wptserve.utils import isomorphic_decode 6 7 import importlib 8 subresource = importlib.import_module("common.security-features.subresource.subresource") 9 10 class Image: 11 """This class partially implements the interface of the PIL.Image.Image. 12 One day in the future WPT might support the PIL module or another imaging 13 library, so this hacky BMP implementation will no longer be required. 14 """ 15 def __init__(self, width, height): 16 self.width = width 17 self.height = height 18 self.img = bytearray([0 for i in range(3 * width * height)]) 19 20 @staticmethod 21 def new(mode, size, color=0): 22 return Image(size[0], size[1]) 23 24 def _int_to_bytes(self, number): 25 packed_bytes = [0, 0, 0, 0] 26 for i in range(4): 27 packed_bytes[i] = number & 0xFF 28 number >>= 8 29 30 return packed_bytes 31 32 def putdata(self, color_data): 33 for y in range(self.height): 34 for x in range(self.width): 35 i = x + y * self.width 36 if i > len(color_data) - 1: 37 return 38 39 self.img[i * 3: i * 3 + 3] = color_data[i][::-1] 40 41 def save(self, f, type): 42 assert type == "BMP" 43 # 54 bytes of preambule + image color data. 44 filesize = 54 + 3 * self.width * self.height 45 # 14 bytes of header. 46 bmpfileheader = bytearray([ord('B'), ord('M')] + self._int_to_bytes(filesize) + 47 [0, 0, 0, 0, 54, 0, 0, 0]) 48 # 40 bytes of info. 49 bmpinfoheader = bytearray([40, 0, 0, 0] + 50 self._int_to_bytes(self.width) + 51 self._int_to_bytes(self.height) + 52 [1, 0, 24] + (25 * [0])) 53 54 padlength = (4 - (self.width * 3) % 4) % 4 55 bmppad = bytearray([0, 0, 0]) 56 padding = bmppad[0 : padlength] 57 58 f.write(bmpfileheader) 59 f.write(bmpinfoheader) 60 61 for i in range(self.height): 62 offset = self.width * (self.height - i - 1) * 3 63 f.write(self.img[offset : offset + 3 * self.width]) 64 f.write(padding) 65 66 def encode_string_as_bmp_image(string_data): 67 data_bytes = array.array("B", string_data.encode("utf-8")) 68 69 num_bytes = len(data_bytes) 70 71 # Encode data bytes to color data (RGB), one bit per channel. 72 # This is to avoid errors due to different color spaces used in decoding. 73 color_data = [] 74 for byte in data_bytes: 75 p = [int(x) * 255 for x in '{0:08b}'.format(byte)] 76 color_data.append((p[0], p[1], p[2])) 77 color_data.append((p[3], p[4], p[5])) 78 color_data.append((p[6], p[7], 0)) 79 80 # Render image. 81 num_pixels = len(color_data) 82 sqrt = int(math.ceil(math.sqrt(num_pixels))) 83 img = Image.new("RGB", (sqrt, sqrt), "black") 84 img.putdata(color_data) 85 86 # Flush image to string. 87 f = BytesIO() 88 img.save(f, "BMP") 89 f.seek(0) 90 91 return f.read() 92 93 def generate_payload(request, server_data): 94 data = (u'{"headers": %(headers)s}') % server_data 95 if b"id" in request.GET: 96 request.server.stash.put(request.GET[b"id"], data) 97 data = encode_string_as_bmp_image(data) 98 return data 99 100 def generate_report_headers_payload(request, server_data): 101 stashed_data = request.server.stash.take(request.GET[b"id"]) 102 return stashed_data 103 104 def main(request, response): 105 handler = lambda data: generate_payload(request, data) 106 content_type = b'image/bmp' 107 108 if b"report-headers" in request.GET: 109 handler = lambda data: generate_report_headers_payload(request, data) 110 content_type = b'application/json' 111 112 subresource.respond(request, 113 response, 114 payload_generator = handler, 115 content_type = content_type, 116 access_control_allow_origin = b"*")