@@ -7,9 +7,11 @@ export default class BMPDecoder {
7
7
pixelDataOffset : number ;
8
8
width : number ;
9
9
height : number ;
10
- bitDepth : number ;
10
+ bitsPerPixel : number ;
11
11
xPixelsPerMeter : number ;
12
12
yPixelsPerMeter : number ;
13
+ compression : number ;
14
+ colorMasks : number [ ] ;
13
15
constructor ( bufferData : Buffer ) {
14
16
this . bufferData = new IOBuffer ( bufferData ) ;
15
17
const formatCheck = this . bufferData . readBytes ( 2 ) ;
@@ -21,45 +23,138 @@ export default class BMPDecoder {
21
23
this . pixelDataOffset = this . bufferData . skip ( 8 ) . readUint32 ( ) ;
22
24
this . width = this . bufferData . skip ( 4 ) . readUint32 ( ) ;
23
25
this . height = this . bufferData . readUint32 ( ) ;
24
- this . bitDepth = this . bufferData . seek ( 28 ) . readUint16 ( ) ;
26
+ this . bitsPerPixel = this . bufferData . seek ( 28 ) . readUint16 ( ) ;
27
+ if (
28
+ this . bitsPerPixel !== 1 &&
29
+ this . bitsPerPixel !== 8 &&
30
+ this . bitsPerPixel !== 24 &&
31
+ this . bitsPerPixel !== 32
32
+ ) {
33
+ throw new Error (
34
+ `Invalid number of bits per pixel. Supported number of bits per pixel: 1, 8, 24, 32. Received: ${ this . bitsPerPixel } `
35
+ ) ;
36
+ }
37
+ this . compression = this . bufferData . readUint32 ( ) ;
38
+ if ( this . compression !== 0 && this . compression !== 3 ) {
39
+ throw new Error (
40
+ `Only BI_RGB and BI_BITFIELDS compression methods are allowed. `
41
+ ) ;
42
+ }
43
+
44
+ this . colorMasks = [
45
+ this . bufferData . seek ( 54 ) . readUint32 ( ) ,
46
+ this . bufferData . readUint32 ( ) ,
47
+ this . bufferData . readUint32 ( ) ,
48
+ ] ;
49
+
50
+ if (
51
+ this . bitsPerPixel === 32 &&
52
+ ( this . colorMasks [ 0 ] !== 0x00ff0000 ||
53
+ this . colorMasks [ 1 ] !== 0x0000ff00 ||
54
+ this . colorMasks [ 2 ] !== 0x000000ff )
55
+ ) {
56
+ throw new Error (
57
+ `Unsupported color masks detected in 32-bit BMP image. Only standard RGBA (${ ( 0x00ff0000 ) . toString (
58
+ 16
59
+ ) } , ${ ( 0x0000ff00 ) . toString ( 16 ) } , ${ ( 0x000000ff ) . toString (
60
+ 16
61
+ ) } ) masks are supported. Received: ${ this . colorMasks [ 0 ] . toString (
62
+ 16
63
+ ) } ,${ this . colorMasks [ 1 ] . toString ( 16 ) } ,${ this . colorMasks [ 2 ] . toString (
64
+ 16
65
+ ) } .`
66
+ ) ;
67
+ }
68
+ this . bufferData . skip ( 1 ) ; // skipping image size.
25
69
this . xPixelsPerMeter = this . bufferData . seek ( 38 ) . readInt32 ( ) ;
26
70
this . yPixelsPerMeter = this . bufferData . readInt32 ( ) ;
27
- if ( this . bitDepth !== 1 ) {
28
- throw new Error ( 'only bitDepth of 1 is supported' ) ;
29
- }
71
+ this . bufferData . skip ( 1 ) ;
30
72
}
31
73
32
74
decode ( ) : ImageCodec {
33
75
this . bufferData . seek ( this . pixelDataOffset ) ;
34
- const data = new Uint8Array ( this . height * this . width * this . bitDepth ) ;
35
76
this . bufferData . setBigEndian ( ) ;
77
+ const channels = Math . ceil ( this . bitsPerPixel / 8 ) ;
78
+ const components = channels % 2 === 0 ? channels - 1 : channels ;
79
+ const data : Uint8Array = this . decodePixelData ( channels , components ) ;
80
+ return {
81
+ width : this . width ,
82
+ height : this . height ,
83
+ bitsPerPixel : this . bitsPerPixel ,
84
+ compression : this . compression ,
85
+ colorMasks : this . colorMasks ,
86
+ channels,
87
+ components,
88
+ data,
89
+ yPixelsPerMeter : this . yPixelsPerMeter ,
90
+ xPixelsPerMeter : this . xPixelsPerMeter ,
91
+ } ;
92
+ }
36
93
37
- let currentNumber = 0 ;
94
+ decodePixelData ( channels : number , components : number ) : Uint8Array {
95
+ const data = new Uint8Array ( this . height * this . width * channels ) ;
96
+ if ( this . bitsPerPixel === 1 ) {
97
+ this . decodeBitDepth1Pixels ( data ) ;
98
+ } else if ( channels === components ) {
99
+ this . decodeStandardPixels ( data , channels ) ;
100
+ } else {
101
+ this . decodePixelsWithAlpha ( data , channels , components ) ;
102
+ }
103
+ return data ;
104
+ }
38
105
106
+ private decodeBitDepth1Pixels ( data : Uint8Array ) {
107
+ let currentNumber = 0 ;
39
108
for ( let row = 0 ; row < this . height ; row ++ ) {
40
109
for ( let col = 0 ; col < this . width ; col ++ ) {
41
110
const bitIndex = col % 32 ;
42
111
if ( bitIndex === 0 ) {
43
112
currentNumber = this . bufferData . readUint32 ( ) ;
44
113
}
45
-
46
114
if ( currentNumber & ( 1 << ( 31 - bitIndex ) ) ) {
47
115
data [ ( this . height - row - 1 ) * this . width + col ] = 1 ;
48
116
}
49
117
}
50
118
}
119
+ }
51
120
52
- const channels = Math . ceil ( this . bitDepth / 8 ) ;
53
- const components = channels % 2 === 0 ? channels - 1 : channels ;
54
- return {
55
- width : this . width ,
56
- height : this . height ,
57
- bitDepth : this . bitDepth ,
58
- channels,
59
- components,
60
- data,
61
- yPixelsPerMeter : this . yPixelsPerMeter ,
62
- xPixelsPerMeter : this . xPixelsPerMeter ,
63
- } ;
121
+ private decodeStandardPixels ( data : Uint8Array , channels : number ) {
122
+ const padding = this . calculatePadding ( channels ) ;
123
+ for ( let row = 0 ; row < this . height ; row ++ ) {
124
+ const rowOffset = ( this . height - row - 1 ) * this . width ;
125
+ for ( let col = 0 ; col < this . width ; col ++ ) {
126
+ for ( let channel = channels - 1 ; channel >= 0 ; channel -- ) {
127
+ data [ ( rowOffset + col ) * channels + channel ] =
128
+ this . bufferData . readByte ( ) ;
129
+ }
130
+ }
131
+ this . bufferData . skip ( padding ) ;
132
+ }
133
+ }
134
+
135
+ private decodePixelsWithAlpha (
136
+ data : Uint8Array ,
137
+ channels : number ,
138
+ components : number
139
+ ) {
140
+ for ( let row = 0 ; row < this . height ; row ++ ) {
141
+ const rowOffset = ( this . height - row - 1 ) * this . width ;
142
+
143
+ for ( let col = 0 ; col < this . width ; col ++ ) {
144
+ const pixelBaseIndex = ( rowOffset + col ) * channels ;
145
+ // Decode color components
146
+ for ( let component = components - 1 ; component >= 0 ; component -- ) {
147
+ data [ pixelBaseIndex + component ] = this . bufferData . readByte ( ) ;
148
+ }
149
+ // Decode alpha channel
150
+ data [ pixelBaseIndex + components ] = this . bufferData . readByte ( ) ;
151
+ }
152
+ }
153
+ }
154
+
155
+ private calculatePadding ( channels : number ) : number {
156
+ return ( this . width * channels ) % 4 === 0
157
+ ? 0
158
+ : 4 - ( ( this . width * channels ) % 4 ) ;
64
159
}
65
160
}
0 commit comments