From d30dfc89bf1b673b2fdc0638766b930adaec228c Mon Sep 17 00:00:00 2001
From: nsfisis 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)
+}
+
以降は、writeSignature や writeChunkIhdr などを実装していく。
@@ -189,21 +190,23 @@
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 バイトを書き込む。
@@ -238,55 +241,59 @@
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))
+}
+
仕様どおり、chunkType と data から CRC を計算し、data の長さと合わせて書き込んでいる。PNG では基本的に big endian を使うことに注意する。
@@ -372,20 +379,22 @@
今回ほとんどのデータは決め打ちするので、データに応じて変わるのは 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())
+}
+ 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 の実装をサボるために使う。 @@ -473,30 +484,32 @@ 実際にこの手抜き 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()
+}
+ 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()))
+}
+ IEND くらいなので実装は簡単:
- func writeChunkIend(w io.Writer) {
- writeChunk(w, "IEND", nil)
-}
+ func writeChunkIend(w io.Writer) {
+ writeChunk(w, "IEND", nil)
+}
+ 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)
+}
+