@@ -27,19 +27,25 @@ export interface ImageCodec {
27
27
* Image number of channels excluding alpha.
28
28
*/
29
29
components : number ;
30
+ /**
31
+ * Horizontal number of pixels per meter.
32
+ */
33
+ xPixelsPerMeter ?: number ;
34
+ /**
35
+ * Vertical number of pixels per meter.
36
+ */
37
+ yPixelsPerMeter ?: number ;
30
38
}
31
39
32
- const tableLeft : number [ ] = [ ] ;
33
- for ( let i = 0 ; i <= 8 ; i ++ ) {
34
- tableLeft . push ( 0b11111111 << i ) ;
35
- }
36
-
37
- export default class BMPEncoder extends IOBuffer {
40
+ export default class BMPEncoder {
38
41
width : number ;
39
42
height : number ;
40
43
bitDepth : number ;
41
44
channels : number ;
42
45
components : number ;
46
+ data : Uint8Array ;
47
+ xPixelsPerMeter : number ;
48
+ yPixelsPerMeter : number ;
43
49
encoded : IOBuffer = new IOBuffer ( ) ;
44
50
constructor ( data : ImageCodec ) {
45
51
if ( data . bitDepth !== 1 ) {
@@ -48,84 +54,52 @@ export default class BMPEncoder extends IOBuffer {
48
54
if ( ! data . height || ! data . width ) {
49
55
throw new Error ( 'ImageData width and height are required' ) ;
50
56
}
51
-
52
- super ( data . data ) ;
53
-
57
+ this . data = data . data as Uint8Array ;
54
58
this . width = data . width ;
55
59
this . height = data . height ;
56
60
this . bitDepth = data . bitDepth ;
57
61
this . channels = data . channels ;
58
62
this . components = data . components ;
63
+ this . xPixelsPerMeter =
64
+ data . xPixelsPerMeter ?? BITMAPV5HEADER . DEFAULT_PIXELS_PER_METER ;
65
+ this . yPixelsPerMeter =
66
+ data . yPixelsPerMeter ?? BITMAPV5HEADER . DEFAULT_PIXELS_PER_METER ;
59
67
}
60
68
61
69
encode ( ) {
62
70
this . encoded = new IOBuffer ( ) ;
63
71
this . encoded . skip ( 14 ) ;
72
+
64
73
this . writeBitmapV5Header ( ) ;
65
74
this . writeColorTable ( ) ;
66
75
const offset = this . encoded . offset ;
76
+
67
77
this . writePixelArray ( ) ;
78
+
68
79
const imageSize = this . encoded . getWrittenByteLength ( ) ;
80
+
69
81
this . encoded . rewind ( ) ;
70
82
this . writeBitmapFileHeader ( offset , imageSize ) ;
83
+
71
84
return this . encoded . toArray ( ) ;
72
85
}
73
86
74
87
writePixelArray ( ) {
75
- const io = this . encoded ;
76
- const rowSize = Math . floor ( ( this . bitDepth * this . width + 31 ) / 32 ) * 4 ;
77
- const dataRowSize = Math . ceil ( ( this . bitDepth * this . width ) / 8 ) ;
78
- const skipSize = rowSize - dataRowSize ;
79
- const bitOverflow = ( this . bitDepth * this . width ) % 8 ;
80
- const bitSkip = bitOverflow === 0 ? 0 : 8 - bitOverflow ;
81
- const totalBytes = rowSize * this . height ;
82
- let byteA , byteB ;
83
- let offset = 0 ; // Current off set in the ioData
84
- let relOffset = 0 ;
85
- let iOffset = 8 ;
86
- io . mark ( ) ;
87
- byteB = this . readUint8 ( ) ;
88
- for ( let i = this . height - 1 ; i >= 0 ; i -- ) {
89
- const lastRow = i === 0 ;
90
- io . reset ( ) ;
91
- io . skip ( i * rowSize ) ;
92
- for ( let j = 0 ; j < dataRowSize ; j ++ ) {
93
- const lastCol = j === dataRowSize - 1 ;
94
- if ( relOffset <= bitSkip && lastCol ) {
95
- // no need to read new data
88
+ this . encoded . setBigEndian ( ) ;
89
+ let byte = 0 ;
96
90
97
- io . writeByte ( byteB << relOffset ) ;
98
- if ( ( bitSkip === 0 || bitSkip === relOffset ) && ! lastRow ) {
99
- byteA = byteB ;
100
- byteB = this . readByte ( ) ;
101
- }
102
- } else if ( relOffset === 0 ) {
103
- byteA = byteB ;
104
- byteB = this . readUint8 ( ) ;
105
-
106
- io . writeByte ( byteA ) ;
107
- } else {
108
- byteA = byteB ;
109
- byteB = this . readUint8 ( ) ;
110
- io . writeByte (
111
- ( ( byteA << relOffset ) & tableLeft [ relOffset ] ) | ( byteB >> iOffset )
112
- ) ;
113
- }
114
-
115
- if ( lastCol ) {
116
- offset += bitOverflow || 0 ;
117
- io . skip ( skipSize ) ;
118
- relOffset = offset % 8 ;
119
- iOffset = 8 - relOffset ;
91
+ for ( let row = this . height - 1 ; row >= 0 ; row -- ) {
92
+ for ( let col = 0 ; col < this . width ; col ++ ) {
93
+ if ( col % 32 === 0 && row * this . width !== row * this . width + col ) {
94
+ this . encoded . writeUint32 ( byte ) ;
95
+ byte = 0 ;
120
96
}
97
+ byte |= this . data [ row * this . width + col ] << ( 31 - ( col % 32 ) ) ;
121
98
}
99
+ this . encoded . writeUint32 ( byte ) ;
100
+ byte = 0 ;
122
101
}
123
- if ( rowSize > dataRowSize ) {
124
- // make sure last written byte is correct
125
- io . reset ( ) ;
126
- io . skip ( totalBytes - 1 ) ;
127
- io . writeUint8 ( 0 ) ;
128
- }
102
+ this . encoded . setLittleEndian ( ) ;
129
103
}
130
104
131
105
writeColorTable ( ) {
@@ -156,18 +130,18 @@ export default class BMPEncoder extends IOBuffer {
156
130
. writeUint16 ( this . bitDepth ) // bV5BitCount
157
131
. writeUint32 ( BITMAPV5HEADER . Compression . BI_RGB ) // bV5Compression - No compression
158
132
. writeUint32 ( totalBytes ) // bv5SizeImage - size of pixel buffer (can be 0 if uncompressed)
159
- . writeInt32 ( 0 ) // bV5XPelsPerMeter - resolution
160
- . writeInt32 ( 0 ) // bV5YPelsPerMeter - resolution
133
+ . writeInt32 ( this . xPixelsPerMeter ) // bV5XPelsPerMeter - resolution
134
+ . writeInt32 ( this . yPixelsPerMeter ) // bV5YPelsPerMeter - resolution
161
135
. writeUint32 ( 2 ** this . bitDepth )
162
136
. writeUint32 ( 2 ** this . bitDepth )
163
- . writeUint32 ( 0xff000000 ) // bV5RedMask
164
- . writeUint32 ( 0x00ff0000 ) // bV5GreenMask
165
- . writeUint32 ( 0x0000ff00 ) // bV5BlueMask
166
- . writeUint32 ( 0x000000ff ) // bV5AlphaMask
137
+ . writeUint32 ( 0x00ff0000 ) // bV5BlueMask
138
+ . writeUint32 ( 0x0000ff00 ) // bV5GreenMask
139
+ . writeUint32 ( 0x000000ff ) // bV5RedMask
140
+ . writeUint32 ( 0x00000000 ) // bv5ReservedData
167
141
. writeUint32 ( BITMAPV5HEADER . LogicalColorSpace . LCS_sRGB )
168
142
. skip ( 36 ) // bV5Endpoints
169
143
. skip ( 12 ) // bV5GammaRed, Green, Blue
170
- . writeUint32 ( BITMAPV5HEADER . GamutMappingIntent . LCS_GM_IMAGES )
144
+ . writeUint32 ( BITMAPV5HEADER . GamutMappingIntent . LCS_GM_GRAPHICS )
171
145
. skip ( 12 ) ; // ProfileData, ProfileSize, Reserved
172
146
}
173
147
}
0 commit comments