|
1 | | -import {blurImage, Delaunay, randomLcg, rgb} from "d3"; |
| 1 | +import {blurImage, Delaunay, randomLcg} from "d3"; |
2 | 2 | import {valueObject} from "../channel.js"; |
3 | 3 | import {create} from "../context.js"; |
4 | 4 | import {map, first, second, third, isTuples, isNumeric, isTemporal, identity} from "../options.js"; |
@@ -36,6 +36,7 @@ export class AbstractRaster extends Mark { |
36 | 36 | y1 = y == null ? 0 : undefined, |
37 | 37 | x2 = x == null ? width : undefined, |
38 | 38 | y2 = y == null ? height : undefined, |
| 39 | + colorSpace = "srgb", |
39 | 40 | pixelSize = defaults.pixelSize, |
40 | 41 | blur = 0, |
41 | 42 | interpolate |
@@ -79,6 +80,7 @@ export class AbstractRaster extends Mark { |
79 | 80 | this.pixelSize = number(pixelSize, "pixelSize"); |
80 | 81 | this.blur = number(blur, "blur"); |
81 | 82 | this.interpolate = x == null || y == null ? null : maybeInterpolate(interpolate); // interpolation requires x & y |
| 83 | + this.colorSpace = String(colorSpace).toLowerCase(); |
82 | 84 | } |
83 | 85 | } |
84 | 86 |
|
@@ -126,29 +128,23 @@ export class Raster extends AbstractRaster { |
126 | 128 | else if (this.data == null && index) offset = index.fi * n; |
127 | 129 |
|
128 | 130 | // Render the raster grid to the canvas, blurring if needed. |
| 131 | + const colorConverter = (this.colorConverter ??= getColorConverter(this.colorSpace, context)); |
129 | 132 | const canvas = document.createElement("canvas"); |
130 | 133 | canvas.width = w; |
131 | 134 | canvas.height = h; |
132 | | - const context2d = canvas.getContext("2d"); |
| 135 | + const context2d = canvas.getContext("2d", {colorSpace: this.colorSpace}); |
133 | 136 | const image = context2d.createImageData(w, h); |
134 | 137 | const imageData = image.data; |
135 | | - let {r, g, b} = rgb(this.fill) ?? {r: 0, g: 0, b: 0}; |
136 | | - let a = (this.fillOpacity ?? 1) * 255; |
| 138 | + let rgba = colorConverter(this.fill ?? "black"); |
| 139 | + let a = this.fillOpacity ?? 1; |
137 | 140 | for (let i = 0; i < n; ++i) { |
138 | 141 | const j = i << 2; |
139 | | - if (F) { |
140 | | - const fi = color(F[i + offset]); |
141 | | - if (fi == null) { |
142 | | - imageData[j + 3] = 0; |
143 | | - continue; |
144 | | - } |
145 | | - ({r, g, b} = rgb(fi)); |
146 | | - } |
147 | | - if (FO) a = FO[i + offset] * 255; |
148 | | - imageData[j + 0] = r; |
149 | | - imageData[j + 1] = g; |
150 | | - imageData[j + 2] = b; |
151 | | - imageData[j + 3] = a; |
| 142 | + if (F) rgba = colorConverter(color(F[i + offset])); |
| 143 | + if (FO) a = FO[i + offset]; |
| 144 | + imageData[j + 0] = rgba[0]; |
| 145 | + imageData[j + 1] = rgba[1]; |
| 146 | + imageData[j + 2] = rgba[2]; |
| 147 | + imageData[j + 3] = rgba[3] * a; |
152 | 148 | } |
153 | 149 | if (this.blur > 0) blurImage(image, this.blur); |
154 | 150 | context2d.putImageData(image, 0, 0); |
@@ -502,3 +498,24 @@ function denseY(y1, y2, width, height) { |
502 | 498 | } |
503 | 499 | }; |
504 | 500 | } |
| 501 | + |
| 502 | +const transparent = new Uint8ClampedArray(4); |
| 503 | + |
| 504 | +function getColorConverter(colorSpace, {document}) { |
| 505 | + const cache = new Map(); |
| 506 | + const canvas = document.createElement("canvas"); |
| 507 | + canvas.width = 1; |
| 508 | + canvas.height = 1; |
| 509 | + const context = canvas.getContext("2d", {colorSpace, willReadFrequently: true}); |
| 510 | + return (color) => { |
| 511 | + if (color == null) return transparent; |
| 512 | + let data = cache.get(color); |
| 513 | + if (data !== undefined) return data; |
| 514 | + context.clearRect(0, 0, 1, 1); |
| 515 | + context.fillStyle = color; |
| 516 | + context.fillRect(0, 0, 1, 1); |
| 517 | + data = context.getImageData(0, 0, 1, 1).data; |
| 518 | + cache.set(color, data); |
| 519 | + return data; |
| 520 | + }; |
| 521 | +} |
0 commit comments