在画布中捏合/折叠图像

aki*_*uri 5 javascript geometry canvas distortion

如何在画布中捏/皱图像的某些区域?

前段时间我做了一个太阳系动画,我开始重写它。现在,我想为质量添加重力效果。为了使效果可见,我将背景变成了一个网格,我将对其进行修改。

想要的效果是这样的(PS制作)

在此处输入图片说明

在此处输入图片说明


context.background("rgb(120,130,145)");
context.grid(25, "rgba(255,255,255,.1)");

var sun = {
    fill        : "rgb(220,210,120)",
    radius      : 30,
    boundingBox : 30*2 + 3*2,
    position    : {
        x       : 200,
        y       : 200,
    },
};
sun.img = saveToImage(sun);

context.drawImage(sun.img, sun.position.x - sun.boundingBox/2, sun.position.y - sun.boundingBox/2);
Run Code Online (Sandbox Code Playgroud)

js小提琴


更新:我已经做了一些谷歌搜索并找到了一些资源,但由于我以前从未进行过像素操作,因此无法将它们放在一起。

在 HTML5 Canvas 中使用双线性过滤的像素失真 | Splashnology.com(仅限功能)

glfx.js(带有演示的 WebGL 库)

JSFiddle球面化、缩放、旋转示例)

我想,倒置形式的球形效果对这项工作有好处。

aki*_*uri 1

我有时间重新审视这个问题并提出了解决方案。首先,我需要了解计算和像素操作背后的数学原理,而不是直接解决问题。

因此,我决定使用particles. JavaScript 对象是我更熟悉的东西,因此它很容易操作。

我不会尝试解释该方法,因为我认为它是不言自明的,并且我试图使其尽可能简单。

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

canvas.width  = 400;
canvas.height = 400;

var particles = [];

function Particle() {
    this.position = {
        actual : {
            x : 0,
            y : 0
        },
        affected : {
            x : 0,
            y : 0
        },
    };
}

// space between particles
var gridSize = 25;

var columns  = canvas.width / gridSize;
var rows     = canvas.height / gridSize;

// create grid using particles
for (var i = 0; i < rows+1; i++) {
    for (var j = 0; j < canvas.width; j += 2) {
        var p = new Particle();
        p.position.actual.x = j;
        p.position.actual.y = i * gridSize;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}
for (var i = 0; i < columns+1; i++) {
    for (var j = 0; j < canvas.height; j += 2) {
        var p = new Particle();
        p.position.actual.x = i * gridSize;
        p.position.actual.y = j;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}

// track mouse coordinates as it is the source of mass/gravity
var mouse = {
    x : -100,
    y : -100,
};

var effectRadius = 75;
var effectStrength = 50;

function draw() {
    context.clearRect(0, 0, canvas.width, canvas.height);
    
    particles.forEach(function (particle) {
        // move the particle to its original position
        particle.position.affected = Object.create(particle.position.actual);
        
        // calculate the effect area
        var a = mouse.y - particle.position.actual.y;
        var b = mouse.x - particle.position.actual.x;
        var dist = Math.sqrt(a*a + b*b);
        
        // check if the particle is in the affected area
        if (dist < effectRadius) {
            
            // angle of the mouse relative to the particle
            var a = angle(particle.position.actual.x, particle.position.actual.y, mouse.x, mouse.y);
            
            // pull is stronger on the closest particle
            var strength = dist.map(0, effectRadius, effectStrength, 0);
            
            if (strength > dist) {
                strength = dist;
            }
            
            // new position for the particle that's affected by gravity
            var p = pos(particle.position.actual.x, particle.position.actual.y, a, strength);
            
            particle.position.affected.x = p.x;
            particle.position.affected.y = p.y;
        }
        
        context.beginPath();
        context.rect(particle.position.affected.x -1, particle.position.affected.y -1, 2, 2);
        context.fill();
    });
}

draw();

window.addEventListener("mousemove", function (e) {
    mouse.x = e.x - canvas.offsetLeft;
    mouse.y = e.y - canvas.offsetTop;
    requestAnimationFrame(draw);
});

function angle(originX, originY, targetX, targetY) {
    var dx = targetX - originX;
    var dy = targetY - originY;
    var theta = Math.atan2(dy, dx) * (180 / Math.PI);
    if (theta < 0) theta = 360 + theta;
    return theta;
}

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
    return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
};

function pos(x, y, angle, length) {
    angle *= Math.PI / 180;
    return {
        x : Math.round(x + length * Math.cos(angle)),
        y : Math.round(y + length * Math.sin(angle)),
    };
}
Run Code Online (Sandbox Code Playgroud)
* {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
  line-height: inherit;
  font-size: inherit;
  font-family: inherit;
}

body {
  font-family: sans-serif;
  box-sizing: border-box;
  background-color: hsl(0, 0%, 90%);
}

canvas {
  display: block;
  background: white;
  box-shadow: 0 0 2px rgba(0, 0, 0, .2), 0 1px 1px rgba(0, 0, 0, .1);
  margin: 20px auto;
}

canvas:hover {
  cursor: none;
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

我可能会尝试在其他时间创建旋转效果,并将它们移至 WebGL 中以获得更好的性能。


更新:

现在,我正在研究旋转效果,并且已经在一定程度上发挥了作用。

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

canvas.width  = 400;
canvas.height = 400;

var particles = [];

function Particle() {
    this.position = {
        actual : {
            x : 0,
            y : 0
        },
        affected : {
            x : 0,
            y : 0
        },
    };
}

// space between particles
var gridSize = 25;

var columns  = canvas.width / gridSize;
var rows     = canvas.height / gridSize;

// create grid using particles
for (var i = 0; i < rows+1; i++) {
    for (var j = 0; j < canvas.width; j += 2) {
        var p = new Particle();
        p.position.actual.x = j;
        p.position.actual.y = i * gridSize;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}
for (var i = 0; i < columns+1; i++) {
    for (var j = 0; j < canvas.height; j += 2) {
        var p = new Particle();
        p.position.actual.x = i * gridSize;
        p.position.actual.y = j;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}

// track mouse coordinates as it is the source of mass/gravity
var mouse = {
    x : -100,
    y : -100,
};

var effectRadius = 75;
var twirlAngle   = 90;

function draw(e) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    
    particles.forEach(function (particle) {
        // move the particle to its original position
        particle.position.affected = Object.create(particle.position.actual);
        
        // calculate the effect area
        var a = mouse.y - particle.position.actual.y;
        var b = mouse.x - particle.position.actual.x;
        var dist = Math.sqrt(a*a + b*b);
        
        // check if the particle is in the affected area
        if (dist < effectRadius) {
            
            // angle of the particle relative to the mouse
            var a = angle(mouse.x, mouse.y, particle.position.actual.x, particle.position.actual.y);
            
            var strength = dist.map(0, effectRadius, twirlAngle, 0);
            
            // twirl
            a += strength;
            
            // new position for the particle that's affected by gravity
            var p = rotate(a, dist, mouse.x, mouse.y);
            
            particle.position.affected.x = p.x;
            particle.position.affected.y = p.y;
        }
        
        context.beginPath();
        context.rect(particle.position.affected.x -1, particle.position.affected.y -1, 2, 2);
        context.fillStyle = "black";
        context.fill();
    });
}

draw();

window.addEventListener("mousemove", function (e) {
    mouse.x = e.x - canvas.offsetLeft;
    mouse.y = e.y - canvas.offsetTop;
    requestAnimationFrame(draw);
});

function angle(originX, originY, targetX, targetY) {
    var dx = targetX - originX;
    var dy = targetY - originY;
    var theta = Math.atan2(dy, dx) * (180 / Math.PI);
    if (theta < 0) theta = 360 + theta;
    return theta;
}

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
    return (this - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;
};

function pos(x, y, angle, length) {
    angle *= Math.PI / 180;
    return {
        x : Math.round(x + length * Math.cos(angle)),
        y : Math.round(y + length * Math.sin(angle)),
    };
}

function rotate(angle, distance, originX, originY) {
    return {
        x : originX + Math.cos(angle * Math.PI/180) * distance,
        y : originY + Math.sin(angle * Math.PI/180) * distance,
    }
}
Run Code Online (Sandbox Code Playgroud)
* {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
  line-height: inherit;
  font-size: inherit;
  font-family: inherit;
}

body {
  font-family: sans-serif;
  box-sizing: border-box;
  background-color: hsl(0, 0%, 90%);
}

canvas {
  display: block;
  background: white;
  box-shadow: 0 0 2px rgba(0, 0, 0, .2), 0 1px 1px rgba(0, 0, 0, .1);
  margin: 20px auto;
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)

旋转强度的映射存在一个小问题。我使用了与捏合效果相同的函数map,但我认为旋转不使用线性映射,而是使用缓和映射。比较 JS 版本和 PS 滤镜。PS滤镜更平滑。我需要重写这个map函数。

在此输入图像描述

更新2:

我已经设法让它像 PS 滤镜一样工作。使用缓动函数,即easeOutQuad解决了该问题。享受 :)

var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");

canvas.width  = 400;
canvas.height = 400;

var particles = [];

function Particle() {
    this.position = {
        actual : {
            x : 0,
            y : 0
        },
        affected : {
            x : 0,
            y : 0
        },
    };
}

// space between particles
var gridSize = 25;

var columns  = canvas.width / gridSize;
var rows     = canvas.height / gridSize;

// create grid using particles
for (var i = 0; i < rows+1; i++) {
    for (var j = 0; j < canvas.width; j+=2) {
        var p = new Particle();
        p.position.actual.x = j;
        p.position.actual.y = i * gridSize;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}
for (var i = 0; i < columns+1; i++) {
    for (var j = 0; j < canvas.height; j+=2) {
        var p = new Particle();
        p.position.actual.x = i * gridSize;
        p.position.actual.y = j;
        p.position.affected = Object.create(p.position.actual);
        particles.push(p);
    }
}

// track mouse coordinates as it is the source of mass/gravity
var mouse = {
    x : -100,
    y : -100,
};

var effectRadius = 75;
var twirlAngle   = 90;

function draw(e) {
    context.clearRect(0, 0, canvas.width, canvas.height);
    
    particles.forEach(function (particle) {
        // move the particle to its original position
        particle.position.affected = Object.create(particle.position.actual);
        
        // calculate the effect area
        var a = mouse.y - particle.position.actual.y;
        var b = mouse.x - particle.position.actual.x;
        var dist = Math.sqrt(a*a + b*b);
        
        // check if the particle is in the affected area
        if (dist < effectRadius) {
            
            // angle of the particle relative to the mouse
            var a = angle(mouse.x, mouse.y, particle.position.actual.x, particle.position.actual.y);
            
            var strength = twirlAngle - easeOutQuad(dist, 0, twirlAngle, effectRadius);
            
            // twirl
            a += strength;
            
            // new position for the particle that's affected by gravity
            var p = rotate(a, dist, mouse.x, mouse.y);
            
            particle.position.affected.x = p.x;
            particle.position.affected.y = p.y;
        }
        
        context.beginPath();
        context.rect(particle.position.affected.x-1, particle.position.affected.y-1, 2, 2);
        context.fillStyle = "black";
        context.fill();
    });
}

draw();

window.addEventListener("mousemove", function (e) {
    mouse.x = e.x - canvas.offsetLeft;
    mouse.y = e.y - canvas.offsetTop;
    requestAnimationFrame(draw);
});

function easeOutQuad(t, b, c, d) {
    t /= d;
    return -c * t*(t-2) + b;
};

function angle(originX, originY, targetX, targetY) {
    var dx = targetX - originX;
    var dy = targetY - originY;
    var theta = Math.atan2(dy, dx) * (180 / Math.PI);
    if (theta < 0) theta = 360 + theta;
    return theta;
}

Number.prototype.map = function (in_min, in_max, out_min, out_max) {
    return (this - in_min) / (in_max - in_min) * (out_max - out_min) + out_min;
};

function pos(x, y, angle, length) {
    angle *= Math.PI / 180;
    return {
        x : Math.round(x + length * Math.cos(angle)),
        y : Math.round(y + length * Math.sin(angle)),
    };
}

function rotate(angle, distance, originX, originY) {
    return {
        x : originX + Math.cos(angle * Math.PI/180) * distance,
        y : originY + Math.sin(angle * Math.PI/180) * distance,
    }
}
Run Code Online (Sandbox Code Playgroud)
* {
  margin: 0;
  padding: 0;
  box-sizing: inherit;
  line-height: inherit;
  font-size: inherit;
  font-family: inherit;
}

body {
  font-family: sans-serif;
  box-sizing: border-box;
  background-color: hsl(0, 0%, 90%);
}

canvas {
  display: block;
  background: white;
  box-shadow: 0 0 2px rgba(0, 0, 0, .2), 0 1px 1px rgba(0, 0, 0, .1);
  margin: 20px auto;
}
Run Code Online (Sandbox Code Playgroud)
<canvas id="canvas"></canvas>
Run Code Online (Sandbox Code Playgroud)