Чтобы подражать Bash type -P cmd
, мы можем использовать POSIX-совместимый env -i type cmd 1>/dev/null 2>&1
.
man env
# "The option '-i' causes env to completely ignore the environment it inherits."
# In other words, there are no aliases or functions to be looked up by the type command.
ls() { echo 'Hello, world!'; }
ls
type ls
env -i type ls
cmd=ls
cmd=lsx
env -i type $cmd 1>/dev/null 2>&1 || { echo "$cmd not found"; exit 1; }
. Один из способов сделать это - использовать четырехугольные преобразования. Они отличаются от 3D-преобразований и позволяют рисовать на холсте, если вы хотите экспортировать результат.
Пример, показанный здесь, упрощен и использует базовые субдивизины и «читы» для рендеринга сам, то есть он рисует в небольшом квадрате вместо формы разделенной ячейки, но из-за небольшого размера и перекрытия мы можем избежать этого во многих случаях без крайностей.
Правильный способ состоял бы в том, чтобы разделить фигуру на два треугольника, затем сканировать пиксель в растровом изображении адресата, сопоставить точку от треугольника назначения до исходного треугольника. Если значение позиции было дробным, вы использовали бы это для определения интерполяции пикселей (f.ex. двухлинейный 2x2 или би-кубический 4x4).
Я не собираюсь покрывать все это в этом ответе, поскольку это быстро станет недоступным для формата SO, но этот метод, вероятно, будет подходящим в этом случае, если вам не нужно его анимировать (это недостаточно для этого, если вы хотите высокого разрешения).
Давайте начнем с начальной четырехсторонней формы:
[/g10]
Первым шагом является интерполяция Y-позиций на каждом баре C1 -C4 и C2-C3. Нам нужна текущая позиция, а также следующая позиция. Для этого мы будем использовать линейную интерполяцию («lerp»), используя нормированное значение для t
:
y1current = lerp( C1, C4, y / height)
y2current = lerp( C2, C3, y / height)
y1next = lerp(C1, C4, (y + step) / height)
y2next = lerp(C2, C3, (y + step) / height)
Это дает нам новую строку между и вдоль внешних вертикальных баров.
[/g11]
Далее нам нужны позиции X на этой строке, как текущей, так и следующей. Это даст нам четыре позиции, которые мы будем заполнять текущим пикселем, либо как-есть, либо интерполировать его (здесь не показано):
p1 = lerp(y1current, y2current, x / width)
p2 = lerp(y1current, y2current, (x + step) / width)
p3 = lerp(y1next, y2next, (x + step) / width)
p4 = lerp(y1next, y2next, x / width)
x
и y
будет позицией в источнике изображение с использованием целых значений.
[/g12]
Мы можем использовать эту настройку внутри цикла, который будет перебирать каждый пиксель в исходном растровом изображении.
Демо можно найти в нижней части ответа. Переместите круговые ручки вокруг, чтобы преобразовать и воспроизвести с помощью значения шага, чтобы увидеть его влияние на производительность и результат.
Демонстрация будет иметь муар и другие артефакты, но, как упоминалось ранее,
Снимок из демонстрации:
[/g13]
Вы также можете использовать WebGL или Three.js для настройки 3D-среды и рендеринга на холст. Вот ссылка на последнее решение:
и пример того, как использовать текстурированную поверхность :
Используя это подход позволит вам экспортировать результат на холст или изображение, но для производительности требуется GPU на клиенте.
Если вам не нужно экспортировать или манипулировать результатом, я бы предложил для использования простого CSS 3D-преобразования, как показано в других ответах.
/* Quadrilateral Transform - (c) Ken Nilsen, CC3.0-Attr */
var img = new Image(); img.onload = go;
img.src = "https://i.imgur.com/EWoZkZm.jpg";
function go() {
var me = this,
stepEl = document.querySelector("input"),
stepTxt = document.querySelector("span"),
c = document.querySelector("canvas"),
ctx = c.getContext("2d"),
corners = [
{x: 100, y: 20}, // ul
{x: 520, y: 20}, // ur
{x: 520, y: 380}, // br
{x: 100, y: 380} // bl
],
radius = 10, cPoint, timer, // for mouse handling
step = 4; // resolution
update();
// render image to quad using current settings
function render() {
var p1, p2, p3, p4, y1c, y2c, y1n, y2n,
w = img.width - 1, // -1 to give room for the "next" points
h = img.height - 1;
ctx.clearRect(0, 0, c.width, c.height);
for(y = 0; y < h; y += step) {
for(x = 0; x < w; x += step) {
y1c = lerp(corners[0], corners[3], y / h);
y2c = lerp(corners[1], corners[2], y / h);
y1n = lerp(corners[0], corners[3], (y + step) / h);
y2n = lerp(corners[1], corners[2], (y + step) / h);
// corners of the new sub-divided cell p1 (ul) -> p2 (ur) -> p3 (br) -> p4 (bl)
p1 = lerp(y1c, y2c, x / w);
p2 = lerp(y1c, y2c, (x + step) / w);
p3 = lerp(y1n, y2n, (x + step) / w);
p4 = lerp(y1n, y2n, x / w);
ctx.drawImage(img, x, y, step, step, p1.x, p1.y, // get most coverage for w/h:
Math.ceil(Math.max(step, Math.abs(p2.x - p1.x), Math.abs(p4.x - p3.x))) + 1,
Math.ceil(Math.max(step, Math.abs(p1.y - p4.y), Math.abs(p2.y - p3.y))) + 1)
}
}
}
function lerp(p1, p2, t) {
return {
x: p1.x + (p2.x - p1.x) * t,
y: p1.y + (p2.y - p1.y) * t}
}
/* Stuff for demo: -----------------*/
function drawCorners() {
ctx.strokeStyle = "#09f";
ctx.lineWidth = 2;
ctx.beginPath();
// border
for(var i = 0, p; p = corners[i++];) ctx[i ? "lineTo" : "moveTo"](p.x, p.y);
ctx.closePath();
// circular handles
for(i = 0; p = corners[i++];) {
ctx.moveTo(p.x + radius, p.y);
ctx.arc(p.x, p.y, radius, 0, 6.28);
}
ctx.stroke()
}
function getXY(e) {
var r = c.getBoundingClientRect();
return {x: e.clientX - r.left, y: e.clientY - r.top}
}
function inCircle(p, pos) {
var dx = pos.x - p.x,
dy = pos.y - p.y;
return dx*dx + dy*dy <= radius * radius
}
// handle mouse
c.onmousedown = function(e) {
var pos = getXY(e);
for(var i = 0, p; p = corners[i++];) {if (inCircle(p, pos)) {cPoint = p; break}}
}
window.onmousemove = function(e) {
if (cPoint) {
var pos = getXY(e);
cPoint.x = pos.x; cPoint.y = pos.y;
cancelAnimationFrame(timer);
timer = requestAnimationFrame(update.bind(me))
}
}
window.onmouseup = function() {cPoint = null}
stepEl.oninput = function() {
stepTxt.innerHTML = (step = Math.pow(2, +this.value));
update();
}
function update() {render(); drawCorners()}
}
body {margin:20px;font:16px sans-serif}
canvas {border:1px solid #000;margin-top:10px}
<label>Step: <input type=range min=0 max=5 value=2></label><span>4</span><br>
<canvas width=620 height=400></canvas>
Это решение зависит от браузера, выполняющего композицию.
Затем используйте свойство CSS transform
для применения любого преобразования перспективы к элементу наложения.
Чтобы найти матрицу преобразования, вы можете использовать ответ из: Как сопоставить 3D-перспективу реальной фотографии и объекта в 3D3-преобразованиях CSS3
Вы можете использовать CSS Transforms, чтобы ваше изображение выглядело как это поле. Например:
img {
margin: 50px;
transform: perspective(500px) rotateY(20deg) rotateX(20deg);
}
<img src="http://lorempixel.com/400/200/">
Подробнее о CSS Transforms на MDN .