0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

GIF 画像データを作る

Last updated at Posted at 2022-05-11

GIF データ生成用クラス

bytearray の派生として GIF 形式のデータを生成します。

mkgif.py
#!/usr/bin/env python3

class File(bytearray):
    def __init__(self, version=b'89a'):
        super().__init__()
        self += b'GIF' + version
        self.logical_screen_width = 0
        self.logical_screen_height = 0

    @staticmethod
    def fix_color(data):
        color = []
        for d in data:
            if type(d) in (bytes, bytearray, list, tuple):
                color += d
            else:
                color.append(d)

        mod3 = len(color) % 3
        if mod3:
            color += [0] * (3 - mod3)
        color_size = 3
        size = 0
        while color_size < len(color):
            color_size <<= 1
            size += 1
        color += [0] * (color_size - len(color))
        return (color, size - 1)

    def append_data(self, data): self += bytes(data)
    def append_bytes(self, *data): self += bytes(data)
    def append_word(self, word): self += bytes((word & 255, (word >> 8) & 255))
    def append_words(self, *data): [self.append_word(word) for word in data]

    def append_sub_blocks(self, data):
        pos = [i for i in range(0, len(data), 255)] + [len(data)]
        cnt = [pos[i + 1] - pos[i] for i in range(len(pos) - 1)]
        for i in range(len(cnt)):
            p, n = pos[i], cnt[i]
            self.append(n)
            self.append_data(data[p:p+n])
        self.append(0)

    # Descriptor

    def append_logical_screen_descriptor(
            self, width, height, color_resolution=7, sort_flag=False,
            background_color_index=0, pixel_aspect_ratio=49,
            global_color=None):
        self.logical_screen_width = width
        self.logical_screen_height = height
        packed_fields = (color_resolution & 7) << 4
        if sort_flag:
            packed_fields |= (1 << 3)
        if global_color:
            global_color, color_size = File.fix_color(global_color)
            packed_fields |= (1 << 7) | color_size
        self.append_words(width, height)
        self.append_bytes(packed_fields,
                          background_color_index,
                          pixel_aspect_ratio)
        if global_color:
            self.append_data(global_color)

    def append_image_descriptr(
            self, image_data, left=0, top=0, width=None, height=None,
            local_color=None, sort_flag=False, interlace_flag=False):
        image_data = bytes(image_data)
        if width is None:
            width = self.logical_screen_width
        if height is None:
            height = self.logical_screen_height
        packed_fields = 0
        if sort_flag:
            packed_fields |= (1 << 5)
        if interlace_flag:
            packed_fields |= (1 << 6)
        if local_color:
            local_color, color_size = File.fix_color(local_color)
            packed_fields |= (1 << 7) | color_size
        self.append(0x2c)
        self.append_words(left, top, width, height)
        self.append(packed_fields)
        if local_color:
            self.append_data(local_color)
        lzw_minimun_code_size = 0
        image_data_max = max(image_data)
        while (1 << lzw_minimun_code_size) <= image_data_max:
            lzw_minimun_code_size += 1
        if lzw_minimun_code_size < 2:
            lzw_minimun_code_size = 2
        self.append(lzw_minimun_code_size)
        self.append_sub_blocks(File.LZWEncode(lzw_minimun_code_size, image_data))

    def append_trailer(self):
        self.append(0x3B)

    # Extension

    def append_graphic_control_extension(
            self, delay_time=0, tranparent_color_index=None,
            disposal_method=1, user_input_flag=False):
        packed_fields = (disposal_method & 7) << 3
        if user_input_flag:
            packed_fields |= (1 << 6)
        if tranparent_color_index:
            packed_fields |= (1 << 7)
        else:
            tranparent_color_index = 0
        self.append_bytes(0x21, 0xF9, 4, packed_fields)
        self.append_word(delay_time)
        self.append_bytes(tranparent_color_index, 0)

    def append_comment_extension(self, comment):
        self.append_bytes(0x21, 0xFE)
        self.append_sub_blocks(comment)

    def append_application_extension(self, identifier, code, data):
        identifier = bytes(identifier)
        if len(identifier) < 8:
            identifier += b' ' * 8
        code = bytes(code)
        if len(code) < 3:
            code += b' ' * 3
        self.append_bytes(0x21, 0xFF, 11)
        self += identifier[:8]
        self += code[:3]
        self.append_sub_blocks(data)

    def append_animaition_loop(self, count=0):
        data = bytes([1, count & 0xff, (count >> 8) & 0xff])
        self.append_application_extension(b'NETSCAPE', b'2.0', data)

    class BitStream(bytearray):
        def __init__(self):
            super().__init__()
            self.bit_pos = 0

        def write(self, data, bits):
            bpos = self.bit_pos & 7
            self.bit_pos += bits
            if bpos != 0:
                data <<= bpos
                bits += bpos
                self[-1] |= data & 0xff
                data >>= 8
                bits -= 8
            for n in range((bits + 7) >> 3):
                self.append(data & 0xff)
                data >>= 8

    class LZWEncode(BitStream):
        def __init__(self, minbits, data):
            super().__init__()
            self.bits_init = minbits
            self.code_clear = 1 << self.bits_init
            self.code_end = self.code_clear + 1
            self.code_max = (1 << 12) - 1

            self.clear()
            self.write(self.code_clear)
            s = ''
            for b in data:
                c = chr(b)
                s += c
                if s in self.code_tree:
                    continue
                self.write(self.code_tree[s[:-1]])
                self.code_curr += 1
                if self.code_curr >= self.code_step:
                    self.bits_curr += 1
                    self.code_step <<= 1
                if self.code_curr < self.code_max:
                    self.code_tree[s] = self.code_curr
                else:
                    self.write(self.code_clear)
                    self.clear()
                s = c
            self.write(self.code_tree[s])
            self.write(self.code_end)

        def clear(self):
            self.bits_curr = self.bits_init + 1
            self.code_curr = self.code_end
            self.code_step = 1 << self.bits_curr
            self.code_tree = {chr(n): n for n in range(self.code_clear)}

        def write(self, data):
            super().write(data, self.bits_curr)

使用例

以下、静止画と動画の生成例です。

静止画

静止画(sample-1.gif)の生成
import mkgif

width = 256   # 画像の幅
height = 64  # 画像の高さ

# 256色
color = tuple((n & 0xe0, (n << 3) & 0xe0, (n << 5) & 0xc0) for n in range(256))

# mkgif.File オブジェクトを用意
gif = mkgif.File()

# Logical Screen Descriptor は最初で必須
gif.append_logical_screen_descriptor(width, height, global_color=color)

# 画像データ (8ビットのデータを width * height 個)
gif.append_image_descriptr(n & 0xff for n in range(width * height))

# Trailer は最後で必須
gif.append_trailer()

# ファイルに出力
open('sample-1.gif', 'wb').write(gif)

sample-1.gif

動画

動画(sample-2.gif)の生成
import mkgif

width = 256   # 画像の幅
height = 64  # 画像の高さ

# 256色
color = tuple((n & 0xe0, (n << 3) & 0xe0, (n << 5) & 0xc0) for n in range(256))

# mkgif.File オブジェクトを用意
gif = mkgif.File()

# Logical Screen Descriptor は最初で必須
gif.append_logical_screen_descriptor(width, height, global_color=color)

# 無限ループのアニメーションを設定
gif.append_animaition_loop()  # 回数を引数として渡す。なし(0)は無限ループ。

# 1 ループ分の画像を追加
for m in range(32):
    # 更新画像の情報
    gif.append_graphic_control_extension(5)  # 画像の表示時間(1/100秒単位)

    # 画像データ (8ビットのデータを width * height 個)
    gif.append_image_descriptr((n + m * 8) & 0xff for n in range(width * height))

# Trailer は最後で必須
gif.append_trailer()

# ファイルに出力
open('sample-2.gif', 'wb').write(gif)

sample-2.gif

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?