diff --git a/CHANGELOG.md b/CHANGELOG.md index 022c23d8..22ea6515 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Changelog ## [Unreleased] +## Add +- Add support of 16-bit BMP. ## [3.6.0] - 2022-06-13 ### Add diff --git a/vips/bmp.go b/vips/bmp.go index 9cc9ca62..08de3c8d 100644 --- a/vips/bmp.go +++ b/vips/bmp.go @@ -314,6 +314,52 @@ func (img *Image) decodeBmpRGB(r io.Reader, width, height, bands int, topDown, n return nil } +// decodeBmpRGB16 reads a 16 bit-per-pixel BMP image from r. +// If topDown is false, the image rows will be read bottom-up. +func (img *Image) decodeBmpRGB16(r io.Reader, width, height int, topDown, bmp565 bool) error { + tmp, imgData, err := prepareBmpCanvas(width, height, 3) + if err != nil { + return err + } + + defer bmpClearOnPanic(&tmp) + + // Each row is 4-byte aligned. + b := make([]byte, (2*width+3)&^3) + + y0, y1, yDelta := height-1, -1, -1 + if topDown { + y0, y1, yDelta = 0, height, +1 + } + + stride := width * 3 + + for y := y0; y != y1; y += yDelta { + if _, err = io.ReadFull(r, b); err != nil { + C.clear_image(&tmp) + return err + } + + p := imgData[y*stride : (y+1)*stride] + for i, j := 0, 0; i < len(p); i, j = i+3, j+2 { + pixel := readUint16(b[j:]) + + if bmp565 { + p[i+0] = uint8((pixel&0xF800)>>11) << 3 + p[i+1] = uint8((pixel&0x7E0)>>5) << 2 + } else { + p[i+0] = uint8((pixel&0x7C00)>>10) << 3 + p[i+1] = uint8((pixel&0x3E0)>>5) << 3 + } + p[i+2] = uint8(pixel&0x1F) << 3 + } + } + + C.swap_and_clear(&img.VipsImage, tmp) + + return nil +} + func (img *Image) loadBmp(data []byte, noAlpha bool) error { // We only support those BMP images that are a BITMAPFILEHEADER // immediately followed by a BITMAPINFOHEADER. @@ -369,18 +415,41 @@ func (img *Image) loadBmp(data []byte, noAlpha bool) error { return errBmpUnsupported } - // if compression is set to BITFIELDS, but the bitmask is set to the default bitmask - // that would be used if compression was set to 0, we can continue as if compression was 0 - if compression == 3 && infoLen > infoHeaderLen && - readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 && - readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 { - compression = 0 - } - rle := false - if compression == 1 && bpp == 8 || compression == 2 && bpp == 4 { + bmp565 := false + + switch { + case compression == 0: + // Go ahead + case compression == 1 && bpp == 8 || compression == 2 && bpp == 4: rle = true - } else if compression != 0 { + case compression == 3 && infoLen >= infoHeaderLen: + if infoLen == infoHeaderLen { + // Color mask is stored after the info header + if _, err := io.ReadFull(r, b[54:66]); err != nil { + if err == io.EOF { + err = io.ErrUnexpectedEOF + } + return err + } + } + + rmask := readUint32(b[54:58]) + gmask := readUint32(b[58:62]) + bmask := readUint32(b[62:66]) + amask := readUint32(b[66:70]) + + switch { + case bpp == 16 && rmask == 0xF800 && gmask == 0x7E0 && bmask == 0x1F: + bmp565 = true + case bpp == 16 && rmask == 0x7C00 && gmask == 0x3E0 && bmask == 0x1F: + // Go ahead, it's a regular 16 bit image + case bpp == 32 && rmask == 0xff0000 && gmask == 0xff00 && bmask == 0xff && amask == 0xff000000: + // Go ahead, it's a regular 32-bit image + default: + return errBmpUnsupported + } + default: return errBmpUnsupported } @@ -415,6 +484,8 @@ func (img *Image) loadBmp(data []byte, noAlpha bool) error { switch bpp { case 1, 2, 4, 8: return img.decodeBmpPaletted(r, width, height, int(bpp), palette, topDown) + case 16: + return img.decodeBmpRGB16(r, width, height, topDown, bmp565) case 24: return img.decodeBmpRGB(r, width, height, 3, topDown, true) case 32: