git.lucas.co / go_mono
git clone https://git.lucas.co/go_mono.git

commit864256f267a095420f1f2e6fb3e2462ff9150703
parentde306d5329
authorChaiShushan <[email protected]>
date2013-09-10 17:19
go.image/bmp: add Encode

R=nigeltao
CC=golang-dev
https://golang.org/cl/13407045

 bmp/reader.go      |   2 +-
 bmp/writer.go      | 158 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 bmp/writer_test.go |  56 +++++++++++++++++++
 3 files changed, 215 insertions(+), 1 deletion(-)

diff --git a/bmp/reader.go b/bmp/reader.go
index 4799d15..a124018 100644
--- a/bmp/reader.go
+++ b/bmp/reader.go
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Package bmp implements a BMP image decoder.
+// Package bmp implements a BMP image decoder and encoder.
 //
 // The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
 package bmp
diff --git a/bmp/writer.go b/bmp/writer.go
new file mode 100644
index 0000000..4e75e56
--- /dev/null
+++ b/bmp/writer.go
@@ -0,0 +1,158 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bmp
+
+import (
+	"encoding/binary"
+	"image"
+	"io"
+)
+
+type header struct {
+	sigBM           [2]byte
+	fileSize        uint32
+	resverved       [2]uint16
+	pixOffset       uint32
+	dibHeaderSize   uint32
+	width           uint32
+	height          uint32
+	colorPlane      uint16
+	bpp             uint16
+	compression     uint32
+	imageSize       uint32
+	xPixelsPerMeter uint32
+	yPixelsPerMeter uint32
+	colorUse        uint32
+	colorImportant  uint32
+}
+
+func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
+	var padding []byte
+	if dx < step {
+		padding = make([]byte, step-dx)
+	}
+	for y := dy - 1; y >= 0; y-- {
+		min := y*stride + 0
+		max := y*stride + dx
+		if _, err := w.Write(pix[min:max]); err != nil {
+			return err
+		}
+		if padding != nil {
+			if _, err := w.Write(padding); err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
+	buf := make([]byte, step)
+	for y := dy - 1; y >= 0; y-- {
+		min := y*stride + 0
+		max := y*stride + dx*4
+		off := 0
+		for i := min; i < max; i += 4 {
+			buf[off+2] = pix[i+0]
+			buf[off+1] = pix[i+1]
+			buf[off+0] = pix[i+2]
+			off += 3
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func encode(w io.Writer, m image.Image, step int) error {
+	b := m.Bounds()
+	buf := make([]byte, step)
+	for y := b.Max.Y - 1; y >= b.Min.Y; y-- {
+		off := 0
+		for x := b.Min.X; x < b.Max.X; x++ {
+			r, g, b, _ := m.At(x, y).RGBA()
+			buf[off+2] = byte(r >> 8)
+			buf[off+1] = byte(g >> 8)
+			buf[off+0] = byte(b >> 8)
+			off += 3
+		}
+		if _, err := w.Write(buf); err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+// Encode writes the image m to w in BMP format.
+func Encode(w io.Writer, m image.Image) error {
+	d := m.Bounds().Size()
+	h := &header{
+		sigBM:         [2]byte{'B', 'M'},
+		fileSize:      14 + 40,
+		pixOffset:     14 + 40,
+		dibHeaderSize: 40,
+		width:         uint32(d.X),
+		height:        uint32(d.Y),
+		colorPlane:    1,
+	}
+
+	var step int
+	var palette []byte
+	switch m := m.(type) {
+	case *image.Gray:
+		step = (d.X + 3) &^ 3
+		palette = make([]byte, 1024)
+		for i := 0; i < 256; i++ {
+			palette[i*4+0] = uint8(i)
+			palette[i*4+1] = uint8(i)
+			palette[i*4+2] = uint8(i)
+			palette[i*4+3] = 0xFF
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += uint32(len(palette)) + h.imageSize
+		h.pixOffset += uint32(len(palette))
+		h.bpp = 8
+
+	case *image.Paletted:
+		step = (d.X + 3) &^ 3
+		palette = make([]byte, 1024)
+		for i := 0; i < len(m.Palette) && i < 256; i++ {
+			r, g, b, _ := m.Palette[i].RGBA()
+			palette[i*4+0] = uint8(b >> 8)
+			palette[i*4+1] = uint8(g >> 8)
+			palette[i*4+2] = uint8(r >> 8)
+			palette[i*4+3] = 0xFF
+		}
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += uint32(len(palette)) + h.imageSize
+		h.pixOffset += uint32(len(palette))
+		h.bpp = 8
+	default:
+		step = (3*d.X + 3) &^ 3
+		h.imageSize = uint32(d.Y * step)
+		h.fileSize += h.imageSize
+		h.bpp = 24
+	}
+
+	if err := binary.Write(w, binary.LittleEndian, h); err != nil {
+		return err
+	}
+	if palette != nil {
+		if err := binary.Write(w, binary.LittleEndian, palette); err != nil {
+			return err
+		}
+	}
+
+	switch m := m.(type) {
+	case *image.Gray:
+		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
+	case *image.Paletted:
+		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
+	case *image.RGBA:
+		return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step)
+	}
+	return encode(w, m, step)
+}
diff --git a/bmp/writer_test.go b/bmp/writer_test.go
new file mode 100644
index 0000000..cf1dbc5
--- /dev/null
+++ b/bmp/writer_test.go
@@ -0,0 +1,56 @@
+// Copyright 2013 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package bmp
+
+import (
+	"bytes"
+	"image"
+	"io/ioutil"
+	"os"
+	"testing"
+)
+
+func openImage(filename string) (image.Image, error) {
+	f, err := os.Open(testdataDir + filename)
+	if err != nil {
+		return nil, err
+	}
+	defer f.Close()
+	return Decode(f)
+}
+
+func TestEncode(t *testing.T) {
+	img0, err := openImage("video-001.bmp")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	buf := new(bytes.Buffer)
+	err = Encode(buf, img0)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	img1, err := Decode(buf)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	compare(t, img0, img1)
+}
+
+// BenchmarkEncode benchmarks the encoding of an image.
+func BenchmarkEncode(b *testing.B) {
+	img, err := openImage("video-001.bmp")
+	if err != nil {
+		b.Fatal(err)
+	}
+	s := img.Bounds().Size()
+	b.SetBytes(int64(s.X * s.Y * 4))
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		Encode(ioutil.Discard, img)
+	}
+}