-
Notifications
You must be signed in to change notification settings - Fork 254
bmp: Add decoder #1376
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
bmp: Add decoder #1376
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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", | ||
| 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")) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 { | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
| } | ||
| 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) | | |
| 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| |..| | |
| 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 | ||
|
Owner
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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| |..........| | | ||
There was a problem hiding this comment.
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