|
1 | 1 | import p5 from './main'; |
2 | 2 | import * as constants from './constants'; |
3 | | -import filters from '../image/filters'; |
4 | 3 |
|
5 | 4 | import './p5.Renderer'; |
6 | 5 |
|
@@ -162,13 +161,8 @@ p5.Renderer2D.prototype.image = function( |
162 | 161 | } |
163 | 162 |
|
164 | 163 | try { |
165 | | - if (this._tint) { |
166 | | - if (p5.MediaElement && img instanceof p5.MediaElement) { |
167 | | - img.loadPixels(); |
168 | | - } |
169 | | - if (img.canvas) { |
170 | | - cnv = this._getTintedImageCanvas(img); |
171 | | - } |
| 164 | + if (this._tint && img.canvas) { |
| 165 | + cnv = this._getTintedImageCanvas(img); |
172 | 166 | } |
173 | 167 | if (!cnv) { |
174 | 168 | cnv = img.canvas || img.elt; |
@@ -205,25 +199,66 @@ p5.Renderer2D.prototype._getTintedImageCanvas = function(img) { |
205 | 199 | if (!img.canvas) { |
206 | 200 | return img; |
207 | 201 | } |
208 | | - const pixels = filters._toPixels(img.canvas); |
209 | | - const tmpCanvas = document.createElement('canvas'); |
210 | | - tmpCanvas.width = img.canvas.width; |
211 | | - tmpCanvas.height = img.canvas.height; |
212 | | - const tmpCtx = tmpCanvas.getContext('2d'); |
213 | | - const id = tmpCtx.createImageData(img.canvas.width, img.canvas.height); |
214 | | - const newPixels = id.data; |
215 | | - for (let i = 0; i < pixels.length; i += 4) { |
216 | | - const r = pixels[i]; |
217 | | - const g = pixels[i + 1]; |
218 | | - const b = pixels[i + 2]; |
219 | | - const a = pixels[i + 3]; |
220 | | - newPixels[i] = r * this._tint[0] / 255; |
221 | | - newPixels[i + 1] = g * this._tint[1] / 255; |
222 | | - newPixels[i + 2] = b * this._tint[2] / 255; |
223 | | - newPixels[i + 3] = a * this._tint[3] / 255; |
| 202 | + |
| 203 | + if (!img.tintCanvas) { |
| 204 | + // Once an image has been tinted, keep its tint canvas |
| 205 | + // around so we don't need to re-incur the cost of |
| 206 | + // creating a new one for each tint |
| 207 | + img.tintCanvas = document.createElement('canvas'); |
| 208 | + } |
| 209 | + |
| 210 | + // Keep the size of the tint canvas up-to-date |
| 211 | + if (img.tintCanvas.width !== img.canvas.width) { |
| 212 | + img.tintCanvas.width = img.canvas.width; |
| 213 | + } |
| 214 | + if (img.tintCanvas.height !== img.canvas.height) { |
| 215 | + img.tintCanvas.height = img.canvas.height; |
224 | 216 | } |
225 | | - tmpCtx.putImageData(id, 0, 0); |
226 | | - return tmpCanvas; |
| 217 | + |
| 218 | + // Goal: multiply the r,g,b,a values of the source by |
| 219 | + // the r,g,b,a values of the tint color |
| 220 | + const ctx = img.tintCanvas.getContext('2d'); |
| 221 | + |
| 222 | + ctx.save(); |
| 223 | + ctx.clearRect(0, 0, img.canvas.width, img.canvas.height); |
| 224 | + |
| 225 | + if (this._tint[0] < 255 || this._tint[1] < 255 || this._tint[2] < 255) { |
| 226 | + // Color tint: we need to use the multiply blend mode to change the colors. |
| 227 | + // However, the canvas implementation of this destroys the alpha channel of |
| 228 | + // the image. To accommodate, we first get a version of the image with full |
| 229 | + // opacity everywhere, tint using multiply, and then use the destination-in |
| 230 | + // blend mode to restore the alpha channel again. |
| 231 | + |
| 232 | + // Start with the original image |
| 233 | + ctx.drawImage(img.canvas, 0, 0); |
| 234 | + |
| 235 | + // This blend mode makes everything opaque but forces the luma to match |
| 236 | + // the original image again |
| 237 | + ctx.globalCompositeOperation = 'luminosity'; |
| 238 | + ctx.drawImage(img.canvas, 0, 0); |
| 239 | + |
| 240 | + // This blend mode forces the hue and chroma to match the original image. |
| 241 | + // After this we should have the original again, but with full opacity. |
| 242 | + ctx.globalCompositeOperation = 'color'; |
| 243 | + ctx.drawImage(img.canvas, 0, 0); |
| 244 | + |
| 245 | + // Apply color tint |
| 246 | + ctx.globalCompositeOperation = 'multiply'; |
| 247 | + ctx.fillStyle = `rgb(${this._tint.slice(0, 3).join(', ')})`; |
| 248 | + ctx.fillRect(0, 0, img.canvas.width, img.canvas.height); |
| 249 | + |
| 250 | + // Replace the alpha channel with the original alpha * the alpha tint |
| 251 | + ctx.globalCompositeOperation = 'destination-in'; |
| 252 | + ctx.globalAlpha = this._tint[3] / 255; |
| 253 | + ctx.drawImage(img.canvas, 0, 0); |
| 254 | + } else { |
| 255 | + // If we only need to change the alpha, we can skip all the extra work! |
| 256 | + ctx.globalAlpha = this._tint[3] / 255; |
| 257 | + ctx.drawImage(img.canvas, 0, 0); |
| 258 | + } |
| 259 | + |
| 260 | + ctx.restore(); |
| 261 | + return img.tintCanvas; |
227 | 262 | }; |
228 | 263 |
|
229 | 264 | ////////////////////////////////////////////// |
|
0 commit comments