mirror of
https://github.com/imgproxy/imgproxy.git
synced 2026-06-18 17:43:40 +02:00
Add support of RLE-encoded BMP
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
## Add
|
||||
- Add support of RLE-encoded BMP.
|
||||
|
||||
## [3.4.0] - 2022-04-07
|
||||
### Add
|
||||
|
||||
+171
-39
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/imgproxy/imgproxy/v3/imagedata"
|
||||
"github.com/imgproxy/imgproxy/v3/imagetype"
|
||||
"github.com/imgproxy/imgproxy/v3/imath"
|
||||
)
|
||||
|
||||
type bmpHeader struct {
|
||||
@@ -60,7 +61,29 @@ func prepareBmpCanvas(width, height, bands int) (*C.VipsImage, []byte, error) {
|
||||
return tmp, ptrToBytes(data, datalen), nil
|
||||
}
|
||||
|
||||
// decodeBmpPaletted reads an 8 bit-per-pixel BMP image from r.
|
||||
func bmpClearOnPanic(img **C.VipsImage) {
|
||||
if rerr := recover(); rerr != nil {
|
||||
C.clear_image(img)
|
||||
panic(rerr)
|
||||
}
|
||||
}
|
||||
|
||||
func bmpSetBitDepth(img *Image, colors int) {
|
||||
var bitdepth int
|
||||
|
||||
switch {
|
||||
case colors > 16:
|
||||
bitdepth = 8
|
||||
case colors > 4:
|
||||
bitdepth = 4
|
||||
case colors > 2:
|
||||
bitdepth = 2
|
||||
}
|
||||
|
||||
img.SetInt("palette-bit-depth", bitdepth)
|
||||
}
|
||||
|
||||
// decodeBmpPaletted reads an 8/4/2/1 bit-per-pixel BMP image from r.
|
||||
// If topDown is false, the image rows will be read bottom-up.
|
||||
func (img *Image) decodeBmpPaletted(r io.Reader, width, height, bpp int, palette []Color, topDown bool) error {
|
||||
tmp, imgData, err := prepareBmpCanvas(width, height, 3)
|
||||
@@ -68,12 +91,7 @@ func (img *Image) decodeBmpPaletted(r io.Reader, width, height, bpp int, palette
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
C.clear_image(&tmp)
|
||||
panic(rerr)
|
||||
}
|
||||
}()
|
||||
defer bmpClearOnPanic(&tmp)
|
||||
|
||||
// Each row is 4-byte aligned.
|
||||
cap := 8 / bpp
|
||||
@@ -115,19 +133,129 @@ func (img *Image) decodeBmpPaletted(r io.Reader, width, height, bpp int, palette
|
||||
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
|
||||
var bitdepth int
|
||||
colors := len(palette)
|
||||
bmpSetBitDepth(img, len(palette))
|
||||
|
||||
switch {
|
||||
case colors > 16:
|
||||
bitdepth = 8
|
||||
case colors > 4:
|
||||
bitdepth = 4
|
||||
case colors > 2:
|
||||
bitdepth = 2
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBmpRLE reads an 8/4 bit-per-pixel RLE-encoded BMP image from r.
|
||||
func (img *Image) decodeBmpRLE(r io.Reader, width, height, bpp int, palette []Color) error {
|
||||
tmp, imgData, err := prepareBmpCanvas(width, height, 3)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img.SetInt("palette-bit-depth", bitdepth)
|
||||
defer bmpClearOnPanic(&tmp)
|
||||
|
||||
b := make([]byte, 256)
|
||||
|
||||
readPair := func() (byte, byte, error) {
|
||||
_, err := io.ReadFull(r, b[:2])
|
||||
return b[0], b[1], err
|
||||
}
|
||||
|
||||
x, y := 0, height-1
|
||||
cap := 8 / bpp
|
||||
|
||||
Loop:
|
||||
for {
|
||||
b1, b2, err := readPair()
|
||||
if err != nil {
|
||||
C.clear_image(&tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
if b1 == 0 {
|
||||
switch b2 {
|
||||
case 0: // End of line
|
||||
x, y = 0, y-1
|
||||
if y < 0 {
|
||||
// We should probably return an error here,
|
||||
// but it's safier to just stop decoding
|
||||
break Loop
|
||||
}
|
||||
case 1: // End of file
|
||||
break Loop
|
||||
case 2:
|
||||
dx, dy, err := readPair()
|
||||
if err != nil {
|
||||
C.clear_image(&tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
x = imath.Min(x+int(dx), width)
|
||||
y -= int(dy)
|
||||
if y < 0 {
|
||||
break Loop
|
||||
}
|
||||
default:
|
||||
pixelsCount := int(b2)
|
||||
|
||||
n := ((pixelsCount+cap-1)/cap + 1) &^ 1
|
||||
if _, err := io.ReadFull(r, b[:n]); err != nil {
|
||||
C.clear_image(&tmp)
|
||||
return err
|
||||
}
|
||||
|
||||
pixelsCount = imath.Min(pixelsCount, width-x)
|
||||
|
||||
if pixelsCount > 0 {
|
||||
start := (y*width + x) * 3
|
||||
p := imgData[start : start+pixelsCount*3]
|
||||
|
||||
j, bit := 0, 8-bpp
|
||||
for i := 0; i < len(p); i += 3 {
|
||||
pind := (b[j] >> bit) & (1<<bpp - 1)
|
||||
|
||||
if bit == 0 {
|
||||
bit = 8 - bpp
|
||||
j++
|
||||
} else {
|
||||
bit -= bpp
|
||||
}
|
||||
|
||||
c := palette[pind]
|
||||
|
||||
p[i+0] = c.R
|
||||
p[i+1] = c.G
|
||||
p[i+2] = c.B
|
||||
}
|
||||
|
||||
x += pixelsCount
|
||||
}
|
||||
}
|
||||
} else {
|
||||
pixelsCount := imath.Min(int(b1), width-x)
|
||||
|
||||
if pixelsCount > 0 {
|
||||
start := (y*width + x) * 3
|
||||
p := imgData[start : start+pixelsCount*3]
|
||||
|
||||
bit := 8 - bpp
|
||||
for i := 0; i < len(p); i += 3 {
|
||||
pind := (b2 >> bit) & (1<<bpp - 1)
|
||||
|
||||
if bit == 0 {
|
||||
bit = 8 - bpp
|
||||
} else {
|
||||
bit -= bpp
|
||||
}
|
||||
|
||||
c := palette[pind]
|
||||
|
||||
p[i+0] = c.R
|
||||
p[i+1] = c.G
|
||||
p[i+2] = c.B
|
||||
}
|
||||
|
||||
x += pixelsCount
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
C.swap_and_clear(&img.VipsImage, tmp)
|
||||
|
||||
bmpSetBitDepth(img, len(palette))
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -150,12 +278,7 @@ func (img *Image) decodeBmpRGB(r io.Reader, width, height, bands int, topDown, n
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if rerr := recover(); rerr != nil {
|
||||
C.clear_image(&tmp)
|
||||
panic(rerr)
|
||||
}
|
||||
}()
|
||||
defer bmpClearOnPanic(&tmp)
|
||||
|
||||
// Each row is 4-byte aligned.
|
||||
b := make([]byte, (bands*width+3)&^3)
|
||||
@@ -239,10 +362,13 @@ func (img *Image) loadBmp(data []byte, noAlpha bool) error {
|
||||
return errBmpUnsupported
|
||||
}
|
||||
|
||||
// We only support 1 plane and 8, 24 or 32 bits per pixel and no
|
||||
// compression.
|
||||
// We only support 1 plane and 8, 24 or 32 bits per pixel
|
||||
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
|
||||
|
||||
if planes != 1 {
|
||||
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 &&
|
||||
@@ -251,35 +377,45 @@ func (img *Image) loadBmp(data []byte, noAlpha bool) error {
|
||||
compression = 0
|
||||
}
|
||||
|
||||
if planes != 1 || compression != 0 {
|
||||
rle := false
|
||||
if compression == 1 && bpp == 8 || compression == 2 && bpp == 4 {
|
||||
rle = true
|
||||
} else if compression != 0 {
|
||||
return errBmpUnsupported
|
||||
}
|
||||
|
||||
switch bpp {
|
||||
case 1, 2, 4, 8:
|
||||
var palette []Color
|
||||
if bpp <= 8 {
|
||||
palColors := readUint32(b[46:50])
|
||||
if palColors == 0 {
|
||||
palColors = 1 << bpp
|
||||
}
|
||||
|
||||
_, err := io.ReadFull(r, b[:palColors*4])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
palette := make([]Color, palColors)
|
||||
palette = make([]Color, palColors)
|
||||
for i := range palette {
|
||||
// BMP images are stored in BGR order rather than RGB order.
|
||||
// Every 4th byte is padding.
|
||||
palette[i] = Color{b[4*i+2], b[4*i+1], b[4*i+0]}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if rle {
|
||||
return img.decodeBmpRLE(r, width, height, int(bpp), palette)
|
||||
}
|
||||
|
||||
switch bpp {
|
||||
case 1, 2, 4, 8:
|
||||
return img.decodeBmpPaletted(r, width, height, int(bpp), palette, topDown)
|
||||
case 24:
|
||||
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
return img.decodeBmpRGB(r, width, height, 3, topDown, true)
|
||||
case 32:
|
||||
if infoLen >= 70 {
|
||||
@@ -287,10 +423,6 @@ func (img *Image) loadBmp(data []byte, noAlpha bool) error {
|
||||
noAlpha = readUint32(b[66:70]) == 0
|
||||
}
|
||||
|
||||
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return img.decodeBmpRGB(r, width, height, 4, topDown, noAlpha)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user