Skip to content

Commit 4af98f4

Browse files
tuyenpthustnigeltao
authored andcommitted
bmp: decode 1, 2 and 4 bits-per-pixel palettes
Specification: https://www.digicamsoft.com/bmp/bmp.html Fixes golang/go#29711 Fixes golang/go#58005 Change-Id: Ia6f7ac61af26c6379d9e610261070dfc9a9001b3 GitHub-Last-Rev: 12c6ab1 GitHub-Pull-Request: #22 Reviewed-on: https://go-review.googlesource.com/c/image/+/636975 Reviewed-by: Dmitri Shuralyov <[email protected]> Reviewed-by: Nigel Tao <[email protected]> Reviewed-by: Nigel Tao <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent aed2316 commit 4af98f4

File tree

8 files changed

+26
-16
lines changed

8 files changed

+26
-16
lines changed

bmp/reader.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,28 +26,35 @@ func readUint32(b []byte) uint32 {
2626
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
2727
}
2828

29-
// decodePaletted reads an 8 bit-per-pixel BMP image from r.
29+
// decodePaletted reads a 1, 2, 4 or 8 bit-per-pixel BMP image from r.
3030
// 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) {
31+
func decodePaletted(r io.Reader, c image.Config, topDown bool, bpp int) (image.Image, error) {
3232
paletted := image.NewPaletted(image.Rect(0, 0, c.Width, c.Height), c.ColorModel.(color.Palette))
3333
if c.Width == 0 || c.Height == 0 {
3434
return paletted, nil
3535
}
36-
var tmp [4]byte
3736
y0, y1, yDelta := c.Height-1, -1, -1
3837
if topDown {
3938
y0, y1, yDelta = 0, c.Height, +1
4039
}
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+
4146
for y := y0; y != y1; y += yDelta {
4247
p := paletted.Pix[y*paletted.Stride : y*paletted.Stride+c.Width]
43-
if _, err := io.ReadFull(r, p); err != nil {
48+
if _, err := io.ReadFull(r, b); err != nil {
4449
return nil, err
4550
}
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+
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
5158
}
5259
}
5360
}
@@ -118,8 +125,8 @@ func Decode(r io.Reader) (image.Image, error) {
118125
return nil, err
119126
}
120127
switch bpp {
121-
case 8:
122-
return decodePaletted(r, c, topDown)
128+
case 1, 2, 4, 8:
129+
return decodePaletted(r, c, topDown, bpp)
123130
case 24:
124131
return decodeRGB(r, c, topDown)
125132
case 32:
@@ -190,12 +197,12 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
190197
return image.Config{}, 0, false, false, ErrUnsupported
191198
}
192199
switch bpp {
193-
case 8:
200+
case 1, 2, 4, 8:
194201
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.
202+
196203
if colorUsed == 0 {
197-
colorUsed = 256
198-
} else if colorUsed > 256 {
204+
colorUsed = 1 << bpp
205+
} else if colorUsed > (1 << bpp) {
199206
return image.Config{}, 0, false, false, ErrUnsupported
200207
}
201208

@@ -212,7 +219,7 @@ func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown b
212219
// Every 4th byte is padding.
213220
pcm[i] = color.RGBA{b[4*i+2], b[4*i+1], b[4*i+0], 0xFF}
214221
}
215-
return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, false, nil
222+
return image.Config{ColorModel: pcm, Width: width, Height: height}, int(bpp), topDown, false, nil
216223
case 24:
217224
if offset != fileHeaderLen+infoLen {
218225
return image.Config{}, 0, false, false, ErrUnsupported

bmp/reader_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,9 @@ func TestDecode(t *testing.T) {
4646
"video-001",
4747
"yellow_rose-small",
4848
"yellow_rose-small-v5",
49+
"bmp_1bpp",
50+
"bmp_4bpp",
51+
"bmp_8bpp",
4952
}
5053

5154
for _, tc := range testCases {

testdata/bmp_1bpp.bmp

938 Bytes
Binary file not shown.

testdata/bmp_1bpp.png

3.85 KB
Loading

testdata/bmp_4bpp.bmp

3.54 KB
Binary file not shown.

testdata/bmp_4bpp.png

1.11 KB
Loading

testdata/bmp_8bpp.bmp

6.96 KB
Binary file not shown.

testdata/bmp_8bpp.png

1.11 KB
Loading

0 commit comments

Comments
 (0)