Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions format/all/all.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import (
_ "github.com/wader/fq/format/bencode"
_ "github.com/wader/fq/format/bitcoin"
_ "github.com/wader/fq/format/bits"
_ "github.com/wader/fq/format/bmp"
_ "github.com/wader/fq/format/bson"
_ "github.com/wader/fq/format/bzip2"
_ "github.com/wader/fq/format/caff"
Expand Down
110 changes: 110 additions & 0 deletions format/bmp/bmp.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package bmp

// https://en.wikipedia.org/wiki/BMP_file_format
// https://learn.microsoft.com/en-us/windows/win32/gdi/bitmap-header-types

// Decodes the file header and the BITMAPINFOHEADER family of DIB headers
// (size >= 40), which covers essentially all BMPs seen in the wild. The
// 12 byte BITMAPCOREHEADER is not handled specially.

import (
"github.com/wader/fq/format"
"github.com/wader/fq/pkg/decode"
"github.com/wader/fq/pkg/interp"
"github.com/wader/fq/pkg/scalar"
)

func init() {
interp.RegisterFormat(
format.BMP,
&decode.Format{
Description: "Bitmap image",
Groups: []*decode.Group{format.Probe, format.Image},
DecodeFn: bmpDecode,
})
}

const (
compressionRGB = 0
compressionRLE8 = 1
compressionRLE4 = 2
compressionBitfields = 3
compressionJPEG = 4
compressionPNG = 5
compressionAlphaBitfields = 6
compressionCMYK = 11
compressionCMYKRLE8 = 12
compressionCMYKRLE4 = 13
)

var compressionNames = scalar.UintMapSymStr{
compressionRGB: "rgb",
compressionRLE8: "rle8",
compressionRLE4: "rle4",
compressionBitfields: "bitfields",
compressionJPEG: "jpeg",

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

jpeg and png could be decoded as subformats but maybe should be done in a separate PR

compressionPNG: "png",
compressionAlphaBitfields: "alpha_bitfields",
compressionCMYK: "cmyk",
compressionCMYKRLE8: "cmyk_rle8",
compressionCMYKRLE4: "cmyk_rle4",
}

func bmpDecode(d *decode.D) any {
d.Endian = decode.LittleEndian

var bitmapOffset uint64
var bitsPerPixel uint64
var colorsUsed uint64

d.FieldStruct("file_header", func(d *decode.D) {
d.FieldUTF8("type", 2, d.StrAssert("BM"))

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Being in the probe group require that it can be detected without high risk of false positives and not doing much reading. Maybe check for "BM" and make sure file length is at least size big? anything else?

d.FieldU32("size")
d.FieldU16("reserved1")
d.FieldU16("reserved2")
bitmapOffset = d.FieldU32("bitmap_offset")
})

d.FieldStruct("dib_header", func(d *decode.D) {
headerSize := d.FieldU32("size")
d.FieldS32("width")
d.FieldS32("height")
d.FieldU16("planes")
bitsPerPixel = d.FieldU16("bits_per_pixel")
d.FieldU32("compression", compressionNames)
d.FieldU32("image_size")
d.FieldS32("x_pixels_per_meter")
d.FieldS32("y_pixels_per_meter")
colorsUsed = d.FieldU32("colors_used")
d.FieldU32("colors_important")
// v4/v5 headers add bit masks, color space and gamma after the common part
if headerSize > 40 {
d.FieldRawLen("rest", int64(headerSize-40)*8)
}
})

if bitsPerPixel <= 8 {
n := colorsUsed
if n == 0 {
n = 1 << bitsPerPixel
}
d.FieldArray("color_table", func(d *decode.D) {
for range int(n) {
d.FieldStruct("color", func(d *decode.D) {
d.FieldU8("blue")
d.FieldU8("green")
d.FieldU8("red")
d.FieldU8("reserved")
})
}
})
}

if gap := int64(bitmapOffset)*8 - d.Pos(); gap > 0 {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can try remove this, gaps will be added a fields automatically

d.FieldRawLen("gap", gap)
}

d.FieldRawLen("pixels", d.BitsLeft())

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we know the size of pixels data instead of assuming all remaining? derive from image_size but specs seems to be a bit unclear if there are special values like 0 that has to be handled


return nil
}
Binary file added format/bmp/testdata/4x4.bmp
Binary file not shown.
27 changes: 27 additions & 0 deletions format/bmp/testdata/4x4.fqtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# convert -size 4x4 pattern:gray50 -define bmp:format=bmp3 -type truecolor BMP3:4x4.bmp
$ fq -d bmp dv 4x4.bmp
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: 4x4.bmp (bmp) 0x0-0x66 (102)
| | | file_header{}: 0x0-0xe (14)
0x00|42 4d |BM | type: "BM" (valid) 0x0-0x2 (2)
0x00| 66 00 00 00 | f... | size: 102 0x2-0x6 (4)
0x00| 00 00 | .. | reserved1: 0 0x6-0x8 (2)
0x00| 00 00 | .. | reserved2: 0 0x8-0xa (2)
0x00| 36 00 00 00 | 6... | bitmap_offset: 54 0xa-0xe (4)
| | | dib_header{}: 0xe-0x36 (40)
0x00| 28 00| (.| size: 40 0xe-0x12 (4)
0x10|00 00 |.. |
0x10| 04 00 00 00 | .... | width: 4 0x12-0x16 (4)
0x10| 04 00 00 00 | .... | height: 4 0x16-0x1a (4)
0x10| 01 00 | .. | planes: 1 0x1a-0x1c (2)
0x10| 18 00 | .. | bits_per_pixel: 24 0x1c-0x1e (2)
0x10| 00 00| ..| compression: "rgb" (0) 0x1e-0x22 (4)
0x20|00 00 |.. |
0x20| 30 00 00 00 | 0... | image_size: 48 0x22-0x26 (4)
0x20| 00 00 00 00 | .... | x_pixels_per_meter: 0 0x26-0x2a (4)
0x20| 00 00 00 00 | .... | y_pixels_per_meter: 0 0x2a-0x2e (4)
0x20| 00 00| ..| colors_used: 0 0x2e-0x32 (4)
0x30|00 00 |.. |
0x30| 00 00 00 00 | .... | colors_important: 0 0x32-0x36 (4)
0x30| ff ff ff 00 00 00 ff ff ff 00| ..........| pixels: raw bits 0x36-0x66 (48)
0x40|00 00 00 00 00 ff ff ff 00 00 00 ff ff ff ff ff|................|
* |until 0x65.7 (end) (48) | |
Binary file added format/bmp/testdata/palette.bmp
Binary file not shown.
37 changes: 37 additions & 0 deletions format/bmp/testdata/palette.fqtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# convert -size 8x1 pattern:gray50 -monochrome -define bmp:format=bmp3 BMP3:palette.bmp
$ fq -d bmp dv palette.bmp
|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: palette.bmp (bmp) 0x0-0x42 (66)
| | | file_header{}: 0x0-0xe (14)
0x00|42 4d |BM | type: "BM" (valid) 0x0-0x2 (2)
0x00| 42 00 00 00 | B... | size: 66 0x2-0x6 (4)
0x00| 00 00 | .. | reserved1: 0 0x6-0x8 (2)
0x00| 00 00 | .. | reserved2: 0 0x8-0xa (2)
0x00| 3e 00 00 00 | >... | bitmap_offset: 62 0xa-0xe (4)
| | | dib_header{}: 0xe-0x36 (40)
0x00| 28 00| (.| size: 40 0xe-0x12 (4)
0x10|00 00 |.. |
0x10| 08 00 00 00 | .... | width: 8 0x12-0x16 (4)
0x10| 01 00 00 00 | .... | height: 1 0x16-0x1a (4)
0x10| 01 00 | .. | planes: 1 0x1a-0x1c (2)
0x10| 01 00 | .. | bits_per_pixel: 1 0x1c-0x1e (2)
0x10| 00 00| ..| compression: "rgb" (0) 0x1e-0x22 (4)
0x20|00 00 |.. |
0x20| 04 00 00 00 | .... | image_size: 4 0x22-0x26 (4)
0x20| 00 00 00 00 | .... | x_pixels_per_meter: 0 0x26-0x2a (4)
0x20| 00 00 00 00 | .... | y_pixels_per_meter: 0 0x2a-0x2e (4)
0x20| 02 00| ..| colors_used: 2 0x2e-0x32 (4)
0x30|00 00 |.. |
0x30| 02 00 00 00 | .... | colors_important: 2 0x32-0x36 (4)
| | | color_table[0:2]: 0x36-0x3e (8)
| | | [0]{}: color 0x36-0x3a (4)
0x30| 00 | . | blue: 0 0x36-0x37 (1)
0x30| 00 | . | green: 0 0x37-0x38 (1)
0x30| 00 | . | red: 0 0x38-0x39 (1)
0x30| 00 | . | reserved: 0 0x39-0x3a (1)
| | | [1]{}: color 0x3a-0x3e (4)
0x30| ff | . | blue: 255 0x3a-0x3b (1)
0x30| ff | . | green: 255 0x3b-0x3c (1)
0x30| ff | . | red: 255 0x3c-0x3d (1)
0x30| 00 | . | reserved: 0 0x3d-0x3e (1)
0x30| 55 00| U.| pixels: raw bits 0x3e-0x42 (4)
0x40|00 00| |..| |
Binary file added format/bmp/testdata/v4.bmp
Binary file not shown.
29 changes: 29 additions & 0 deletions format/bmp/testdata/v4.fqtest
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# BITMAPV4HEADER (108 byte) 2x2 32-bit BGRA image, generated by hand
$ fq -d bmp dv v4.bmp

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have one test that don't use -d to test probe

|00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f|0123456789abcdef|.{}: v4.bmp (bmp) 0x0-0x8a (138)
| | | file_header{}: 0x0-0xe (14)
0x00|42 4d |BM | type: "BM" (valid) 0x0-0x2 (2)
0x00| 8a 00 00 00 | .... | size: 138 0x2-0x6 (4)
0x00| 00 00 | .. | reserved1: 0 0x6-0x8 (2)
0x00| 00 00 | .. | reserved2: 0 0x8-0xa (2)
0x00| 7a 00 00 00 | z... | bitmap_offset: 122 0xa-0xe (4)
| | | dib_header{}: 0xe-0x7a (108)
0x00| 6c 00| l.| size: 108 0xe-0x12 (4)
0x10|00 00 |.. |
0x10| 02 00 00 00 | .... | width: 2 0x12-0x16 (4)
0x10| 02 00 00 00 | .... | height: 2 0x16-0x1a (4)
0x10| 01 00 | .. | planes: 1 0x1a-0x1c (2)
0x10| 20 00 | . | bits_per_pixel: 32 0x1c-0x1e (2)
0x10| 03 00| ..| compression: "bitfields" (3) 0x1e-0x22 (4)
0x20|00 00 |.. |
0x20| 10 00 00 00 | .... | image_size: 16 0x22-0x26 (4)
0x20| 00 00 00 00 | .... | x_pixels_per_meter: 0 0x26-0x2a (4)
0x20| 00 00 00 00 | .... | y_pixels_per_meter: 0 0x2a-0x2e (4)
0x20| 00 00| ..| colors_used: 0 0x2e-0x32 (4)
0x30|00 00 |.. |
0x30| 00 00 00 00 | .... | colors_important: 0 0x32-0x36 (4)
0x30| 00 00 ff 00 00 ff 00 00 ff 00| ..........| rest: raw bits 0x36-0x7a (68)
0x40|00 00 00 00 00 ff 00 00 00 00 00 00 00 00 00 00|................|
* |until 0x79.7 (68) | |
0x70| 00 00 ff ff 00 ff| ......| pixels: raw bits 0x7a-0x8a (16)
0x80|00 ff ff 00 00 ff ff ff ff ff| |..........| |
1 change: 1 addition & 0 deletions format/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ var (
Bitcoin_Block = &decode.Group{Name: "bitcoin_block"}
Bitcoin_Script = &decode.Group{Name: "bitcoin_script"}
Bitcoin_Transaction = &decode.Group{Name: "bitcoin_transaction"}
BMP = &decode.Group{Name: "bmp"}
Bplist = &decode.Group{Name: "bplist"}
BSD_Loopback_Frame = &decode.Group{Name: "bsd_loopback_frame"}
BSON = &decode.Group{Name: "bson"}
Expand Down
Loading