// 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" "errors" "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, opaque bool) error { buf := make([]byte, step) if opaque { 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 } } } else { 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 { a := uint32(pix[i+3]) if a == 0 { buf[off+2] = 0 buf[off+1] = 0 buf[off+0] = 0 buf[off+3] = 0 off += 4 continue } else if a == 0xff { buf[off+2] = pix[i+0] buf[off+1] = pix[i+1] buf[off+0] = pix[i+2] buf[off+3] = 0xff off += 4 continue } buf[off+2] = uint8(((uint32(pix[i+0]) * 0xffff) / a) >> 8) buf[off+1] = uint8(((uint32(pix[i+1]) * 0xffff) / a) >> 8) buf[off+0] = uint8(((uint32(pix[i+2]) * 0xffff) / a) >> 8) buf[off+3] = uint8(a) off += 4 } if _, err := w.Write(buf); err != nil { return err } } } return nil } func encodeNRGBA(w io.Writer, pix []uint8, dx, dy, stride, step int, opaque bool) error { buf := make([]byte, step) if opaque { 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 } } } else { 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] buf[off+3] = pix[i+3] off += 4 } 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() if d.X < 0 || d.Y < 0 { return errors.New("bmp: negative bounds") } 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 var opaque bool 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 case *image.RGBA: opaque = m.Opaque() if opaque { step = (3*d.X + 3) &^ 3 h.bpp = 24 } else { step = 4 * d.X h.bpp = 32 } h.imageSize = uint32(d.Y * step) h.fileSize += h.imageSize case *image.NRGBA: opaque = m.Opaque() if opaque { step = (3*d.X + 3) &^ 3 h.bpp = 24 } else { step = 4 * d.X h.bpp = 32 } h.imageSize = uint32(d.Y * step) h.fileSize += h.imageSize 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 } } if d.X == 0 || d.Y == 0 { return nil } 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, opaque) case *image.NRGBA: return encodeNRGBA(w, m.Pix, d.X, d.Y, m.Stride, step, opaque) } return encode(w, m, step) }