歡迎光臨
每天分享高質量文章

【福利】乳搖動畫初探

作者:AlloyTeam

網址:http://www.alloyteam.com/2015/08/fu-li-ru-yao-dong-hua-chu-tan/

咳,以探索技術的精神進行一些猥瑣的實現,先說明,如果你只想看最後乳搖的結果那就請ctrl+F4吧,因為網上有那些乳搖的APP,製作出來絕對比我這個初探的方法好,我這個只是介紹我實現乳搖的過程思路與方法。

關於乳搖如何實現,我第一個想法是使用metaball,因為是兩個球嘛,然而發現根本就不行,Fail。最終使用的是液化演演算法去實現。

好了,下麵是對液化演演算法的介紹。

如果你從來不使用ps,暫時想不起來液化是什麼不要緊,請看下圖

這個是採用液化使一隻靜態的小狗有了動的感覺

總而言之,液化是使一張圖片的部分進行平滑的有規律的變化,這個變化有扭曲的平移之感

是如何實現的呢,來看演演算法,先給一張圖

區域性變化以一個圈為變化環境,C點是圓心,r是半徑,當C移動到M這個點時,U移動到X。

透過以上這句話我們得出了這樣的結論:

液化的變化只在半徑為r的圓內發生,距離圓心越近,變化越明顯。這是不是和乳搖這一現象特別的吻合?

下麵是演演算法公式

公式終於實現的是你知道X點的坐標,可以推算出U點的坐標,反之知道U點也可以推算出X的坐標

怎麼推算出來的這個我也不知道,需要結合圓的範圍,進行插值處理,但是具體如何得到這個結論,感興趣的同學可以閱讀

Andreas Gustafsson 的 Interactive Image Warping一文,這個公式就是從這而來

好了,接下來該碼程式了

乳搖首先你得有圖

var sImg = new Image();

sImg.src = ‘./dd.png’;

var leftImage = new Image();

leftImage.src = ‘./dd_left.png’;

var rightImage = new Image();

rightImage.src = ‘./dd_right.png’;

var timer = null;

sImg.onload = function() {

oGC.drawImage(sImg, 0, 0);

};

這裡上來就有三張圖,其中一張是完整的,還有兩張分別是美女左右對半分開的【其實就是左胸和右胸】,因為需要這兩張圖進行乳搖【液化】後的還原。當然你也可以只用一張圖,先取出還原的畫素存起來也是可以的,我在這裡偷懶了

function liquify(imgData, cx, cy, mx, my, r) {

var imgDataBuff = copyImageDataBuff(imgData);

eachCircleDot(imgData, cx, cy, r, function(posi) {

var tx = posi.x,

ty = posi.y;

var u = transFormula(cx, cy, mx, my, tx, ty, r);

moveDot(imgData, imgDataBuff, posi, u);

function transFormula(cx, cy, mx, my, tx, ty, r) {

var relativity = sqr(r) – distanceSqr(tx, ty, cx, cy);

var distanceMovedSqr = distanceSqr(mx, my, cx, cy);

var rate = sqr(relativity / (relativity + distanceMovedSqr));

var ux = (tx – rate*(mx-cx)),

uy = (ty – rate*(my-cy));

return {

x: ux,

y: uy

};

}

});

return imgData;

}

上面是液化演演算法的函式,結合上面的圖來看,引數分別是圖片畫素data,圓心C的x軸和y軸,M點的x軸和y軸,圓的半徑r

copyImageDataBuff是將將要液化部分的畫素copy一份,程式碼如下

function copyImageDataBuff(imgData) {

var data = imgData.data,

imgDataBuff = [];

for(var i in data) {

imgDataBuff[i] = data[i];

}

return imgDataBuff;

}

eachCircleDot是將每個圓內的畫素取出來進行處理

function eachCircleDot(imageData, ox, oy, r, callback) {

var imgWidth = imageData.width,

imgHeight = imageData.height,

data = imageData.data,

left = ox – r,

right = ox + r,

top = oy – r,

bottom = oy + r;

for(var x = left; x < right; x++) {

for(var y = top; y < bottom; y++) {

if(distanceSqr(x,y,ox,oy) <= sqr(r)) {

callback({

x: x,

y: y

});

}

}

}

}

distanceSqr和sqr是求圓心距離和平方的函式,很簡單

function distanceSqr(x1, y1, x2, y2) {

return sqr(x1 – x2) + sqr(y1 – y2);

}

function sqr(x) {

return x * x;

}

transFormula這個方法就是液化公式的使用,傳入的是c點的x,y值、m點的x,y;t點就是上面圖中的x點,return出來u點的x,y值之後傳入moveDot,這個就是液化最終的表現函式

function moveDot(imgData, dataBuff, posi, u) {

var imgWidth = imgData.width,

imgHeight = imgData.height,

data = imgData.data;

u.x = Math.floor(u.x);

u.y = Math.floor(u.y);

data[(posi.y * imgWidth + posi.x) * 4] = dataBuff[(u.y * imgWidth + u.x) * 4];

data[(posi.y * imgWidth + posi.x) * 4 + 1] = dataBuff[(u.y * imgWidth + u.x) * 4 + 1];

data[(posi.y * imgWidth + posi.x) * 4 + 2] = dataBuff[(u.y * imgWidth + u.x) * 4 + 2];

data[(posi.y * imgWidth + posi.x) * 4 + 3] = dataBuff[(u.y * imgWidth + u.x) * 4 + 3];

}

將公式算出的u點rgba資訊換給之前的t點,也就是圖中的x點,完成液化

最終給個結果圖

因為只時間緣故(LPL決賽呢)只設定了左胸的搖動觸發,觸發程式碼如下

var sX = 5;

var sY = 5;

var iX = -200;

var x = -10;

var iY = ev.clientY – oC.offsetTop;

if(iY > 296) {

iY = 200;

y = 10;

} else {

iY = -200;

y = -10;

}

timer = setInterval(function() {

oGC.drawImage(leftImage, 0, 0); // 只做了左半邊的效果

var d = oGC.getImageData(23, 140, 140, 200);

var c = liquify(d, 60, 170, sX + 65, sY + 170, 58);

oGC.putImageData(c, 23, 140);

sX = sX + x;

sY = sY + y;

if(Math.abs(sX) > Math.abs(iX) || Math.abs(sY) > Math.abs(iY)) {

clearInterval(timer);

}

}, 30);

這裡面的數值都是自己測出來的,sX和sY是搖動的頻率,getImageData的xywh四個值也是試出來的,意味著你想要胸變化的範圍,註意:

這個範圍必須要比液化公式中的圓大

iY和y是根據點選在胸上方還是胸下方來確定搖動的方向

liquify傳入的引數已經介紹過了

這次的乳搖還是很初步的,只是優化了速度,最早還有一個版本非常的卡,demo就不放出來了……一些幅度,方向都很簡單,而且是寫死的,如果你有興趣可以更多的去最佳化和新增功能~

贊(0)

分享創造快樂