From 6dedddc545e2f1930bdc2256784eb1551bd4231d Mon Sep 17 00:00:00 2001 From: nsfisis Date: Sun, 1 Feb 2026 00:49:15 +0900 Subject: feat(nuldoc): rewrite nuldoc in Ruby --- .../implementation-of-minimal-png-image-encoder.md | 18 +- .../index.html | 694 +++++++++++---------- 2 files changed, 361 insertions(+), 351 deletions(-) (limited to 'services/nuldoc/public/blog/posts/2023-04-01') diff --git a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md index 6f4fb3c8..2fd69590 100644 --- a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md +++ b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder.md @@ -189,19 +189,19 @@ IHDR chunk は最初に配置される chunk である。次のようなデー 1. 画像の幅 (符号なし 4 バイト整数) 1. 画像の高さ (符号なし 4 バイト整数) 1. ビット深度 (符号なし 1 バイト整数) - * 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる + * 1 色に使うビット数。1 ピクセルに 24 bit 使う truecolor 画像では 8 になる 1. 色タイプ (符号なし 1 バイト整数) - * 0: グレースケール - * 2: Truecolor (今回はこれに決め打ち) - * 3: パレットのインデックス - * 4: グレースケール + アルファ - * 6: Truecolor + アルファ + * 0: グレースケール + * 2: Truecolor (今回はこれに決め打ち) + * 3: パレットのインデックス + * 4: グレースケール + アルファ + * 6: Truecolor + アルファ 1. 圧縮方式 (符号なし 1 バイト整数) - * PNG の仕様書に 0 しか定義されていないので 0 で固定 + * PNG の仕様書に 0 しか定義されていないので 0 で固定 1. フィルタ方式 (符号なし 1 バイト整数) - * PNG の仕様書に 0 しか定義されていないので 0 で固定 + * PNG の仕様書に 0 しか定義されていないので 0 で固定 1. インターレース方式 (符号なし 1 バイト整数) - * 今回はインターレースしないので 0 + * 今回はインターレースしないので 0 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。 diff --git a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html index 7ab1e9b5..07518f23 100644 --- a/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html +++ b/services/nuldoc/public/blog/posts/2023-04-01/implementation-of-minimal-png-image-encoder/index.html @@ -135,44 +135,45 @@ 以下のソースコードをベースにする。今回 PNG のデコーダは扱わないので、読み込みには Go の標準ライブラリ image/png を用いる。

-
package main
-
-import (
-	"image"
-	_ "image/png"
-	"io"
-	"os"
-)
-
-func main() {
-	inFile, err := os.Open("input.png")
-	if err != nil {
-		panic(err)
-	}
-	defer inFile.Close()
-
-	img, _, err := image.Decode(inFile)
-	if err != nil {
-		panic(err)
-	}
-
-	outFile, err := os.Create("output.png")
-	if err != nil {
-		panic(err)
-	}
-	defer outFile.Close()
-
-	writePng(outFile, img)
-}
-
-func writePng(w io.Writer, img image.Image) {
-	width := uint32(img.Bounds().Dx())
-	height := uint32(img.Bounds().Dy())
-	writeSignature(w)
-	writeChunkIhdr(w, width, height)
-	writeChunkIdat(w, width, height, img)
-	writeChunkIend(w)
-}
+
package main
+
+import (
+	"image"
+	_ "image/png"
+	"io"
+	"os"
+)
+
+func main() {
+	inFile, err := os.Open("input.png")
+	if err != nil {
+		panic(err)
+	}
+	defer inFile.Close()
+
+	img, _, err := image.Decode(inFile)
+	if err != nil {
+		panic(err)
+	}
+
+	outFile, err := os.Create("output.png")
+	if err != nil {
+		panic(err)
+	}
+	defer outFile.Close()
+
+	writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+	width := uint32(img.Bounds().Dx())
+	height := uint32(img.Bounds().Dy())
+	writeSignature(w)
+	writeChunkIhdr(w, width, height)
+	writeChunkIdat(w, width, height, img)
+	writeChunkIend(w)
+}
+

以降は、writeSignaturewriteChunkIhdr などを実装していく。 @@ -215,21 +216,22 @@ writeSignature の実装はこちら:

-
import "encoding/binary"
-
-func writeSignature(w io.Writer) {
-	sig := [8]uint8{
-		0x89,
-		0x50, // P
-		0x4E, // N
-		0x47, // G
-		0x0D, // CR
-		0x0A, // LF
-		0x1A, // EOF (^Z)
-		0x0A, // LF
-	}
-	binary.Write(w, binary.BigEndian, sig)
-}
+
import "encoding/binary"
+
+func writeSignature(w io.Writer) {
+	sig := [8]uint8{
+		0x89,
+		0x50, // P
+		0x4E, // N
+		0x47, // G
+		0x0D, // CR
+		0x0A, // LF
+		0x1A, // EOF (^Z)
+		0x0A, // LF
+	}
+	binary.Write(w, binary.BigEndian, sig)
+}
+

encoding/binary パッケージの binary.Write を使い、固定の 8 バイトを書き込む。 @@ -258,55 +260,57 @@ CRC (Cyclic Redundancy Check) は誤り検出符号の一種。Go 言語では hash/crc32 パッケージにあるが、今回はこれも自前で実装する。PNG の仕様書に C 言語のサンプルコードが載っている ( D. Sample CRC implementation ) ので、これを Go に移植する。

-
var (
-	crcTable         [256]uint32
-	crcTableComputed bool
-)
-
-func makeCrcTable() {
-	for n := 0; n < 256; n++ {
-		c := uint32(n)
-		for k := 0; k < 8; k++ {
-			if (c & 1) != 0 {
-				c = 0xEDB88320 ^ (c >> 1)
-			} else {
-				c = c >> 1
-			}
-		}
-		crcTable[n] = c
-	}
-	crcTableComputed = true
-}
-
-func updateCrc(crc uint32, buf []byte) uint32 {
-	if !crcTableComputed {
-		makeCrcTable()
-	}
-
-	c := crc
-	for n := 0; n < len(buf); n++ {
-		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
-	}
-	return c
-}
-
-func crc(buf []byte) uint32 {
-	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
-}
+
var (
+	crcTable         [256]uint32
+	crcTableComputed bool
+)
+
+func makeCrcTable() {
+	for n := 0; n < 256; n++ {
+		c := uint32(n)
+		for k := 0; k < 8; k++ {
+			if (c & 1) != 0 {
+				c = 0xEDB88320 ^ (c >> 1)
+			} else {
+				c = c >> 1
+			}
+		}
+		crcTable[n] = c
+	}
+	crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+	if !crcTableComputed {
+		makeCrcTable()
+	}
+
+	c := crc
+	for n := 0; n < len(buf); n++ {
+		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+	}
+	return c
+}
+
+func crc(buf []byte) uint32 {
+	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+

できた crc 関数を使って、chunk 一般を書き込む関数も用意しておこう。

-
func writeChunk(w io.Writer, chunkType string, data []byte) {
-	typeAndData := make([]byte, 0, len(chunkType)+len(data))
-	typeAndData = append(typeAndData, []byte(chunkType)...)
-	typeAndData = append(typeAndData, data...)
-
-	binary.Write(w, binary.BigEndian, uint32(len(data)))
-	binary.Write(w, binary.BigEndian, typeAndData)
-	binary.Write(w, binary.BigEndian, crc(typeAndData))
-}
+
func writeChunk(w io.Writer, chunkType string, data []byte) {
+	typeAndData := make([]byte, 0, len(chunkType)+len(data))
+	typeAndData = append(typeAndData, []byte(chunkType)...)
+	typeAndData = append(typeAndData, data...)
+
+	binary.Write(w, binary.BigEndian, uint32(len(data)))
+	binary.Write(w, binary.BigEndian, typeAndData)
+	binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+

仕様どおり、chunkTypedata から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。 @@ -384,20 +388,21 @@ 今回ほとんどのデータは決め打ちするので、データに応じて変わるのは width と height だけになる。コードは次のようになる。

-
import "bytes"
-
-func writeChunkIhdr(w io.Writer, width, height uint32) {
-	var buf bytes.Buffer
-	binary.Write(&buf, binary.BigEndian, width)
-	binary.Write(&buf, binary.BigEndian, height)
-	binary.Write(&buf, binary.BigEndian, uint8(8))
-	binary.Write(&buf, binary.BigEndian, uint8(2))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-
-	writeChunk(w, "IHDR", buf.Bytes())
-}
+
import "bytes"
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+	var buf bytes.Buffer
+	binary.Write(&buf, binary.BigEndian, width)
+	binary.Write(&buf, binary.BigEndian, height)
+	binary.Write(&buf, binary.BigEndian, uint8(8))
+	binary.Write(&buf, binary.BigEndian, uint8(2))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+
+	writeChunk(w, "IHDR", buf.Bytes())
+}
+
@@ -431,22 +436,23 @@ Adler-32 も CRC と同じく誤り検出符号である。こちらも zlib の仕様書に C 言語でサンプルコードが記載されている ( 9. Appendix: Sample code ) ので、Go に移植する。

-
const adler32Base = 65521
-
-func updateAdler32(adler uint32, buf []byte) uint32 {
-	s1 := adler & 0xFFFF
-	s2 := (adler >> 16) & 0xFFFF
-
-	for n := 0; n < len(buf); n++ {
-		s1 = (s1 + uint32(buf[n])) % adler32Base
-		s2 = (s2 + s1) % adler32Base
-	}
-	return (s2 << 16) + s1
-}
-
-func adler32(buf []byte) uint32 {
-	return updateAdler32(1, buf)
-}
+
const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+	s1 := adler & 0xFFFF
+	s2 := (adler >> 16) & 0xFFFF
+
+	for n := 0; n < len(buf); n++ {
+		s1 = (s1 + uint32(buf[n])) % adler32Base
+		s2 = (s2 + s1) % adler32Base
+	}
+	return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+	return updateAdler32(1, buf)
+}
+

「データ」の部分には圧縮したデータが入るのだが、真面目に deflate アルゴリズムを実装する必要はない。Zlib には無圧縮のデータブロックを格納することができるので、これを使う。本来は、データの圧縮効率の悪いランダムなデータをそのまま格納するためのものだが、今回は deflate の実装をサボるために使う。 @@ -472,30 +478,31 @@ 実際にこの手抜き zlib を実装したものがこちら:

-
func encodeZlib(data []byte) []byte {
-	var buf bytes.Buffer
-
-	binary.Write(&buf, binary.BigEndian, uint8(0x78))
-	binary.Write(&buf, binary.BigEndian, uint8(0x01))
-	blockSize := 65535
-	isFinalBlock := false
-	for i := 0; !isFinalBlock; i++ {
-		var block []byte
-		if len(data) <= (i+1)*blockSize {
-			block = data[i*blockSize:]
-			isFinalBlock = true
-		} else {
-			block = data[i*blockSize : (i+1)*blockSize]
-		}
-		binary.Write(&buf, binary.BigEndian, isFinalBlock)
-		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
-		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
-		binary.Write(&buf, binary.LittleEndian, block)
-	}
-	binary.Write(&buf, binary.BigEndian, adler32(data))
-
-	return buf.Bytes()
-}
+
func encodeZlib(data []byte) []byte {
+	var buf bytes.Buffer
+
+	binary.Write(&buf, binary.BigEndian, uint8(0x78))
+	binary.Write(&buf, binary.BigEndian, uint8(0x01))
+	blockSize := 65535
+	isFinalBlock := false
+	for i := 0; !isFinalBlock; i++ {
+		var block []byte
+		if len(data) <= (i+1)*blockSize {
+			block = data[i*blockSize:]
+			isFinalBlock = true
+		} else {
+			block = data[i*blockSize : (i+1)*blockSize]
+		}
+		binary.Write(&buf, binary.BigEndian, isFinalBlock)
+		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+		binary.Write(&buf, binary.LittleEndian, block)
+	}
+	binary.Write(&buf, binary.BigEndian, adler32(data))
+
+	return buf.Bytes()
+}
+
@@ -510,20 +517,21 @@ 先ほどの encodeZlib も使って実際に実装したものがこちら:

-
func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
-	var pixels bytes.Buffer
-	for y := uint32(0); y < height; y++ {
-		binary.Write(&pixels, binary.BigEndian, uint8(0))
-		for x := uint32(0); x < width; x++ {
-			r, g, b, _ := img.At(int(x), int(y)).RGBA()
-			binary.Write(&pixels, binary.BigEndian, uint8(r))
-			binary.Write(&pixels, binary.BigEndian, uint8(g))
-			binary.Write(&pixels, binary.BigEndian, uint8(b))
-		}
-	}
-
-	writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
-}
+
func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+	var pixels bytes.Buffer
+	for y := uint32(0); y < height; y++ {
+		binary.Write(&pixels, binary.BigEndian, uint8(0))
+		for x := uint32(0); x < width; x++ {
+			r, g, b, _ := img.At(int(x), int(y)).RGBA()
+			binary.Write(&pixels, binary.BigEndian, uint8(r))
+			binary.Write(&pixels, binary.BigEndian, uint8(g))
+			binary.Write(&pixels, binary.BigEndian, uint8(b))
+		}
+	}
+
+	writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+
@@ -536,9 +544,10 @@ 特に追加のデータはなく、必要なのは chunk type の IEND くらいなので実装は簡単:

-
func writeChunkIend(w io.Writer) {
-	writeChunk(w, "IEND", nil)
-}
+
func writeChunkIend(w io.Writer) {
+	writeChunk(w, "IEND", nil)
+}
+
@@ -548,180 +557,181 @@ 最後に全ソースコードを再掲しておく。

-
package main
-
-import (
-	"bytes"
-	"encoding/binary"
-	"image"
-	_ "image/png"
-	"io"
-	"os"
-)
-
-func main() {
-	inFile, err := os.Open("input.png")
-	if err != nil {
-		panic(err)
-	}
-	defer inFile.Close()
-
-	img, _, err := image.Decode(inFile)
-	if err != nil {
-		panic(err)
-	}
-
-	outFile, err := os.Create("output.png")
-	if err != nil {
-		panic(err)
-	}
-	defer outFile.Close()
-
-	writePng(outFile, img)
-}
-
-func writePng(w io.Writer, img image.Image) {
-	width := uint32(img.Bounds().Dx())
-	height := uint32(img.Bounds().Dy())
-	writeSignature(w)
-	writeChunkIhdr(w, width, height)
-	writeChunkIdat(w, width, height, img)
-	writeChunkIend(w)
-}
-
-func writeSignature(w io.Writer) {
-	sig := [8]uint8{
-		0x89,
-		0x50, // P
-		0x4E, // N
-		0x47, // G
-		0x0D, // CR
-		0x0A, // LF
-		0x1A, // EOF (^Z)
-		0x0A, // LF
-	}
-	binary.Write(w, binary.BigEndian, sig)
-}
-
-func writeChunkIhdr(w io.Writer, width, height uint32) {
-	var buf bytes.Buffer
-	binary.Write(&buf, binary.BigEndian, width)
-	binary.Write(&buf, binary.BigEndian, height)
-	binary.Write(&buf, binary.BigEndian, uint8(8))
-	binary.Write(&buf, binary.BigEndian, uint8(2))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-	binary.Write(&buf, binary.BigEndian, uint8(0))
-
-	writeChunk(w, "IHDR", buf.Bytes())
-}
-
-func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
-	var pixels bytes.Buffer
-	for y := uint32(0); y < height; y++ {
-		binary.Write(&pixels, binary.BigEndian, uint8(0))
-		for x := uint32(0); x < width; x++ {
-			r, g, b, _ := img.At(int(x), int(y)).RGBA()
-			binary.Write(&pixels, binary.BigEndian, uint8(r))
-			binary.Write(&pixels, binary.BigEndian, uint8(g))
-			binary.Write(&pixels, binary.BigEndian, uint8(b))
-		}
-	}
-
-	writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
-}
-
-func encodeZlib(data []byte) []byte {
-	var buf bytes.Buffer
-
-	binary.Write(&buf, binary.BigEndian, uint8(0x78))
-	binary.Write(&buf, binary.BigEndian, uint8(0x01))
-	blockSize := 65535
-	isFinalBlock := false
-	for i := 0; !isFinalBlock; i++ {
-		var block []byte
-		if len(data) <= (i+1)*blockSize {
-			block = data[i*blockSize:]
-			isFinalBlock = true
-		} else {
-			block = data[i*blockSize : (i+1)*blockSize]
-		}
-		binary.Write(&buf, binary.BigEndian, isFinalBlock)
-		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
-		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
-		binary.Write(&buf, binary.LittleEndian, block)
-	}
-	binary.Write(&buf, binary.BigEndian, adler32(data))
-
-	return buf.Bytes()
-}
-
-func writeChunkIend(w io.Writer) {
-	writeChunk(w, "IEND", nil)
-}
-
-func writeChunk(w io.Writer, chunkType string, data []byte) {
-	typeAndData := make([]byte, 0, len(chunkType)+len(data))
-	typeAndData = append(typeAndData, []byte(chunkType)...)
-	typeAndData = append(typeAndData, data...)
-
-	binary.Write(w, binary.BigEndian, uint32(len(data)))
-	binary.Write(w, binary.BigEndian, typeAndData)
-	binary.Write(w, binary.BigEndian, crc(typeAndData))
-}
-
-var (
-	crcTable         [256]uint32
-	crcTableComputed bool
-)
-
-func makeCrcTable() {
-	for n := 0; n < 256; n++ {
-		c := uint32(n)
-		for k := 0; k < 8; k++ {
-			if (c & 1) != 0 {
-				c = 0xEDB88320 ^ (c >> 1)
-			} else {
-				c = c >> 1
-			}
-		}
-		crcTable[n] = c
-	}
-	crcTableComputed = true
-}
-
-func updateCrc(crc uint32, buf []byte) uint32 {
-	if !crcTableComputed {
-		makeCrcTable()
-	}
-
-	c := crc
-	for n := 0; n < len(buf); n++ {
-		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
-	}
-	return c
-}
-
-func crc(buf []byte) uint32 {
-	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
-}
-
-const adler32Base = 65521
-
-func updateAdler32(adler uint32, buf []byte) uint32 {
-	s1 := adler & 0xFFFF
-	s2 := (adler >> 16) & 0xFFFF
-
-	for n := 0; n < len(buf); n++ {
-		s1 = (s1 + uint32(buf[n])) % adler32Base
-		s2 = (s2 + s1) % adler32Base
-	}
-	return (s2 << 16) + s1
-}
-
-func adler32(buf []byte) uint32 {
-	return updateAdler32(1, buf)
-}
+
package main
+
+import (
+	"bytes"
+	"encoding/binary"
+	"image"
+	_ "image/png"
+	"io"
+	"os"
+)
+
+func main() {
+	inFile, err := os.Open("input.png")
+	if err != nil {
+		panic(err)
+	}
+	defer inFile.Close()
+
+	img, _, err := image.Decode(inFile)
+	if err != nil {
+		panic(err)
+	}
+
+	outFile, err := os.Create("output.png")
+	if err != nil {
+		panic(err)
+	}
+	defer outFile.Close()
+
+	writePng(outFile, img)
+}
+
+func writePng(w io.Writer, img image.Image) {
+	width := uint32(img.Bounds().Dx())
+	height := uint32(img.Bounds().Dy())
+	writeSignature(w)
+	writeChunkIhdr(w, width, height)
+	writeChunkIdat(w, width, height, img)
+	writeChunkIend(w)
+}
+
+func writeSignature(w io.Writer) {
+	sig := [8]uint8{
+		0x89,
+		0x50, // P
+		0x4E, // N
+		0x47, // G
+		0x0D, // CR
+		0x0A, // LF
+		0x1A, // EOF (^Z)
+		0x0A, // LF
+	}
+	binary.Write(w, binary.BigEndian, sig)
+}
+
+func writeChunkIhdr(w io.Writer, width, height uint32) {
+	var buf bytes.Buffer
+	binary.Write(&buf, binary.BigEndian, width)
+	binary.Write(&buf, binary.BigEndian, height)
+	binary.Write(&buf, binary.BigEndian, uint8(8))
+	binary.Write(&buf, binary.BigEndian, uint8(2))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+	binary.Write(&buf, binary.BigEndian, uint8(0))
+
+	writeChunk(w, "IHDR", buf.Bytes())
+}
+
+func writeChunkIdat(w io.Writer, width, height uint32, img image.Image) {
+	var pixels bytes.Buffer
+	for y := uint32(0); y < height; y++ {
+		binary.Write(&pixels, binary.BigEndian, uint8(0))
+		for x := uint32(0); x < width; x++ {
+			r, g, b, _ := img.At(int(x), int(y)).RGBA()
+			binary.Write(&pixels, binary.BigEndian, uint8(r))
+			binary.Write(&pixels, binary.BigEndian, uint8(g))
+			binary.Write(&pixels, binary.BigEndian, uint8(b))
+		}
+	}
+
+	writeChunk(w, "IDAT", encodeZlib(pixels.Bytes()))
+}
+
+func encodeZlib(data []byte) []byte {
+	var buf bytes.Buffer
+
+	binary.Write(&buf, binary.BigEndian, uint8(0x78))
+	binary.Write(&buf, binary.BigEndian, uint8(0x01))
+	blockSize := 65535
+	isFinalBlock := false
+	for i := 0; !isFinalBlock; i++ {
+		var block []byte
+		if len(data) <= (i+1)*blockSize {
+			block = data[i*blockSize:]
+			isFinalBlock = true
+		} else {
+			block = data[i*blockSize : (i+1)*blockSize]
+		}
+		binary.Write(&buf, binary.BigEndian, isFinalBlock)
+		binary.Write(&buf, binary.LittleEndian, uint16(len(block)))
+		binary.Write(&buf, binary.LittleEndian, uint16(^len(block)))
+		binary.Write(&buf, binary.LittleEndian, block)
+	}
+	binary.Write(&buf, binary.BigEndian, adler32(data))
+
+	return buf.Bytes()
+}
+
+func writeChunkIend(w io.Writer) {
+	writeChunk(w, "IEND", nil)
+}
+
+func writeChunk(w io.Writer, chunkType string, data []byte) {
+	typeAndData := make([]byte, 0, len(chunkType)+len(data))
+	typeAndData = append(typeAndData, []byte(chunkType)...)
+	typeAndData = append(typeAndData, data...)
+
+	binary.Write(w, binary.BigEndian, uint32(len(data)))
+	binary.Write(w, binary.BigEndian, typeAndData)
+	binary.Write(w, binary.BigEndian, crc(typeAndData))
+}
+
+var (
+	crcTable         [256]uint32
+	crcTableComputed bool
+)
+
+func makeCrcTable() {
+	for n := 0; n < 256; n++ {
+		c := uint32(n)
+		for k := 0; k < 8; k++ {
+			if (c & 1) != 0 {
+				c = 0xEDB88320 ^ (c >> 1)
+			} else {
+				c = c >> 1
+			}
+		}
+		crcTable[n] = c
+	}
+	crcTableComputed = true
+}
+
+func updateCrc(crc uint32, buf []byte) uint32 {
+	if !crcTableComputed {
+		makeCrcTable()
+	}
+
+	c := crc
+	for n := 0; n < len(buf); n++ {
+		c = crcTable[(c^uint32(buf[n]))&0xFF] ^ (c >> 8)
+	}
+	return c
+}
+
+func crc(buf []byte) uint32 {
+	return updateCrc(0xFFFFFFFF, buf) ^ 0xFFFFFFFF
+}
+
+const adler32Base = 65521
+
+func updateAdler32(adler uint32, buf []byte) uint32 {
+	s1 := adler & 0xFFFF
+	s2 := (adler >> 16) & 0xFFFF
+
+	for n := 0; n < len(buf); n++ {
+		s1 = (s1 + uint32(buf[n])) % adler32Base
+		s2 = (s2 + s1) % adler32Base
+	}
+	return (s2 << 16) + s1
+}
+
+func adler32(buf []byte) uint32 {
+	return updateAdler32(1, buf)
+}
+
-- cgit v1.3-1-g0d28