zoukankan      html  css  js  c++  java
  • 【福利】乳摇动画初探

    咳,以探索技术的精神进行一些猥琐的实现,先说明,如果你只想看最后乳摇的结果那就请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点,完成液化

    最终给一个成果图

    因为时间缘故只设置了左胸的摇动触发,触发代码如下

    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就不放出来了……一些幅度,方向都很简单,而且是写死的,如果你有兴趣可以更多的去优化和添加功能~

  • 相关阅读:
    Vue中data数据,使用v-model属性绑定第三方插件(例如Jquery的日期插件)无法自动更新
    Mybatis的XML文件调用静态方法
    将博客搬至CSDN
    深入理解Java:类加载机制及反射
    JDBC中Statement与PreparedStatement的区别
    响应实体类
    MD5加密
    idea的注入和自动编译配置
    mybatis三剑客之插件---MyBatis plugins
    通过git从码云克隆项目到本地
  • 原文地址:https://www.cnblogs.com/constructor/p/4752969.html
Copyright © 2011-2022 走看看