diff --git a/app.js b/app.js index c19f7b1..d7e3b94 100644 --- a/app.js +++ b/app.js @@ -80,6 +80,190 @@ function A9(fun, a, b, c, d, e, f, g, h, i) { console.warn('Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.1/optimize for better performance and smaller assets.'); +// EQUALITY + +function _Utils_eq(x, y) +{ + for ( + var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack); + isEqual && (pair = stack.pop()); + isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack) + ) + {} + + return isEqual; +} + +function _Utils_eqHelp(x, y, depth, stack) +{ + if (x === y) + { + return true; + } + + if (typeof x !== 'object' || x === null || y === null) + { + typeof x === 'function' && _Debug_crash(5); + return false; + } + + if (depth > 100) + { + stack.push(_Utils_Tuple2(x,y)); + return true; + } + + /**/ + if (x.$ === 'Set_elm_builtin') + { + x = $elm$core$Set$toList(x); + y = $elm$core$Set$toList(y); + } + if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin') + { + x = $elm$core$Dict$toList(x); + y = $elm$core$Dict$toList(y); + } + //*/ + + /**_UNUSED/ + if (x.$ < 0) + { + x = $elm$core$Dict$toList(x); + y = $elm$core$Dict$toList(y); + } + //*/ + + for (var key in x) + { + if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack)) + { + return false; + } + } + return true; +} + +var _Utils_equal = F2(_Utils_eq); +var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); }); + + + +// COMPARISONS + +// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on +// the particular integer values assigned to LT, EQ, and GT. + +function _Utils_cmp(x, y, ord) +{ + if (typeof x !== 'object') + { + return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1; + } + + /**/ + if (x instanceof String) + { + var a = x.valueOf(); + var b = y.valueOf(); + return a === b ? 0 : a < b ? -1 : 1; + } + //*/ + + /**_UNUSED/ + if (typeof x.$ === 'undefined') + //*/ + /**/ + if (x.$[0] === '#') + //*/ + { + return (ord = _Utils_cmp(x.a, y.a)) + ? ord + : (ord = _Utils_cmp(x.b, y.b)) + ? ord + : _Utils_cmp(x.c, y.c); + } + + // traverse conses until end of a list or a mismatch + for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES + return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0); +} + +var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; }); +var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; }); +var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; }); +var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; }); + +var _Utils_compare = F2(function(x, y) +{ + var n = _Utils_cmp(x, y); + return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ; +}); + + +// COMMON VALUES + +var _Utils_Tuple0_UNUSED = 0; +var _Utils_Tuple0 = { $: '#0' }; + +function _Utils_Tuple2_UNUSED(a, b) { return { a: a, b: b }; } +function _Utils_Tuple2(a, b) { return { $: '#2', a: a, b: b }; } + +function _Utils_Tuple3_UNUSED(a, b, c) { return { a: a, b: b, c: c }; } +function _Utils_Tuple3(a, b, c) { return { $: '#3', a: a, b: b, c: c }; } + +function _Utils_chr_UNUSED(c) { return c; } +function _Utils_chr(c) { return new String(c); } + + +// RECORDS + +function _Utils_update(oldRecord, updatedFields) +{ + var newRecord = {}; + + for (var key in oldRecord) + { + newRecord[key] = oldRecord[key]; + } + + for (var key in updatedFields) + { + newRecord[key] = updatedFields[key]; + } + + return newRecord; +} + + +// APPEND + +var _Utils_append = F2(_Utils_ap); + +function _Utils_ap(xs, ys) +{ + // append Strings + if (typeof xs === 'string') + { + return xs + ys; + } + + // append Lists + if (!xs.b) + { + return ys; + } + var root = _List_Cons(xs.a, ys); + xs = xs.b + for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS + { + curr = curr.b = _List_Cons(xs.a, ys); + } + return root; +} + + + var _List_Nil_UNUSED = { $: 0 }; var _List_Nil = { $: '[]' }; @@ -609,190 +793,6 @@ function _Debug_regionToString(region) -// EQUALITY - -function _Utils_eq(x, y) -{ - for ( - var pair, stack = [], isEqual = _Utils_eqHelp(x, y, 0, stack); - isEqual && (pair = stack.pop()); - isEqual = _Utils_eqHelp(pair.a, pair.b, 0, stack) - ) - {} - - return isEqual; -} - -function _Utils_eqHelp(x, y, depth, stack) -{ - if (x === y) - { - return true; - } - - if (typeof x !== 'object' || x === null || y === null) - { - typeof x === 'function' && _Debug_crash(5); - return false; - } - - if (depth > 100) - { - stack.push(_Utils_Tuple2(x,y)); - return true; - } - - /**/ - if (x.$ === 'Set_elm_builtin') - { - x = $elm$core$Set$toList(x); - y = $elm$core$Set$toList(y); - } - if (x.$ === 'RBNode_elm_builtin' || x.$ === 'RBEmpty_elm_builtin') - { - x = $elm$core$Dict$toList(x); - y = $elm$core$Dict$toList(y); - } - //*/ - - /**_UNUSED/ - if (x.$ < 0) - { - x = $elm$core$Dict$toList(x); - y = $elm$core$Dict$toList(y); - } - //*/ - - for (var key in x) - { - if (!_Utils_eqHelp(x[key], y[key], depth + 1, stack)) - { - return false; - } - } - return true; -} - -var _Utils_equal = F2(_Utils_eq); -var _Utils_notEqual = F2(function(a, b) { return !_Utils_eq(a,b); }); - - - -// COMPARISONS - -// Code in Generate/JavaScript.hs, Basics.js, and List.js depends on -// the particular integer values assigned to LT, EQ, and GT. - -function _Utils_cmp(x, y, ord) -{ - if (typeof x !== 'object') - { - return x === y ? /*EQ*/ 0 : x < y ? /*LT*/ -1 : /*GT*/ 1; - } - - /**/ - if (x instanceof String) - { - var a = x.valueOf(); - var b = y.valueOf(); - return a === b ? 0 : a < b ? -1 : 1; - } - //*/ - - /**_UNUSED/ - if (typeof x.$ === 'undefined') - //*/ - /**/ - if (x.$[0] === '#') - //*/ - { - return (ord = _Utils_cmp(x.a, y.a)) - ? ord - : (ord = _Utils_cmp(x.b, y.b)) - ? ord - : _Utils_cmp(x.c, y.c); - } - - // traverse conses until end of a list or a mismatch - for (; x.b && y.b && !(ord = _Utils_cmp(x.a, y.a)); x = x.b, y = y.b) {} // WHILE_CONSES - return ord || (x.b ? /*GT*/ 1 : y.b ? /*LT*/ -1 : /*EQ*/ 0); -} - -var _Utils_lt = F2(function(a, b) { return _Utils_cmp(a, b) < 0; }); -var _Utils_le = F2(function(a, b) { return _Utils_cmp(a, b) < 1; }); -var _Utils_gt = F2(function(a, b) { return _Utils_cmp(a, b) > 0; }); -var _Utils_ge = F2(function(a, b) { return _Utils_cmp(a, b) >= 0; }); - -var _Utils_compare = F2(function(x, y) -{ - var n = _Utils_cmp(x, y); - return n < 0 ? $elm$core$Basics$LT : n ? $elm$core$Basics$GT : $elm$core$Basics$EQ; -}); - - -// COMMON VALUES - -var _Utils_Tuple0_UNUSED = 0; -var _Utils_Tuple0 = { $: '#0' }; - -function _Utils_Tuple2_UNUSED(a, b) { return { a: a, b: b }; } -function _Utils_Tuple2(a, b) { return { $: '#2', a: a, b: b }; } - -function _Utils_Tuple3_UNUSED(a, b, c) { return { a: a, b: b, c: c }; } -function _Utils_Tuple3(a, b, c) { return { $: '#3', a: a, b: b, c: c }; } - -function _Utils_chr_UNUSED(c) { return c; } -function _Utils_chr(c) { return new String(c); } - - -// RECORDS - -function _Utils_update(oldRecord, updatedFields) -{ - var newRecord = {}; - - for (var key in oldRecord) - { - newRecord[key] = oldRecord[key]; - } - - for (var key in updatedFields) - { - newRecord[key] = updatedFields[key]; - } - - return newRecord; -} - - -// APPEND - -var _Utils_append = F2(_Utils_ap); - -function _Utils_ap(xs, ys) -{ - // append Strings - if (typeof xs === 'string') - { - return xs + ys; - } - - // append Lists - if (!xs.b) - { - return ys; - } - var root = _List_Cons(xs.a, ys); - xs = xs.b - for (var curr = root; xs.b; xs = xs.b) // WHILE_CONS - { - curr = curr.b = _List_Cons(xs.a, ys); - } - return root; -} - - - // MATH var _Basics_add = F2(function(a, b) { return a + b; }); @@ -4629,32 +4629,9 @@ function _Time_getZoneName() }); } var $elm$core$Basics$EQ = {$: 'EQ'}; +var $elm$core$Basics$GT = {$: 'GT'}; var $elm$core$Basics$LT = {$: 'LT'}; var $elm$core$List$cons = _List_cons; -var $elm$core$Elm$JsArray$foldr = _JsArray_foldr; -var $elm$core$Array$foldr = F3( - function (func, baseCase, _v0) { - var tree = _v0.c; - var tail = _v0.d; - var helper = F2( - function (node, acc) { - if (node.$ === 'SubTree') { - var subTree = node.a; - return A3($elm$core$Elm$JsArray$foldr, helper, acc, subTree); - } else { - var values = node.a; - return A3($elm$core$Elm$JsArray$foldr, func, acc, values); - } - }); - return A3( - $elm$core$Elm$JsArray$foldr, - helper, - A3($elm$core$Elm$JsArray$foldr, func, baseCase, tail), - tree); - }); -var $elm$core$Array$toList = function (array) { - return A3($elm$core$Array$foldr, $elm$core$List$cons, _List_Nil, array); -}; var $elm$core$Dict$foldr = F3( function (func, acc, t) { foldr: @@ -4707,7 +4684,30 @@ var $elm$core$Set$toList = function (_v0) { var dict = _v0.a; return $elm$core$Dict$keys(dict); }; -var $elm$core$Basics$GT = {$: 'GT'}; +var $elm$core$Elm$JsArray$foldr = _JsArray_foldr; +var $elm$core$Array$foldr = F3( + function (func, baseCase, _v0) { + var tree = _v0.c; + var tail = _v0.d; + var helper = F2( + function (node, acc) { + if (node.$ === 'SubTree') { + var subTree = node.a; + return A3($elm$core$Elm$JsArray$foldr, helper, acc, subTree); + } else { + var values = node.a; + return A3($elm$core$Elm$JsArray$foldr, func, acc, values); + } + }); + return A3( + $elm$core$Elm$JsArray$foldr, + helper, + A3($elm$core$Elm$JsArray$foldr, func, baseCase, tail), + tree); + }); +var $elm$core$Array$toList = function (array) { + return A3($elm$core$Array$foldr, $elm$core$List$cons, _List_Nil, array); +}; var $elm$core$Result$Err = function (a) { return {$: 'Err', a: a}; }; @@ -5417,6 +5417,8 @@ var $elm$core$Task$perform = F2( A2($elm$core$Task$map, toMessage, task))); }); var $elm$browser$Browser$element = _Browser_element; +var $elm$json$Json$Decode$float = _Json_decodeFloat; +var $elm$core$String$fromFloat = _String_fromNumber; var $author$project$PhotoGroove$GotPhotos = function (a) { return {$: 'GotPhotos', a: a}; }; @@ -6302,11 +6304,29 @@ var $author$project$PhotoGroove$initialCommand = $elm$http$Http$get( $elm$json$Json$Decode$list($author$project$PhotoGroove$photoDecoder)), url: 'list' }); -var $author$project$PhotoGroove$Large = {$: 'Large'}; var $author$project$PhotoGroove$Loading = {$: 'Loading'}; -var $author$project$PhotoGroove$initialModel = {chosenSize: $author$project$PhotoGroove$Large, status: $author$project$PhotoGroove$Loading}; -var $elm$core$Platform$Sub$batch = _Platform_batch; -var $elm$core$Platform$Sub$none = $elm$core$Platform$Sub$batch(_List_Nil); +var $author$project$PhotoGroove$Medium = {$: 'Medium'}; +var $author$project$PhotoGroove$initialModel = { + activity: '', + chosenSize: $author$project$PhotoGroove$Medium, + filterValues: {hue: 0, noise: 0, ripple: 0}, + status: $author$project$PhotoGroove$Loading +}; +var $author$project$PhotoGroove$init = function (flags) { + var activity = 'Initializing Pasta v' + $elm$core$String$fromFloat(flags); + return _Utils_Tuple2( + _Utils_update( + $author$project$PhotoGroove$initialModel, + {activity: activity}), + $author$project$PhotoGroove$initialCommand); +}; +var $author$project$PhotoGroove$GotActivity = function (a) { + return {$: 'GotActivity', a: a}; +}; +var $author$project$PhotoGroove$activityChanges = _Platform_incomingPort('activityChanges', $elm$json$Json$Decode$string); +var $author$project$PhotoGroove$subscriptions = function (_v0) { + return $author$project$PhotoGroove$activityChanges($author$project$PhotoGroove$GotActivity); +}; var $author$project$PhotoGroove$Errored = function (a) { return {$: 'Errored', a: a}; }; @@ -6317,6 +6337,107 @@ var $author$project$PhotoGroove$Loaded = F2( function (a, b) { return {$: 'Loaded', a: a, b: b}; }); +var $author$project$PhotoGroove$Hue = {$: 'Hue'}; +var $author$project$PhotoGroove$Noise = {$: 'Noise'}; +var $author$project$PhotoGroove$Ripple = {$: 'Ripple'}; +var $author$project$PhotoGroove$filterTypeToName = function (t) { + switch (t.$) { + case 'Hue': + return 'Hue'; + case 'Ripple': + return 'Ripple'; + default: + return 'Noise'; + } +}; +var $elm$core$Platform$Cmd$batch = _Platform_batch; +var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); +var $elm$json$Json$Encode$float = _Json_wrap; +var $elm$json$Json$Encode$list = F2( + function (func, entries) { + return _Json_wrap( + A3( + $elm$core$List$foldl, + _Json_addEntry(func), + _Json_emptyArray(_Utils_Tuple0), + entries)); + }); +var $elm$json$Json$Encode$object = function (pairs) { + return _Json_wrap( + A3( + $elm$core$List$foldl, + F2( + function (_v0, obj) { + var k = _v0.a; + var v = _v0.b; + return A3(_Json_addField, k, v, obj); + }), + _Json_emptyObject(_Utils_Tuple0), + pairs)); +}; +var $elm$json$Json$Encode$string = _Json_wrap; +var $author$project$PhotoGroove$setFilters = _Platform_outgoingPort( + 'setFilters', + function ($) { + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + 'filters', + $elm$json$Json$Encode$list( + function ($) { + return $elm$json$Json$Encode$object( + _List_fromArray( + [ + _Utils_Tuple2( + 'amount', + $elm$json$Json$Encode$float($.amount)), + _Utils_Tuple2( + 'name', + $elm$json$Json$Encode$string($.name)) + ])); + })($.filters)), + _Utils_Tuple2( + 'url', + $elm$json$Json$Encode$string($.url)) + ])); + }); +var $author$project$PhotoGroove$urlPrefix = 'http://elm-in-action.com/'; +var $author$project$PhotoGroove$applyFilters = function (model) { + var _v0 = model.status; + switch (_v0.$) { + case 'Loaded': + var photos = _v0.a; + var selectedUrl = _v0.b; + var url = $author$project$PhotoGroove$urlPrefix + ('large/' + selectedUrl); + var inPecent = function (v) { + return v / 11; + }; + var filters = _List_fromArray( + [ + { + amount: inPecent(model.filterValues.hue), + name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Hue) + }, + { + amount: inPecent(model.filterValues.ripple), + name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Ripple) + }, + { + amount: inPecent(model.filterValues.noise), + name: $author$project$PhotoGroove$filterTypeToName($author$project$PhotoGroove$Noise) + } + ]); + return _Utils_Tuple2( + model, + $author$project$PhotoGroove$setFilters( + {filters: filters, url: url})); + case 'Loading': + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + default: + return _Utils_Tuple2(model, $elm$core$Platform$Cmd$none); + } +}; var $elm$random$Random$Generate = function (a) { return {$: 'Generate', a: a}; }; @@ -6441,8 +6562,6 @@ var $author$project$PhotoGroove$httpErrorToString = function (err) { return 'bad body: ' + msg; } }; -var $elm$core$Platform$Cmd$batch = _Platform_batch; -var $elm$core$Platform$Cmd$none = $elm$core$Platform$Cmd$batch(_List_Nil); var $elm$core$Tuple$pair = F2( function (a, b) { return _Utils_Tuple2(a, b); @@ -6459,6 +6578,23 @@ var $author$project$PhotoGroove$selectUrl = F2( return status; } }); +var $author$project$PhotoGroove$setFilterValue = F3( + function (filterType, val, values) { + switch (filterType.$) { + case 'Hue': + return _Utils_update( + values, + {hue: val}); + case 'Ripple': + return _Utils_update( + values, + {ripple: val}); + default: + return _Utils_update( + values, + {noise: val}); + } + }); var $elm$random$Random$addOne = function (value) { return _Utils_Tuple2(1, value); }; @@ -6547,13 +6683,12 @@ var $author$project$PhotoGroove$update = F2( switch (msg.$) { case 'ClickedThumbnail': var thumb = msg.a; - return _Utils_Tuple2( + return $author$project$PhotoGroove$applyFilters( _Utils_update( model, { status: A2($author$project$PhotoGroove$selectUrl, thumb, model.status) - }), - $elm$core$Platform$Cmd$none); + })); case 'ClickedSurpriseMe': var _v1 = model.status; switch (_v1.$) { @@ -6584,27 +6719,34 @@ var $author$project$PhotoGroove$update = F2( model, {chosenSize: size}), $elm$core$Platform$Cmd$none); + case 'ChangedFilter': + var f = msg.a; + var val = msg.b; + return $author$project$PhotoGroove$applyFilters( + _Utils_update( + model, + { + filterValues: A3($author$project$PhotoGroove$setFilterValue, f, val, model.filterValues) + })); case 'GotRandomPhoto': var photo = msg.a; - return _Utils_Tuple2( + return $author$project$PhotoGroove$applyFilters( _Utils_update( model, { status: A2($author$project$PhotoGroove$selectUrl, photo.url, model.status) - }), - $elm$core$Platform$Cmd$none); - default: + })); + case 'GotPhotos': if (msg.a.$ === 'Ok') { if (msg.a.a.b) { var photos = msg.a.a; var firstPhoto = photos.a; - return _Utils_Tuple2( + return $author$project$PhotoGroove$applyFilters( _Utils_update( model, { status: A2($author$project$PhotoGroove$Loaded, photos, firstPhoto.url) - }), - $elm$core$Platform$Cmd$none); + })); } else { return _Utils_Tuple2( _Utils_update( @@ -6625,9 +6767,15 @@ var $author$project$PhotoGroove$update = F2( }), $elm$core$Platform$Cmd$none); } + default: + var activity = msg.a; + return _Utils_Tuple2( + _Utils_update( + model, + {activity: activity}), + $elm$core$Platform$Cmd$none); } }); -var $elm$json$Json$Encode$string = _Json_wrap; var $elm$html$Html$Attributes$stringProperty = F2( function (key, string) { return A2( @@ -6640,9 +6788,10 @@ var $elm$html$Html$div = _VirtualDom_node('div'); var $elm$virtual_dom$VirtualDom$text = _VirtualDom_text; var $elm$html$Html$text = $elm$virtual_dom$VirtualDom$text; var $author$project$PhotoGroove$ClickedSurpriseMe = {$: 'ClickedSurpriseMe'}; -var $author$project$PhotoGroove$Medium = {$: 'Medium'}; +var $author$project$PhotoGroove$Large = {$: 'Large'}; var $author$project$PhotoGroove$Small = {$: 'Small'}; var $elm$html$Html$button = _VirtualDom_node('button'); +var $elm$html$Html$canvas = _VirtualDom_node('canvas'); var $elm$html$Html$h1 = _VirtualDom_node('h1'); var $elm$html$Html$h3 = _VirtualDom_node('h3'); var $elm$html$Html$Attributes$id = $elm$html$Html$Attributes$stringProperty('id'); @@ -6680,12 +6829,85 @@ var $elm$html$Html$Attributes$src = function (url) { 'src', _VirtualDom_noJavaScriptOrHtmlUri(url)); }; -var $author$project$PhotoGroove$urlPrefix = 'http://elm-in-action.com/'; +var $author$project$PhotoGroove$ChangedFilter = F2( + function (a, b) { + return {$: 'ChangedFilter', a: a, b: b}; + }); +var $elm$json$Json$Encode$int = _Json_wrap; +var $elm$html$Html$label = _VirtualDom_node('label'); +var $elm$html$Html$Attributes$max = $elm$html$Html$Attributes$stringProperty('max'); +var $author$project$PhotoGroove$onSlide = function (toMsg) { + return A2( + $elm$html$Html$Events$on, + 'slide', + A2( + $elm$json$Json$Decode$map, + toMsg, + A2( + $elm$json$Json$Decode$at, + _List_fromArray( + ['detail', 'userSlidTo']), + $elm$json$Json$Decode$int))); +}; +var $elm$virtual_dom$VirtualDom$property = F2( + function (key, value) { + return A2( + _VirtualDom_property, + _VirtualDom_noInnerHtmlOrFormAction(key), + _VirtualDom_noJavaScriptOrHtmlJson(value)); + }); +var $elm$html$Html$Attributes$property = $elm$virtual_dom$VirtualDom$property; +var $elm$virtual_dom$VirtualDom$node = function (tag) { + return _VirtualDom_node( + _VirtualDom_noScript(tag)); +}; +var $elm$html$Html$node = $elm$virtual_dom$VirtualDom$node; +var $author$project$PhotoGroove$rangeSlider = $elm$html$Html$node('range-slider'); +var $author$project$PhotoGroove$viewFilter = F2( + function (filterType, magnitude) { + return A2( + $elm$html$Html$div, + _List_fromArray( + [ + $elm$html$Html$Attributes$class('filter-slider') + ]), + _List_fromArray( + [ + A2( + $elm$html$Html$label, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text( + $author$project$PhotoGroove$filterTypeToName(filterType)) + ])), + A2( + $author$project$PhotoGroove$rangeSlider, + _List_fromArray( + [ + $elm$html$Html$Attributes$max('11'), + A2( + $elm$html$Html$Attributes$property, + 'val', + $elm$json$Json$Encode$int(magnitude)), + $author$project$PhotoGroove$onSlide( + $author$project$PhotoGroove$ChangedFilter(filterType)) + ]), + _List_Nil), + A2( + $elm$html$Html$label, + _List_Nil, + _List_fromArray( + [ + $elm$html$Html$text( + $elm$core$String$fromInt(magnitude)) + ])) + ])); + }); var $author$project$PhotoGroove$ClickedSize = function (a) { return {$: 'ClickedSize', a: a}; }; var $elm$html$Html$input = _VirtualDom_node('input'); -var $elm$html$Html$label = _VirtualDom_node('label'); var $elm$html$Html$Attributes$name = $elm$html$Html$Attributes$stringProperty('name'); var $author$project$PhotoGroove$sizeToString = function (size) { switch (size.$) { @@ -6770,7 +6992,7 @@ var $author$project$PhotoGroove$viewThumbnail = F2( _List_Nil); }); var $author$project$PhotoGroove$viewLoaded = F3( - function (photos, selected, size) { + function (model, photos, selected) { return _List_fromArray( [ A2( @@ -6791,6 +7013,28 @@ var $author$project$PhotoGroove$viewLoaded = F3( $elm$html$Html$text('Surprise me!') ])), A2( + $elm$html$Html$div, + _List_fromArray( + [ + $elm$html$Html$Attributes$class('activity') + ]), + _List_fromArray( + [ + $elm$html$Html$text(model.activity) + ])), + A2( + $elm$html$Html$div, + _List_fromArray( + [ + $elm$html$Html$Attributes$class('filters') + ]), + _List_fromArray( + [ + A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Hue, model.filterValues.hue), + A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Ripple, model.filterValues.ripple), + A2($author$project$PhotoGroove$viewFilter, $author$project$PhotoGroove$Noise, model.filterValues.noise) + ])), + A2( $elm$html$Html$h3, _List_Nil, _List_fromArray( @@ -6814,7 +7058,7 @@ var $author$project$PhotoGroove$viewLoaded = F3( [ $elm$html$Html$Attributes$id('thumbnails'), $elm$html$Html$Attributes$class( - $author$project$PhotoGroove$sizeToClass(size)) + $author$project$PhotoGroove$sizeToClass(model.chosenSize)) ]), A2( $elm$core$List$map, @@ -6827,6 +7071,14 @@ var $author$project$PhotoGroove$viewLoaded = F3( $elm$html$Html$Attributes$class('large'), $elm$html$Html$Attributes$src($author$project$PhotoGroove$urlPrefix + ('large/' + selected)) ]), + _List_Nil), + A2( + $elm$html$Html$canvas, + _List_fromArray( + [ + $elm$html$Html$Attributes$id('canvas-main'), + $elm$html$Html$Attributes$class('large') + ]), _List_Nil) ]); }); @@ -6845,7 +7097,7 @@ var $author$project$PhotoGroove$view = function (model) { case 'Loaded': var photos = _v0.a; var selected = _v0.b; - return A3($author$project$PhotoGroove$viewLoaded, photos, selected, model.chosenSize); + return A3($author$project$PhotoGroove$viewLoaded, model, photos, selected); default: var error = _v0.a; return _List_fromArray( @@ -6856,15 +7108,5 @@ var $author$project$PhotoGroove$view = function (model) { }()); }; var $author$project$PhotoGroove$main = $elm$browser$Browser$element( - { - init: function (_v0) { - return _Utils_Tuple2($author$project$PhotoGroove$initialModel, $author$project$PhotoGroove$initialCommand); - }, - subscriptions: function (_v1) { - return $elm$core$Platform$Sub$none; - }, - update: $author$project$PhotoGroove$update, - view: $author$project$PhotoGroove$view - }); -_Platform_export({'PhotoGroove':{'init':$author$project$PhotoGroove$main( - $elm$json$Json$Decode$succeed(_Utils_Tuple0))(0)}});}(this)); \ No newline at end of file + {init: $author$project$PhotoGroove$init, subscriptions: $author$project$PhotoGroove$subscriptions, update: $author$project$PhotoGroove$update, view: $author$project$PhotoGroove$view}); +_Platform_export({'PhotoGroove':{'init':$author$project$PhotoGroove$main($elm$json$Json$Decode$float)(0)}});}(this)); \ No newline at end of file diff --git a/index.html b/index.html index 25a8b19..6b72b5d 100644 --- a/index.html +++ b/index.html @@ -2,12 +2,49 @@
+ + + + diff --git a/pasta.js b/pasta.js new file mode 100644 index 0000000..ec43eb3 --- /dev/null +++ b/pasta.js @@ -0,0 +1,2418 @@ +/* +========================================================================= + JSManipulate v1.0 (2011-08-01) + +Javascript image filter & effect library + +Developed by Joel Besada (http://www.joelb.me) +Demo page: http://www.joelb.me/jsmanipulate + +MIT LICENSED (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2011, Joel Besada +========================================================================= +*/ + + +/** + * Contains common filter functions. + */ +function FilterUtils(){ + this.HSVtoRGB = function (h, s, v){ + var r, g, b; + var i = Math.floor(h * 6); + var f = h * 6 - i; + var p = v * (1 - s); + var q = v * (1 - f * s); + var t = v * (1 - (1 - f) * s); + switch(i % 6){ + case 0: r = v; g = t; b = p; break; + case 1: r = q; g = v; b = p; break; + case 2: r = p; g = v; b = t; break; + case 3: r = p; g = q; b = v; break; + case 4: r = t; g = p; b = v; break; + case 5: r = v; g = p; b = q; break; + default: break; + } + return [r * 255, g * 255, b * 255]; + }; + this.RGBtoHSV = function (r, g, b){ + r = r/255; g = g/255; b = b/255; + var max = Math.max(r, g, b); + var min = Math.min(r, g, b); + var h, s, v = max; + var d = max - min; + s = max === 0 ? 0 : d / max; + if(max === min){ + h = 0; + }else{ + switch(max){ + case r: h = (g - b) / d + (g < b ? 6 : 0); break; + case g: h = (b - r) / d + 2; break; + case b: h = (r - g) / d + 4; break; + default: break; + } + h /= 6; + } + return [h, s, v]; + }; + this.getPixel = function (pixels,x,y,width,height){ + var pix = (y*width + x)*4; + if (x < 0 || x >= width || y < 0 || y >= height) { + return [pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4], + pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 1], + pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 2], + pixels[((this.clampPixel(y, 0, height-1) * width) + this.clampPixel(x, 0, width-1))*4 + 3]]; + } + return [pixels[pix],pixels[pix+1],pixels[pix+2],pixels[pix+3]]; + }; + var haveNextGaussian = false; + var nextGaussian; + this.gaussianRandom = function(){ + if(haveNextGaussian){ + haveNextGaussian = false; + return nextGaussian; + } else { + var v1, v2, s; + do { + v1 = 2 * Math.random() - 1; + v2 = 2 * Math.random() - 1; + s = v1 * v1 + v2 * v2; + } while (s >= 1 || s === 0); + var mult = Math.sqrt(-2 * Math.log(s)/s); + nextGaussian = v2 * mult; + haveNextGaussian = true; + return v1 * mult; + } + }; + this.clampPixel = function (x,a,b){ + return (x < a) ? a : (x > b) ? b : x; + }; + this.triangle = function(x){ + var r = this.mod(x, 1); + return 2*(r < 0.5 ? r : 1-r); + }; + this.mod = function(a,b){ + var n = parseInt(a/b,10); + a -= n*b; + if(a < 0){ + return a + b; + } + return a; + }; + this.mixColors = function(t, rgb1, rgb2){ + var r = this.linearInterpolate(t,rgb1[0],rgb2[0]); + var g = this.linearInterpolate(t,rgb1[1],rgb2[1]); + var b = this.linearInterpolate(t,rgb1[2],rgb2[2]); + var a = this.linearInterpolate(t,rgb1[3],rgb2[3]); + return [r,g,b,a]; + }; + + this.linearInterpolate = function(t,a,b){ + return a + t * (b-a); + }; + this.bilinearInterpolate = function (x,y,nw,ne,sw,se){ + var m0, m1; + var r0 = nw[0]; var g0 = nw[1]; var b0 = nw[2]; var a0 = nw[3]; + var r1 = ne[0]; var g1 = ne[1]; var b1 = ne[2]; var a1 = ne[3]; + var r2 = sw[0]; var g2 = sw[1]; var b2 = sw[2]; var a2 = sw[3]; + var r3 = se[0]; var g3 = se[1]; var b3 = se[2]; var a3 = se[3]; + var cx = 1.0 - x; var cy = 1.0 - y; + + m0 = cx * a0 + x * a1; + m1 = cx * a2 + x * a3; + var a = cy * m0 + y * m1; + + m0 = cx * r0 + x * r1; + m1 = cx * r2 + x * r3; + var r = cy * m0 + y * m1; + + m0 = cx * g0 + x * g1; + m1 = cx * g2 + x * g3; + var g = cy * m0 + y * m1; + + m0 = cx * b0 + x * b1; + m1 = cx * b2 + x * b3; + var b =cy * m0 + y * m1; + return [r,g,b,a]; + }; + this.tableFilter = function (inputData, table, width, height){ + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + for(var i = 0; i < 3; i++){ + inputData[pixel+i] = table[inputData[pixel+i]]; + } + } + } + }; + this.convolveFilter = function(inputData, matrix, width, height){ + var outputData = []; + var rows, cols; + rows = cols = Math.sqrt(matrix.length); + var rows2 = parseInt(rows/2,10); + var cols2 = parseInt(cols/2,10); + var trace = true; + for(var y = 0; y < height; y++){ + for (var x = 0; x < width; x++){ + var pixel = (y*width + x)*4; + var r = 0, g = 0, b = 0; + for(var row = -rows2; row <= rows2; row++){ + var iy = y+row; + var ioffset; + if (0 <= iy && iy < height) { + ioffset = iy*width; + } else { + ioffset = y*width; + } + var moffset = cols*(row+rows2)+cols2; + for (var col = -cols2; col <= cols2; col++) { + var f = matrix[moffset+col]; + if (f !== 0) { + var ix = x+col; + if (!(0 <= ix && ix < width)) { + ix = x; + } + var iPixel = (ioffset+ix)*4; + r += f * inputData[iPixel]; + g += f * inputData[iPixel+1]; + b += f * inputData[iPixel+2]; + } + } + } + outputData[pixel] = parseInt(r+0.5,10); + outputData[pixel+1] = parseInt(g+0.5,10); + outputData[pixel+2] = parseInt(b+0.5,10); + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; + this.transformFilter = function(inputData, transformInverse, width, height){ + var out = []; + var outputData = []; + for(var j = 0; j < inputData.length; j++){ + outputData[j] = inputData[j]; + } + for(var y = 0; y < height; y++){ + for (var x = 0; x < width; x++){ + var pixel = (y*width + x)*4; + transformInverse.apply(this,[x,y,out]); + var srcX = Math.floor(out[0]); + var srcY = Math.floor(out[1]); + var xWeight = out[0]-srcX; + var yWeight = out[1]-srcY; + var nw,ne,sw,se; + if(srcX >= 0 && srcX < width-1 && srcY >= 0 && srcY < height-1){ + var i = (width*srcY + srcX)*4; + nw = [inputData[i],inputData[i+1],inputData[i+2],inputData[i+3]]; + ne = [inputData[i+4],inputData[i+5],inputData[i+6],inputData[i+7]]; + sw = [inputData[i+width*4],inputData[i+width*4+1],inputData[i+width*4+2],inputData[i+width*4+3]]; + se = [inputData[i+(width + 1)*4],inputData[i+(width + 1)*4+1],inputData[i+(width + 1)*4+2],inputData[i+(width + 1)*4+3]]; + } else { + nw = this.getPixel( inputData, srcX, srcY, width, height ); + ne = this.getPixel( inputData, srcX+1, srcY, width, height ); + sw = this.getPixel( inputData, srcX, srcY+1, width, height ); + se = this.getPixel( inputData, srcX+1, srcY+1, width, height ); + } + var rgba = this.bilinearInterpolate(xWeight,yWeight,nw,ne,sw,se); + outputData[pixel] = rgba[0]; + outputData[pixel + 1] = rgba[1]; + outputData[pixel + 2] = rgba[2]; + outputData[pixel + 3] = rgba[3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Blurs the image with Gaussian blur. + */ +function BlurFilter(){ + this.name = "Blur"; + this.isDirAnimatable = false; + this.defaultValues = { + amount : 3 + }; + this.valueRanges = { + amount : {min:0, max:10} + }; + this.filter = function(input,values){ + var width = input.width; + var width4 = width << 2; + var height = input.height; + var inputData = input.data; + var q; + var amount = values.amount; + if (amount < 0.0) { + amount = 0.0; + } + if (amount >= 2.5) { + q = 0.98711 * amount - 0.96330; + } else if (amount >= 0.5) { + q = 3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * amount); + } else { + q = 2 * amount * (3.97156 - 4.14554 * Math.sqrt(1.0 - 0.26891 * 0.5)); + } + var qq = q * q; + var qqq = qq * q; + var b0 = 1.57825 + (2.44413 * q) + (1.4281 * qq ) + (0.422205 * qqq); + var b1 = ((2.44413 * q) + (2.85619 * qq) + (1.26661 * qqq)) / b0; + var b2 = (-((1.4281 * qq) + (1.26661 * qqq))) / b0; + var b3 = (0.422205 * qqq) / b0; + var bigB = 1.0 - (b1 + b2 + b3); + var c = 0; + var index; + var indexLast; + var pixel; + var ppixel; + var pppixel; + var ppppixel; + for (c = 0; c < 3; c++) { + for (var y = 0; y < height; y++) { + index = y * width4 + c; + indexLast = y * width4 + ((width - 1) << 2) + c; + pixel = inputData[index]; + ppixel = pixel; + pppixel = ppixel; + ppppixel = pppixel; + for (; index <= indexLast; index += 4) { + pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; + inputData[index] = pixel; + ppppixel = pppixel; + pppixel = ppixel; + ppixel = pixel; + } + index = y * width4 + ((width - 1) << 2) + c; + indexLast = y * width4 + c; + pixel = inputData[index]; + ppixel = pixel; + pppixel = ppixel; + ppppixel = pppixel; + for (; index >= indexLast; index -= 4) { + pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; + inputData[index] = pixel; + ppppixel = pppixel; + pppixel = ppixel; + ppixel = pixel; + } + } + } + for (c = 0; c < 3; c++) { + for (var x = 0; x < width; x++) { + index = (x << 2) + c; + indexLast = (height - 1) * width4 + (x << 2) + c; + pixel = inputData[index]; + ppixel = pixel; + pppixel = ppixel; + ppppixel = pppixel; + for (; index <= indexLast; index += width4) { + pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; + inputData[index] = pixel; + ppppixel = pppixel; + pppixel = ppixel; + ppixel = pixel; + } + index = (height - 1) * width4 + (x << 2) + c; + indexLast = (x << 2) + c; + pixel = inputData[index]; + ppixel = pixel; + pppixel = ppixel; + ppppixel = pppixel; + for (; index >= indexLast; index -= width4) { + pixel = bigB * inputData[index] + b1 * ppixel + b2 * pppixel + b3 * ppppixel; + inputData[index] = pixel; + ppppixel = pppixel; + pppixel = ppixel; + ppixel = pixel; + } + } + } + }; +} +/** + * Adjusts the brightness of the image by going over to HSV values. + * Negative values decrease brightness while positive values increase brightness. + */ +function BrightnessFilter(){ + this.name = "Brightness"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 0.0 + }; + this.valueRanges = { + amount : {min:-1.0, max:1.0} + }; + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); + hsv[2] += amount; + if(hsv[2] < 0){ + hsv[2] = 0; + } else if (hsv[2] > 1){ + hsv[2] = 1; + } + var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); + for(var i = 0; i < 3; i++){ + inputData[pixel+i] = rgb[i]; + } + } + } + }; +} +/** + * Embosses the edges of the image. + * This filter takes no parameters but can be applied several times for + * further effect. + */ +function BumpFilter(){ + this.name = "Bump"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var matrix = [-1,-1, 0, + -1, 1, 1, + 0, 1, 1]; + filterUtils.convolveFilter(inputData,matrix,width,height); + }; +} +/** + * Smears out the image with circular shapes to create a painting style effect. + * The mix values sets the intensity of the effect. + * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. + */ +function CircleSmearFilter(){ + this.name = "Circle Smear"; + this.isDirAnimatable = false; + this.defaultValues = { + size : 4, + density : 0.5, + mix : 0.5 + }; + this.valueRanges = { + size : {min:1, max:10}, + density : {min:0.0, max:1.0}, + mix : {min:0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for(var j = 0; j < inputData.length; j++){ + outputData[j] = inputData[j]; + } + if(values === undefined){ values = this.defaultValues; } + var size = (values.size === undefined) ? this.defaultValues.size : values.size; + if(size < 1){ size = 1;} + size = parseInt(size,10); + var density = (values.density === undefined) ? this.defaultValues.density : values.density; + var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; + var radius = size+1; + var radius2 = radius*radius; + var numShapes = parseInt(2*density/30*width*height / 2,10); + for(var i = 0; i < numShapes; i++){ + var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; + var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; + var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; + for(var x = sx - radius; x < sx + radius + 1; x++){ + for(var y = sy - radius; y < sy + radius + 1; y++){ + var f = (x - sx) * (x - sx) + (y - sy) * (y - sy); + if (x >= 0 && x < width && y >= 0 && y < height && f <= radius2) { + var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; + var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(var k = 0; k < 3; k++){ + outputData[(y*width+x)*4+k] = mixedRGB[k]; + } + } + } + } + } + for(var l = 0; l < outputData.length; l++){ + inputData[l] = outputData[l]; + } + }; +} +/** + * Adjusts the contrast of the image. + */ +function ContrastFilter(){ + this.name = "Contrast"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 1.0 + }; + this.valueRanges = { + amount : {min:0.0, max:2.0} + }; + if(!FilterUtils){ + if(console){ + console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); + } + return; + } + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + if(amount < 0){ + amount = 0.0; + } + var table = []; + + for(var i = 0; i < 256; i++){ + table[i] = parseInt(255 * (((i/255)-0.5)*amount+0.5),10); + } + filterUtils.tableFilter(inputData,table,width,height); + }; +} +/** + * Smears out the image with cross shapes to create a painting style effect. + * The mix values sets the intensity of the effect. + */ +function CrossSmearFilter(){ + this.name = "Cross Smear"; + this.isDirAnimatable = false; + this.defaultValues = { + distance : 8, + density : 0.5, + mix : 0.5 + }; + this.valueRanges = { + distance : {min:0, max:30}, + density : {min:0.0, max:1.0}, + mix : {min:0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for(var j = 0; j < inputData.length; j++){ + outputData[j] = inputData[j]; + } + if(values === undefined){ values = this.defaultValues; } + var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; + if(distance < 0){ distance = 0;} + distance = parseInt(distance,10); + var density = (values.density === undefined) ? this.defaultValues.density : values.density; + var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; + var numShapes = parseInt(2*density*width * height / (distance + 1),10); + for(var i = 0; i < numShapes; i++){ + var x = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; + var y = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; + var length = (Math.random()*Math.pow(2,32)) % distance + 1; + var rgb2 = [inputData[(y*width+x)*4],inputData[(y*width+x)*4+1],inputData[(y*width+x)*4+2],inputData[(y*width+x)*4+3]]; + var rgb1; + var mixedRGB; + var k; + for (var x1 = x-length; x1 < x+length+1; x1++) { + if(x1 >= 0 && x1 < width){ + rgb1 = [outputData[(y*width+x1)*4],outputData[(y*width+x1)*4+1],outputData[(y*width+x1)*4+2],outputData[(y*width+x1)*4+3]]; + mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y*width+x1)*4+k] = mixedRGB[k]; + } + } + + } + for (var y1 = y-length; y1 < y+length+1; y1++) { + if(y1 >= 0 && y1 < height){ + rgb1 = [outputData[(y1*width+x)*4],outputData[(y1*width+x)*4+1],outputData[(y1*width+x)*4+2],outputData[(y1*width+x)*4+3]]; + mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y1*width+x)*4+k] = mixedRGB[k]; + } + } + + } + } + for(var l = 0; l < outputData.length; l++){ + inputData[l] = outputData[l]; + } + }; +} +/** + * Diffuses the image creating a frosted glass effect. + */ +function DiffusionFilter(){ + this.name = "Diffusion"; + this.isDirAnimatable = false; + this.defaultValues = { + scale: 4 + }; + this.valueRanges = { + scale: {min: 1, max: 100} + }; + + var filterUtils = new FilterUtils(); + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var scale = (values.scale === undefined) ? this.defaultValues.scale : values.scale; + var out = []; + var outputData = []; + var sinTable = []; + var cosTable = []; + for(var i = 0; i < 256; i++){ + var angle = Math.PI*2*i/256; + sinTable[i] = scale*Math.sin(angle); + cosTable[i] = scale*Math.cos(angle); + } + transInverse = function (x,y,out){ + var angle = parseInt(Math.random() * 255,10); + var distance = Math.random(); + out[0] = x + distance * sinTable[angle]; + out[1] = y + distance * cosTable[angle]; + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Dithers the image to the specified number of colors. Setting color to false + * grayscales the image. + */ +function DitherFilter(){ + this.name = "Dither"; + this.isDirAnimatable = false; + this.defaultValues = { + levels : 3, + color : true + }; + this.valueRanges = { + levels : {min:2, max:30}, + color : {min:false, max:true} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + var i, j; + for (j=0; j < inputData.length; j++) { + outputData[j] = 0; + } + if(values === undefined){ values = this.defaultValues; } + var levels = (values.levels === undefined) ? this.defaultValues.levels : values.levels; + var color = (values.color === undefined) ? this.defaultValues.color : values.color; + if(levels <= 1){ + levels = 1; + } + var matrix = [0,0,0, + 0,0,7, + 3,5,1]; + var sum = 7+3+5+1; + var index = 0; + var map = []; + + for (i=0; i < levels; i++) { + map[i] = parseInt(255* i / (levels-1),10); + } + var div = []; + for (i=0; i < 256; i++) { + div[i] = parseInt(levels*i / 256,10); + } + for (var y = 0; y < height; y++) { + var reverse = ((y & 1) == 1); + var direction; + if(reverse){ + index = (y*width+width-1)*4; + direction = -1; + } else { + index = y*width*4; + direction = 1; + } + for (var x = 0; x < width; x++) { + var r1 = inputData[index]; var g1 = inputData[index+1]; var b1 = inputData[index+2]; + if(!color){ + r1 = g1 = b1 = parseInt((r1+g1+b1) / 3,10); + } + var r2 = map[div[r1]];var g2 = map[div[g1]];var b2 = map[div[b1]]; + + outputData[index] = r2; outputData[index + 1] = g2; outputData[index+2] = b2; outputData[index+3] = inputData[index+3]; + + var er = r1-r2; var eg = g1-g2; var eb = b1-b2; + + for (i = -1; i <= 1; i++) { + var iy = i+y; + if (0 <= iy && iy < height) { + for (j = -1; j <= 1; j++) { + var jx = j+x; + if (0 <= jx && jx < width) { + var w; + if (reverse){ + w = matrix[(i+1)*3-j+1]; + } else{ + w = matrix[(i+1)*3+j+1]; + } + if (w !== 0) { + var k = (reverse) ? index - j*4 : index + j*4; + r1 = inputData[k]; g1 = inputData[k+1]; b1 = inputData[k+2]; + var factor = w/sum; + r1 += er * factor; g1 += eg * factor; b1 += eb * factor; + inputData[k] = r1; inputData[k+1] = g1 ;inputData[k+2] = b1; + } + } + } + } + } + index += direction*4; + } + } + for(j = 0; j < outputData.length; j++){ + inputData[j] = outputData[j]; + } + }; +} +/** + * Highlights the edges of the image. + */ +function EdgeFilter(){ + this.name = "Edge Detection"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + var matrixH = [-1,-2,-1, + 0, 0, 0, + 1, 2, 1]; + var matrixV = [-1, 0, 1, + -2, 0, 2, + -1, 0, 1]; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var rh = 0; gh = 0; bh = 0; + var rv = 0; gv = 0; bv = 0; + for(var row = -1; row <= 1; row++){ + var iy = y+row; + var ioffset; + if(iy >= 0 && iy < height){ + ioffset = iy*width*4; + } else { + ioffset = y*width*4; + } + var moffset = 3*(row+1)+1; + for(var col = -1; col <= 1; col++){ + var ix = x+col; + if(!(ix >= 0 && ix < width)){ + ix = x; + } + ix *= 4; + var r = inputData[ioffset+ix]; + var g = inputData[ioffset+ix+1]; + var b = inputData[ioffset+ix+2]; + var h = matrixH[moffset+col]; + var v = matrixV[moffset+col]; + rh += parseInt(h*r,10); + bh += parseInt(h*g,10); + gh += parseInt(h*b,10); + rv += parseInt(v*r,10); + gv += parseInt(v*g,10); + bv += parseInt(v*b,10); + } + } + r = parseInt(Math.sqrt(rh*rh + rv*rv) / 1.8,10); + g = parseInt(Math.sqrt(gh*gh + gv*gv) / 1.8,10); + b = parseInt(Math.sqrt(bh*bh + bv*bv) / 1.8,10); + + outputData[pixel] = r; + outputData[pixel+1] = g; + outputData[pixel+2] = b; + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Embosses the image with a simulated light source. + * Angle and elevation sets the position of the light. + */ +function EmbossFilter(){ + this.name = "Emboss"; + this.isDirAnimatable = false; + this.defaultValues = { + height : 1, + angle : 135, + elevation : 30 + }; + this.valueRanges = { + height : {min:1, max:10}, + angle : {min:0, max:360}, + elevation : {min:0, max:180} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var bumpHeight = (values.height === undefined) ? this.defaultValues.height : values.height; + var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; + var elevation = (values.elevation === undefined) ? this.defaultValues.elevation : values.elevation; + angle = angle / 180 * Math.PI; + elevation = elevation / 180 * Math.PI; + var width45 = 3 * bumpHeight; + var pixelScale = 255.9; + + var bumpPixels = []; + var bumpMapWidth = width; + var bumpMapHeight = height; + for(var i = 0; i < inputData.length; i+=4){ + bumpPixels[i/4] = (inputData[i] + inputData[i+1] + inputData[i+2])/3; + } + var Nx, Ny, Nz, Lx, Ly, Lz, Nz2, NzLz, NdotL; + var shade, background; + + Lx = parseInt(Math.cos(angle) * Math.cos(elevation) * pixelScale,10); + Ly = parseInt(Math.sin(angle) * Math.cos(elevation) * pixelScale,10); + Lz = parseInt(Math.sin(elevation) * pixelScale,10); + + Nz = parseInt(6 * 255 / width45,10); + Nz2 = Nz * Nz; + NzLz = Nz * Lz; + background = Lz; + + var bumpIndex = 0; + + for (var y = 0; y < height; y++, bumpIndex += bumpMapWidth) { + var s1 = bumpIndex; + var s2 = s1 + bumpMapWidth; + var s3 = s2 + bumpMapWidth; + for (var x = 0; x < width; x++, s1++, s2++, s3++) { + var pixel = (y*width + x)*4; + if (y !== 0 && y < height-2 && x !== 0 && x < width-2) { + Nx = bumpPixels[s1-1] + bumpPixels[s2-1] + bumpPixels[s3-1] - bumpPixels[s1+1] - bumpPixels[s2+1] - bumpPixels[s3+1]; + Ny = bumpPixels[s3-1] + bumpPixels[s3] + bumpPixels[s3+1] - bumpPixels[s1-1] - bumpPixels[s1] - bumpPixels[s1+1]; + if (Nx === 0 && Ny === 0){ + shade = background; + } else if ((NdotL = Nx*Lx + Ny*Ly + NzLz) < 0){ + shade = 0; + } else { + shade = parseInt(NdotL / Math.sqrt(Nx*Nx + Ny*Ny + Nz2),10); + } + } else { + shade = background; + } + inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = shade; + } + } + }; +} +/** + * Adjust simulated exposure values on the image. + */ +function ExposureFilter(){ + this.name = "Exposure"; + this.isDirAnimatable = true; + this.defaultValues = { + exposure : 1.0 + }; + this.valueRanges = { + exposure : {min:0, max:5} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var exposure = (values.exposure === undefined) ? this.defaultValues.exposure : values.exposure; + var table = []; + for(var i = 0; i < 256; i++){ + table[i] = parseInt(255 *(1-Math.exp(-(i/255) * exposure)),10); + } + filterUtils.tableFilter(inputData, table, width, height); + }; +} +/** + * Adjusts the gain and bias of the image. Gain alters the contrast while bias biases + * colors towards lighter or darker. + */ +function GainFilter(){ + this.name = "Gain/Bias"; + this.isDirAnimatable = true; + this.defaultValues = { + gain: 0.5, + bias: 0.5 + }; + this.valueRanges = { + gain: {min:0.0, max:1.0}, + bias: {min:0.0, max:1.0} + }; + var table = []; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var gain = (values.gain === undefined) ? this.defaultValues.gain : values.gain; + var bias = (values.bias === undefined) ? this.defaultValues.bias : values.bias; + + var table = []; + + for(var i = 0; i < 256; i++){ + var val = i/255; + var k = (1/gain-2) * (1-2*val); + val = (val < 0.5) ? val/(k+1) : (k-val)/(k-1); + val /= (1/bias-2)*(1-val)+1; + table[i] = parseInt(255 * val,10); + } + filterUtils.tableFilter(inputData,table,width,height); + }; +} +/** + * Adjusts the gamma values of the image. Values over 1 increase the gamma while values over 0 decrease gamma. + */ +function GammaFilter(){ + this.name = "Gamma"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 1.0 + }; + this.valueRanges = { + amount : {min:0.0, max:2.0} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + if(amount < 0){ + amount = 0.0; + } + if(!FilterUtils){ + if(console){ + console.error("Unable to find filterutils.js, please include this file! (Required by " + this.name + " filter)"); + } + return; + } + var filterUtils = new FilterUtils(); + var table = []; + for(var i = 0; i < 256; i++){ + table[i] = 255 * Math.pow(i/255, 1/amount) + 0.5; + } + filterUtils.tableFilter(inputData,table,width,height); + }; +} +/** + * Sets the image to grayscale. + */ +function GrayscaleFilter(){ + this.name = "Grayscale"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; + inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = luma; + } + } + }; +} +/** + * Adjusts the hue of the image by going over to HSV values. + */ +function HueFilter(){ + this.name = "Hue"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 0.0 + }; + this.valueRanges = { + amount : {min:-1.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var hsv = filterUtils.RGBtoHSV(inputData[pixel],inputData[pixel+1],inputData[pixel+2]); + hsv[0] += amount; + while(hsv[0] < 0){ + hsv[0] += 360; + } + var rgb = filterUtils.HSVtoRGB(hsv[0],hsv[1],hsv[2]); + for(var i = 0; i < 3; i++){ + inputData[pixel+i] = rgb[i]; + } + } + } + }; +} +/** + * Inverts the colors of the image. + */ +function InvertFilter(){ + this.name = "Invert"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + for(var i = 0; i < 3; i++){ + inputData[pixel+i] = 255 - inputData[pixel+i]; + } + } + } + }; +} +/** + * Creates a kaleidoscope effect on the image. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function KaleidoscopeFilter(){ + this.name = "Kaleidoscope"; + this.isDirAnimatable = false; + this.defaultValues = { + angle : 0, + rotation : 0, + sides : 3, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + angle : {min: 0, max: 360}, + rotation : {min: 0, max: 360}, + sides : {min: 1, max: 30}, + centerX : {min: 0.0, max:1.0}, + centerY : {min: 0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; + var rotation = (values.rotation === undefined) ? this.defaultValues.rotation : values.rotation; + var sides = (values.sides === undefined) ? this.defaultValues.sides : values.sides; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var iCenterX = width * centerX; var iCenterY = height * centerY; + angle = angle/180 * Math.PI; + rotation = rotation/180 * Math.PI; + var transInverse = function(x,y,out){ + var dx = x - iCenterX; + var dy = y - iCenterY; + var r = Math.sqrt(dx*dx + dy*dy); + var theta = Math.atan2(dy,dx) - angle - rotation; + theta = filterUtils.triangle(theta/Math.PI*sides*0.5); + theta += angle; + out[0] = iCenterX + r*Math.cos(theta); + out[1] = iCenterY + r*Math.sin(theta); + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Applies a fisheye lens distortion effect on the image. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function LensDistortionFilter(){ + this.name = "Lens Distortion"; + this.isDirAnimatable = false; + this.defaultValues = { + refraction : 1.5, + radius : 50, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + refraction : {min: 1, max: 10}, + radius : {min: 1, max: 200}, + centerX : {min: 0.0, max:1.0}, + centerY : {min: 0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var refraction = (values.refraction === undefined) ? this.defaultValues.refraction : values.refraction; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; + var radius2 = radius*radius; + var iCenterX = width * centerX; var iCenterY = height * centerY; + var transInverse = function(x,y,out){ + var dx = x-iCenterX; + var dy = y-iCenterY; + var x2 = dx*dx; + var y2 = dy*dy; + if (y2 >= (radius2 - (radius2*x2)/radius2)) { + out[0] = x; + out[1] = y; + } else { + var rRefraction = 1.0 / refraction; + + var z = Math.sqrt((1.0 - x2/radius2 - y2/radius2) * radius2); + var z2 = z*z; + + var xAngle = Math.acos(dx / Math.sqrt(x2+z2)); + var angle1 = Math.PI/2 - xAngle; + var angle2 = Math.asin(Math.sin(angle1)*rRefraction); + angle2 = Math.PI/2 - xAngle - angle2; + out[0] = x - Math.tan(angle2)*z; + + var yAngle = Math.acos(dy / Math.sqrt(y2+z2)); + angle1 = Math.PI/2 - yAngle; + angle2 = Math.asin(Math.sin(angle1)*rRefraction); + angle2 = Math.PI/2 - yAngle - angle2; + out[1] = y - Math.tan(angle2)*z; + } + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Smears out the image with line shapes to create a painting style effect. Mix specifies + * the intensity of the effect. + */ +function LineSmearFilter(){ + this.name = "Line Smear"; + this.isDirAnimatable = false; + this.defaultValues = { + distance : 8, + density : 0.5, + angle : 0, + mix : 0.5 + }; + this.valueRanges = { + distance : {min:1, max:30}, + density : {min:0.0, max:1.0}, + angle : {min:0, max:360}, + mix : {min:0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + var k; + for(k = 0; k < inputData.length; k++){ + outputData[k] = inputData[k]; + } + if(values === undefined){ values = this.defaultValues; } + var distance = (values.distance === undefined) ? this.defaultValues.distance : values.distance; + if(distance < 1){ distance = 1;} + distance = parseInt(distance,10); + var density = (values.density === undefined) ? this.defaultValues.density : values.density; + var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; + var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; + angle = angle/180*Math.PI; + var sinAngle = Math.sin(angle); + var cosAngle = Math.cos(angle); + var numShapes = parseInt(2*density*width*height / 2,10); + for(var i = 0; i < numShapes; i++){ + var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; + var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; + var length = (Math.random()*Math.pow(2,32) & 0x7fffffff) % distance + 1; + var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; + var dx = parseInt(length*cosAngle,10); + var dy = parseInt(length*sinAngle,10); + + var x0 = sx-dx; + var y0 = sy-dy; + var x1 = sx+dx; + var y1 = sy+dy; + var x, y, d, incrE, incrNE, ddx, ddy; + + if (x1 < x0){ + ddx = -1; + } else { + ddx = 1; + } + if (y1 < y0){ + ddy = -1; + } else { + ddy = 1; + } + dx = x1-x0; + dy = y1-y0; + dx = Math.abs(dx); + dy = Math.abs(dy); + x = x0; + y = y0; + var rgb1; + var mixedRGB; + if (x < width && x >= 0 && y < height && y >= 0) { + rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; + mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y*width+x)*4+k] = mixedRGB[k]; + } + } + if (Math.abs(dx) > Math.abs(dy)) { + d = 2*dy-dx; + incrE = 2*dy; + incrNE = 2*(dy-dx); + + while (x != x1) { + if (d <= 0){ + d += incrE; + } else { + d += incrNE; + y += ddy; + } + x += ddx; + if (x < width && x >= 0 && y < height && y >= 0) { + rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; + mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y*width+x)*4+k] = mixedRGB[k]; + } + } + } + } else { + d = 2*dx-dy; + incrE = 2*dx; + incrNE = 2*(dx-dy); + + while (y != y1) { + if (d <= 0) { + d += incrE; + }else { + d += incrNE; + x += ddx; + } + y += ddy; + if (x < width && x >= 0 && y < height && y >= 0) { + rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; + mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y*width+x)*4+k] = mixedRGB[k]; + } + } + } + } + } + for(k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Replaces every pixel with the maximum RGB value of the neighboring pixels. Each color is + * considered separately. + */ +function MaximumFilter(){ + this.name = "Maximum"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var maxR = 0; + var maxG = 0; + var maxB = 0; + for (var dy = -1; dy <= 1; dy++){ + var iy = y+dy; + if(iy >= 0 && iy < height){ + for (var dx = -1; dx <= 1; dx++){ + var ix = x+dx; + if(ix >= 0 && ix < width){ + var iPixel = (iy*width + ix)*4; + maxR = Math.max(maxR,inputData[iPixel]); + maxG = Math.max(maxG,inputData[iPixel+1]); + maxB = Math.max(maxB,inputData[iPixel+2]); + } + } + } + } + outputData[pixel] = maxR; + outputData[pixel+1] = maxG; + outputData[pixel+2] = maxB; + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Replaces every pixel with the median RGB value of the neighboring pixels. Each color is + * considered separately. + */ +function MedianFilter(){ + this.name = "Median"; + this.isDirAnimatable = false; + this.defaultValues = { + }; + this.valueRanges = { + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var rList = []; + var gList = []; + var bList = []; + for (var dy = -1; dy <= 1; dy++){ + var iy = y+dy; + if(iy >= 0 && iy < height){ + for (var dx = -1; dx <= 1; dx++){ + var ix = x+dx; + if(ix >= 0 && ix < width){ + var iPixel = (iy*width + ix)*4; + rList.push(inputData[iPixel]); + gList.push(inputData[iPixel+1]); + bList.push(inputData[iPixel+2]); + + } + } + } + } + var sortFunc = function(a,b){ + return a-b; + }; + rList.sort(sortFunc); + gList.sort(sortFunc); + bList.sort(sortFunc); + outputData[pixel] = rList[4]; + outputData[pixel+1] = gList[4]; + outputData[pixel+2] = bList[4]; + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Replaces every pixel with the minimum RGB value of the neighboring pixels. Each color is + * considered separately. + */ +function MinimumFilter(){ + this.name = "Minimum"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var minR = 255; + var minG = 255; + var minB = 255; + for (var dy = -1; dy <= 1; dy++){ + var iy = y+dy; + if(iy >= 0 && iy < height){ + for (var dx = -1; dx <= 1; dx++){ + var ix = x+dx; + if(ix >= 0 && ix < width){ + var iPixel = (iy*width + ix)*4; + minR = Math.min(minR,inputData[iPixel]); + minG = Math.min(minG,inputData[iPixel+1]); + minB = Math.min(minB,inputData[iPixel+2]); + } + } + } + } + outputData[pixel] = minR; + outputData[pixel+1] = minG; + outputData[pixel+2] = minB; + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Creates random noise on the image, with or without color. + */ +function NoiseFilter(){ + this.name = "Noise"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 25, + density : 1, + monochrome : true + }; + this.valueRanges = { + amount : {min:0, max:100}, + density : {min:0, max:1.0}, + monochrome : {min:false, max:true} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + var density = (values.density === undefined) ? this.defaultValues.density : values.density; + var monochrome = (values.monochrome === undefined) ? this.defaultValues.monochrome : values.monochrome; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + if(Math.random() <= density){ + var n; + if(monochrome){ + n = parseInt((2*Math.random()-1) * amount,10); + inputData[pixel] += n; + inputData[pixel+1] += n; + inputData[pixel+2] += n; + } else { + for(var i = 0; i < 3; i++){ + n = parseInt((2*Math.random()-1) * amount,10); + inputData[pixel+i] += n; + } + } + } + } + } + }; +} +/** + * Produces an oil painting effect on the image. + * NOTE: This filter can be very slow, especially at higher ranges. Use with caution. + */ +function OilFilter(){ + this.name = "Oil Painting"; + this.isDirAnimatable = false; + this.defaultValues = { + range : 3 + }; + this.valueRanges = { + range : {min:0, max:5} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + if(values === undefined){ values = this.defaultValues; } + var range = (values.range === undefined) ? this.defaultValues.range : values.range; + range = parseInt(range,10); + var index = 0; + var rHistogram = []; + var gHistogram = []; + var bHistogram = []; + var rTotal = []; + var gTotal = []; + var bTotal = []; + var levels = 256; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + for (var j = 0; j < levels; j++){ + rHistogram[j] = gHistogram[j] = bHistogram[j] = rTotal[j] = gTotal[j] = bTotal[j] = 0; + } + for (var row = -range; row <= range; row++) { + var iy = y+row; + var ioffset; + if (0 <= iy && iy < height) { + ioffset = iy*width; + for (var col = -range; col <= range; col++) { + var ix = x+col; + if (0 <= ix && ix < width) { + var ro = inputData[(ioffset+ix)*4]; + var go = inputData[(ioffset+ix)*4+1]; + var bo = inputData[(ioffset+ix)*4+2]; + var ri = ro*levels/256; + var gi = go*levels/256; + var bi = bo*levels/256; + rTotal[ri] += ro; + gTotal[gi] += go; + bTotal[bi] += bo; + rHistogram[ri]++; + gHistogram[gi]++; + bHistogram[bi]++; + } + } + } + } + var r = 0, g = 0, b = 0; + for (var i = 1; i < levels; i++) { + if (rHistogram[i] > rHistogram[r]){ + r = i; + } + if (gHistogram[i] > gHistogram[g]){ + g = i; + } + if (bHistogram[i] > bHistogram[b]){ + b = i; + } + } + r = rTotal[r] / rHistogram[r]; + g = gTotal[g] / gHistogram[g]; + b = bTotal[b] / bHistogram[b]; + outputData[pixel] = r; + outputData[pixel+1] = g; + outputData[pixel+2] = b; + outputData[pixel+3] = inputData[pixel+3]; + } + } + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Changes the opacity of the image. + */ +function OpacityFilter(){ + this.name = "Opacity"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 1.0 + }; + this.valueRanges = { + amount : {min:0.0, max:1.0} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + inputData[pixel+3] = 255*amount; + } + } + }; +} +/** + * Pinches and whirls the image toward the center point. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function PinchFilter(){ + this.name = "Pinch/Whirl"; + this.isDirAnimatable = false; + this.defaultValues = { + amount : 0.5, + radius : 100, + angle : 0, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + amount : {min: -1.0, max: 1.0}, + radius : {min: 1, max: 200}, + angle : {min: 0, max: 360}, + centerX : {min: 0.0, max:1.0}, + centerY : {min: 0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; + var radius2 = radius*radius; + angle = angle/180 * Math.PI; + var iCenterX = width * centerX; var iCenterY = height * centerY; + var transInverse = function(x,y,out){ + var dx = x-iCenterX; + var dy = y-iCenterY; + var distance = dx*dx + dy*dy; + if(distance > radius2 || distance === 0){ + out[0] = x; + out[1] = y; + } else { + var d = Math.sqrt( distance / radius2 ); + var t = Math.pow( Math.sin( Math.PI*0.5 * d ), -amount); + dx *= t; + dy *= t; + var e = 1 - d; + var a = angle * e * e; + var s = Math.sin(a); + var c = Math.cos(a); + out[0] = iCenterX + c*dx - s*dy; + out[1] = iCenterY + s*dx + c*dy; + } + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Pixelates the image i.e. divides the image into blocks of color. + */ +function PixelationFilter(){ + this.name = "Pixelation"; + this.isDirAnimatable = false; + this.defaultValues = { + size : 5 + }; + this.valueRanges = { + size : {min:1, max:50} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var size = (values.size === undefined) ? this.defaultValues.size : values.size; + size = parseInt(size,10); + var pixels = []; + var by, bx, bPixel; + for (var y = 0; y < height; y+=size) { + for (var x = 0; x < width; x+=size) { + var pixel = (y*width + x)*4; + var w = Math.min(size, width-x); + var h = Math.min(size, height-y); + var t = w*h; + var r = 0, g = 0, b = 0; + for(by = y; by < y+h; by++){ + for(bx = x; bx < x+w; bx++){ + bPixel = (by*width + bx)*4; + r += inputData[bPixel]; + g += inputData[bPixel+1]; + b += inputData[bPixel+2]; + } + } + for(by = y; by < y+h; by++){ + for(bx = x; bx < x+w; bx++){ + bPixel = (by*width + bx)*4; + inputData[bPixel] = r/t; + inputData[bPixel+1] = g/t; + inputData[bPixel+2] = b/t; + } + } + } + } + }; +} +/** + * Posterizes the image, i.e. restricts the color values to a set amount of levels. + */ +function PosterizeFilter(){ + this.name = "Posterize"; + this.isDirAnimatable = false; + this.defaultValues = { + levels : 6 + }; + this.valueRanges = { + levels : {min:2, max:30 } + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var levels = (values.levels === undefined) ? this.defaultValues.levels : parseInt(values.levels,10); + if(levels <= 1){ + return; + } + var table = []; + for(var i = 0; i < 256; i++){ + table[i] = parseInt(255 * parseInt(i*levels/256,10) / (levels-1),10); + } + filterUtils.tableFilter(inputData,table,width,height); + }; +} +/** + * Adjust the factor of each RGB color value in the image. + */ +function RGBAdjustFilter(){ + this.name = "RGBAdjust"; + this.isDirAnimatable = true; + this.defaultValues = { + red: 1.0, + green: 1.0, + blue: 1.0 + }; + this.valueRanges = { + red: {min: 0.0, max: 2.0}, + green: {min: 0.0, max: 2.0}, + blue: {min: 0.0, max: 2.0} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var red = (values.red === undefined) ? this.defaultValues.red : values.red; + var green = (values.green === undefined) ? this.defaultValues.green : values.green; + var blue = (values.blue === undefined) ? this.defaultValues.blue : values.blue; + if(red < 0){ red = 0; } + if(green < 0){ green = 0; } + if(blue < 0){ blue = 0; } + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + inputData[pixel] *= red; + inputData[pixel+1] *= green; + inputData[pixel+2] *= blue; + } + } + }; +} +/** + * Adjusts the saturation value of the image. Values over 1 increase saturation while values below decrease saturation. + * For a true grayscale effect, use the grayscale filter instead. + */ +function SaturationFilter(){ + this.name = "Saturation"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 1.0 + }; + this.valueRanges = { + amount : {min:0.0, max:2.0} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + var RW = 0.3; + var RG = 0.59; + var RB = 0.11; + var a = (1 - amount) * RW + amount; + var b = (1 - amount) * RW; + var c = (1 - amount) * RW; + var d = (1 - amount) * RG; + var e = (1 - amount) * RG + amount; + var f = (1 - amount) * RG; + var g = (1 - amount) * RB; + var h = (1 - amount) * RB; + var i = (1 - amount) * RB + amount; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var pR = inputData[pixel]; + var pG = inputData[pixel+1]; + var pB = inputData[pixel+2]; + inputData[pixel] = a*pR + d*pG + g*pB; + inputData[pixel+1] = b*pR + e*pG + h*pB; + inputData[pixel+2] = c*pR + f*pG + i*pB; + } + } + }; +} +/** + * Creates ripples on the image horizontally/vertically in a sawtooth pattern. + */ +function SawtoothRippleFilter(){ + this.name = "Sawtooth Ripples"; + this.isDirAnimatable = false; + this.defaultValues = { + xAmplitude : 5, + yAmplitude : 5, + xWavelength : 16, + yWavelength : 16 + }; + this.valueRanges = { + xAmplitude : {min:0, max:30}, + yAmplitude : {min:0, max:30}, + xWavelength : {min:1, max:50}, + yWavelength : {min:1, max:50} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; + var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; + var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; + var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; + var transInverse = function(x,y,out){ + var nx = y/xWavelength; + var ny = x/yWavelength; + var fx = filterUtils.mod(nx,1); + var fy = filterUtils.mod(ny,1); + out[0] = x + xAmplitude * fx; + out[1] = y + yAmplitude * fy; + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Creates a sepia effect on the image i.e. gives the image a yellow-brownish tone. + */ +function SepiaFilter(){ + this.name = "Sepia"; + this.isDirAnimatable = true; + this.defaultValues = { + amount : 10 + }; + this.valueRanges = { + amount : {min:0, max:30} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + amount *= 255/100; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var luma = inputData[pixel]*0.3 + inputData[pixel+1]*0.59 + inputData[pixel+2]*0.11; + var r,g,b; + r = g = b = luma; + r += 40; + g += 20; + b -= amount; + + inputData[pixel] = r; + inputData[pixel+1] = g; + inputData[pixel+2] = b; + } + } + }; +} +/** + * Sharpens the image slightly. For increased effect, apply the filter multiple times. + */ +function SharpenFilter(){ + this.name = "Sharpen"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var matrix = [ 0.0,-0.2, 0.0, + -0.2, 1.8,-0.2, + 0.0, -0.2, 0.0]; + filterUtils.convolveFilter(inputData,matrix,width,height); + }; +} +/** + * Creates ripples on the image horizontally/vertically in a sine pattern. + */ +function SineRippleFilter(){ + this.name = "Sine Ripples"; + this.isDirAnimatable = false; + this.defaultValues = { + xAmplitude : 5, + yAmplitude : 5, + xWavelength : 16, + yWavelength : 16 + }; + this.valueRanges = { + xAmplitude : {min:0, max:30}, + yAmplitude : {min:0, max:30}, + xWavelength : {min:1, max:50}, + yWavelength : {min:1, max:50} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; + var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; + var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; + var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; + var transInverse = function(x,y,out){ + var nx = y/xWavelength; + var ny = x/yWavelength; + var fx = Math.sin(nx); + var fy = Math.sin(ny); + out[0] = x + xAmplitude * fx; + out[1] = y + yAmplitude * fy; + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Produces a solarization effect on the image. + */ +function SolarizeFilter(){ + this.name = "Solarize"; + this.isDirAnimatable = true; + this.defaultValues = { + }; + this.valueRanges = { + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var table = []; + for(var i = 0; i < 256; i++){ + var val = (i/255 > 0.5) ? 2*(i/255-0.5) : 2*(0.5-i/255); + table[i] = parseInt(255 * val,10); + } + filterUtils.tableFilter(inputData, table, width, height); + }; +} +/** + * Generates a sparkle/sunburst effect on the image. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function SparkleFilter(){ + this.name = "Sparkle"; + this.isDirAnimatable = false; + this.defaultValues = { + rays : 50, + size : 25, + amount : 50, + randomness : 25, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + rays : {min:1, max:100}, + size : {min:1, max:200}, + amount : {min:0, max:100}, + randomness : {min:0, max:50}, + centerX : {min:0, max:1.0}, + centerY : {min:0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var rays = (values.rays === undefined) ? this.defaultValues.rays : values.rays; + rays = parseInt(rays, 10); + var size = (values.size === undefined) ? this.defaultValues.size : values.size; + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + var randomness = (values.randomness === undefined) ? this.defaultValues.randomness : values.randomness; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var iCenterX = centerX * width; + var iCenterY = centerY * height; + var rayLengths = []; + for(var j = 0; j < rays; j++){ + rayLengths[j]= size + randomness / 100 * size * filterUtils.gaussianRandom(); + } + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var dx = x-iCenterX; + var dy = y-iCenterY; + var distance = dx*dx + dy*dy; + var angle = Math.atan2(dy,dx); + var d = (angle+Math.PI) / (Math.PI*2) * rays; + var i = parseInt(d,10); + var f = d - i; + if(size !== 0){ + var length = filterUtils.linearInterpolate(f, rayLengths[i % rays], rayLengths[(i+1) % rays]); + var g = length*length / (distance+0.0001); + g = Math.pow(g, (100-amount) / 50); + f -= 0.5; + f = 1 - f*f; + f *= g; + } + f = filterUtils.clampPixel(f,0,1); + var mixedRGB = filterUtils.mixColors(f,[inputData[pixel],inputData[pixel+1],inputData[pixel+2],inputData[pixel+3]],[255,255,255,255]); + for(var k = 0; k < 3; k++){ + inputData[pixel+k] = mixedRGB[k]; + } + } + } + }; +} +/** + * Smears out the image with square shapes to create a painting style effect. + * The mix values sets the intensity of the effect. + * NOTE: This filter can be very slow, especially at higher densities/sizes. Use with caution. + */ +function SquareSmearFilter(){ + this.name = "Square Smear"; + this.isDirAnimatable = false; + this.defaultValues = { + size : 4, + density : 0.5, + mix : 0.5 + }; + this.valueRanges = { + size : {min:1, max:10}, + density : {min:0.0, max:1.0}, + mix : {min:0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + var k; + for(k = 0; k < inputData.length; k++){ + outputData[k] = inputData[k]; + } + if(values === undefined){ values = this.defaultValues; } + var size = (values.size === undefined) ? this.defaultValues.size : values.size; + if(size < 1){ size = 1;} + size = parseInt(size,10); + var density = (values.density === undefined) ? this.defaultValues.density : values.density; + var mix = (values.mix === undefined) ? this.defaultValues.mix : values.mix; + var radius = size+1; + var radius2 = radius*radius; + var numShapes = parseInt(2*density/30*width*height / 2,10); + for(var i = 0; i < numShapes; i++){ + var sx = (Math.random()*Math.pow(2,32) & 0x7fffffff) % width; + var sy = (Math.random()*Math.pow(2,32) & 0x7fffffff) % height; + var rgb2 = [inputData[(sy*width+sx)*4],inputData[(sy*width+sx)*4+1],inputData[(sy*width+sx)*4+2],inputData[(sy*width+sx)*4+3]]; + for(var x = sx - radius; x < sx + radius + 1; x++){ + + for(var y = sy - radius; y < sy + radius + 1; y++){ + if (x >= 0 && x < width && y >= 0 && y < height) { + var rgb1 = [outputData[(y*width+x)*4],outputData[(y*width+x)*4+1],outputData[(y*width+x)*4+2],outputData[(y*width+x)*4+3]]; + var mixedRGB = filterUtils.mixColors(mix,rgb1,rgb2); + for(k = 0; k < 3; k++){ + outputData[(y*width+x)*4+k] = mixedRGB[k]; + } + } + } + } + } + for(k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Divides the colors into black and white following the treshold value. Brightnesses above the threshold + * sets the color to white while values below the threshold sets the color to black. + */ +function ThresholdFilter(){ + this.name = "Black & White"; + this.isDirAnimatable = true; + this.defaultValues = { + threshold : 127 + }; + this.valueRanges = { + threshold : {min:0, max:255} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var threshold = (values.threshold === undefined) ? this.defaultValues.threshold : values.threshold; + for (var y = 0; y < height; y++) { + for (var x = 0; x < width; x++) { + var pixel = (y*width + x)*4; + var brightness = (inputData[pixel] + inputData[pixel+1] + inputData[pixel+2])/3; + var colorVal = 0; + if(brightness > threshold){ + colorVal = 255; + } + inputData[pixel] = inputData[pixel+1] = inputData[pixel+2] = colorVal; + } + } + }; +} +/** + * Creates ripples on the image horizontally/vertically in a sine pattern. + */ +function TriangleRippleFilter(){ + this.name = "Triangle Ripples"; + this.isDirAnimatable = false; + this.defaultValues = { + xAmplitude : 5, + yAmplitude : 5, + xWavelength : 16, + yWavelength : 16 + }; + this.valueRanges = { + xAmplitude : {min:0, max:30}, + yAmplitude : {min:0, max:30}, + xWavelength : {min:1, max:50}, + yWavelength : {min:1, max:50} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var xAmplitude = (values.xAmplitude === undefined) ? this.defaultValues.xAmplitude : values.xAmplitude; + var yAmplitude = (values.yAmplitude === undefined) ? this.defaultValues.yAmplitude : values.yAmplitude; + var xWavelength = (values.xWavelength === undefined) ? this.defaultValues.xWavelength : values.xWavelength; + var yWavelength = (values.yWavelength === undefined) ? this.defaultValues.yWavelength : values.yWavelength; + var transInverse = function(x,y,out){ + var nx = y/xWavelength; + var ny = x/yWavelength; + var fx = filterUtils.triangle(nx,1); + var fy = filterUtils.triangle(ny,1); + out[0] = x + xAmplitude * fx; + out[1] = y + yAmplitude * fy; + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Twists the image around a given center point. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function TwirlFilter(){ + this.name = "Twirl"; + this.isDirAnimatable = false; + this.defaultValues = { + radius : 100, + angle : 180, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + radius : {min: 1, max: 200}, + angle : {min: 0, max: 360}, + centerX : {min: 0.0, max:1.0}, + centerY : {min: 0.0, max:1.0} + }; + + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var angle = (values.angle === undefined) ? this.defaultValues.angle : values.angle; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; + var radius2 = radius*radius; + angle = angle/180 * Math.PI; + var iCenterX = width * centerX; var iCenterY = height * centerY; + var transInverse = function(x,y,out){ + var dx = x-iCenterX; + var dy = y-iCenterY; + var distance = dx*dx + dy*dy; + if(distance > radius2){ + out[0] = x; + out[1] = y; + } else { + distance = Math.sqrt(distance); + var a = Math.atan2(dy, dx) + angle * (radius-distance) / radius; + out[0] = iCenterX + distance*Math.cos(a); + out[1] = iCenterY + distance*Math.sin(a); + } + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * Creates a classical vignette effect on the image i.e. darkens the corners. + */ +function VignetteFilter(){ + this.name = "Vignette"; + this.isDirAnimatable = false; + this.defaultValues = { + amount : 0.3 + }; + this.valueRanges = { + amount : {min:0.0, max:1.0} + }; + this.filter = function(input,values){ + var width = input.width, height = input.height; + var inputData = input.data; + var outputData = []; + if(values === undefined){ values = this.defaultValues; } + var amount = (values.amount === undefined) ? this.defaultValues.amount : values.amount; + var canvas = document.createElement("canvas"); + canvas.width = width; + canvas.height = height; + var context = canvas.getContext("2d"); + var gradient; + var radius = Math.sqrt( Math.pow(width/2, 2) + Math.pow(height/2, 2) ); + context.putImageData(input,0,0); + context.globalCompositeOperation = 'source-over'; + + gradient = context.createRadialGradient(width/2, height/2, 0, width/2, height/2, radius); + gradient.addColorStop(0, 'rgba(0,0,0,0)'); + gradient.addColorStop(0.5, 'rgba(0,0,0,0)'); + gradient.addColorStop(1, 'rgba(0,0,0,' + amount + ')'); + context.fillStyle = gradient; + context.fillRect(0, 0, width, height); + outputData = context.getImageData(0,0,width,height).data; + for(var k = 0; k < outputData.length; k++){ + inputData[k] = outputData[k]; + } + }; +} +/** + * Produces a water ripple/waves on the image. CenterX and CenterY specify the + * position in terms of ratios of width and height. + */ +function WaterRippleFilter(){ + this.name = "Water Ripples"; + this.isDirAnimatable = false; + this.defaultValues = { + phase : 0, + radius : 50, + wavelength : 16, + amplitude : 10, + centerX : 0.5, + centerY : 0.5 + }; + this.valueRanges = { + phase : {min: 0, max: 100}, + radius : {min: 1, max: 200}, + wavelength : {min: 1, max: 100}, + amplitude : {min: 1, max: 100}, + centerX : {min: 0.0, max:1.0}, + centerY : {min: 0.0, max:1.0} + }; + var filterUtils = new FilterUtils(); + + this.filter = function (input, values){ + var width = input.width, height = input.height; + var inputData = input.data; + if(values === undefined){ values = this.defaultValues; } + var wavelength = (values.wavelength === undefined) ? this.defaultValues.wavelength : values.wavelength; + var amplitude = (values.amplitude === undefined) ? this.defaultValues.amplitude : values.amplitude; + var phase = (values.phase === undefined) ? this.defaultValues.phase : values.phase; + var centerX = (values.centerX === undefined) ? this.defaultValues.centerX : values.centerX; + var centerY = (values.centerY === undefined) ? this.defaultValues.centerY : values.centerY; + var radius = (values.radius === undefined) ? this.defaultValues.radius : values.radius; + var radius2 = radius*radius; + var iCenterX = width * centerX; var iCenterY = height * centerY; + var transInverse = function(x,y,out){ + var dx = x-iCenterX; + var dy = y-iCenterY; + var distance2 = dx*dx + dy*dy; + if(distance2 > radius2){ + out[0] = x; + out[1] = y; + } else { + var distance = Math.sqrt(distance2); + var amount = amplitude * Math.sin(distance/wavelength * Math.PI * 2 - phase); + amount *= (radius-distance)/radius; + if(distance !== 0){ + amount *= wavelength/distance; + } + out[0] = x + dx*amount; + out[1] = y + dy*amount; + } + }; + filterUtils.transformFilter(inputData,transInverse,width,height); + }; +} +/** + * A collection of all the filters. + */ +var JSManipulate = { + blur : new BlurFilter(), + brightness : new BrightnessFilter(), + bump : new BumpFilter(), + circlesmear : new CircleSmearFilter(), + contrast : new ContrastFilter(), + crosssmear : new CrossSmearFilter(), + diffusion : new DiffusionFilter(), + dither : new DitherFilter(), + edge : new EdgeFilter(), + emboss : new EmbossFilter(), + exposure : new ExposureFilter(), + gain : new GainFilter(), + gamma : new GammaFilter(), + grayscale : new GrayscaleFilter(), + hue : new HueFilter(), + invert : new InvertFilter(), + kaleidoscope : new KaleidoscopeFilter(), + lensdistortion : new LensDistortionFilter(), + linesmear : new LineSmearFilter(), + maximum : new MaximumFilter(), + median : new MedianFilter(), + minimum : new MinimumFilter(), + noise : new NoiseFilter(), + oil : new OilFilter(), + opacity : new OpacityFilter(), + pinch : new PinchFilter(), + pixelate : new PixelationFilter(), + posterize : new PosterizeFilter(), + rgbadjust : new RGBAdjustFilter(), + saturation : new SaturationFilter(), + sawtoothripple : new SawtoothRippleFilter(), + sepia : new SepiaFilter(), + sharpen : new SharpenFilter(), + sineripple : new SineRippleFilter(), + solarize : new SolarizeFilter(), + sparkle : new SparkleFilter(), + squaresmear : new SquareSmearFilter(), + threshold : new ThresholdFilter(), + triangleripple : new TriangleRippleFilter(), + twirl : new TwirlFilter(), + vignette : new VignetteFilter(), + waterripple : new WaterRippleFilter() +}; + +/* +========================================================================= + Pasta + +The code below was added by Richard Feldman + +MIT LICENSED (http://www.opensource.org/licenses/mit-license.php) +Copyright (c) 2016, Richard Feldman +========================================================================= +*/ +var statusListeners = []; + +var addActivityListener = function(callback) { + statusListeners.push(callback); +}; + +var updatePastaActivity = function(status) { + statusListeners.forEach(function(callback) { callback(status); }); +}; + +var activatePasta = function(canvas, opts) { + if (!canvas) { + return; + } + + var url = opts.url; + var filterName = opts.filter; + + var imageObj = new Image(); + + imageObj.setAttribute("crossorigin", true); + + imageObj.onload = function() { + updatePastaActivity("Rendering a " + imageObj.width + "x" + imageObj.height + " image..."); + + var startTimeMs = new Date().getTime(); + + canvas.width = imageObj.width; + canvas.height = imageObj.height; + + var context = canvas.getContext("2d"); + context.drawImage(imageObj, 0, 0); + + var data = context.getImageData(0, 0, canvas.width, canvas.height); + + opts.filters.forEach(function(filter) { + var filterName = (typeof filter === "string" ? filter : filter.name).toLowerCase(); + var filterOpts; + + if (filterName === "ripple") { + filterName = "sineripple"; + } + + if (typeof filter === "object") { + if (filter.amount === 0) { + return; + } + + var multiplier = Math.max(0.0, Math.min(1.0, filter.amount)); + + switch (filterName) { + case "sepia": + filterOpts = {amount: Math.round(multiplier * 30)}; + break; + + case "circlesmear": + filterOpts = {density: multiplier}; + break; + + case "noise": + filterOpts = {amount: Math.round(multiplier * 100)}; + break; + + case "oil": + filterOpts = {range: Math.round(multiplier * 5)}; + break; + + case "sineripple": + filterOpts = {xAmplitude: Math.round(multiplier * 30)}; + break; + + case "hue": + filterOpts = {amount: 1.0 - (multiplier * 2.0)}; + break; + + case "vignette": + filterOpts = {amount: multiplier}; + break; + } + } + + if (typeof JSManipulate[filterName] === "object") { + JSManipulate[filterName].filter(data, filterOpts); + } else { + console.warn("Unregoznied filter:", filterName); + } + }); + + context.putImageData(data, 0, 0); + + updatePastaActivity("Applied some tasty filters in " + (new Date().getTime() - startTimeMs) + " ms."); + }; + + imageObj.src = url; +} + +Pasta = { apply: activatePasta, addActivityListener: addActivityListener, version: 4.2 }; diff --git a/range-slider.css b/range-slider.css new file mode 100644 index 0000000..cfe10e4 --- /dev/null +++ b/range-slider.css @@ -0,0 +1,141 @@ +/* https://github.com/mm-jsr/jsr + +Copyright 2017 Mateusz "Soanvig" Koteja + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +.jsr, .jsr_bar, .jsr_label, .jsr_rail, +.jsr_rail-outer, .jsr_slider { + box-sizing: border-box; +} + +.jsr { + position: relative; + z-index: 1; + + display: block; + + box-sizing: border-box; + width: 100%; + + margin: 20px 0; + + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + + -webkit-touch-callout: none; + -khtml-user-select: none; + + font: 14px sans-serif; +} + +.jsr_rail-outer { + position: relative; + padding: 10px 0; + cursor: pointer; +} + +.jsr_rail { + height: 5px; + background: #444; + z-index: 1; +} + +.jsr_bar { + position: absolute; + height: 5px; + background-color: #999; + z-index: 2; + cursor: move; +} + +.jsr_bar--limit { + background-color: #7e7e7e; + z-index: 1; + cursor: auto; +} + +.jsr_slider { + position: absolute; + top: calc(5px / 2 + 10px); + left: 0; + + transform: translate(-50%, -50%); + + width: 25px; + height: 25px; + + cursor: col-resize; + transition: background 0.1s ease-in-out; + + outline: 0; + + z-index: 3; +} + +.jsr_slider::before { + content: ''; + width: 15px; + height: 15px; + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background: #999; + border-radius: 50%; +} + +.jsr_slider:focus::before { + background: #c00; +} + +.jsr_label { + position: absolute; + top: calc(10px + 5px + 15px / 1.5); + padding: 0.2em 0.4em; + background: #444; + color: #fff; + font-size: 0.9em; + white-space: nowrap; + border-radius: 0.3em; + z-index: 2; +} + +.jsr_label--minmax { + z-index: 1; + color: #999; + background: #333; + transition: opacity 0.2s ease-in-out; +} + +/* Merged labels */ +.jsr_label .jsr_label { + position: static; + display: inline-block; + font-size: 1em; + padding-top: 0; + padding-right: 0; + padding-bottom: 0; +} + +.jsr_label .jsr_label::before { + content: ' - '; +} + +.jsr_canvas { + margin-top: 5px; +} + +/* Lock screen for touch */ +.jsr_lockscreen { + overflow: hidden; + height: 100%; + width: 100%; +} diff --git a/range-slider.js b/range-slider.js new file mode 100644 index 0000000..d763e0a --- /dev/null +++ b/range-slider.js @@ -0,0 +1,12 @@ +/* https://github.com/mm-jsr/jsr + +Copyright 2017 Mateusz "Soanvig" Koteja + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +*/ + +!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.JSR=t()}(this,function(){"use strict";function e(e,t,i){e.addEventListener(t,i)}function t(i,n,s){i instanceof Array?i.forEach(function(i){i instanceof Array?t(i,n,s):e(i,n,s)}):e(i,n,s)}function i(e){var t=e.toString().split(".");return t[1]?t[1].length:0}function n(e,t,i){return(i-e)/(t-e)}function s(e,t,n,s){return function(e,t){var n=i(t),s=Math.pow(10,n);return e=Math.round(e/t)*t,Math.round(e*s)/s}(n=(t-e)*n+e,s)}var r=function(e){return function(e){return!!e&&"object"==typeof e}(e)&&!function(e){var t=Object.prototype.toString.call(e);return"[object RegExp]"===t||"[object Date]"===t||function(e){return e.$$typeof===o}(e)}(e)};var o="function"==typeof Symbol&&Symbol.for?Symbol.for("react.element"):60103;function l(e,t){var i;return(!t||!1!==t.clone)&&r(e)?u((i=e,Array.isArray(i)?[]:{}),e,t):e}function a(e,t,i){return e.concat(t).map(function(e){return l(e,i)})}function u(e,t,i){var n=Array.isArray(t);return n===Array.isArray(e)?n?((i||{arrayMerge:a}).arrayMerge||a)(e,t,i):function(e,t,i){var n={};return r(e)&&Object.keys(e).forEach(function(t){n[t]=l(e[t],i)}),Object.keys(t).forEach(function(s){r(t[s])&&e[s]?n[s]=u(e[s],t[s],i):n[s]=l(t[s],i)}),n}(e,t,i):l(t,i)}u.all=function(e,t){if(!Array.isArray(e))throw new Error("first argument should be an array");return e.reduce(function(e,i){return u(e,i,t)},{})};var c=u,h=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},d=function(){function e(e,t){for(var i=0;i