reader.go (8528B)
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 if offset != fileHeaderLen+infoLen+256*4 { 195 return image.Config{}, 0, false, false, ErrUnsupported 196 } 197 _, err = io.ReadFull(r, b[:256*4]) 198 if err != nil { 199 return image.Config{}, 0, false, false, err 200 } 201 pcm := make(color.Palette, 256) 202 for i := range pcm { 203 // BMP images are stored in BGR order rather than RGB order. 204 // Every 4th byte is padding. 205 pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF} 206 } 207 return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, false, nil 208 case 24: 209 if offset != fileHeaderLen+infoLen { 210 return image.Config{}, 0, false, false, ErrUnsupported 211 } 212 return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, false, nil 213 case 32: 214 if offset != fileHeaderLen+infoLen { 215 return image.Config{}, 0, false, false, ErrUnsupported 216 } 217 // 32 bits per pixel is possibly RGBX (X is padding) or RGBA (A is 218 // alpha transparency). However, for BMP images, "Alpha is a 219 // poorly-documented and inconsistently-used feature" says 220 // https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc;l=621 221 // 222 // That goes on to say "BITMAPV3HEADER+ have an alpha bitmask in the 223 // info header... so we respect it at all times... [For earlier 224 // (smaller) headers we] ignore alpha in Windows V3 BMPs except inside 225 // ICO files". 226 // 227 // "Ignore" means to always set alpha to 0xFF (fully opaque): 228 // https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.h;l=272 229 // 230 // Confusingly, "Windows V3" does not correspond to BITMAPV3HEADER, but 231 // instead corresponds to the earlier (smaller) BITMAPINFOHEADER: 232 // https://source.chromium.org/chromium/chromium/src/+/bc0a792d7ebc587190d1a62ccddba10abeea274b:third_party/blink/renderer/platform/image-decoders/bmp/bmp_image_reader.cc;l=258 233 // 234 // This Go package does not support ICO files and the (infoLen > 235 // infoHeaderLen) condition distinguishes BITMAPINFOHEADER (40 bytes) 236 // vs later (larger) headers. 237 allowAlpha = infoLen > infoHeaderLen 238 return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, allowAlpha, nil 239 } 240 return image.Config{}, 0, false, false, ErrUnsupported 241 } 242 243 func init() { 244 image.RegisterFormat("bmp", "BM????\x00\x00\x00\x00", Decode, DecodeConfig) 245 }