一見したところ、Phrogz の リンクは関連性がないように見えましたが (私も以前に独自の調査を行ったときに同じリンクをたどりました)、詳しく調べてみると、まさに私が探していたものでした。
残念ながら、これは非常に遅いので、私が目指している種類のアニメーション、特に画面いっぱいの画像にはあまり適していません。いくつかのマイクロ最適化を行うことで多少速くなりましたが、結局のところ、この種のことは今日の JavaScript には強すぎるようです。
とにかく、これが私が最終的に得たものです。多分それは誰かを助けるか、誰かがそれを30または60 fpsで使用するためにそれをスピードアップする方法を考え出すかもしれません(ワーカー?)。少しいじっただけなので、コードはまったくきれいではありません。出発点として使用してください。
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title></title>
<script src="displacement.js"></script>
</head>
<body>
<script>
(function() {
'use strict';
var source, map, target;
var sourceCtx, mapCtx, targetCtx;
var loaded = false;
var x = 0, y = 0;
var filter;
function render() {
filter = new filters.DisplacementMap(
source,
map,
target,
new filters.Point(0, 0),
x,
y,
filters.ColorChannel.RED,
filters.ColorChannel.RED);
filter.draw();
}
(function init() {
var img = new Image();
function prepareCanvases() {
// Create canvases and add them to the document
source = document.createElement('canvas');
map = document.createElement('canvas');
target = document.createElement('canvas');
[target, source, map].forEach(function(canvas) {
canvas.setAttribute('width', img.width);
canvas.setAttribute('height', img.height);
document.body.appendChild(canvas);
});
// Get contexts
sourceCtx = source.getContext('2d');
mapCtx = map.getContext('2d');
targetCtx = target.getContext('2d');
}
// Load source image
(function loadImage() {
img.onload = function() {
prepareCanvases();
sourceCtx.drawImage(img, 0, 0);
img.onload = function() {
mapCtx.drawImage(img, 0, 0);
addEventListeners();
loaded = true;
};
// Put your displacement map here
img.src = 'jn-map-small.jpg';
};
// Put your image here
img.src = 'jn-edit-small.jpg';
}());
// Event listener
function addEventListeners() {
target.onmousemove = function(e) {
x = ((-e.offsetX / source.width) * 10) + 5;
y = ((-e.offsetY / source.height) * 10) + 5;
render();
};
}
}());
}());
</script>
</body>
</html>
変位.js:
/**
* Based on Romuald Quantin's code
* @see http://www.soundstep.com/blog/2012/04/25/javascript-displacement-mapping/
*/
var filters = {} || filters;
(function() {
filters.ColorChannel = {
RED: 0,
GREEN: 1,
BLUE: 2,
ALPHA: 3
};
filters.Point = function(x, y) {
this.x = x || 0;
this.y = y || 0;
};
filters.DisplacementMap = function(source, map, target, point, scaleX, scaleY, channelX, channelY) {
this.source = source;
this.map = map;
this.target = target;
this.sourceCtx = this.source.getContext("2d");
this.mapCtx = this.map.getContext("2d");
this.targetCtx = this.target.getContext("2d");
this.point = point || new filters.Point();
this.scaleX = scaleX || 0;
this.scaleY = scaleY || 0;
this.channelX = channelX || filters.ColorChannel.RED;
this.channelY = channelY || filters.ColorChannel.RED;
if (this.channelX !== 0 && this.channelX !== 1 && this.channelX !== 2 && this.channelX !== 3)
this.channelX = filters.ColorChannel.RED;
if (this.channelY !== 0 && this.channelY !== 1 && this.channelY !== 2 && this.channelY !== 3)
this.channelY = filters.ColorChannel.RED;
};
var p = filters.DisplacementMap.prototype;
p.draw = function() {
var sourceData = this.sourceCtx.getImageData(0, 0, this.source.width, this.source.height);
var mapData = this.mapCtx.getImageData(0, 0, this.map.width, this.map.height);
var targetDataX = this.sourceCtx.getImageData(0, 0, this.source.width, this.source.height);
var targetDataY = this.sourceCtx.getImageData(0, 0, this.source.width, this.source.height);
var pixelsLength = mapData.data.length / 4;
var colorValue,
alphaValue,
ratio,
ratioWithAlpha,
pixelShift,
sourcePosition,
targetPosition,
x,
y;
var i = 0;
var map = this.map;
var point = this.point;
var channelX = this.channelX;
var channelY = this.channelY;
var scaleX = this.scaleX;
var scaleY = this.scaleY;
var source = this.source;
var target = this.target;
while (i < pixelsLength) {
x = ((i % map.width) + point.x) | 0;
y = (((i / map.width) | 0) + point.y) | 0;
// alphaValue = mapData.data[i * 4 + filters.ColorChannel.ALPHA];
// ratioWithAlpha = ratio * (alphaValue / 0xFF);
targetDataX.data = this.setPixels(targetDataX.data, (target.width * y) + x + ((((mapData.data[i * 4 + channelX]) / 0xFF * 2) - 1) * scaleX | 0), sourceData.data, (source.width * y) + x);
i++;
}
i = 0;
while (i < pixelsLength) {
x = ((i % map.width) + point.x) | 0;
y = (((i / map.width) | 0) + point.y) | 0;
// alphaValue = mapData.data[i * 4 + filters.ColorChannel.ALPHA];
// ratioWithAlpha = ratio * (alphaValue / 0xFF);
targetDataY.data = this.setPixels(targetDataY.data, (target.width * (y + ((((mapData.data[i * 4 + channelY]) / 0xFF * 2) - 1) * scaleY | 0))) + x, targetDataX.data, (source.width * y) + x);
i++;
}
this.targetCtx.putImageData(targetDataY, 0, 0);
};
p.setPixels = function(target, pos, source, i) {
i = i * 4;
pos = pos * 4;
target[i] = source[pos];
target[i + 1] = source[pos + 1];
target[i + 2] = source[pos + 2];
target[i + 3] = source[pos + 3];
return target;
};
})();