1// Copyright 2013 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package bmp
6
7import (
8	"encoding/binary"
9	"errors"
10	"image"
11	"io"
12)
13
14type header struct {
15	sigBM           [2]byte
16	fileSize        uint32
17	resverved       [2]uint16
18	pixOffset       uint32
19	dibHeaderSize   uint32
20	width           uint32
21	height          uint32
22	colorPlane      uint16
23	bpp             uint16
24	compression     uint32
25	imageSize       uint32
26	xPixelsPerMeter uint32
27	yPixelsPerMeter uint32
28	colorUse        uint32
29	colorImportant  uint32
30}
31
32func encodePaletted(w io.Writer, pix []uint8, dx, dy, stride, step int) error {
33	var padding []byte
34	if dx < step {
35		padding = make([]byte, step-dx)
36	}
37	for y := dy - 1; y >= 0; y-- {
38		min := y*stride + 0
39		max := y*stride + dx
40		if _, err := w.Write(pix[min:max]); err != nil {
41			return err
42		}
43		if padding != nil {
44			if _, err := w.Write(padding); err != nil {
45				return err
46			}
47		}
48	}
49	return nil
50}
51
52func encodeRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
53	buf := make([]byte, step)
54	if opaque {
55		for y := dy - 1; y >= 0; y-- {
56			min := y*stride + 0
57			max := y*stride + dx*4
58			off := 0
59			for i := min; i < max; i += 4 {
60				buf[off+2] = pix[i+0]
61				buf[off+1] = pix[i+1]
62				buf[off+0] = pix[i+2]
63				off += 3
64			}
65			if _, err := w.Write(buf); err != nil {
66				return err
67			}
68		}
69	} else {
70		for y := dy - 1; y >= 0; y-- {
71			min := y*stride + 0
72			max := y*stride + dx*4
73			off := 0
74			for i := min; i < max; i += 4 {
75				a := uint32(pix[i+3])
76				if a == 0 {
77					buf[off+2] = 0
78					buf[off+1] = 0
79					buf[off+0] = 0
80					buf[off+3] = 0
81					off += 4
82					continue
83				} else if a == 0xff {
84					buf[off+2] = pix[i+0]
85					buf[off+1] = pix[i+1]
86					buf[off+0] = pix[i+2]
87					buf[off+3] = 0xff
88					off += 4
89					continue
90				}
91				buf[off+2] = uint8(((uint32(pix[i+0]) * 0xffff) / a) >> 8)
92				buf[off+1] = uint8(((uint32(pix[i+1]) * 0xffff) / a) >> 8)
93				buf[off+0] = uint8(((uint32(pix[i+2]) * 0xffff) / a) >> 8)
94				buf[off+3] = uint8(a)
95				off += 4
96			}
97			if _, err := w.Write(buf); err != nil {
98				return err
99			}
100		}
101	}
102	return nil
103}
104
105func encodeNRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error {
106	buf := make([]byte, step)
107	if opaque {
108		for y := dy - 1; y >= 0; y-- {
109			min := y*stride + 0
110			max := y*stride + dx*4
111			off := 0
112			for i := min; i < max; i += 4 {
113				buf[off+2] = pix[i+0]
114				buf[off+1] = pix[i+1]
115				buf[off+0] = pix[i+2]
116				off += 3
117			}
118			if _, err := w.Write(buf); err != nil {
119				return err
120			}
121		}
122	} else {
123		for y := dy - 1; y >= 0; y-- {
124			min := y*stride + 0
125			max := y*stride + dx*4
126			off := 0
127			for i := min; i < max; i += 4 {
128				buf[off+2] = pix[i+0]
129				buf[off+1] = pix[i+1]
130				buf[off+0] = pix[i+2]
131				buf[off+3] = pix[i+3]
132				off += 4
133			}
134			if _, err := w.Write(buf); err != nil {
135				return err
136			}
137		}
138	}
139	return nil
140}
141
142func encode(w io.Writer, m image.Image, step int) error {
143	b := m.Bounds()
144	buf := make([]byte, step)
145	for y := b.Max.Y - 1; y >= b.Min.Y; y-- {
146		off := 0
147		for x := b.Min.X; x < b.Max.X; x++ {
148			r, g, b, _ := m.At(x, y).RGBA()
149			buf[off+2] = byte(r >> 8)
150			buf[off+1] = byte(g >> 8)
151			buf[off+0] = byte(b >> 8)
152			off += 3
153		}
154		if _, err := w.Write(buf); err != nil {
155			return err
156		}
157	}
158	return nil
159}
160
161// Encode writes the image m to w in BMP format.
162func Encode(w io.Writer, m image.Image) error {
163	d := m.Bounds().Size()
164	if d.X < 0 || d.Y < 0 {
165		return errors.New("bmp: negative bounds")
166	}
167	h := &header{
168		sigBM:         [2]byte{'B', 'M'},
169		fileSize:      14 + 40,
170		pixOffset:     14 + 40,
171		dibHeaderSize: 40,
172		width:         uint32(d.X),
173		height:        uint32(d.Y),
174		colorPlane:    1,
175	}
176
177	var step int
178	var palette []byte
179	var opaque bool
180	switch m := m.(type) {
181	case *image.Gray:
182		step = (d.X + 3) &^ 3
183		palette = make([]byte, 1024)
184		for i := 0; i < 256; i++ {
185			palette[i*4+0] = uint8(i)
186			palette[i*4+1] = uint8(i)
187			palette[i*4+2] = uint8(i)
188			palette[i*4+3] = 0xFF
189		}
190		h.imageSize = uint32(d.Y * step)
191		h.fileSize += uint32(len(palette)) + h.imageSize
192		h.pixOffset += uint32(len(palette))
193		h.bpp = 8
194
195	case *image.Paletted:
196		step = (d.X + 3) &^ 3
197		palette = make([]byte, 1024)
198		for i := 0; i < len(m.Palette) && i < 256; i++ {
199			r, g, b, _ := m.Palette[i].RGBA()
200			palette[i*4+0] = uint8(b >> 8)
201			palette[i*4+1] = uint8(g >> 8)
202			palette[i*4+2] = uint8(r >> 8)
203			palette[i*4+3] = 0xFF
204		}
205		h.imageSize = uint32(d.Y * step)
206		h.fileSize += uint32(len(palette)) + h.imageSize
207		h.pixOffset += uint32(len(palette))
208		h.bpp = 8
209	case *image.RGBA:
210		opaque = m.Opaque()
211		if opaque {
212			step = (3*d.X + 3) &^ 3
213			h.bpp = 24
214		} else {
215			step = 4 * d.X
216			h.bpp = 32
217		}
218		h.imageSize = uint32(d.Y * step)
219		h.fileSize += h.imageSize
220	case *image.NRGBA:
221		opaque = m.Opaque()
222		if opaque {
223			step = (3*d.X + 3) &^ 3
224			h.bpp = 24
225		} else {
226			step = 4 * d.X
227			h.bpp = 32
228		}
229		h.imageSize = uint32(d.Y * step)
230		h.fileSize += h.imageSize
231	default:
232		step = (3*d.X + 3) &^ 3
233		h.imageSize = uint32(d.Y * step)
234		h.fileSize += h.imageSize
235		h.bpp = 24
236	}
237
238	if err := binary.Write(w, binary.LittleEndian, h); err != nil {
239		return err
240	}
241	if palette != nil {
242		if err := binary.Write(w, binary.LittleEndian, palette); err != nil {
243			return err
244		}
245	}
246
247	if d.X == 0 || d.Y == 0 {
248		return nil
249	}
250
251	switch m := m.(type) {
252	case *image.Gray:
253		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
254	case *image.Paletted:
255		return encodePaletted(w, m.Pix, d.X, d.Y, m.Stride, step)
256	case *image.RGBA:
257		return encodeRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
258	case *image.NRGBA:
259		return encodeNRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque)
260	}
261	return encode(w, m, step)
262}
263