diff --git a/README.md b/README.md index c546ad5..6e62448 100644 --- a/README.md +++ b/README.md @@ -897,6 +897,32 @@ This would *increase* the canvas size by 100px horizontally and vertically, esse Note that expanding regenerates the underlying canvas object. It effectively creates a new canvas at the expanded size, then copies the source into the destination, then discards the source. +### exposure + +**Live Demo:** [Exposure](https://jhuckaby.github.io/canvas-plus-playground/?t=load/eyJpbWFnZSI6ImtpdHRlbi5qcGcifQ%3D%3D&t=exposure/eyJhbW91bnQiOjI1fQ%3D%3D&f=png) + +The `exposure()` method simulates increasing or decreasing the camera film exposure. Interally, this is accomplished by a special [curve](#curves). To use, specify an `amount` between -100 (darken) and 100 (lighten). The method accepts an object containing the following properties: + +| Property Name | Type | Description | +|---------------|------|-------------| +| `amount` | Integer | Exposure adjustment, from -100 (darken) to 100 (lighten). | +| `channels` | String | Which channels to apply the filter to, defaults to `rgb`. See [Channels](#channels). | +| `clip` | Object | Optional clipping rectangle (see [Clipping](#clipping) below). | + +Example use: + +```js +canvas.exposure({ + "amount": 25 +}); +``` + +Note that if `amount` is the sole argument, you can simply pass the number itself: + +```js +canvas.exposure( 25 ); +``` + ### findEdges **Live Demo:** [Find Edges](https://jhuckaby.github.io/canvas-plus-playground/?t=load/eyJpbWFnZSI6InN1bnNldC5qcGcifQ%3D%3D&t=resize/eyJ3aWR0aCI6NjQwLCJoZWlnaHQiOjQ4MCwibW9kZSI6ImZpdCJ9&t=findEdges/e30%3D&f=png) @@ -1019,6 +1045,27 @@ canvas.curves({ }); ``` +### lighting + +**Live Demo:** [Shadow Detail](https://jhuckaby.github.io/canvas-plus-playground/?t=load/eyJpbWFnZSI6InN1bnNldC5qcGcifQ%3D%3D&t=lighting/eyJzaGFkb3dzIjo1MH0%3D&f=png) + +The `lighting()` method can adjust both shadows and highlights, to bring out hidden details. Interally, this is accomplished by a special multi-point [curve](#curves). The method accepts an object containing the following properties: + +| Property Name | Type | Description | +|---------------|------|-------------| +| `shadows` | Integer | Shadow detail adjustment, from -100 (darken) to 100 (lighten). | +| `highlights` | Integer | Highlight detail adjustment, from -100 (darken) to 100 (lighten). | +| `channels` | String | Which channels to apply the filter to, defaults to `rgb`. See [Channels](#channels). | +| `clip` | Object | Optional clipping rectangle (see [Clipping](#clipping) below). | + +Example use: + +```js +canvas.lighting({ + "shadows": 25 +}); +``` + ### mask **Live Demo:** [Apply Mask](https://jhuckaby.github.io/canvas-plus-playground/?t=load/eyJpbWFnZSI6IndhdGVyZmFsbC5qcGcifQ%3D%3D&t=resize/eyJ3aWR0aCI6NjQwLCJoZWlnaHQiOjQ4MCwibW9kZSI6ImZpdCJ9&t=mask/e30%3D&f=png) diff --git a/canvas-plus.js b/canvas-plus.js index 6dffcc2..fa90306 100644 --- a/canvas-plus.js +++ b/canvas-plus.js @@ -1554,6 +1554,83 @@ module.exports = Class.create({ return this.curves(opts); }, + lighting: function(opts) { + // apply lighting functions, e.g. shadows, highlights + // opts: { shadows, highlights, channels, clip } + opts = this.copyHash( opts || {} ); + var curve = [ [0,0], [63,63], [191,191], [255,255] ]; + + // shadows + if (opts.shadows) { + curve[1][1] += opts.shadows; + } + + // highlights + if (opts.highlights) { + curve[2][1] += opts.highlights; + } + + for (var idx = 0, len = curve.length; idx < len; idx++) { + curve[idx][1] = Math.max(0, Math.min(255, curve[idx][1]) ); + } + + var channels = opts.channels || 'rgb'; + delete opts.channels; + + if (channels.match(/rgb/i)) opts.rgb = curve; + else { + if (channels.match(/r/i)) opts.red = curve; + if (channels.match(/g/i)) opts.green = curve; + if (channels.match(/b/i)) opts.blue = curve; + } + if (channels.match(/a/i)) opts.alpha = curve; + + return this.curves(opts); + }, + + exposure: function(opts) { + // apply exposure adjustment + // opts: { amount, channels, clip } + if (typeof(opts) == 'number') { + opts = { amount: opts }; + } + opts = this.copyHash( opts || {} ); + var curve = [ 0, 63, 127, 191, 255 ]; + + // exposure + if (opts.amount) { + if (opts.amount > 0) { + curve[1] += (opts.amount * 2); + curve[2] += (opts.amount * 3); + curve[3] += (opts.amount * 4); + } + else { + opts.amount /= 2; + curve[4] += (opts.amount * 5); + curve[3] += (opts.amount * 4); + curve[2] += (opts.amount * 3); + curve[1] += (opts.amount * 2); + } + } + + for (var idx = 0, len = curve.length; idx < len; idx++) { + curve[idx] = Math.max(0, Math.min(255, curve[idx]) ); + } + + var channels = opts.channels || 'rgb'; + delete opts.channels; + + if (channels.match(/rgb/i)) opts.rgb = curve; + else { + if (channels.match(/r/i)) opts.red = curve; + if (channels.match(/g/i)) opts.green = curve; + if (channels.match(/b/i)) opts.blue = curve; + } + if (channels.match(/a/i)) opts.alpha = curve; + + return this.curves(opts); + }, + generateCurve: function(points) { // Generate curve from points using monotone cubic interpolation. // This is somewhat like Adobe Photoshop's 'Curves' filter. @@ -3765,7 +3842,7 @@ module.exports = Class.create({ this.logDebug(6, "Compressing into WebP format", opts ); this.webp.encode( imgData, opts, function(err, data) { - if (err) return doError('webp', "WebP Encode Error: " + err, callback); + if (err) return self.doError('webp', "WebP Encode Error: " + err, callback); self.logDebug(6, "WebP compression complete"); callback(null, data); } ); diff --git a/lib/filter/curves.js b/lib/filter/curves.js index 020ac66..222f03c 100644 --- a/lib/filter/curves.js +++ b/lib/filter/curves.js @@ -302,6 +302,83 @@ module.exports = Class.create({ return this.curves(opts); }, + lighting: function(opts) { + // apply lighting functions, e.g. shadows, highlights + // opts: { shadows, highlights, channels, clip } + opts = this.copyHash( opts || {} ); + var curve = [ [0,0], [63,63], [191,191], [255,255] ]; + + // shadows + if (opts.shadows) { + curve[1][1] += opts.shadows; + } + + // highlights + if (opts.highlights) { + curve[2][1] += opts.highlights; + } + + for (var idx = 0, len = curve.length; idx < len; idx++) { + curve[idx][1] = Math.max(0, Math.min(255, curve[idx][1]) ); + } + + var channels = opts.channels || 'rgb'; + delete opts.channels; + + if (channels.match(/rgb/i)) opts.rgb = curve; + else { + if (channels.match(/r/i)) opts.red = curve; + if (channels.match(/g/i)) opts.green = curve; + if (channels.match(/b/i)) opts.blue = curve; + } + if (channels.match(/a/i)) opts.alpha = curve; + + return this.curves(opts); + }, + + exposure: function(opts) { + // apply exposure adjustment + // opts: { amount, channels, clip } + if (typeof(opts) == 'number') { + opts = { amount: opts }; + } + opts = this.copyHash( opts || {} ); + var curve = [ 0, 63, 127, 191, 255 ]; + + // exposure + if (opts.amount) { + if (opts.amount > 0) { + curve[1] += (opts.amount * 2); + curve[2] += (opts.amount * 3); + curve[3] += (opts.amount * 4); + } + else { + opts.amount /= 2; + curve[4] += (opts.amount * 5); + curve[3] += (opts.amount * 4); + curve[2] += (opts.amount * 3); + curve[1] += (opts.amount * 2); + } + } + + for (var idx = 0, len = curve.length; idx < len; idx++) { + curve[idx] = Math.max(0, Math.min(255, curve[idx]) ); + } + + var channels = opts.channels || 'rgb'; + delete opts.channels; + + if (channels.match(/rgb/i)) opts.rgb = curve; + else { + if (channels.match(/r/i)) opts.red = curve; + if (channels.match(/g/i)) opts.green = curve; + if (channels.match(/b/i)) opts.blue = curve; + } + if (channels.match(/a/i)) opts.alpha = curve; + + return this.curves(opts); + }, + generateCurve: function(points) { // Generate curve from points using monotone cubic interpolation. // This is somewhat like Adobe Photoshop's 'Curves' filter. diff --git a/package.json b/package.json index a747afa..4afb0fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixl-canvas-plus", - "version": "2.1.5", + "version": "2.1.6", "description": "A universal library for manipulating images, built on canvas.", "author": "Joseph Huckaby ", "homepage": "https://github.com/jhuckaby/canvas-plus",