diff --git a/Readme.md b/Readme.md index 88b4794..e1b1053 100644 --- a/Readme.md +++ b/Readme.md @@ -7,7 +7,7 @@ Add a retro/vintage effect to images using the HTML5 canvas element. vintagejs :: TSourceElement -> $Shape -> Promise ``` -The `vintagejs` function takes two arguments, a source element (image or canvas) and an effect object and returns a promise that resolves to a result object with the following methods: +The `vintagejs` function takes two arguments, a source (url, base64 data uri, image element or canvas element) and an effect object and returns a promise that resolves to a result object with the following methods: ```javascript // returns the data url of the updated image. Use it to update the source of an existing image @@ -37,6 +37,13 @@ vintagejs(srcEl, { brightness: 0.2 }) .then(res => { ctx.drawImage(res.getCanvas(), 0, 0, srcEl.width, srcEl.height); }); + +// use a url (file path or data-uri) as source and insert result image into DOM +vintagejs('./path/to/image.jpg', { brightness: 0.2 }) + .then(res => res.genImage()) + .then(img => { + document.body.appendChild(img); + }); ``` ## Effect options @@ -107,7 +114,10 @@ Higher performance when canvas blend modes are supported [caniuse.com/#feat=canv ## Changelog -### Version 2.0.0 - Mar, 2017 +### Version 2.1.0 - Mar 5, 2017 +* Add support for strings (URI or base64 encoded data-uri) as a source + +### Version 2.0.0 - Mar 4, 2017 * Rewrite from ground up * Functional API diff --git a/bower.json b/bower.json index d19776a..1523bcd 100644 --- a/bower.json +++ b/bower.json @@ -1,5 +1,5 @@ { "name": "vintageJS", - "version": "2.0.0", + "version": "2.1.0", "main": "dist/vintage.js" } diff --git a/dist/vintage.js b/dist/vintage.js index 0a528a0..08493ee 100644 --- a/dist/vintage.js +++ b/dist/vintage.js @@ -17,10 +17,10 @@ Object.defineProperty(exports, "__esModule", { value: true }); -var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); - var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; +var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }(); + var _utils = require('./utils.js'); var defaultEffect = { @@ -104,77 +104,73 @@ var getLUT = function getLUT(effect) { return [idArr.map(rMod), idArr.map(gMod), idArr.map(bMod)]; }; -// ApplyEffect :: SourceElement -> $Shape -> Promise +var applyEffect = function applyEffect(effect) { + var LUT = getLUT(effect); + return function (_ref) { + var _ref2 = _slicedToArray(_ref, 2), + canvas = _ref2[0], + ctx = _ref2[1]; + + return new Promise(function (resolve, reject) { + var width = canvas.width, + height = canvas.height; + + ctx.globalCompositeOperation = 'multiply'; + var supportsBlendModes = ctx.globalCompositeOperation === 'multiply'; + var data = ctx.getImageData(0, 0, width, height); + var id = data.data.slice(0); + var sepia = effect.sepia, + saturation = effect.saturation; + + + for (var i = id.length / 4; i >= 0; --i) { + var ri = i << 2; + var gi = ri + 1; + var bi = ri + 2; + + var r = LUT[0][id[ri]]; + var g = LUT[1][id[gi]]; + var b = LUT[2][id[bi]]; + + if (sepia) { + var _ref3 = [r * 0.393 + g * 0.769 + b * 0.189, r * 0.349 + g * 0.686 + b * 0.168, r * 0.272 + g * 0.534 + b * 0.131]; + r = _ref3[0]; + g = _ref3[1]; + b = _ref3[2]; + } -exports.default = function (srcEl, partialEffect) { - return new Promise(function (resolve, reject) { - var effect = _extends({}, defaultEffect, partialEffect); - var LUT = getLUT(effect); - - var _getCanvasAndCtx = (0, _utils.getCanvasAndCtx)(srcEl), - _getCanvasAndCtx2 = _slicedToArray(_getCanvasAndCtx, 2), - canvas = _getCanvasAndCtx2[0], - ctx = _getCanvasAndCtx2[1]; - - var width = canvas.width, - height = canvas.height; - - ctx.globalCompositeOperation = 'multiply'; - var supportsBlendModes = ctx.globalCompositeOperation === 'multiply'; - var data = ctx.getImageData(0, 0, width, height); - var id = data.data.slice(0); - var sepia = effect.sepia, - saturation = effect.saturation, - viewfinder = effect.viewfinder; - - - for (var i = id.length / 4; i >= 0; --i) { - var ri = i << 2; - var gi = ri + 1; - var bi = ri + 2; - - var r = LUT[0][id[ri]]; - var g = LUT[1][id[gi]]; - var b = LUT[2][id[bi]]; - - if (sepia) { - var _ref = [r * 0.393 + g * 0.769 + b * 0.189, r * 0.349 + g * 0.686 + b * 0.168, r * 0.272 + g * 0.534 + b * 0.131]; - r = _ref[0]; - g = _ref[1]; - b = _ref[2]; - } + if (saturation < 1) { + var avg = (r + g + b) / 3; + r += (avg - r) * (1 - saturation); + g += (avg - g) * (1 - saturation); + b += (avg - b) * (1 - saturation); + } - if (saturation < 1) { - var avg = (r + g + b) / 3; - r += (avg - r) * (1 - saturation); - g += (avg - g) * (1 - saturation); - b += (avg - b) * (1 - saturation); + id[ri] = r; + id[gi] = g; + id[bi] = b; } - id[ri] = r; - id[gi] = g; - id[bi] = b; - } + data.data.set(id); + ctx.putImageData(data, 0, 0); - data.data.set(id); - ctx.putImageData(data, 0, 0); + if (effect.vignette) { + ctx.globalCompositeOperation = supportsBlendModes ? 'multiply' : 'source-over'; + ctx.fillStyle = (0, _utils.getGradient)(ctx, width, height, ['rgba(0,0,0,0)', 'rgba(0,0,0,0)', 'rgba(0,0,0,' + effect.vignette + ')']); + ctx.fillRect(0, 0, width, height); + } - if (effect.vignette) { - ctx.globalCompositeOperation = supportsBlendModes ? 'multiply' : 'source-over'; - ctx.fillStyle = (0, _utils.getGradient)(ctx, width, height, ['rgba(0,0,0,0)', 'rgba(0,0,0,0)', 'rgba(0,0,0,' + effect.vignette + ')']); - ctx.fillRect(0, 0, width, height); - } + if (effect.lighten) { + ctx.globalCompositeOperation = supportsBlendModes ? 'screen' : 'lighter'; + ctx.fillStyle = (0, _utils.getGradient)(ctx, width, height, ['rgba(255,255,255,' + effect.lighten + ')', 'rgba(255,255,255,0)', 'rgba(0,0,0,0)']); + ctx.fillRect(0, 0, width, height); + } - if (effect.lighten) { - ctx.globalCompositeOperation = supportsBlendModes ? 'screen' : 'lighter'; - ctx.fillStyle = (0, _utils.getGradient)(ctx, width, height, ['rgba(255,255,255,' + effect.lighten + ')', 'rgba(255,255,255,0)', 'rgba(0,0,0,0)']); - ctx.fillRect(0, 0, width, height); - } + if (!effect.viewfinder) { + return resolve((0, _utils.getResult)(canvas)); + } - if (!viewfinder) { - resolve((0, _utils.getResult)(canvas)); - } else { - return (0, _utils.loadImageWithCache)(viewfinder).then(function (img) { + return (0, _utils.loadImageWithCache)(effect.viewfinder).then(function (img) { if (supportsBlendModes) { ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(img, 0, 0, width, height); @@ -193,10 +189,19 @@ exports.default = function (srcEl, partialEffect) { })); ctx.putImageData(imageData, 0, 0); } - resolve((0, _utils.getResult)(canvas)); + + return resolve((0, _utils.getResult)(canvas)); }); - } - }); + }); + }; +}; + +// vintagejs :: TSource -> $Shape -> Promise + +exports.default = function (src, partialEffect) { + var genSource = typeof src === 'string' ? (0, _utils.loadImage)(src).then(_utils.getCanvasAndCtx) : Promise.resolve((0, _utils.getCanvasAndCtx)(src)); + + return genSource.then(applyEffect(_extends({}, defaultEffect, partialEffect))); }; module.exports = exports['default']; diff --git a/dist/vintage.min.js b/dist/vintage.min.js index 5c8f647..e3751fa 100644 --- a/dist/vintage.min.js +++ b/dist/vintage.min.js @@ -1 +1 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vintagejs=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=0;--i){var ri=i<<2;var gi=ri+1;var bi=ri+2;var r=LUT[0][id[ri]];var g=LUT[1][id[gi]];var b=LUT[2][id[bi]];if(sepia){var _ref=[r*.393+g*.769+b*.189,r*.349+g*.686+b*.168,r*.272+g*.534+b*.131];r=_ref[0];g=_ref[1];b=_ref[2]}if(saturation<1){var avg=(r+g+b)/3;r+=(avg-r)*(1-saturation);g+=(avg-g)*(1-saturation);b+=(avg-b)*(1-saturation)}id[ri]=r;id[gi]=g;id[bi]=b}data.data.set(id);ctx.putImageData(data,0,0);if(effect.vignette){ctx.globalCompositeOperation=supportsBlendModes?"multiply":"source-over";ctx.fillStyle=(0,_utils.getGradient)(ctx,width,height,["rgba(0,0,0,0)","rgba(0,0,0,0)","rgba(0,0,0,"+effect.vignette+")"]);ctx.fillRect(0,0,width,height)}if(effect.lighten){ctx.globalCompositeOperation=supportsBlendModes?"screen":"lighter";ctx.fillStyle=(0,_utils.getGradient)(ctx,width,height,["rgba(255,255,255,"+effect.lighten+")","rgba(255,255,255,0)","rgba(0,0,0,0)"]);ctx.fillRect(0,0,width,height)}if(!viewfinder){resolve((0,_utils.getResult)(canvas))}else{return(0,_utils.loadImageWithCache)(viewfinder).then(function(img){if(supportsBlendModes){ctx.globalCompositeOperation="multiply";ctx.drawImage(img,0,0,width,height)}else{var _createCanvasAndCtxFr=(0,_utils.createCanvasAndCtxFromImage)(img,width,height),_createCanvasAndCtxFr2=_slicedToArray(_createCanvasAndCtxFr,2),_=_createCanvasAndCtxFr2[0],vfCtx=_createCanvasAndCtxFr2[1];var _vfCtx$getImageData=vfCtx.getImageData(0,0,width,height),vfData=_vfCtx$getImageData.data;var imageData=ctx.getImageData(0,0,width,height);imageData.data.set(imageData.data.map(function(v,i){return v*vfData[i]/255}));ctx.putImageData(imageData,0,0)}resolve((0,_utils.getResult)(canvas))})}})};module.exports=exports["default"]},{"./utils.js":3}],3:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getResult=exports.loadImageWithCache=exports.loadImage=exports.getGradient=exports.getCanvasAndCtx=exports.cloneCanvasAndCtx=exports.createCanvasAndCtxFromImage=exports.compose=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj};var _nullthrows=require("nullthrows");var _nullthrows2=_interopRequireDefault(_nullthrows);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var IMAGE_TYPE="image/jpeg";var IMAGE_QUALITY=1;var compose=exports.compose=function compose(f,g){return function(x){return f(g(x))}};var createCanvasAndCtxFromImage=exports.createCanvasAndCtxFromImage=function createCanvasAndCtxFromImage(el,width,height){var canvas=document.createElement("canvas");if(!width)width=el.width;if(!height)height=el.height;canvas.width=width;canvas.height=height;var ctx=(0,_nullthrows2.default)(canvas.getContext("2d"),"Could not get 2d context for canvas");ctx.drawImage(el,0,0,width,height);return[canvas,ctx]};var cloneCanvasAndCtx=exports.cloneCanvasAndCtx=function cloneCanvasAndCtx(source){var width=source.width,height=source.height;var target=document.createElement("canvas");var targetCtx=(0,_nullthrows2.default)(target.getContext("2d"),"Could not get 2d context for canvas");target.width=width;target.height=height;targetCtx.drawImage(source,0,0,width,height);return[target,targetCtx]};var getCanvasAndCtx=exports.getCanvasAndCtx=function getCanvasAndCtx(el){if(el instanceof HTMLImageElement){return createCanvasAndCtxFromImage(el)}if(el instanceof HTMLCanvasElement){return cloneCanvasAndCtx(el)}throw new Error("Unsupported source element. Expected HTMLCanvasElement or HTMLImageElement, got "+(typeof el==="undefined"?"undefined":_typeof(el))+".")};var getGradient=exports.getGradient=function getGradient(ctx,width,height,colorSteps){var gradient=ctx.createRadialGradient(width/2,height/2,0,width/2,height/2,Math.sqrt(Math.pow(width/2,2)+Math.pow(height/2,2)));colorSteps.forEach(function(color,idx,steps){gradient.addColorStop(idx/(steps.length-1),color)});return gradient};var loadImage=exports.loadImage=function loadImage(src){return new Promise(function(resolve,reject){var img=new Image;img.onload=function(){return resolve(img)};img.onerror=function(err){return reject(err)};img.src=src})};var loadImageWithCache=exports.loadImageWithCache=function(){var cache={};return function(src){return cache[src]?Promise.resolve(cache[src]):loadImage(src).then(function(img){cache[src]=img;return img})}}();var getResult=exports.getResult=function getResult(canvas){return{getDataURL:function getDataURL(){var mimeType=arguments.length>0&&arguments[0]!==undefined?arguments[0]:IMAGE_TYPE;var quality=arguments.length>1&&arguments[1]!==undefined?arguments[1]:IMAGE_QUALITY;return canvas.toDataURL(mimeType,quality)},getCanvas:function getCanvas(){return canvas},genImage:function genImage(){var mimeType=arguments.length>0&&arguments[0]!==undefined?arguments[0]:IMAGE_TYPE;var quality=arguments.length>1&&arguments[1]!==undefined?arguments[1]:IMAGE_QUALITY;return loadImage(canvas.toDataURL(mimeType,quality))}}}},{nullthrows:1}]},{},[2])(2)}); +(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vintagejs=f()}})(function(){var define,module,exports;return function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o=0;--i){var ri=i<<2;var gi=ri+1;var bi=ri+2;var r=LUT[0][id[ri]];var g=LUT[1][id[gi]];var b=LUT[2][id[bi]];if(sepia){var _ref3=[r*.393+g*.769+b*.189,r*.349+g*.686+b*.168,r*.272+g*.534+b*.131];r=_ref3[0];g=_ref3[1];b=_ref3[2]}if(saturation<1){var avg=(r+g+b)/3;r+=(avg-r)*(1-saturation);g+=(avg-g)*(1-saturation);b+=(avg-b)*(1-saturation)}id[ri]=r;id[gi]=g;id[bi]=b}data.data.set(id);ctx.putImageData(data,0,0);if(effect.vignette){ctx.globalCompositeOperation=supportsBlendModes?"multiply":"source-over";ctx.fillStyle=(0,_utils.getGradient)(ctx,width,height,["rgba(0,0,0,0)","rgba(0,0,0,0)","rgba(0,0,0,"+effect.vignette+")"]);ctx.fillRect(0,0,width,height)}if(effect.lighten){ctx.globalCompositeOperation=supportsBlendModes?"screen":"lighter";ctx.fillStyle=(0,_utils.getGradient)(ctx,width,height,["rgba(255,255,255,"+effect.lighten+")","rgba(255,255,255,0)","rgba(0,0,0,0)"]);ctx.fillRect(0,0,width,height)}if(!effect.viewfinder){return resolve((0,_utils.getResult)(canvas))}return(0,_utils.loadImageWithCache)(effect.viewfinder).then(function(img){if(supportsBlendModes){ctx.globalCompositeOperation="multiply";ctx.drawImage(img,0,0,width,height)}else{var _createCanvasAndCtxFr=(0,_utils.createCanvasAndCtxFromImage)(img,width,height),_createCanvasAndCtxFr2=_slicedToArray(_createCanvasAndCtxFr,2),_=_createCanvasAndCtxFr2[0],vfCtx=_createCanvasAndCtxFr2[1];var _vfCtx$getImageData=vfCtx.getImageData(0,0,width,height),vfData=_vfCtx$getImageData.data;var imageData=ctx.getImageData(0,0,width,height);imageData.data.set(imageData.data.map(function(v,i){return v*vfData[i]/255}));ctx.putImageData(imageData,0,0)}return resolve((0,_utils.getResult)(canvas))})})}};exports.default=function(src,partialEffect){var genSource=typeof src==="string"?(0,_utils.loadImage)(src).then(_utils.getCanvasAndCtx):Promise.resolve((0,_utils.getCanvasAndCtx)(src));return genSource.then(applyEffect(_extends({},defaultEffect,partialEffect)))};module.exports=exports["default"]},{"./utils.js":3}],3:[function(require,module,exports){"use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.getResult=exports.loadImageWithCache=exports.loadImage=exports.getGradient=exports.getCanvasAndCtx=exports.cloneCanvasAndCtx=exports.createCanvasAndCtxFromImage=exports.compose=undefined;var _typeof=typeof Symbol==="function"&&typeof Symbol.iterator==="symbol"?function(obj){return typeof obj}:function(obj){return obj&&typeof Symbol==="function"&&obj.constructor===Symbol&&obj!==Symbol.prototype?"symbol":typeof obj};var _nullthrows=require("nullthrows");var _nullthrows2=_interopRequireDefault(_nullthrows);function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var IMAGE_TYPE="image/jpeg";var IMAGE_QUALITY=1;var compose=exports.compose=function compose(f,g){return function(x){return f(g(x))}};var createCanvasAndCtxFromImage=exports.createCanvasAndCtxFromImage=function createCanvasAndCtxFromImage(el,width,height){var canvas=document.createElement("canvas");if(!width)width=el.width;if(!height)height=el.height;canvas.width=width;canvas.height=height;var ctx=(0,_nullthrows2.default)(canvas.getContext("2d"),"Could not get 2d context for canvas");ctx.drawImage(el,0,0,width,height);return[canvas,ctx]};var cloneCanvasAndCtx=exports.cloneCanvasAndCtx=function cloneCanvasAndCtx(source){var width=source.width,height=source.height;var target=document.createElement("canvas");var targetCtx=(0,_nullthrows2.default)(target.getContext("2d"),"Could not get 2d context for canvas");target.width=width;target.height=height;targetCtx.drawImage(source,0,0,width,height);return[target,targetCtx]};var getCanvasAndCtx=exports.getCanvasAndCtx=function getCanvasAndCtx(el){if(el instanceof HTMLImageElement){return createCanvasAndCtxFromImage(el)}if(el instanceof HTMLCanvasElement){return cloneCanvasAndCtx(el)}throw new Error("Unsupported source element. Expected HTMLCanvasElement or HTMLImageElement, got "+(typeof el==="undefined"?"undefined":_typeof(el))+".")};var getGradient=exports.getGradient=function getGradient(ctx,width,height,colorSteps){var gradient=ctx.createRadialGradient(width/2,height/2,0,width/2,height/2,Math.sqrt(Math.pow(width/2,2)+Math.pow(height/2,2)));colorSteps.forEach(function(color,idx,steps){gradient.addColorStop(idx/(steps.length-1),color)});return gradient};var loadImage=exports.loadImage=function loadImage(src){return new Promise(function(resolve,reject){var img=new Image;img.onload=function(){return resolve(img)};img.onerror=function(err){return reject(err)};img.src=src})};var loadImageWithCache=exports.loadImageWithCache=function(){var cache={};return function(src){return cache[src]?Promise.resolve(cache[src]):loadImage(src).then(function(img){cache[src]=img;return img})}}();var getResult=exports.getResult=function getResult(canvas){return{getDataURL:function getDataURL(){var mimeType=arguments.length>0&&arguments[0]!==undefined?arguments[0]:IMAGE_TYPE;var quality=arguments.length>1&&arguments[1]!==undefined?arguments[1]:IMAGE_QUALITY;return canvas.toDataURL(mimeType,quality)},getCanvas:function getCanvas(){return canvas},genImage:function genImage(){var mimeType=arguments.length>0&&arguments[0]!==undefined?arguments[0]:IMAGE_TYPE;var quality=arguments.length>1&&arguments[1]!==undefined?arguments[1]:IMAGE_QUALITY;return loadImage(canvas.toDataURL(mimeType,quality))}}}},{nullthrows:1}]},{},[2])(2)}); diff --git a/examples/example.js b/examples/example.js index f278304..074efa2 100644 --- a/examples/example.js +++ b/examples/example.js @@ -35,7 +35,7 @@ const effect = { brightness: -0.1, contrast: 0.15, curves: curves2, - saturation: 0.7, + saturation: 0.8, viewfinder: './film-1.jpg', screen: { r: 227, @@ -43,9 +43,12 @@ const effect = { b: 169, a: 0.15, }, - sepia: true, }; +vintagejs('./dude.jpg', effect).then(res => res.genImage()).then(img => { + document.body.appendChild(img); +}); + const img = document.getElementById('picture'); vintagejs(img, effect).then(res => { img.src = res.getDataURL(); diff --git a/package.json b/package.json index 2a5792e..56d4840 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "vintagejs", "description": "Add a retro/vintage effect to images using the HTML5 canvas element", "license": "MIT", - "version": "2.0.0", + "version": "2.1.0", "author": { "name": "Robert Fleischmann", "email": "rendro87@gmail.com", diff --git a/src/index.js b/src/index.js index bd5087a..b880d5a 100644 --- a/src/index.js +++ b/src/index.js @@ -2,10 +2,10 @@ import type { TEffect, - TSourceElement, TResult, - TUnaryFn, + TSource, TUint8Array, + TUnaryFn, } from './types.js'; import { @@ -78,83 +78,80 @@ const getLUT = (effect: TEffect): Array => { return [idArr.map(rMod), idArr.map(gMod), idArr.map(bMod)]; }; -// ApplyEffect :: SourceElement -> $Shape -> Promise -export default ( - srcEl: TSourceElement, - partialEffect: $Shape, -): Promise => - new Promise((resolve, reject) => { - const effect = { - ...defaultEffect, - ...partialEffect, - }; - const LUT = getLUT(effect); - const [canvas, ctx] = getCanvasAndCtx(srcEl); - const { width, height } = canvas; - ctx.globalCompositeOperation = 'multiply'; - const supportsBlendModes = ctx.globalCompositeOperation === 'multiply'; - const data = ctx.getImageData(0, 0, width, height); - const id = data.data.slice(0); - const { sepia, saturation, viewfinder } = effect; - - for (let i = id.length / 4; i >= 0; --i) { - let ri = i << 2; - let gi = ri + 1; - let bi = ri + 2; - - let r = LUT[0][id[ri]]; - let g = LUT[1][id[gi]]; - let b = LUT[2][id[bi]]; - - if (sepia) { - [r, g, b] = [ - r * 0.393 + g * 0.769 + b * 0.189, - r * 0.349 + g * 0.686 + b * 0.168, - r * 0.272 + g * 0.534 + b * 0.131, - ]; +const applyEffect = (effect: TEffect) => { + const LUT = getLUT(effect); + return ( + [canvas, ctx]: [HTMLCanvasElement, CanvasRenderingContext2D], + ): Promise => + new Promise((resolve, reject) => { + const { width, height } = canvas; + ctx.globalCompositeOperation = 'multiply'; + const supportsBlendModes = ctx.globalCompositeOperation === 'multiply'; + const data = ctx.getImageData(0, 0, width, height); + const id = data.data.slice(0); + const { sepia, saturation } = effect; + + for (let i = id.length / 4; i >= 0; --i) { + let ri = i << 2; + let gi = ri + 1; + let bi = ri + 2; + + let r = LUT[0][id[ri]]; + let g = LUT[1][id[gi]]; + let b = LUT[2][id[bi]]; + + if (sepia) { + [r, g, b] = [ + r * 0.393 + g * 0.769 + b * 0.189, + r * 0.349 + g * 0.686 + b * 0.168, + r * 0.272 + g * 0.534 + b * 0.131, + ]; + } + + if (saturation < 1) { + const avg = (r + g + b) / 3; + r += (avg - r) * (1 - saturation); + g += (avg - g) * (1 - saturation); + b += (avg - b) * (1 - saturation); + } + + id[ri] = r; + id[gi] = g; + id[bi] = b; + } + + data.data.set(id); + ctx.putImageData(data, 0, 0); + + if (effect.vignette) { + ctx.globalCompositeOperation = supportsBlendModes + ? 'multiply' + : 'source-over'; + ctx.fillStyle = getGradient(ctx, width, height, [ + 'rgba(0,0,0,0)', + 'rgba(0,0,0,0)', + `rgba(0,0,0,${effect.vignette})`, + ]); + ctx.fillRect(0, 0, width, height); } - if (saturation < 1) { - const avg = (r + g + b) / 3; - r += (avg - r) * (1 - saturation); - g += (avg - g) * (1 - saturation); - b += (avg - b) * (1 - saturation); + if (effect.lighten) { + ctx.globalCompositeOperation = supportsBlendModes + ? 'screen' + : 'lighter'; + ctx.fillStyle = getGradient(ctx, width, height, [ + `rgba(255,255,255,${effect.lighten})`, + 'rgba(255,255,255,0)', + 'rgba(0,0,0,0)', + ]); + ctx.fillRect(0, 0, width, height); } - id[ri] = r; - id[gi] = g; - id[bi] = b; - } - - data.data.set(id); - ctx.putImageData(data, 0, 0); - - if (effect.vignette) { - ctx.globalCompositeOperation = supportsBlendModes - ? 'multiply' - : 'source-over'; - ctx.fillStyle = getGradient(ctx, width, height, [ - 'rgba(0,0,0,0)', - 'rgba(0,0,0,0)', - `rgba(0,0,0,${effect.vignette})`, - ]); - ctx.fillRect(0, 0, width, height); - } - - if (effect.lighten) { - ctx.globalCompositeOperation = supportsBlendModes ? 'screen' : 'lighter'; - ctx.fillStyle = getGradient(ctx, width, height, [ - `rgba(255,255,255,${effect.lighten})`, - 'rgba(255,255,255,0)', - 'rgba(0,0,0,0)', - ]); - ctx.fillRect(0, 0, width, height); - } - - if (!viewfinder) { - resolve(getResult(canvas)); - } else { - return loadImageWithCache(viewfinder).then(img => { + if (!effect.viewfinder) { + return resolve(getResult(canvas)); + } + + return loadImageWithCache(effect.viewfinder).then(img => { if (supportsBlendModes) { ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(img, 0, 0, width, height); @@ -165,7 +162,25 @@ export default ( imageData.data.set(imageData.data.map((v, i) => v * vfData[i] / 255)); ctx.putImageData(imageData, 0, 0); } - resolve(getResult(canvas)); + + return resolve(getResult(canvas)); }); - } - }); + }); +}; + +// vintagejs :: TSource -> $Shape -> Promise +export default ( + src: TSource, + partialEffect: $Shape, +): Promise => { + const genSource = typeof src === 'string' + ? loadImage(src).then(getCanvasAndCtx) + : Promise.resolve(getCanvasAndCtx(src)); + + return genSource.then( + applyEffect({ + ...defaultEffect, + ...partialEffect, + }), + ); +}; diff --git a/src/types.js b/src/types.js index aaa17c4..66ea544 100644 --- a/src/types.js +++ b/src/types.js @@ -6,7 +6,7 @@ export type TUnaryFn = (a: A) => B; export type TPixel = [number, number, number]; -export type TSourceElement = HTMLImageElement | HTMLCanvasElement; +export type TSource = string | HTMLImageElement | HTMLCanvasElement; export type TRGBAColor = { +r: number, diff --git a/src/utils.js b/src/utils.js index f5bb096..06c54a8 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,6 +1,6 @@ // @flow -import type { TUnaryFn, TSourceElement, TResult } from './types.js'; +import type { TUnaryFn, TSource, TResult } from './types.js'; import nullthrows from 'nullthrows'; @@ -49,7 +49,7 @@ export const cloneCanvasAndCtx = ( }; export const getCanvasAndCtx = ( - el: TSourceElement, + el: HTMLCanvasElement | HTMLImageElement, ): [HTMLCanvasElement, CanvasRenderingContext2D] => { if (el instanceof HTMLImageElement) { return createCanvasAndCtxFromImage(el);