...

Source file src/golang.org/x/image/bmp/reader.go

Documentation: golang.org/x/image/bmp

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

View as plain text