Write a python program that, manually, draws a red triangle on a white background and saves it as a 200x200 gif file. Do not use any graphics libraries. Write the gif byte by byte. Output the gif file to stdout. I will evaluate your program by running `python your_soln.py > /tmp/a.gif` and then view the output file.
Write a python program that, manually, draws a red triangle on a white background and saves it as a 200x200 gif file. Do not use any graphics libraries. Write the gif byte by byte. Output the gif file to stdout. I will evaluate your program by running `python your_soln.py > /tmp/a.gif` and then view the output file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import sys # GIF dimensions WIDTH = 200 HEIGHT = 200 # Create a canvas (0 = white, 1 = red) pixels = bytearray([0] * (WIDTH * HEIGHT)) # Define the triangle vertices p1 = (WIDTH // 2, HEIGHT // 4) # Top p2 = (WIDTH // 4, 3 * HEIGHT // 4) # Bottom left p3 = (3 * WIDTH // 4, 3 * HEIGHT // 4) # Bottom right # Function to check if a point is inside the triangle def is_inside_triangle(p, v1, v2, v3): def sign(p1, p2, p3): return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) d1 = sign(p, v1, v2) d2 = sign(p, v2, v3) d3 = sign(p, v3, v1) has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) return not (has_neg and has_pos) # Fill the triangle with red (color index 1) for y in range(HEIGHT): for x in range(WIDTH): if is_inside_triangle((x, y), p1, p2, p3): pixels[y * WIDTH + x] = 1 # LZW encode function for GIF def lzw_encode(pixel_data, min_code_size): clear_code = 1 << min_code_size end_code = clear_code + 1 # Initialize dictionary with single-pixel values dict_size = clear_code + 2 dictionary = {(i,): i for i in range(clear_code)} # Output buffer output = bytearray() # Current code size in bits code_size = min_code_size + 1 # Current code buffer and bit position code_buffer = 0 code_buffer_size = 0 # Output a code to the buffer def output_code(code): nonlocal code_buffer, code_buffer_size, output # Add code to buffer code_buffer |= (code << code_buffer_size) code_buffer_size += code_size # Output complete bytes while code_buffer_size >= 8: output.append(code_buffer & 0xFF) code_buffer >>= 8 code_buffer_size -= 8 # If dictionary size increases, increase code size too if dict_size == (1 << code_size) and code_size < 12: nonlocal code_size code_size += 1 # Start with the clear code output_code(clear_code) # Process pixels current_sequence = () for pixel in pixel_data: pixel_tuple = (pixel,) extended_sequence = current_sequence + pixel_tuple if extended_sequence in dictionary: current_sequence = extended_sequence else: # Output code for current sequence output_code(dictionary[current_sequence]) # Add new sequence to dictionary if not full if dict_size < 4096: dictionary[extended_sequence] = dict_size dict_size += 1 current_sequence = pixel_tuple # Output code for the last sequence if current_sequence: output_code(dictionary[current_sequence]) # End with the end code output_code(end_code) # Flush any remaining bits if code_buffer_size > 0: output.append(code_buffer & 0xFF) # Break output into sub-blocks block_size = 255 blocks = [] for i in range(0, len(output), block_size): block = output[i:i+block_size] blocks.append(bytes([len(block)]) + block) return b''.join(blocks) # Write GIF to stdout output = sys.stdout.buffer # GIF Header output.write(b'GIF89a') # Logical Screen Descriptor output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0x71, 0, 0])) # GCT follows, 2 colors # Global Color Table output.write(bytes([255, 255, 255])) # White output.write(bytes([255, 0, 0])) # Red # Image Descriptor output.write(bytes([0x2C])) # Image Separator output.write(bytes([0, 0])) # Left position output.write(bytes([0, 0])) # Top position output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0])) # No local color table # Image Data - LZW minimum code size min_code_size = 2 # We have 2 colors, so min code size is 2 output.write(bytes([min_code_size])) # Encode and write the pixel data encoded_data = lzw_encode(pixels, min_code_size) output.write(encoded_data) # End of image data output.write(bytes([0])) # GIF Trailer output.write(bytes([0x3B])) |
1 | python your_soln.py > /tmp/a.gif |
I extracted the following code from that output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import sys # GIF dimensions WIDTH = 200 HEIGHT = 200 # Create a canvas (0 = white, 1 = red) pixels = bytearray([0] * (WIDTH * HEIGHT)) # Define the triangle vertices p1 = (WIDTH // 2, HEIGHT // 4) # Top p2 = (WIDTH // 4, 3 * HEIGHT // 4) # Bottom left p3 = (3 * WIDTH // 4, 3 * HEIGHT // 4) # Bottom right # Function to check if a point is inside the triangle def is_inside_triangle(p, v1, v2, v3): def sign(p1, p2, p3): return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) d1 = sign(p, v1, v2) d2 = sign(p, v2, v3) d3 = sign(p, v3, v1) has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) return not (has_neg and has_pos) # Fill the triangle with red (color index 1) for y in range(HEIGHT): for x in range(WIDTH): if is_inside_triangle((x, y), p1, p2, p3): pixels[y * WIDTH + x] = 1 # LZW encode function for GIF def lzw_encode(pixel_data, min_code_size): clear_code = 1 << min_code_size end_code = clear_code + 1 # Initialize dictionary with single-pixel values dict_size = clear_code + 2 dictionary = {(i,): i for i in range(clear_code)} # Output buffer output = bytearray() # Current code size in bits code_size = min_code_size + 1 # Current code buffer and bit position code_buffer = 0 code_buffer_size = 0 # Output a code to the buffer def output_code(code): nonlocal code_buffer, code_buffer_size, output # Add code to buffer code_buffer |= (code << code_buffer_size) code_buffer_size += code_size # Output complete bytes while code_buffer_size >= 8: output.append(code_buffer & 0xFF) code_buffer >>= 8 code_buffer_size -= 8 # If dictionary size increases, increase code size too if dict_size == (1 << code_size) and code_size < 12: nonlocal code_size code_size += 1 # Start with the clear code output_code(clear_code) # Process pixels current_sequence = () for pixel in pixel_data: pixel_tuple = (pixel,) extended_sequence = current_sequence + pixel_tuple if extended_sequence in dictionary: current_sequence = extended_sequence else: # Output code for current sequence output_code(dictionary[current_sequence]) # Add new sequence to dictionary if not full if dict_size < 4096: dictionary[extended_sequence] = dict_size dict_size += 1 current_sequence = pixel_tuple # Output code for the last sequence if current_sequence: output_code(dictionary[current_sequence]) # End with the end code output_code(end_code) # Flush any remaining bits if code_buffer_size > 0: output.append(code_buffer & 0xFF) # Break output into sub-blocks block_size = 255 blocks = [] for i in range(0, len(output), block_size): block = output[i:i+block_size] blocks.append(bytes([len(block)]) + block) return b''.join(blocks) # Write GIF to stdout output = sys.stdout.buffer # GIF Header output.write(b'GIF89a') # Logical Screen Descriptor output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0x71, 0, 0])) # GCT follows, 2 colors # Global Color Table output.write(bytes([255, 255, 255])) # White output.write(bytes([255, 0, 0])) # Red # Image Descriptor output.write(bytes([0x2C])) # Image Separator output.write(bytes([0, 0])) # Left position output.write(bytes([0, 0])) # Top position output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0])) # No local color table # Image Data - LZW minimum code size min_code_size = 2 # We have 2 colors, so min code size is 2 output.write(bytes([min_code_size])) # Encode and write the pixel data encoded_data = lzw_encode(pixels, min_code_size) output.write(encoded_data) # End of image data output.write(bytes([0])) # GIF Trailer output.write(bytes([0x3B])) |
Running the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | import sys # GIF dimensions WIDTH = 200 HEIGHT = 200 # Create a canvas (0 = white, 1 = red) pixels = bytearray([0] * (WIDTH * HEIGHT)) # Define the triangle vertices p1 = (WIDTH // 2, HEIGHT // 4) # Top p2 = (WIDTH // 4, 3 * HEIGHT // 4) # Bottom left p3 = (3 * WIDTH // 4, 3 * HEIGHT // 4) # Bottom right # Function to check if a point is inside the triangle def is_inside_triangle(p, v1, v2, v3): def sign(p1, p2, p3): return (p1[0] - p3[0]) * (p2[1] - p3[1]) - (p2[0] - p3[0]) * (p1[1] - p3[1]) d1 = sign(p, v1, v2) d2 = sign(p, v2, v3) d3 = sign(p, v3, v1) has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) return not (has_neg and has_pos) # Fill the triangle with red (color index 1) for y in range(HEIGHT): for x in range(WIDTH): if is_inside_triangle((x, y), p1, p2, p3): pixels[y * WIDTH + x] = 1 # LZW encode function for GIF def lzw_encode(pixel_data, min_code_size): clear_code = 1 << min_code_size end_code = clear_code + 1 # Initialize dictionary with single-pixel values dict_size = clear_code + 2 dictionary = {(i,): i for i in range(clear_code)} # Output buffer output = bytearray() # Current code size in bits code_size = min_code_size + 1 # Current code buffer and bit position code_buffer = 0 code_buffer_size = 0 # Output a code to the buffer def output_code(code): nonlocal code_buffer, code_buffer_size, output # Add code to buffer code_buffer |= (code << code_buffer_size) code_buffer_size += code_size # Output complete bytes while code_buffer_size >= 8: output.append(code_buffer & 0xFF) code_buffer >>= 8 code_buffer_size -= 8 # If dictionary size increases, increase code size too if dict_size == (1 << code_size) and code_size < 12: nonlocal code_size code_size += 1 # Start with the clear code output_code(clear_code) # Process pixels current_sequence = () for pixel in pixel_data: pixel_tuple = (pixel,) extended_sequence = current_sequence + pixel_tuple if extended_sequence in dictionary: current_sequence = extended_sequence else: # Output code for current sequence output_code(dictionary[current_sequence]) # Add new sequence to dictionary if not full if dict_size < 4096: dictionary[extended_sequence] = dict_size dict_size += 1 current_sequence = pixel_tuple # Output code for the last sequence if current_sequence: output_code(dictionary[current_sequence]) # End with the end code output_code(end_code) # Flush any remaining bits if code_buffer_size > 0: output.append(code_buffer & 0xFF) # Break output into sub-blocks block_size = 255 blocks = [] for i in range(0, len(output), block_size): block = output[i:i+block_size] blocks.append(bytes([len(block)]) + block) return b''.join(blocks) # Write GIF to stdout output = sys.stdout.buffer # GIF Header output.write(b'GIF89a') # Logical Screen Descriptor output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0x71, 0, 0])) # GCT follows, 2 colors # Global Color Table output.write(bytes([255, 255, 255])) # White output.write(bytes([255, 0, 0])) # Red # Image Descriptor output.write(bytes([0x2C])) # Image Separator output.write(bytes([0, 0])) # Left position output.write(bytes([0, 0])) # Top position output.write(WIDTH.to_bytes(2, byteorder='little')) output.write(HEIGHT.to_bytes(2, byteorder='little')) output.write(bytes([0])) # No local color table # Image Data - LZW minimum code size min_code_size = 2 # We have 2 colors, so min code size is 2 output.write(bytes([min_code_size])) # Encode and write the pixel data encoded_data = lzw_encode(pixels, min_code_size) output.write(encoded_data) # End of image data output.write(bytes([0])) # GIF Trailer output.write(bytes([0x3B])) |
b' File "/usr/src/app/main.py", line 71\n nonlocal code_size\n ^^^^^^^^^^^^^^^^^^\nSyntaxError: name \'code_size\' is used prior to nonlocal declaration\n'
Describe the shapes in this image and their color
cannot identify image file <_io.BytesIO object at 0x7fd84778ac70>
Testing if the previous output contains the string red
: False
Testing if the previous output contains the string triangle
: False
Write a python program that, manually, draws a red triangle on a white background and saves it as a 200x200 gif file. Do not use any graphics libraries. Write the gif byte by byte. Output the gif file to stdout. I will evaluate your program by running `python your_soln.py > /tmp/a.gif` and then view the output file.
Write a python program that, manually, draws a red triangle on a white background and saves it as a 200x200 gif file. Do not use any graphics libraries. Write the gif byte by byte. Output the gif file to stdout. I will evaluate your program by running `python your_soln.py > /tmp/a.gif` and then view the output file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | import sys def write_gif_to_stdout(): # Image dimensions width, height = 200, 200 # Define colors white_index = 0 red_index = 1 # Create image data (0 = white, 1 = red) pixels = [] for y in range(height): for x in range(width): # Define triangle vertices x1, y1 = 100, 50 # Top point x2, y2 = 50, 150 # Bottom left x3, y3 = 150, 150 # Bottom right # Check if point is in triangle if is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): pixels.append(red_index) else: pixels.append(white_index) # GIF Header sys.stdout.buffer.write(b'GIF89a') # Logical Screen Descriptor sys.stdout.buffer.write(bytes([ width & 0xFF, (width >> 8) & 0xFF, # Width (LSB first) height & 0xFF, (height >> 8) & 0xFF, # Height (LSB first) 0x91, # 1001 0001 - Global Color Table, 2 bits/pixel, 2^(2+1)=8 colors white_index, # Background color is white 0 # Default pixel aspect ratio ])) # Global Color Table (8 colors but we only use white and red) sys.stdout.buffer.write(bytes([ 255, 255, 255, # White (index 0) 255, 0, 0, # Red (index 1) 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0 # Unused color ])) # Image Descriptor sys.stdout.buffer.write(bytes([ 0x2C, # Image separator 0, 0, # Left position 0, 0, # Top position width & 0xFF, (width >> 8) & 0xFF, # Width height & 0xFF, (height >> 8) & 0xFF, # Height 0 # No local color table ])) # Image Data min_code_size = 2 # 2 bits for 2 colors (min code size must be at least 2) sys.stdout.buffer.write(bytes([min_code_size])) # LZW encode the pixel data compressed = lzw_compress(pixels, min_code_size) # Write compressed data in blocks for i in range(0, len(compressed), 255): block = compressed[i:i+255] sys.stdout.buffer.write(bytes([len(block)])) sys.stdout.buffer.write(block) # End of image data sys.stdout.buffer.write(bytes([0])) # GIF Trailer sys.stdout.buffer.write(bytes([0x3B])) def is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): # Calculate barycentric coordinates d = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) if d == 0: return False a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / d b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / d c = 1 - a - b # Check if point is inside triangle return 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1 def lzw_compress(pixels, min_code_size): """LZW compress pixel data for GIF""" clear_code = 1 << min_code_size eoi_code = clear_code + 1 next_code = eoi_code + 1 # Initialize dictionary with basic codes dict_size = next_code dictionary = {(i,): i for i in range(clear_code)} # Output stream output = bytearray() bit_buffer = 0 bit_count = 0 code_size = min_code_size + 1 # Start with clear code put_code(output, clear_code, code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 current = () for pixel in pixels: next_pixel = current + (pixel,) if next_pixel in dictionary: current = next_pixel else: # Output code for current sequence put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # Add new sequence to dictionary if there's still room if dict_size < 4096: # GIF max code is 12 bits (0-4095) dictionary[next_pixel] = dict_size dict_size += 1 # Increase code size if needed if dict_size >= (1 << code_size) and code_size < 12: code_size += 1 current = (pixel,) # Output code for the last sequence if current: put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # End with EOI code put_code(output, eoi_code, code_size, bit_buffer, bit_count) # Flush any remaining bits if bit_count > 0: output.append(bit_buffer) return output def put_code(output, code, code_size, bit_buffer, bit_count): # Add code to the byte buffer bit_buffer |= (code << bit_count) bit_count += code_size # Output full bytes while bit_count >= 8: output.append(bit_buffer & 0xFF) bit_buffer >>= 8 bit_count -= 8 if __name__ == "__main__": write_gif_to_stdout() |
I extracted the following code from that output:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | import sys def write_gif_to_stdout(): # Image dimensions width, height = 200, 200 # Define colors white_index = 0 red_index = 1 # Create image data (0 = white, 1 = red) pixels = [] for y in range(height): for x in range(width): # Define triangle vertices x1, y1 = 100, 50 # Top point x2, y2 = 50, 150 # Bottom left x3, y3 = 150, 150 # Bottom right # Check if point is in triangle if is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): pixels.append(red_index) else: pixels.append(white_index) # GIF Header sys.stdout.buffer.write(b'GIF89a') # Logical Screen Descriptor sys.stdout.buffer.write(bytes([ width & 0xFF, (width >> 8) & 0xFF, # Width (LSB first) height & 0xFF, (height >> 8) & 0xFF, # Height (LSB first) 0x91, # 1001 0001 - Global Color Table, 2 bits/pixel, 2^(2+1)=8 colors white_index, # Background color is white 0 # Default pixel aspect ratio ])) # Global Color Table (8 colors but we only use white and red) sys.stdout.buffer.write(bytes([ 255, 255, 255, # White (index 0) 255, 0, 0, # Red (index 1) 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0 # Unused color ])) # Image Descriptor sys.stdout.buffer.write(bytes([ 0x2C, # Image separator 0, 0, # Left position 0, 0, # Top position width & 0xFF, (width >> 8) & 0xFF, # Width height & 0xFF, (height >> 8) & 0xFF, # Height 0 # No local color table ])) # Image Data min_code_size = 2 # 2 bits for 2 colors (min code size must be at least 2) sys.stdout.buffer.write(bytes([min_code_size])) # LZW encode the pixel data compressed = lzw_compress(pixels, min_code_size) # Write compressed data in blocks for i in range(0, len(compressed), 255): block = compressed[i:i+255] sys.stdout.buffer.write(bytes([len(block)])) sys.stdout.buffer.write(block) # End of image data sys.stdout.buffer.write(bytes([0])) # GIF Trailer sys.stdout.buffer.write(bytes([0x3B])) def is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): # Calculate barycentric coordinates d = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) if d == 0: return False a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / d b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / d c = 1 - a - b # Check if point is inside triangle return 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1 def lzw_compress(pixels, min_code_size): """LZW compress pixel data for GIF""" clear_code = 1 << min_code_size eoi_code = clear_code + 1 next_code = eoi_code + 1 # Initialize dictionary with basic codes dict_size = next_code dictionary = {(i,): i for i in range(clear_code)} # Output stream output = bytearray() bit_buffer = 0 bit_count = 0 code_size = min_code_size + 1 # Start with clear code put_code(output, clear_code, code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 current = () for pixel in pixels: next_pixel = current + (pixel,) if next_pixel in dictionary: current = next_pixel else: # Output code for current sequence put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # Add new sequence to dictionary if there's still room if dict_size < 4096: # GIF max code is 12 bits (0-4095) dictionary[next_pixel] = dict_size dict_size += 1 # Increase code size if needed if dict_size >= (1 << code_size) and code_size < 12: code_size += 1 current = (pixel,) # Output code for the last sequence if current: put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # End with EOI code put_code(output, eoi_code, code_size, bit_buffer, bit_count) # Flush any remaining bits if bit_count > 0: output.append(bit_buffer) return output def put_code(output, code, code_size, bit_buffer, bit_count): # Add code to the byte buffer bit_buffer |= (code << bit_count) bit_count += code_size # Output full bytes while bit_count >= 8: output.append(bit_buffer & 0xFF) bit_buffer >>= 8 bit_count -= 8 if __name__ == "__main__": write_gif_to_stdout() |
Running the following program:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | import sys def write_gif_to_stdout(): # Image dimensions width, height = 200, 200 # Define colors white_index = 0 red_index = 1 # Create image data (0 = white, 1 = red) pixels = [] for y in range(height): for x in range(width): # Define triangle vertices x1, y1 = 100, 50 # Top point x2, y2 = 50, 150 # Bottom left x3, y3 = 150, 150 # Bottom right # Check if point is in triangle if is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): pixels.append(red_index) else: pixels.append(white_index) # GIF Header sys.stdout.buffer.write(b'GIF89a') # Logical Screen Descriptor sys.stdout.buffer.write(bytes([ width & 0xFF, (width >> 8) & 0xFF, # Width (LSB first) height & 0xFF, (height >> 8) & 0xFF, # Height (LSB first) 0x91, # 1001 0001 - Global Color Table, 2 bits/pixel, 2^(2+1)=8 colors white_index, # Background color is white 0 # Default pixel aspect ratio ])) # Global Color Table (8 colors but we only use white and red) sys.stdout.buffer.write(bytes([ 255, 255, 255, # White (index 0) 255, 0, 0, # Red (index 1) 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0, # Unused color 0, 0, 0 # Unused color ])) # Image Descriptor sys.stdout.buffer.write(bytes([ 0x2C, # Image separator 0, 0, # Left position 0, 0, # Top position width & 0xFF, (width >> 8) & 0xFF, # Width height & 0xFF, (height >> 8) & 0xFF, # Height 0 # No local color table ])) # Image Data min_code_size = 2 # 2 bits for 2 colors (min code size must be at least 2) sys.stdout.buffer.write(bytes([min_code_size])) # LZW encode the pixel data compressed = lzw_compress(pixels, min_code_size) # Write compressed data in blocks for i in range(0, len(compressed), 255): block = compressed[i:i+255] sys.stdout.buffer.write(bytes([len(block)])) sys.stdout.buffer.write(block) # End of image data sys.stdout.buffer.write(bytes([0])) # GIF Trailer sys.stdout.buffer.write(bytes([0x3B])) def is_point_in_triangle(x, y, x1, y1, x2, y2, x3, y3): # Calculate barycentric coordinates d = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3) if d == 0: return False a = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / d b = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / d c = 1 - a - b # Check if point is inside triangle return 0 <= a <= 1 and 0 <= b <= 1 and 0 <= c <= 1 def lzw_compress(pixels, min_code_size): """LZW compress pixel data for GIF""" clear_code = 1 << min_code_size eoi_code = clear_code + 1 next_code = eoi_code + 1 # Initialize dictionary with basic codes dict_size = next_code dictionary = {(i,): i for i in range(clear_code)} # Output stream output = bytearray() bit_buffer = 0 bit_count = 0 code_size = min_code_size + 1 # Start with clear code put_code(output, clear_code, code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 current = () for pixel in pixels: next_pixel = current + (pixel,) if next_pixel in dictionary: current = next_pixel else: # Output code for current sequence put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # Add new sequence to dictionary if there's still room if dict_size < 4096: # GIF max code is 12 bits (0-4095) dictionary[next_pixel] = dict_size dict_size += 1 # Increase code size if needed if dict_size >= (1 << code_size) and code_size < 12: code_size += 1 current = (pixel,) # Output code for the last sequence if current: put_code(output, dictionary[current], code_size, bit_buffer, bit_count) bit_buffer = 0 bit_count = 0 # End with EOI code put_code(output, eoi_code, code_size, bit_buffer, bit_count) # Flush any remaining bits if bit_count > 0: output.append(bit_buffer) return output def put_code(output, code, code_size, bit_buffer, bit_count): # Add code to the byte buffer bit_buffer |= (code << bit_count) bit_count += code_size # Output full bytes while bit_count >= 8: output.append(bit_buffer & 0xFF) bit_buffer >>= 8 bit_count -= 8 if __name__ == "__main__": write_gif_to_stdout() |
b'GIF89a\xc8\x00\xc8\x00\x91\x00\x00\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00,\x00\x00\x00\x00\xc8\x00\xc8\x00\x00\x02\xff\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91]\x01\x92=\x94\x95:\x01\x9a\x989\x9a\x97\x9c5\x9e\x9b\xa04\xa2\x9f\xa40\xa6\xa3\xa8/\xaa\xa7\xac,\xae\xaf\xb0(\xb2\xb3\xb4&\xb6\xb7\xb8"\xba\xbb\xbc\x1f\xbe\xab\xc0\xbd\xc2\xc4#\xc2\x9e\xc7!\xc9\xc3\xcb\x1b\xcd\xca\xcf\x1c\xd1\xce\xd3\x16\xd5\xd2\xd7\x17\xd9\xd6\xdb\x12\xdd\xde\xdf\x0e\xe1\xe2\xe3\x0c\xe5\xe6\xe7\x08\xe9\xea\xeb\x00\xed\xda\xef\n\xf1\xee\xe3\xf5\xf2\xf3\x06\xf8\xf6\xd7\xfc\xf9\xef\xff\xfd\xcb\x02\x00\xf7\x05\xbf\xfe\x08\t\x04\x0b\x0c\xc7\x0e\x03\xb4\x11\x12\xac\x14\x06\x10\x17\x0f\xb0\x1a\x18\xbc\x1d\x15\x98 \x1e\x1c#\x1b\x9c&$\xa4)!\x90,-\x8d/0\x8c23\x8956\x878*.;\'4>9\x83AB\x81D<\x8bGE~JH\x88MK{PN\x85SQxVT\x82YWu\\Z\x7f_]rb`|ecohfykimnojqritufwxdzl\x8ed}?^\x80{`\x83\x84^\x86~p\x89\x81p\x8c\x8as\x8f\x8d\x8b\x92\x93k\x95\x87W\x98\x90e\x9b\x99T\x9e\x9cb\xa1\x9fQ\xa4\xa2_\xa7\xa5N\xaa\xa8\\\xad\xabK\xb0\xaeY\xb3\xb1H\xb6\xb4V\xb9\xb7E\xbc\xbaS\xbf\xbdB\xc2\xc0P\xc5\xc3?\xc8\xc6M\xcb\xc9<\xce\xccJ\xd1\xcf\x9d\xd4\xd5\xfa\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\xe0\x05\x00;'
Describe the shapes in this image and their color
broken data stream when reading image file
Testing if the previous output contains the string red
: False
Testing if the previous output contains the string triangle
: False