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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
// 纯 MoonBit GIF 编码器
// 单帧 GIF89a,使用 3-3-2 均匀量化(256 色调色板)+ LZW 压缩
// 移植自 src/format/gif_encode.mbt:1-177,仅替换类型引用:
//   @core.Image -> @types.Image, @core.LoadError -> @types.LoadError

// 3-3-2 量化: R 8 levels, G 8 levels, B 4 levels = 256 colors

///|
fn quantize_332(r : Int, g : Int, b : Int) -> Int {
  let r_level = (r >> 5) & 0x07
  let g_level = (g >> 5) & 0x07
  let b_level = (b >> 6) & 0x03
  r_level * 32 + g_level * 4 + b_level
}

// 构建 256 色 3-3-2 调色板

///|
fn build_332_palette() -> Array[Byte] {
  let palette : Array[Byte] = Array::make(768, b'\x00')
  for i in 0..<256 {
    let r_level = i / 32
    let g_level = i % 32 / 4
    let b_level = i % 4
    palette[i * 3] = (r_level * 36).to_byte()
    palette[i * 3 + 1] = (g_level * 36).to_byte()
    palette[i * 3 + 2] = (b_level * 85).to_byte()
  }
  palette
}

// LZW 压缩用于 GIF
// 返回压缩后的字节数组(包含 sub-block 包装)

///|
fn lzw_compress(indices : Array[Int], min_code_size : Int) -> Bytes {
  let clear_code = 1 << min_code_size
  let end_code = clear_code + 1
  let code_table : Array[(Int, Int)] = [] // (prefix_code, pixel_value) pairs
  // 初始化 code table
  for i in 0..<clear_code {
    code_table.push((-1, i))
  }
  code_table.push((-1, -1)) // clear code
  code_table.push((-1, -1)) // end code
  let next_code = Ref(clear_code + 2)
  let code_size = Ref(min_code_size + 1)
  let max_code = Ref((1 << code_size.val) - 1)
  // 输出位流
  let bit_buffer = Ref(0)
  let bit_count = Ref(0)
  let output : Array[Byte] = []
  fn emit_code(code : Int) -> Unit {
    bit_buffer.val = bit_buffer.val | (code << bit_count.val)
    bit_count.val += code_size.val
    while bit_count.val >= 8 {
      output.push((bit_buffer.val & 0xFF).to_byte())
      bit_buffer.val = bit_buffer.val >> 8
      bit_count.val -= 8
    }
  }
  emit_code(clear_code)
  if indices.length() > 0 {
    let prefix = Ref(indices[0])
    for i in 1..<indices.length() {
      let pixel = indices[i]
      // 查找 (prefix, pixel) 在 code table 中
      let found = Ref(-1)
      for j in 0..<code_table.length() {
        let (p, v) = code_table[j]
        if p == prefix.val && v == pixel {
          found.val = j
          break
        }
      }
      if found.val >= 0 {
        prefix.val = found.val
      } else {
        emit_code(prefix.val)
        if next_code.val < 4096 {
          code_table.push((prefix.val, pixel))
          if next_code.val > max_code.val && code_size.val < 12 {
            code_size.val += 1
            max_code.val = (1 << code_size.val) - 1
          }
          next_code.val += 1
        }
        prefix.val = pixel
      }
    }
    emit_code(prefix.val)
  }
  emit_code(end_code)
  // 刷新剩余位
  if bit_count.val > 0 {
    output.push((bit_buffer.val & 0xFF).to_byte())
  }
  // 包装为 sub-blocks (每块最多 255 字节)
  let result : Array[Byte] = []
  let pos = Ref(0)
  while pos.val < output.length() {
    let block_size = if output.length() - pos.val > 255 {
      255
    } else {
      output.length() - pos.val
    }
    result.push(block_size.to_byte())
    for i in 0..<block_size {
      result.push(output[pos.val + i])
    }
    pos.val += block_size
  }
  result.push(b'\x00') // block terminator
  Bytes::from_array(result)
}

///|
/// 将 Image 编码为单帧 GIF89a 字节流
/// 使用 3-3-2 均匀量化(256 色调色板)+ LZW 压缩
/// 输入 channels 应为 3 (RGB) 或 4 (RGBA,alpha 忽略)
pub fn encode_gif_pure(img : @types.Image) -> Bytes raise @types.LoadError {
  if img.channels != 3 && img.channels != 4 {
    raise @types.LoadError::EncodeFailed(
      "GIF 编码需要 channels 为 3 (RGB) 或 4 (RGBA)",
    )
  }
  let out : Array[Byte] = []
  // GIF89a header
  out.push(b'\x47') // G
  out.push(b'\x49') // I
  out.push(b'\x46') // F
  out.push(b'\x38') // 8
  out.push(b'\x39') // 9
  out.push(b'\x61') // a
  // Logical Screen Descriptor (7 bytes)
  out.push((img.width & 0xFF).to_byte())
  out.push(((img.width >> 8) & 0xFF).to_byte())
  out.push((img.height & 0xFF).to_byte())
  out.push(((img.height >> 8) & 0xFF).to_byte())
  out.push(b'\xF7') // packed: global color table = 1, color resolution = 7, sort = 0, size = 7 (256 colors)
  out.push(b'\x00') // background color index
  out.push(b'\x00') // pixel aspect ratio
  // Global Color Table (256 * 3 = 768 bytes)
  let palette = build_332_palette()
  for i in 0..<768 {
    out.push(palette[i])
  }
  // 量化像素
  let indices : Array[Int] = Array::make(img.width * img.height, 0)
  for y in 0..<img.height {
    for x in 0..<img.width {
      let idx = y * img.width + x
      let r = img.data[idx * img.channels].to_int()
      let g = img.data[idx * img.channels + 1].to_int()
      let b = img.data[idx * img.channels + 2].to_int()
      indices[idx] = quantize_332(r, g, b)
    }
  }
  // Image Descriptor
  out.push(b'\x2C') // Image Separator
  out.push(b'\x00') // left position (LE)
  out.push(b'\x00')
  out.push(b'\x00') // top position (LE)
  out.push(b'\x00')
  out.push((img.width & 0xFF).to_byte())
  out.push(((img.width >> 8) & 0xFF).to_byte())
  out.push((img.height & 0xFF).to_byte())
  out.push(((img.height >> 8) & 0xFF).to_byte())
  out.push(b'\x00') // packed: no local color table, not interlaced
  // LZW Image Data
  out.push(b'\x08') // LZW Minimum Code Size
  let lzw_data = lzw_compress(indices, 8)
  for i in 0..<lzw_data.length() {
    out.push(lzw_data[i])
  }
  // Trailer
  out.push(b'\x3B')
  Bytes::from_array(out)
}

///|
/// 将多帧 Image 编码为动画 GIF89a 字节流。
/// frames 为帧数组,delays 为每帧延迟(毫秒,将转换为 1/100 秒单位)。
/// 所有帧应具有相同尺寸和 channels(3 或 4)。
/// 使用 3-3-2 均匀量化 + LZW 压缩,无限循环。
pub fn encode_gif_animation_pure(
  anim : @types.GifAnimation,
) -> Bytes raise @types.LoadError {
  let frames = anim.frames
  let delays = anim.delays
  if frames.length() == 0 {
    raise @types.LoadError::EncodeFailed("GIF 动画至少需要 1 帧")
  }
  if frames.length() != delays.length() {
    raise @types.LoadError::EncodeFailed("frames 和 delays 长度不匹配")
  }
  let first = frames[0]
  if first.channels != 3 && first.channels != 4 {
    raise @types.LoadError::EncodeFailed(
      "GIF 编码需要 channels 为 3 (RGB) 或 4 (RGBA)",
    )
  }
  let w = first.width
  let h = first.height
  let out : Array[Byte] = []
  // GIF89a header
  out.push(b'\x47')
  out.push(b'\x49')
  out.push(b'\x46')
  out.push(b'\x38')
  out.push(b'\x39')
  out.push(b'\x61')
  // Logical Screen Descriptor
  out.push((w & 0xFF).to_byte())
  out.push(((w >> 8) & 0xFF).to_byte())
  out.push((h & 0xFF).to_byte())
  out.push(((h >> 8) & 0xFF).to_byte())
  out.push(b'\xF7') // global color table flag, 256 colors
  out.push(b'\x00') // background color index
  out.push(b'\x00') // pixel aspect ratio
  // Global Color Table (256 * 3 = 768 bytes)
  let palette = build_332_palette()
  for i in 0..<768 {
    out.push(palette[i])
  }
  // Netscape Application Extension (无限循环)
  out.push(b'\x21') // Extension Introducer
  out.push(b'\xFF') // Application Extension Label
  out.push(b'\x0B') // Block Size
  // "NETSCAPE2.0"
  out.push(b'\x4E')
  out.push(b'\x45')
  out.push(b'\x54')
  out.push(b'\x53')
  out.push(b'\x43')
  out.push(b'\x41')
  out.push(b'\x50')
  out.push(b'\x45')
  out.push(b'\x32')
  out.push(b'\x2E')
  out.push(b'\x30')
  out.push(b'\x03') // Sub-block size
  out.push(b'\x01') // Sub-block ID
  out.push(b'\x00') // loop count LE (0 = infinite)
  out.push(b'\x00')
  out.push(b'\x00') // Block Terminator
  // 每帧
  for fi in 0..<frames.length() {
    let frame = frames[fi]
    // Graphic Control Extension
    out.push(b'\x21') // Extension Introducer
    out.push(b'\xF9') // Graphic Control Label
    out.push(b'\x04') // Block Size
    out.push(b'\x00') // packed: disposal=0, no user input, no transparency
    // delay in 1/100 seconds (毫秒 / 10)
    let delay = delays[fi] / 10
    out.push((delay & 0xFF).to_byte())
    out.push(((delay >> 8) & 0xFF).to_byte())
    out.push(b'\x00') // transparent color index
    out.push(b'\x00') // Block Terminator
    // 量化像素
    let indices : Array[Int] = Array::make(frame.width * frame.height, 0)
    for y in 0..<frame.height {
      for x in 0..<frame.width {
        let idx = y * frame.width + x
        let r = frame.data[idx * frame.channels].to_int()
        let g = frame.data[idx * frame.channels + 1].to_int()
        let b = frame.data[idx * frame.channels + 2].to_int()
        indices[idx] = quantize_332(r, g, b)
      }
    }
    // Image Descriptor
    out.push(b'\x2C') // Image Separator
    out.push(b'\x00') // left position LE
    out.push(b'\x00')
    out.push(b'\x00') // top position LE
    out.push(b'\x00')
    out.push((frame.width & 0xFF).to_byte())
    out.push(((frame.width >> 8) & 0xFF).to_byte())
    out.push((frame.height & 0xFF).to_byte())
    out.push(((frame.height >> 8) & 0xFF).to_byte())
    out.push(b'\x00') // packed: no local color table, not interlaced
    // LZW Image Data
    out.push(b'\x08') // LZW Minimum Code Size
    let lzw_data = lzw_compress(indices, 8)
    for i in 0..<lzw_data.length() {
      out.push(lzw_data[i])
    }
  }
  // Trailer
  out.push(b'\x3B')
  Bytes::from_array(out)
}