reader.go raw

   1  // Copyright 2011 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  
   5  // Package bmp implements a BMP image decoder and encoder.
   6  //
   7  // The BMP specification is at http://www.digicamsoft.com/bmp/bmp.html.
   8  package bmp // import "golang.org/x/image/bmp"
   9  
  10  import (
  11  	"errors"
  12  	"image"
  13  	"image/color"
  14  	"io"
  15  )
  16  
  17  // ErrUnsupported means that the input BMP image uses a valid but unsupported
  18  // feature.
  19  var ErrUnsupported = errors.New("bmp: unsupported BMP image")
  20  
  21  func readUint16(b []byte) uint16 {
  22  	return uint16(b[0]) | uint16(b[1])<<8
  23  }
  24  
  25  func readUint32(b []byte) uint32 {
  26  	return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
  27  }
  28  
  29  // decodePaletted reads a 1, 2, 4 or 8 bit-per-pixel BMP image from r.
  30  // If topDown is false, the image rows will be read bottom-up.
  31  func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.Image, error) {
  32  	paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
  33  	if c.Width == 0 || c.Height == 0 {
  34  		return paletted, nil
  35  	}
  36  	y0, y1, yDelta := c.Height-1, -1, -1
  37  	if topDown {
  38  		y0, y1, yDelta = 0, c.Height, +1
  39  	}
  40  
  41  	pixelsPerByte := 8 / bpp
  42  	// Pad up to ensure each row is 4-bytes aligned.
  43  	bytesPerRow := ((c.Width+pixelsPerByte-1)/pixelsPerByte + 3) &^ 3
  44  	b := make([]byte, bytesPerRow)
  45  
  46  	for y := y0; y != y1; y += yDelta {
  47  		p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
  48  		if _, err := io.ReadFull(r, b); err != nil {
  49  			return nil, err
  50  		}
  51  		byteIndex, bitIndex, mask := 0, 8, byte((1<<bpp)-1)
  52  		for pixIndex := 0; pixIndex < c.Width; pixIndex++ {
  53  			bitIndex -= bpp
  54  			p[pixIndex] = (b[byteIndex]) >> bitIndex & mask
  55  			if bitIndex == 0 {
  56  				byteIndex++
  57  				bitIndex = 8
  58  			}
  59  		}
  60  	}
  61  	return paletted, nil
  62  }
  63  
  64  // decodeRGB reads a 24 bit-per-pixel BMP image from r.
  65  // If topDown is false, the image rows will be read bottom-up.
  66  func decodeRGB(r io.Reader, c image.Config, topDown bool) (image.Image, error) {
  67  	rgba := image.NewRGBA(image.Rect(0, 0, c.Width, c.Height))
  68  	if c.Width == 0 || c.Height == 0 {
  69  		return rgba, nil
  70  	}
  71  	// There are 3 bytes per pixel, and each row is 4-byte aligned.
  72  	b := make([]byte, (3*c.Width+3)&^3)
  73  	y0, y1, yDelta := c.Height-1, -1, -1
  74  	if topDown {
  75  		y0, y1, yDelta = 0, c.Height, +1
  76  	}
  77  	for y := y0; y != y1; y += yDelta {
  78  		if _, err := io.ReadFull(r, b); err != nil {
  79  			return nil, err
  80  		}
  81  		p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
  82  		for i, j := 0, 0; i < len(p); i, j = i+4, j+3 {
  83  			// BMP images are stored in BGR order rather than RGB order.
  84  			p[i+0] = b[j+2]
  85  			p[i+1] = b[j+1]
  86  			p[i+2] = b[j+0]
  87  			p[i+3] = 0xFF
  88  		}
  89  	}
  90  	return rgba, nil
  91  }
  92  
  93  // decodeNRGBA reads a 32 bit-per-pixel BMP image from r.
  94  // If topDown is false, the image rows will be read bottom-up.
  95  func decodeNRGBA(r io.Reader, c image.Config, topDown, allowAlpha bool) (image.Image, error) {
  96  	rgba := image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height))
  97  	if c.Width == 0 || c.Height == 0 {
  98  		return rgba, nil
  99  	}
 100  	y0, y1, yDelta := c.Height-1, -1, -1
 101  	if topDown {
 102  		y0, y1, yDelta = 0, c.Height, +1
 103  	}
 104  	for y := y0; y != y1; y += yDelta {
 105  		p := rgba.Pix[y*rgba.Stride : y*rgba.Stride+c.Width*4]
 106  		if _, err := io.ReadFull(r, p); err != nil {
 107  			return nil, err
 108  		}
 109  		for i := 0; i < len(p); i += 4 {
 110  			// BMP images are stored in BGRA order rather than RGBA order.
 111  			p[i+0], p[i+2] = p[i+2], p[i+0]
 112  			if !allowAlpha {
 113  				p[i+3] = 0xFF
 114  			}
 115  		}
 116  	}
 117  	return rgba, nil
 118  }
 119  
 120  // Decode reads a BMP image from r and returns it as an image.Image.
 121  // Limitation: The file must be 8, 24 or 32 bits per pixel.
 122  func Decode(r io.Reader) (image.Image, error) {
 123  	c, bpp, topDown, allowAlpha, err := decodeConfig(r)
 124  	if err != nil {
 125  		return nil, err
 126  	}
 127  	switch bpp {
 128  	case 1, 2, 4, 8:
 129  		return decodePaletted(r, c, topDown, bpp)
 130  	case 24:
 131  		return decodeRGB(r, c, topDown)
 132  	case 32:
 133  		return decodeNRGBA(r, c, topDown, allowAlpha)
 134  	}
 135  	panic("unreachable")
 136  }
 137  
 138  // DecodeConfig returns the color model and dimensions of a BMP image without
 139  // decoding the entire image.
 140  // Limitation: The file must be 8, 24 or 32 bits per pixel.
 141  func DecodeConfig(r io.Reader) (image.Config, error) {
 142  	config, _, _, _, err := decodeConfig(r)
 143  	return config, err
 144  }
 145  
 146  func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, allowAlpha bool, err error) {
 147  	// We only support those BMP images with one of the following DIB headers:
 148  	// - BITMAPINFOHEADER (40 bytes)
 149  	// - BITMAPV4HEADER (108 bytes)
 150  	// - BITMAPV5HEADER (124 bytes)
 151  	const (
 152  		fileHeaderLen   = 14
 153  		infoHeaderLen   = 40
 154  		v4InfoHeaderLen = 108
 155  		v5InfoHeaderLen = 124
 156  	)
 157  	var b [1024]byte
 158  	if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); err != nil {
 159  		if err == io.EOF {
 160  			err = io.ErrUnexpectedEOF
 161  		}
 162  		return image.Config{}, 0, false, false, err
 163  	}
 164  	if string(b[:2]) != "BM" {
 165  		return image.Config{}, 0, false, false, errors.New("bmp: invalid format")
 166  	}
 167  	offset := readUint32(b[10:14])
 168  	infoLen := readUint32(b[14:18])
 169  	if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen {
 170  		return image.Config{}, 0, false, false, ErrUnsupported
 171  	}
 172  	if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil {
 173  		if err == io.EOF {
 174  			err = io.ErrUnexpectedEOF
 175  		}
 176  		return image.Config{}, 0, false, false, err
 177  	}
 178  	width := int(int32(readUint32(b[18:22])))
 179  	height := int(int32(readUint32(b[22:26])))
 180  	if height < 0 {
 181  		height, topDown = -height, true
 182  	}
 183  	if width < 0 || height < 0 {
 184  		return image.Config{}, 0, false, false, ErrUnsupported
 185  	}
 186  	// We only support 1 plane and 8, 24 or 32 bits per pixel and no
 187  	// compression.
 188  	planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
 189  	// if compression is set to BI_BITFIELDS, but the bitmask is set to the default bitmask
 190  	// that would be used if compression was set to 0, we can continue as if compression was 0
 191  	if compression == 3 && infoLen > infoHeaderLen &&
 192  		readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 &&
 193  		readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 {
 194  		compression = 0
 195  	}
 196  	if planes != 1 || compression != 0 {
 197  		return image.Config{}, 0, false, false, ErrUnsupported
 198  	}
 199  	switch bpp {
 200  	case 1, 2, 4, 8:
 201  		colorUsed := readUint32(b[46:50])
 202  
 203  		if colorUsed == 0 {
 204  			colorUsed = 1 << bpp
 205  		} else if colorUsed > (1 << bpp) {
 206  			return image.Config{}, 0, false, false, ErrUnsupported
 207  		}
 208  
 209  		if offset != fileHeaderLen+infoLen+colorUsed*4 {
 210  			return image.Config{}, 0, false, false, ErrUnsupported
 211  		}
 212  		_, err = io.ReadFull(r, b[:colorUsed*4])
 213  		if err != nil {
 214  			return image.Config{}, 0, false, false, err
 215  		}
 216  		pcm := make(color.Palette, colorUsed)
 217  		for i := range pcm {
 218  			// BMP images are stored in BGR order rather than RGB order.
 219  			// Every 4th byte is padding.
 220  			pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
 221  		}
 222  		return image.Config{ColorModel: pcm, Width: width, Height: height}, int(bpp), topDown, false, nil
 223  	case 24:
 224  		if offset != fileHeaderLen+infoLen {
 225  			return image.Config{}, 0, false, false, ErrUnsupported
 226  		}
 227  		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, false, nil
 228  	case 32:
 229  		if offset != fileHeaderLen+infoLen {
 230  			return image.Config{}, 0, false, false, ErrUnsupported
 231  		}
 232  		// 32 bits per pixel is possibly RGBX (X is padding) or RGBA (A is
 233  		// alpha transparency). However, for BMP images, "Alpha is a
 234  		// poorly-documented and inconsistently-used feature" says
 235  		// https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc;l=621
 236  		//
 237  		// That goes on to say "BITMAPV3HEADER+ have an alpha bitmask in the
 238  		// info header... so we respect it at all times... [For earlier
 239  		// (smaller) headers we] ignore alpha in Windows V3 BMPs except inside
 240  		// ICO files".
 241  		//
 242  		// "Ignore" means to always set alpha to 0xFF (fully opaque):
 243  		// https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h;l=272
 244  		//
 245  		// Confusingly, "Windows V3" does not correspond to BITMAPV3HEADER, but
 246  		// instead corresponds to the earlier (smaller) BITMAPINFOHEADER:
 247  		// https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc;l=258
 248  		//
 249  		// This Go package does not support ICO files and the (infoLen >
 250  		// infoHeaderLen) condition distinguishes BITMAPINFOHEADER (40 bytes)
 251  		// vs later (larger) headers.
 252  		allowAlpha = infoLen > infoHeaderLen
 253  		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, allowAlpha, nil
 254  	}
 255  	return image.Config{}, 0, false, false, ErrUnsupported
 256  }
 257  
 258  func init() {
 259  	image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig)
 260  }
 261