#!/usr/bin/python3

import configparser
import gif
import itertools
import math
import png
import struct

class ReferenceImage:
    def __init__ (self, name, width, height, pixels):
        self.width = width
        self.height = height
        self.filename = '%s.rgba' % name
        file = open ('test-suite/%s' % self.filename, 'wb')
        data = b''
        for (red, green, blue, alpha) in pixels:
            data += struct.pack ('BBBB', red, green, blue, alpha)
        file.write (data)
        writer = png.Writer (width, height, alpha = True, greyscale = False)
        writer.write_array (open ('test-suite/%s.png' % name, 'wb'), data)

def make_gif (name, result, width, height, colors = [], background_color = 0, version = gif.Version.GIF89a, loop_count = 0, force_animation = False, buffer_size = None, comment = None, xmp_files = [], icc_files = []):
    # Add to list of tests
    test_list = open ('test-suite/TESTS').readlines ()
    line = name + '\n'
    if not line in test_list:
        test_list.append (line)
        open ('test-suite/TESTS', 'w').writelines (test_list)

    # Write test description
    config = configparser.ConfigParser ()
    config['config'] = { 'input': '%s.gif' % name }
    config['config']['version'] = version.decode ('ascii')
    config['config']['width'] = '%d' % width
    config['config']['height'] = '%d' % height
    if background_color < len (colors):
        (red, green, blue) = colors[background_color]
        config['config']['background'] = '#%02x%02x%02x' % (red, green, blue)
    if loop_count < 0:
        config['config']['loop-count'] = 'infinite'
    else:
        config['config']['loop-count'] = '%d' % loop_count
    if buffer_size is not None:
        config['config']['buffer-size'] = '%d' % buffer_size
    if comment is not None:
        config['config']['comment'] = repr (comment)
    if len (xmp_files) > 0:
        config['config']['xmp-data'] = ','.join (xmp_files)
    if len (icc_files) > 0:
        config['config']['color-profile'] = ','.join (icc_files)
    def yes_or_no (value):
        if value:
            return 'yes'
        else:
            return 'no'
    config['config']['force-animation'] = yes_or_no (force_animation)
    if result is None:
        result = []
    if not isinstance (result, list):
        result = [ result ]
    frames = []
    for (i, reference) in enumerate (result):
        if isinstance (reference, tuple):
            (image, delay) = reference
        else:
            image = reference
            delay = 0
        id = 'frame%d' % i
        frames.append (id)
        config[id] = { 'pixels': image.filename }
        if delay > 0:
            config[id]['delay'] = '%d' % delay
    config['config']['frames'] = ','.join (frames)
    file = open ('test-suite/%s.conf' % name, 'w')
    file.write ('# Automatically generated, do not edit!\n')
    config.write (file)

    # Write test GIF
    filename = 'test-suite/%s.gif' % name
    writer = gif.Writer (open (filename, 'wb'))
    writer.write_header (version)
    if len (colors) > 0:
        depth = math.ceil (math.log2 (len (colors)))
        writer.write_screen_descriptor (width, height, has_color_table = True, depth = depth, background_color = background_color)
        writer.write_color_table (colors, depth)
    else:
        writer.write_screen_descriptor (width, height, background_color = background_color)

    return writer

BLACK        = 0
WHITE        = 1
RED          = 2
GREEN        = 3
BLUE         = 4
CYAN         = 5
MAGENTA      = 6
YELLOW       = 7
DARK_GREY    = 8
LIGHT_GREY   = 9
DARK_RED     = 10
DARK_GREEN   = 11
DARK_BLUE    = 12
DARK_CYAN    = 13
DARK_MAGENTA = 14
DARK_YELLOW  = 15
palette16 =  [ (  0,   0,   0), (255, 255, 255), (255,   0,   0), (  0, 255,   0),
               (  0,   0, 255), (  0, 255, 255), (255,   0, 255), (255, 255,   0),
               ( 85,  85,  85), (170, 170, 170), (128,   0,   0), (  0, 128,   0),
               (  0,   0, 128), (  0, 128, 128), (128,   0, 128), (128, 128,   0)]
palette8 = palette16[:8]
palette4 = palette16[:4]
palette2 = palette16[:2]

def make_grayscale_palette (depth):
    n_colors = 2 ** depth
    colors = []
    for i in range (n_colors):
        v = (i * 255 // (n_colors - 1))
        colors.append ((v, v, v))
    return colors

def filled_pixels (width, height, color):
    return [color] * (width * height)

white_dot = ReferenceImage ('white-dot', 1, 1, [ (255, 255, 255, 255) ])

# Single pixel, all possible color depths
for depth in range (1, 9):
    palette = make_grayscale_palette (depth)
    writer = make_gif ('depth%d' % depth, white_dot, 1, 1, palette)
    writer.write_image (1, 1, depth, [ 2 ** depth - 1 ])
    writer.write_trailer ()

four_colors = ReferenceImage ('four-colors', 2, 2, [ (255, 0, 0, 255),  (0, 255, 0, 255), (0, 0, 255, 255), (255, 255, 255, 255) ])

# Image with different colours in each pixel
writer = make_gif ('four-colors', four_colors, 2, 2, palette8)
writer.write_image (2, 2, 8, [RED, GREEN, BLUE, WHITE])
writer.write_trailer ()

# Local color table overrides global one
writer = make_gif ('local-color-table', white_dot, 1, 1, [(255, 0, 0), (0, 255, 0)])
writer.write_image (1, 1, 1, [1], colors = [(0, 0, 255), (255, 255, 255)])
writer.write_trailer ()

# Global color table not needed if have local one
writer = make_gif ('no-global-color-table', white_dot, 1, 1)
writer.write_image (1, 1, 1, [1], colors = [(0, 0, 255), (255, 255, 255)])
writer.write_trailer ()

transparent_dot = ReferenceImage ('transparent-dot', 1, 1, [ (0, 0, 0, 0) ])

# Image with no data (just shows background)
writer = make_gif ('no-data', transparent_dot, 1, 1, palette2, background_color = WHITE)
writer.write_trailer ()

# Images with zero dimensions
writer = make_gif ('zero-width', None, 0, 1, palette2, background_color = WHITE)
writer.write_trailer ()
writer = make_gif ('zero-height', None, 1, 0, palette2, background_color = WHITE)
writer.write_trailer ()
writer = make_gif ('zero-size', None, 0, 0, palette2, background_color = WHITE)
writer.write_trailer ()
writer = make_gif ('image-zero-width', transparent_dot, 1, 1, palette2, background_color = WHITE)
writer.write_image_descriptor (0, 0, 0, 1, depth = 1)
writer.write_trailer ()
writer = make_gif ('image-zero-height', transparent_dot, 1, 1, palette2, background_color = WHITE)
writer.write_image_descriptor (0, 0, 1, 0, 1, depth = 1)
writer.write_trailer ()
writer = make_gif ('image-zero-size', transparent_dot, 1, 1, palette2, background_color = WHITE)
writer.write_image_descriptor (0, 0, 0, 0, 1, depth = 1)
writer.write_trailer ()

# Image with invalid background value
writer = make_gif ('invalid-background', white_dot, 1, 1, palette2, background_color = 255)
writer.write_image (1, 1, 2, [ WHITE ])
writer.write_trailer ()

# Test all color bits work
pixels = []
colors = []
colors_rgba = []
for i in range (256):
    pixels.append (i)
    colors.append ((i, 0, 0))
    colors_rgba.append ((i, 0, 0, 255))
all_reds = ReferenceImage ('all-reds', 16, 16, colors_rgba)
writer = make_gif ('all-reds', all_reds, 16, 16, colors)
writer.write_image (16, 16, 8, pixels)
writer.write_trailer ()
pixels = []
colors = []
colors_rgba = []
for i in range (256):
    pixels.append (i)
    colors.append ((0, i, 0))
    colors_rgba.append ((0, i, 0, 255))
all_greens = ReferenceImage ('all-greens', 16, 16, colors_rgba)
writer = make_gif ('all-greens', all_greens, 16, 16, colors)
writer.write_image (16, 16, 8, pixels)
writer.write_trailer ()
pixels = []
colors = []
colors_rgba = []
for i in range (256):
    pixels.append (i)
    colors.append ((0, 0, i))
    colors_rgba.append ((0, 0, i, 255))
all_blues = ReferenceImage ('all-blues', 16, 16, colors_rgba)
writer = make_gif ('all-blues', all_blues, 16, 16, colors)
writer.write_image (16, 16, 8, pixels)
writer.write_trailer ()

# Interlaced image
colors = []
for i in range (256):
    colors.append ((i, 0, 0))
pixels = []
def interlace_rows (height):
    return itertools.chain (range (0, 16, 8), range (4, 16, 8), range (2, 16, 4), range (1, 16, 2))
for row in interlace_rows (16):
    for col in range (16):
        pixels.append (row * 16 + col)
writer = make_gif ('interlace', all_reds, 16, 16, colors)
writer.write_image (16, 16, 8, pixels, interlace = True)
writer.write_trailer ()

image_inside_bg = ReferenceImage ('image-inside-bg', 2, 2, [ (255, 0, 0, 255), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0)])
image_overlap_bg = ReferenceImage ('image-overlap-bg', 2, 2, [ (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0), (255, 0, 0, 255)])
image_outside_bg = ReferenceImage ('image-outside-bg', 2, 2, [ (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0) ])

# Images that don't fully cover the background
writer = make_gif ('image-inside-bg', image_inside_bg, 2, 2, palette8, background_color = WHITE)
writer.write_image (1, 1, 3, [ RED ], left = 0, top = 0)
writer.write_trailer ()
writer = make_gif ('image-overlap-bg', image_overlap_bg, 2, 2, palette8, background_color = WHITE)
writer.write_image (2, 2, 3, [ RED ] * 4, left = 1, top = 1)
writer.write_trailer ()
writer = make_gif ('image-outside-bg', image_outside_bg, 2, 2, palette8, background_color = WHITE)
writer.write_image (2, 2, 3, [ RED ] * 4, left = 2, top = 2)
writer.write_trailer ()

# Multiple images in different places
writer = make_gif ('images-combine', four_colors, 2, 2, palette8, background_color = WHITE)
writer.write_image (1, 1, 3, [ RED ],   left = 0, top = 0)
writer.write_image (1, 1, 3, [ GREEN ], left = 1, top = 0)
writer.write_image (1, 1, 3, [ BLUE ],  left = 0, top = 1)
writer.write_image (1, 1, 3, [ WHITE ], left = 1, top = 1)
writer.write_trailer ()

# Multiple images overlapping
writer = make_gif ('images-overlap', white_dot, 1, 1, palette8)
writer.write_image (1, 1, 3, [ RED ])
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# Image with >256 colors by using local color tables
colors = []
def get_color (x, y):
    return (math.floor (x * 256 / 32), math.floor (y * 256 / 32), 0)
for y in range (32):
    for x in range (32):
        (red, green, blue) = get_color (x, y)
        colors.append ((red, green, blue, 255))
high_color = ReferenceImage ('high-color', 32, 32, colors)
pixels = list (range (256))
colors0 = []
colors1 = []
colors2 = []
colors3 = []
for y in range (16):
    for x in range (16):
        colors0.append (get_color (x, y))
        colors1.append (get_color (x + 16, y))
        colors2.append (get_color (x, y + 16))
        colors3.append (get_color (x + 16, y + 16))
writer = make_gif ('high-color', high_color, 32, 32)
writer.write_image (16, 16, 8, pixels, left = 0, top = 0, colors = colors0)
writer.write_image (16, 16, 8, pixels, left = 16, top = 0, colors = colors1)
writer.write_image (16, 16, 8, pixels, left = 0, top = 16, colors = colors2)
writer.write_image (16, 16, 8, pixels, left = 16, top = 16, colors = colors3)
writer.write_trailer ()

missing_pixels = ReferenceImage ('missing-pixels', 2, 2, [ (255, 255, 255, 255), (0, 0, 0, 0), (0, 0, 0, 0), (0, 0, 0, 0) ])

# Image with missing pixels
writer = make_gif ('missing-pixels', missing_pixels, 2, 2, palette8)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# Image with additional pixels
writer = make_gif ('extra-pixels', white_dot, 1, 1, palette8)
writer.write_image (1, 1, 3, filled_pixels (10, 10, WHITE))
writer.write_trailer ()

# Addtional data after end-of-information
writer = make_gif ('extra-data', white_dot, 1, 1, palette8)
writer.write_image_descriptor (0, 0, 1, 1)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3)
encoder.feed ([ WHITE ])
encoder.finish (extra_data = b'HIDDEN MESSAGES')
writer.write_trailer ()

white_hline2 = ReferenceImage ('white-hline2', 2, 1, [ (255, 255, 255, 255), (255, 255, 255, 255) ])

# Optional clear and end-of-information codes
writer = make_gif ('no-clear', white_dot, 1, 1, palette8)
writer.write_image_descriptor (0, 0, 1, 1)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3, start_with_clear = False)
encoder.feed ([ WHITE ])
encoder.finish ()
writer.write_trailer ()
writer = make_gif ('no-eoi', white_dot, 1, 1, palette8)
writer.write_image_descriptor (0, 0, 1, 1)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3)
encoder.feed ([ WHITE ])
encoder.finish (send_eoi = False)
writer.write_trailer ()
# Use 2x1 so the single byte of data contains two codes (6 bits) otherwise the decoder will read a second code due to the lack of EOI
writer = make_gif ('no-clear-and-eoi', white_hline2, 2, 1, palette8)
writer.write_image_descriptor (0, 0, 2, 1)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3, start_with_clear = False)
encoder.feed ([ WHITE, WHITE ])
encoder.finish (send_eoi = False)
writer.write_trailer ()

pixels = []
colors_rgba = []
for y in range (8):
    for x in range (8):
        if (x + y) % 2 == 0:
            pixels.append (WHITE)
            colors_rgba.append ((255, 255, 255, 255))
        else:
            pixels.append (BLACK)
            colors_rgba.append ((0, 0, 0, 255))
checkerboard = ReferenceImage ('checkerboard', 8, 8, colors_rgba)

# Send clear before each pixel
writer = make_gif ('many-clears', checkerboard, 8, 8, palette8)
writer.write_image_descriptor (0, 0, 8, 8)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3, start_with_clear = False)
for pixel in pixels:
    encoder.clear ()
    encoder.feed ([ pixel ])
encoder.finish ()
writer.write_trailer ()

# Send double clear before each pixel
writer = make_gif ('double-clears', checkerboard, 8, 8, palette8)
writer.write_image_descriptor (0, 0, 8, 8)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3, start_with_clear = False)
for pixel in pixels:
    encoder.clear ()
    encoder.clear ()
    encoder.feed ([ pixel ])
encoder.finish ()
writer.write_trailer ()

# LZW data with a code outside the valid range
writer = make_gif ('invalid-code', None, 2, 2, palette2)
writer.write_image_descriptor (0, 0, 2, 2)
writer.file.write (b'\x02' + b'\x02\xff\xff' + b'\x00')
writer.write_trailer ()

# LZW data with indexes outside the color table
writer = make_gif ('invalid-colors', None, 1, 1, palette2, background_color = WHITE)
writer.write_image_descriptor (0, 0, 1, 1)
encoder = gif.LZWEncoder (writer.file, min_code_size = 3)
encoder.feed ([ 2 ])
encoder.finish ()
writer.write_trailer ()

max_width = ReferenceImage ('max-width', 65535, 1, [ (255, 255, 255, 255) ] * 65535)
max_height = ReferenceImage ('max-height', 1, 65535, [ (255, 255, 255, 255) ] * 65535)

# Maximum sizes
writer = make_gif ('max-width', max_width, 65535, 1, palette8)
writer.write_image (65535, 1, 3, filled_pixels (65535, 1, WHITE))
writer.write_trailer ()
writer = make_gif ('max-height', max_height, 1, 65535, palette8)
writer.write_image (1, 65535, 3, filled_pixels (1, 65535, WHITE))
writer.write_trailer ()
writer = make_gif ('max-size', None, 65535, 65535, palette8)
writer.write_trailer ()

# Generate a random image to test LZW compression
random_width = 100
random_height = 100
pixels = []
colors = []
seed = 1
for i in range (random_width*random_height):
    m = 2 ** 32
    seed = (1103515245 * seed + 12345) % m
    value = math.floor (16 * seed / m)
    assert (value < 16)
    pixels.append (value)
    (red, green, blue) = palette16[value]
    colors.append ((red, green, blue, 255))
random_image = ReferenceImage ('random-image', random_width, random_height, colors)

# Clear code when hit 12 bit limit
writer = make_gif ('4095-codes-clear', random_image, random_width, random_height, palette16)
writer.write_image (random_width, random_height, 4, pixels)
writer.write_trailer ()

# Stop adding code words when hit code 12 bit limit
writer = make_gif ('4095-codes', random_image, random_width, random_height, palette16)
writer.write_image_descriptor (0, 0, random_width, random_height)
encoder = gif.LZWEncoder (writer.file, min_code_size = 4, clear_on_max_width = False)
encoder.feed (pixels)
encoder.finish ()
writer.write_trailer ()

# Have lots of clears by having a small code bit limit
writer = make_gif ('255-codes', random_image, random_width, random_height, palette16)
writer.write_image_descriptor (0, 0, random_width, random_height)
encoder = gif.LZWEncoder (writer.file, min_code_size = 4, max_code_size = 8)
encoder.feed (pixels)
encoder.finish ()
writer.write_trailer ()

# Use a minimum code size
writer = make_gif ('large-codes', random_image, random_width, random_height, palette16)
writer.write_image_descriptor (0, 0, random_width, random_height)
encoder = gif.LZWEncoder (writer.file, min_code_size = 7)
encoder.feed (pixels)
encoder.finish ()
writer.write_trailer ()

# Use the maximum supported code size
writer = make_gif ('max-codes', random_image, random_width, random_height, palette16)
writer.write_image_descriptor (0, 0, random_width, random_height)
encoder = gif.LZWEncoder (writer.file, min_code_size = 11)
encoder.feed (pixels)
encoder.finish ()
writer.write_trailer ()

# Use a code size of 12 (overflows max size by one).
writer = make_gif ('overflow-codes', None, 2, 2, palette2)
writer.write_image_descriptor (0, 0, 2, 2)
writer.file.write (b'\x0c' + b'\x02\x10\x01' + b'\x00')
writer.write_trailer ()

# Use a code size of 255 (maximum storable value, exceeds max size).
writer = make_gif ('overflow-codes-max', None, 2, 2, palette2)
writer.write_image_descriptor (0, 0, 2, 2)
writer.file.write (b'\xff' + b'\x20\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01' + b'\x00')
writer.write_trailer ()

four_colors_transparent = ReferenceImage ('four-colors-transparent', 2, 2, [ (0, 0, 0, 0),  (0, 255, 0, 255), (0, 0, 255, 255), (255, 255, 255, 255) ])

# Transparent image
writer = make_gif ('transparent', four_colors_transparent, 2, 2, palette8)
writer.write_graphic_control_extension (has_transparent = True, transparent_color = RED)
writer.write_image (2, 2, 3, [ RED, GREEN, BLUE, WHITE ])
writer.write_trailer ()

# Invalid transparency color
writer = make_gif ('invalid-transparent', four_colors, 2, 2, palette8)
writer.write_graphic_control_extension (has_transparent = True, transparent_color = 255)
writer.write_image (2, 2, 3, [ RED, GREEN, BLUE, WHITE ])
writer.write_trailer ()

# Transparency color set but transparency disabled
writer = make_gif ('disabled-transparent', four_colors, 2, 2, palette8)
writer.write_graphic_control_extension (has_transparent = False, transparent_color = RED)
writer.write_image (2, 2, 3, [ RED, GREEN, BLUE, WHITE ])
writer.write_trailer ()

# Check that color 0 is not defaulting to transparent.
writer = make_gif ('unset-transparent', white_dot, 1, 1, [(255, 255, 255), (0, 0, 0)], background_color = 1)
writer.write_image (1, 1, 2, [ 0 ])
writer.write_trailer ()

# Loops
writer = make_gif ('loop-infinite', white_dot, 1, 1, palette8, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('loop-once', white_dot, 1, 1, palette8, loop_count = 1)
writer.write_netscape_extension (loop_count = 1)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('loop-max', white_dot, 1, 1, palette8, loop_count = 65535)
writer.write_netscape_extension (loop_count = 65535)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('loop-buffer', white_dot, 1, 1, palette8, loop_count = -1, buffer_size = 1024)
writer.write_netscape_extension (loop_count = 0, buffer_size = 1024)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('loop-buffer_max', white_dot, 1, 1, palette8, loop_count = -1, buffer_size = 4294967295)
writer.write_netscape_extension (loop_count = 0, buffer_size = 4294967295)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('loop-animexts', white_dot, 1, 1, palette8, loop_count = -1, buffer_size = 1024)
writer.write_animexts_extension (loop_count = 0, buffer_size = 1024)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# FIXME: NETSCAPE extension without loop field

# FIXME: NETSCAPE with multiple loop fields

animation_0 = ReferenceImage ('animation.0', 2, 2, [ (255, 255, 255, 255), (  0,   0,   0, 255), (  0,   0,   0, 255), (  0,   0,   0, 255) ])
animation_1 = ReferenceImage ('animation.1', 2, 2, [ (  0,   0,   0, 255), (255, 255, 255, 255), (  0,   0,   0, 255), (  0,   0,   0, 255) ])
animation_2 = ReferenceImage ('animation.2', 2, 2, [ (  0,   0,   0, 255), (  0,   0,   0, 255), (  0,   0,   0, 255), (255, 255, 255, 255) ])
animation_3 = ReferenceImage ('animation.3', 2, 2, [ (  0,   0,   0, 255), (  0,   0,   0, 255), (255, 255, 255, 255), (  0,   0,   0, 255) ])

animation_fill_0 = ReferenceImage ('animation-fill.0', 2, 2, [ (255, 255, 255, 255), (  0,   0,   0, 255), (  0,   0,   0, 255), (  0,   0,   0, 255) ])
animation_fill_1 = ReferenceImage ('animation-fill.1', 2, 2, [ (255, 255, 255, 255), (255, 255, 255, 255), (  0,   0,   0, 255), (  0,   0,   0, 255) ])
animation_fill_2 = ReferenceImage ('animation-fill.2', 2, 2, [ (255, 255, 255, 255), (255, 255, 255, 255), (  0,   0,   0, 255), (255, 255, 255, 255) ])
animation_fill_3 = ReferenceImage ('animation-fill.3', 2, 2, [ (255, 255, 255, 255), (255, 255, 255, 255), (255, 255, 255, 255), (255, 255, 255, 255) ])

animation_erase_0 = ReferenceImage ('animation-erase.0', 2, 2, [ (255, 255, 255, 255), (  0,   0,   0,   0), (  0,   0,   0,   0), (  0,   0,   0,   0) ])
animation_erase_1 = ReferenceImage ('animation-erase.1', 2, 2, [ (  0,   0,   0,   0), (255, 255, 255, 255), (  0,   0,   0,   0), (  0,   0,   0,   0) ])
animation_erase_2 = ReferenceImage ('animation-erase.2', 2, 2, [ (  0,   0,   0,   0), (  0,   0,   0,   0), (  0,   0,   0,   0), (255, 255, 255, 255) ])
animation_erase_3 = ReferenceImage ('animation-erase.3', 2, 2, [ (  0,   0,   0,   0), (  0,   0,   0,   0), (255, 255, 255, 255), (  0,   0,   0,   0) ])

# Animated image
writer = make_gif ('animation', [(animation_0, 50), (animation_1, 50), (animation_2, 50), (animation_3, 50)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (delay_time = 50)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 50)
writer.write_image (2, 2, 1, [ BLACK, WHITE, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 50)
writer.write_image (2, 2, 1, [ BLACK, BLACK, BLACK, WHITE ])
writer.write_graphic_control_extension (delay_time = 50)
writer.write_image (2, 2, 1, [ BLACK, BLACK, WHITE, BLACK ])
writer.write_trailer ()

# Animation with variable frame speed
writer = make_gif ('animation-speed', [(animation_0, 25), (animation_1, 50), (animation_2, 100), (animation_3, 200)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (delay_time = 25)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 50)
writer.write_image (2, 2, 1, [ BLACK, WHITE, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 100)
writer.write_image (2, 2, 1, [ BLACK, BLACK, BLACK, WHITE ])
writer.write_graphic_control_extension (delay_time = 200)
writer.write_image (2, 2, 1, [ BLACK, BLACK, WHITE, BLACK ])
writer.write_trailer ()

# Animation without any delays set - GIF renderers commonly assume this should be animated with a default delay value
writer = make_gif ('animation-no-delays', [(animation_0, 0), (animation_1, 0), (animation_2, 0), (animation_3, 0)], 2, 2, palette2, loop_count = -1, force_animation = True)
writer.write_netscape_extension (loop_count = 0)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_image (2, 2, 1, [ BLACK, WHITE, BLACK, BLACK ])
writer.write_image (2, 2, 1, [ BLACK, BLACK, BLACK, WHITE ])
writer.write_image (2, 2, 1, [ BLACK, BLACK, WHITE, BLACK ])
writer.write_trailer ()

# Animation without zero frame times - GIF renderers commonly assume this should be animated with a default delay value
writer = make_gif ('animation-zero-delays', [(animation_0, 0), (animation_1, 0), (animation_2, 0), (animation_3, 0)], 2, 2, palette2, loop_count = -1, force_animation = True)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (delay_time = 0)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 0)
writer.write_image (2, 2, 1, [ BLACK, WHITE, BLACK, BLACK ])
writer.write_graphic_control_extension (delay_time = 0)
writer.write_image (2, 2, 1, [ BLACK, BLACK, BLACK, WHITE ])
writer.write_graphic_control_extension (delay_time = 0)
writer.write_image (2, 2, 1, [ BLACK, BLACK, WHITE, BLACK ])
writer.write_trailer ()

# Background with animated subimages that add together
writer = make_gif ('dispose-none', [(animation_fill_0, 50), (animation_fill_1, 50), (animation_fill_2, 50), (animation_fill_3, 50)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (gif.DisposalMethod.NONE, delay_time = 50)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (gif.DisposalMethod.NONE, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.NONE, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.NONE, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 1)
writer.write_trailer ()

# Background with animated subimages that add together
writer = make_gif ('dispose-keep', [(animation_fill_0, 50), (animation_fill_1, 50), (animation_fill_2, 50), (animation_fill_3, 50)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (2, 2, 1, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 1)
writer.write_trailer ()

# Animated image with subimages
writer = make_gif ('dispose-restore-background', [(animation_erase_0, 50), (animation_erase_1, 50), (animation_erase_2, 50), (animation_erase_3, 50)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_BACKGROUND, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_BACKGROUND, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_BACKGROUND, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_BACKGROUND, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 1)
writer.write_trailer ()

# Background with animated subimages that move over initial background
writer = make_gif ('dispose-restore-previous', [(animation_0, 50), (animation_1, 50), (animation_2, 50), (animation_3, 50)], 2, 2, palette2, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_image (2, 2, 1, [ BLACK, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_PREVIOUS, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_PREVIOUS, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_PREVIOUS, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.RESTORE_PREVIOUS, delay_time = 50)
writer.write_image (1, 1, 1, [ WHITE ], 0, 1)
writer.write_trailer ()

# FIXME: Test restore only applies to area drawn on

# Animation with multiple images per frame - should not see the red as it will be drawn over
# NOTE: Everyone seems to be doing this wrong and delaying each image by 10ms regardless of the fact the delay should be zero
writer = make_gif ('animation-multi-image', [(animation_fill_0, 50), (animation_fill_1, 50), (animation_fill_2, 50), (animation_fill_3, 50)], 2, 2, palette4, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (2, 2, 2, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_image (1, 1, 2, [ RED ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 1, 0)
writer.write_image (1, 1, 2, [ RED ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 1, 1,)
writer.write_image (1, 1, 2, [ RED ], 0, 1)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 0, 1)
writer.write_trailer ()

# Animation with multiple images per frame - should not see the red as it will be drawn over
# NOTE: Everyone seems to be doing this wrong and delaying each image by 10ms regardless of the fact the delay should be zero
writer = make_gif ('animation-multi-image-explicit-zero-delay', [(animation_fill_0, 50), (animation_fill_1, 50), (animation_fill_2, 50), (animation_fill_3, 50)], 2, 2, palette4, loop_count = -1)
writer.write_netscape_extension (loop_count = 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (2, 2, 2, [ WHITE, BLACK, BLACK, BLACK ])
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 0)
writer.write_image (1, 1, 2, [ RED ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 1, 0)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 0)
writer.write_image (1, 1, 2, [ RED ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 1, 1,)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 0)
writer.write_image (1, 1, 2, [ RED ], 0, 1)
writer.write_graphic_control_extension (gif.DisposalMethod.KEEP, delay_time = 50)
writer.write_image (1, 1, 2, [ WHITE ], 0, 1)
writer.write_trailer ()

# FIXME: Animation with explicit delay times of zero

# FIXME: Animation without fixed first frame (everyone seems to be assuming transparent background)

# Comments
comment = 'Hello World!'
writer = make_gif ('comment', white_dot, 1, 1, palette8, comment = comment)
writer.write_comment_extension (comment)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
comment = ' '.join (['Hello World!'] * 1000)
writer = make_gif ('large-comment', white_dot, 1, 1, palette8, comment = comment)
writer.write_comment_extension (comment)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
comment = '\0'
writer = make_gif ('nul-comment', white_dot, 1, 1, palette8, comment = comment)
writer.write_comment_extension (comment)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
comment = '\xff'
writer = make_gif ('invalid-ascii-comment', white_dot, 1, 1, palette8, comment = comment)
writer.write_comment_extension (comment)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
comment = '\xc3\x28'
writer = make_gif ('invalid-utf8-comment', white_dot, 1, 1, palette8, comment = comment)
writer.write_comment_extension (comment)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# Plain Text extension
writer = make_gif ('plain-text', None, 40, 8, palette8)
writer.write_plain_text_extension ('Hello', 0, 0, 5, 1, 8, 8, 1, 0)
writer.write_image (40, 8, 3, filled_pixels (40, 8, BLACK))
writer.write_trailer ()

# XMP Data
data = open ('test-suite/test.xmp').read ()
writer = make_gif ('xmp-data', white_dot, 1, 1, palette8, xmp_files = ['test.xmp'])
writer.write_xmp_data_extension (data)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('xmp-data-empty', white_dot, 1, 1, palette8, xmp_files = ['empty.xmp'])
writer.write_xmp_data_extension ('')
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# ICC profile
data = open ('test-suite/sRGB.icc', 'rb').read ()
writer = make_gif ('icc-color-profile', white_dot, 1, 1, palette8, icc_files = ['sRGB.icc'])
writer.write_icc_color_profile_extension (data)
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('icc-color-profile-empty', white_dot, 1, 1, palette8, icc_files = ['empty.icc'])
writer.write_icc_color_profile_extension (b'')
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# Unknown extensions
writer = make_gif ('unknown-extension', white_dot, 1, 1, palette8)
writer.write_extension (0x2a, [b'Hello', b'World'])
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('unknown-application-extension', white_dot, 1, 1, palette8)
writer.write_application_extension ('UNKNOWN!', 'XXX', [b'Hello', b'World'])
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()
writer = make_gif ('nul-application-extension', white_dot, 1, 1, palette8)
writer.write_application_extension ('\0\0\0\0\0\0\0\0', '\0\0\0', [b'\0\0\0\0', b'\0\0\0\0'])
writer.write_image (1, 1, 3, [ WHITE ])
writer.write_trailer ()

# FIXME: Multiple clears in a row

# Support older version
writer = make_gif ('gif87a', white_dot, 1, 1, palette2, version = gif.Version.GIF87a)
writer.write_image (1, 1, 2, [ WHITE ])
writer.write_trailer ()

# Animation in GIF87a - GIF renderers commonly assume this should be animated
writer = make_gif ('gif87a-animation', [(animation_0, 0), (animation_1, 0), (animation_2, 0), (animation_3, 0)], 2, 2, palette2, loop_count = -1, force_animation = True)
writer.write_image (2, 2, 1, [WHITE, BLACK, BLACK, BLACK])
writer.write_image (2, 2, 1, [BLACK, WHITE, BLACK, BLACK])
writer.write_image (2, 2, 1, [BLACK, BLACK, BLACK, WHITE])
writer.write_image (2, 2, 1, [BLACK, BLACK, WHITE, BLACK])
writer.write_trailer ()

# FIXME: Check 89a features don't work in 87a
