las-laz-js 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,13 +3,15 @@
3
3
  // LASzip RGB12 item, version 2: the 6 colour bytes. A symbol says which
4
4
  // bytes changed and whether green and blue differ from red; red's byte
5
5
  // differences predict green's, and the mean of red's and green's
6
- // predicts blue's (clamped to a byte). Shared by the 1.4 RGB14 item.
6
+ // predicts blue's (clamped to a byte). Shared by the 1.4 RGB14 item;
7
+ // encodeRgb is decodeRgb's mirror, over the same RgbModels.
7
8
  //
8
9
  // Ported from laz-rs src/las/rgb.rs, module v2 (https://github.com/laz-rs/laz-rs),
9
10
  // itself a port of LASzip by rapidlasso. (c) 2007-2014 martin isenburg,
10
11
  // rapidlasso; (c) 2014 Uday Verma, Hobu, Inc.; (c) 2019 Thomas Montaigu.
11
12
  // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
12
- // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
13
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
14
+ // the compressor (encodeRgb, Rgb12Compressor) added beside the decompressor.
13
15
  //==========================================================
14
16
 
15
17
  import { ArithmeticModel } from "../models.js";
@@ -61,13 +63,16 @@ export function decodeRgb(dec, m, last) {
61
63
  last[0] = r; last[1] = g; last[2] = b;
62
64
  }
63
65
 
64
- /** RGB12 v2 decompressor: one per chunk. */
65
- export function Rgb12Decompressor() {
66
- this.size = 6;
67
- this.models = new RgbModels();
68
- this.last = new Uint16Array(3);
66
+ // The state and models both halves start a chunk with
67
+ function initRgb12(self) {
68
+ self.size = 6;
69
+ self.models = new RgbModels();
70
+ self.last = new Uint16Array(3);
69
71
  }
70
72
 
73
+ /** RGB12 v2 decompressor: one per chunk. */
74
+ export function Rgb12Decompressor() { initRgb12(this); }
75
+
71
76
  Rgb12Decompressor.prototype.first = function (src, s, view, o) {
72
77
  for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
73
78
  this.last[0] = view.getUint16(o, LE); this.last[1] = view.getUint16(o + 2, LE); this.last[2] = view.getUint16(o + 4, LE);
@@ -77,3 +82,53 @@ Rgb12Decompressor.prototype.next = function (dec, view, o) {
77
82
  decodeRgb(dec, this.models, this.last);
78
83
  view.setUint16(o, this.last[0], LE); view.setUint16(o + 2, this.last[1], LE); view.setUint16(o + 4, this.last[2], LE);
79
84
  };
85
+
86
+ /**
87
+ * Encode one colour against the last one: the mirror of decodeRgb.
88
+ * @param {object} enc - ArithmeticEncoder
89
+ * @param {RgbModels} m
90
+ * @param {Uint16Array} last - [r, g, b] of the last point (not changed)
91
+ * @param {Uint16Array} cur - [r, g, b] of this point
92
+ * @returns {number} the symbol coded: which bytes changed, and bit 6 when it is not grey
93
+ */
94
+ export function encodeRgb(enc, m, last, cur) {
95
+ var lr = last[0], lg = last[1], lb = last[2], r = cur[0], g = cur[1], b = cur[2];
96
+ var sym = ((lr ^ r) & 255 ? 1 : 0) | ((lr ^ r) & 0xFF00 ? 2 : 0) | ((lg ^ g) & 255 ? 4 : 0) | ((lg ^ g) & 0xFF00 ? 8 : 0) |
97
+ ((lb ^ b) & 255 ? 16 : 0) | ((lb ^ b) & 0xFF00 ? 32 : 0) | (r !== g || r !== b ? 64 : 0);
98
+ var diffL = 0, diffH = 0;
99
+ enc.encodeSymbol(m.byteUsed, sym);
100
+ if (sym & 1) { diffL = (r & 255) - (lr & 255); enc.encodeSymbol(m.lowRed, diffL & 255); }
101
+ if (sym & 2) { diffH = (r >>> 8) - (lr >>> 8); enc.encodeSymbol(m.highRed, diffH & 255); }
102
+ if (sym & 64) {
103
+ if (sym & 4) enc.encodeSymbol(m.lowGreen, ((g & 255) - clamp8(diffL + (lg & 255))) & 255);
104
+ if (sym & 16) {
105
+ diffL = Math.trunc((diffL + (g & 255) - (lg & 255)) / 2);
106
+ enc.encodeSymbol(m.lowBlue, ((b & 255) - clamp8(diffL + (lb & 255))) & 255);
107
+ }
108
+ if (sym & 8) enc.encodeSymbol(m.highGreen, ((g >>> 8) - clamp8(diffH + (lg >>> 8))) & 255);
109
+ if (sym & 32) {
110
+ diffH = Math.trunc((diffH + (g >>> 8) - (lg >>> 8)) / 2);
111
+ enc.encodeSymbol(m.highBlue, ((b >>> 8) - clamp8(diffH + (lb >>> 8))) & 255);
112
+ }
113
+ }
114
+ return sym;
115
+ }
116
+
117
+ /** RGB12 v2 compressor: one per chunk, the mirror of Rgb12Decompressor. */
118
+ export function Rgb12Compressor() {
119
+ initRgb12(this);
120
+ this.cur = new Uint16Array(3);
121
+ }
122
+
123
+ /** [r, g, b] of the record bytes at s, into out (RGB14 reads them the same way). */
124
+ export function readRgb(out, src, s) {
125
+ out[0] = src[s] + src[s + 1] * 256; out[1] = src[s + 2] + src[s + 3] * 256; out[2] = src[s + 4] + src[s + 5] * 256;
126
+ }
127
+
128
+ Rgb12Compressor.prototype.first = function (src, s) { readRgb(this.last, src, s); };
129
+
130
+ Rgb12Compressor.prototype.next = function (enc, src, s) {
131
+ readRgb(this.cur, src, s);
132
+ encodeRgb(enc, this.models, this.last, this.cur);
133
+ var t = this.last; this.last = this.cur; this.cur = t;
134
+ };
@@ -6,20 +6,27 @@
6
6
  // Point14 passes on, with the version-3 quirk preserved: a context
7
7
  // first used is seeded from the last one, and in version 3 later
8
8
  // values are predicted from whichever context it was entered from.
9
+ // Each compressor shares its decompressor's setup and context switch,
10
+ // so the two stay in step point for point.
9
11
  //
10
12
  // Ported from laz-rs src/las/rgb.rs, nir.rs and extra_bytes.rs, modules
11
13
  // v3_v4 (https://github.com/laz-rs/laz-rs), itself a port of LASzip by
12
14
  // rapidlasso. (c) 2007-2015 martin isenburg, rapidlasso; (c) 2019-2023
13
15
  // Thomas Montaigu.
14
16
  // Licensed under the Apache License, Version 2.0 (see LICENSE-APACHE and
15
- // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026.
17
+ // NOTICE). Changes: translated from Rust to JavaScript, Brent Buffham 2026;
18
+ // the compressors (Rgb14Compressor, Nir14Compressor, Byte14Compressor)
19
+ // added beside the decompressors, writing a layer once a value changes
20
+ // as LAZperf (hobuinc/laz-perf, field_rgb14/nir14/byte14.cpp) does.
16
21
  //==========================================================
17
22
 
18
23
  import { ArithmeticDecoder } from "../ArithmeticDecoder.js";
24
+ import { ArithmeticEncoder } from "../ArithmeticEncoder.js";
19
25
  import { ArithmeticModel } from "../models.js";
20
- import { RgbModels, decodeRgb } from "../v2/rgb12.js";
26
+ import { RgbModels, decodeRgb, encodeRgb, readRgb } from "../v2/rgb12.js";
21
27
 
22
28
  var LE = true;
29
+ var EMPTY = new Uint8Array(0);
23
30
 
24
31
  // laz-rs's context switch, shared by the three items: returns the index whose
25
32
  // last value predicts this point (`last_idx`), after switching to `ctx`.
@@ -38,6 +45,13 @@ function enterContext(item, ctx) {
38
45
  return lastIdx;
39
46
  }
40
47
 
48
+ // The first point of a chunk: only its channel's context is in use
49
+ function firstContext(item, ctx) {
50
+ item.unused = [true, true, true, true];
51
+ item.unused[ctx] = false;
52
+ item.lastContext = ctx;
53
+ }
54
+
41
55
  function layer(item, bytes, q, n, fail, e, base) {
42
56
  if (q + n > e) fail(q);
43
57
  if (n === 0) return null;
@@ -46,27 +60,30 @@ function layer(item, bytes, q, n, fail, e, base) {
46
60
  return d;
47
61
  }
48
62
 
63
+ // The state and models both halves of RGB14 start a chunk with
64
+ function initRgb14(self, version) {
65
+ self.size = 6;
66
+ self.version = version;
67
+ self.models = [new RgbModels(), new RgbModels(), new RgbModels(), new RgbModels()];
68
+ self.last = [new Uint16Array(3), new Uint16Array(3), new Uint16Array(3), new Uint16Array(3)];
69
+ self.unused = [true, true, true, true];
70
+ self.lastContext = 0;
71
+ }
72
+ function seedRgb(from, to) { this.last[to].set(this.last[from]); }
73
+
49
74
  /** RGB14 (version 3 or 4). */
50
75
  export function Rgb14Decompressor(version) {
51
- this.size = 6;
52
- this.version = version;
53
- this.models = [new RgbModels(), new RgbModels(), new RgbModels(), new RgbModels()];
54
- this.last = [new Uint16Array(3), new Uint16Array(3), new Uint16Array(3), new Uint16Array(3)];
55
- this.unused = [true, true, true, true];
56
- this.lastContext = 0;
76
+ initRgb14(this, version);
57
77
  this.dec = null;
58
78
  }
59
79
  Rgb14Decompressor.prototype.first = function (src, s, view, o, ctx) {
60
80
  for (var i = 0; i < 6; i++) view.setUint8(o + i, src[s + i]);
61
- var c = ctx.v;
62
- this.last[c][0] = view.getUint16(o, LE); this.last[c][1] = view.getUint16(o + 2, LE); this.last[c][2] = view.getUint16(o + 4, LE);
63
- this.unused = [true, true, true, true];
64
- this.unused[c] = false;
65
- this.lastContext = c;
81
+ readRgb(this.last[ctx.v], src, s);
82
+ firstContext(this, ctx.v);
66
83
  };
67
84
  Rgb14Decompressor.prototype.readLayerSizes = function (dv, q) { this.layerSize = dv.getUint32(q, LE); return q + 4; };
68
85
  Rgb14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) { this.dec = layer(this, bytes, q, this.layerSize, fail, e, base); return q + this.layerSize; };
69
- Rgb14Decompressor.prototype.seed = function (from, to) { this.last[to].set(this.last[from]); };
86
+ Rgb14Decompressor.prototype.seed = seedRgb;
70
87
  Rgb14Decompressor.prototype.next = function (view, o, ctx) {
71
88
  var idx = enterContext(this, ctx.v);
72
89
  var rgb = this.last[idx];
@@ -74,32 +91,57 @@ Rgb14Decompressor.prototype.next = function (view, o, ctx) {
74
91
  view.setUint16(o, rgb[0], LE); view.setUint16(o + 2, rgb[1], LE); view.setUint16(o + 4, rgb[2], LE);
75
92
  };
76
93
 
94
+ /** RGB14 compressor (version 3 or 4): one layer, written once a colour differs or is not grey. */
95
+ export function Rgb14Compressor(version) {
96
+ initRgb14(this, version);
97
+ this.enc = new ArithmeticEncoder();
98
+ this.used = false;
99
+ this.cur = new Uint16Array(3);
100
+ }
101
+ Rgb14Compressor.prototype.first = function (src, s, ctx) {
102
+ readRgb(this.last[ctx.v], src, s);
103
+ firstContext(this, ctx.v);
104
+ };
105
+ Rgb14Compressor.prototype.seed = seedRgb;
106
+ Rgb14Compressor.prototype.next = function (src, s, ctx) {
107
+ var idx = enterContext(this, ctx.v);
108
+ readRgb(this.cur, src, s);
109
+ if (encodeRgb(this.enc, this.models[this.lastContext], this.last[idx], this.cur)) this.used = true;
110
+ this.last[idx].set(this.cur);
111
+ };
112
+ /** @returns {Array<Uint8Array>} the one layer (empty when nothing was coded) */
113
+ Rgb14Compressor.prototype.finish = function () { return [this.used ? this.enc.done() : EMPTY]; };
114
+
115
+ // The state and models both halves of NIR14 start a chunk with
116
+ function initNir14(self, version) {
117
+ self.size = 2;
118
+ self.version = version;
119
+ self.usedModel = []; self.low = []; self.high = [];
120
+ for (var i = 0; i < 4; i++) { self.usedModel.push(new ArithmeticModel(4)); self.low.push(new ArithmeticModel(256)); self.high.push(new ArithmeticModel(256)); }
121
+ self.last = new Uint16Array(4);
122
+ self.unused = [true, true, true, true];
123
+ self.lastContext = 0;
124
+ }
125
+ function seedNir(from, to) { this.last[to] = this.last[from]; }
126
+
77
127
  /** NIR14 (version 3 or 4): the near-infrared word of RGBNIR14. */
78
128
  export function Nir14Decompressor(version) {
79
- this.size = 2;
80
- this.version = version;
81
- this.used = []; this.low = []; this.high = [];
82
- for (var i = 0; i < 4; i++) { this.used.push(new ArithmeticModel(4)); this.low.push(new ArithmeticModel(256)); this.high.push(new ArithmeticModel(256)); }
83
- this.last = new Uint16Array(4);
84
- this.unused = [true, true, true, true];
85
- this.lastContext = 0;
129
+ initNir14(this, version);
86
130
  this.dec = null;
87
131
  }
88
132
  Nir14Decompressor.prototype.first = function (src, s, view, o, ctx) {
89
133
  view.setUint8(o, src[s]); view.setUint8(o + 1, src[s + 1]);
90
- this.last[ctx.v] = view.getUint16(o, LE);
91
- this.unused = [true, true, true, true];
92
- this.unused[ctx.v] = false;
93
- this.lastContext = ctx.v;
134
+ this.last[ctx.v] = src[s] + src[s + 1] * 256;
135
+ firstContext(this, ctx.v);
94
136
  };
95
137
  Nir14Decompressor.prototype.readLayerSizes = Rgb14Decompressor.prototype.readLayerSizes;
96
138
  Nir14Decompressor.prototype.readLayers = Rgb14Decompressor.prototype.readLayers;
97
- Nir14Decompressor.prototype.seed = function (from, to) { this.last[to] = this.last[from]; };
139
+ Nir14Decompressor.prototype.seed = seedNir;
98
140
  Nir14Decompressor.prototype.next = function (view, o, ctx) {
99
141
  var idx = enterContext(this, ctx.v);
100
142
  if (this.dec) {
101
143
  var c = this.lastContext, last = this.last[idx];
102
- var sym = this.dec.decodeSymbol(this.used[c]);
144
+ var sym = this.dec.decodeSymbol(this.usedModel[c]);
103
145
  var v = sym & 1 ? (this.dec.decodeSymbol(this.low[c]) + (last & 255)) & 255 : last & 255;
104
146
  v += sym & 2 ? ((this.dec.decodeSymbol(this.high[c]) + (last >>> 8)) & 255) * 256 : last & 0xFF00;
105
147
  this.last[idx] = v;
@@ -107,25 +149,56 @@ Nir14Decompressor.prototype.next = function (view, o, ctx) {
107
149
  view.setUint16(o, this.last[idx], LE);
108
150
  };
109
151
 
152
+ /** NIR14 compressor (version 3 or 4): one layer, written once the value changes. */
153
+ export function Nir14Compressor(version) {
154
+ initNir14(this, version);
155
+ this.enc = new ArithmeticEncoder();
156
+ this.used = false;
157
+ }
158
+ Nir14Compressor.prototype.first = function (src, s, ctx) {
159
+ this.last[ctx.v] = src[s] + src[s + 1] * 256;
160
+ firstContext(this, ctx.v);
161
+ };
162
+ Nir14Compressor.prototype.seed = seedNir;
163
+ Nir14Compressor.prototype.next = function (src, s, ctx) {
164
+ var idx = enterContext(this, ctx.v), c = this.lastContext;
165
+ var last = this.last[idx], v = src[s] + src[s + 1] * 256;
166
+ var sym = ((last ^ v) & 255 ? 1 : 0) + ((last ^ v) & 0xFF00 ? 2 : 0);
167
+ this.enc.encodeSymbol(this.usedModel[c], sym);
168
+ if (sym & 1) this.enc.encodeSymbol(this.low[c], ((v & 255) - (last & 255)) & 255);
169
+ if (sym & 2) this.enc.encodeSymbol(this.high[c], ((v >>> 8) - (last >>> 8)) & 255);
170
+ if (sym) this.used = true;
171
+ this.last[idx] = v;
172
+ };
173
+ /** @returns {Array<Uint8Array>} the one layer (empty when the value never changed) */
174
+ Nir14Compressor.prototype.finish = Rgb14Compressor.prototype.finish;
175
+
176
+ // The state both halves of Byte14 start a chunk with; a context's models are made when it is first used
177
+ function initByte14(self, count, version) {
178
+ self.size = count;
179
+ self.version = version;
180
+ self.models = [null, null, null, null];
181
+ self.last = [new Uint8Array(count), new Uint8Array(count), new Uint8Array(count), new Uint8Array(count)];
182
+ self.unused = [true, true, true, true];
183
+ self.lastContext = 0;
184
+ }
185
+ function byteModels(count) { var m = []; for (var i = 0; i < count; i++) m.push(new ArithmeticModel(256)); return m; }
186
+ function firstBytes(item, src, s, ctx) {
187
+ item.last[ctx.v].set(src.subarray(s, s + item.size));
188
+ firstContext(item, ctx.v);
189
+ item.models[ctx.v] = byteModels(item.size);
190
+ }
191
+ function seedBytes(from, to) { this.models[to] = byteModels(this.size); this.last[to].set(this.last[from]); }
192
+
110
193
  /** Byte14 (version 3 or 4): `count` extra bytes, one layer each. */
111
194
  export function Byte14Decompressor(count, version) {
112
- this.size = count;
113
- this.version = version;
114
- this.models = [null, null, null, null];
115
- this.last = [new Uint8Array(count), new Uint8Array(count), new Uint8Array(count), new Uint8Array(count)];
116
- this.unused = [true, true, true, true];
117
- this.lastContext = 0;
195
+ initByte14(this, count, version);
118
196
  this.sizes = new Array(count);
119
197
  this.dec = new Array(count);
120
198
  }
121
- function byteModels(count) { var m = []; for (var i = 0; i < count; i++) m.push(new ArithmeticModel(256)); return m; }
122
199
  Byte14Decompressor.prototype.first = function (src, s, view, o, ctx) {
123
200
  for (var i = 0; i < this.size; i++) view.setUint8(o + i, src[s + i]);
124
- this.last[ctx.v].set(src.subarray(s, s + this.size));
125
- this.unused = [true, true, true, true];
126
- this.unused[ctx.v] = false;
127
- this.models[ctx.v] = byteModels(this.size);
128
- this.lastContext = ctx.v;
201
+ firstBytes(this, src, s, ctx);
129
202
  };
130
203
  Byte14Decompressor.prototype.readLayerSizes = function (dv, q) {
131
204
  for (var i = 0; i < this.size; i++) { this.sizes[i] = dv.getUint32(q, LE); q += 4; }
@@ -135,7 +208,7 @@ Byte14Decompressor.prototype.readLayers = function (bytes, q, e, fail, base) {
135
208
  for (var i = 0; i < this.size; i++) { this.dec[i] = layer(this, bytes, q, this.sizes[i], fail, e, base); q += this.sizes[i]; }
136
209
  return q;
137
210
  };
138
- Byte14Decompressor.prototype.seed = function (from, to) { this.models[to] = byteModels(this.size); this.last[to].set(this.last[from]); };
211
+ Byte14Decompressor.prototype.seed = seedBytes;
139
212
  Byte14Decompressor.prototype.next = function (view, o, ctx) {
140
213
  var idx = enterContext(this, ctx.v);
141
214
  var last = this.last[idx], models = this.models[ctx.v];
@@ -144,3 +217,28 @@ Byte14Decompressor.prototype.next = function (view, o, ctx) {
144
217
  view.setUint8(o + i, last[i]);
145
218
  }
146
219
  };
220
+
221
+ /** Byte14 compressor (version 3 or 4): `count` extra bytes, a layer each, written once that byte changes. */
222
+ export function Byte14Compressor(count, version) {
223
+ initByte14(this, count, version);
224
+ this.enc = [];
225
+ this.used = [];
226
+ for (var i = 0; i < count; i++) { this.enc.push(new ArithmeticEncoder()); this.used.push(false); }
227
+ }
228
+ Byte14Compressor.prototype.first = function (src, s, ctx) { firstBytes(this, src, s, ctx); };
229
+ Byte14Compressor.prototype.seed = seedBytes;
230
+ Byte14Compressor.prototype.next = function (src, s, ctx) {
231
+ var idx = enterContext(this, ctx.v);
232
+ var last = this.last[idx], models = this.models[ctx.v];
233
+ for (var i = 0; i < this.size; i++) {
234
+ var d = (src[s + i] - last[i]) & 255; // u8 wrapping_sub
235
+ this.enc[i].encodeSymbol(models[i], d);
236
+ if (d) { this.used[i] = true; last[i] = src[s + i]; }
237
+ }
238
+ };
239
+ /** @returns {Array<Uint8Array>} one layer per byte (empty when that byte never changed) */
240
+ Byte14Compressor.prototype.finish = function () {
241
+ var out = [];
242
+ for (var i = 0; i < this.size; i++) out.push(this.used[i] ? this.enc[i].done() : EMPTY);
243
+ return out;
244
+ };