2017. 3. 21.

[HTML] call and control the image

1. HTML5 canvas with SVG (don't have rotation function)

이것저것 검색하다가 찾은 코드인데, 좌표값을 SVG로 계산하기 때문에 회전하면 캔버스까지 같이 회전한다. 그래서 회전한 후에 모든 이벤트들이 꼬이는 현상이 발생한다. 따라서 좌표값을 전역변수로 설정해서 계산해야 회전까지 쓸 수 있다.

Canvas is rotated when put rotation function on this code because it calculate the x, y with SVG. So, all events become to not work after rotation. if you want to use this, you have to declare the x, y points variables for global.

var img = new Image();
img.src = "https://cdn.pixabay.com/photo/2017/02/11/14/19/valentines-day-2057745_960_720.jpg";

var canvas = document.getElementById("previewImgCanvas");
var ctx = canvas.getContext("2d");
var scaleFactor = 1.1;
var lastX = canvas.width/2, lastY = canvas.height/2;
var globalWidth = 0, globalHeight = 0;
var dragStart;
ctx.canvas.width = $("#previewImgBox").width();
ctx.canvas.height = $("#previewImgBox").height();
//현재 x, y 좌표에 따라 canvas를 지우고 새로운 이미지 호출
function redraw(){
    var p1 = ctx.transformedPoint(0,0);
    var p2 = ctx.transformedPoint(canvas.width, canvas.height);
    
    ctx.clearRect(p1.x, p1.y, p2.x-p1.x, p2.y-p1.y);
    ctx.drawImage(img, 
            canvas.width/2 - globalWidth/2
            canvas.height/2 - globalHeight/2
            globalWidth == 0 ? img.width : globalWidth, 
            globalHeight == 0 ? img.height : globalHeight
    );
}
//현재 이미지의 비율을 계산하여 text창에 입력시킴
function chkRatioPercent() {
    var percent = parseInt((globalWidth / img.width) * 100);
    
    //원본크기인 경우
    if(percent > 200) {
        percent = 200;
    } else if(percent == 0) {
        percent = 100;
    } 
    
    $("#previewRatioBox").val(percent);
}
//text창에 쓴 비율대로 이미지를 제어함
function setImgForRatio() {
    var ratio = parseInt($("#previewRatioBox").val()) / 100;
    var width = 0, height = 0;
    
    //200% 까지만 이미지 제어 
    if(ratio <= 2) {
        ratio = ratio;
    } else if(ratio == 0) {
        $("#previewRatioBox").val(100);
        ratio = 1;
    } else {
        $("#previewRatioBox").val(200);
        ratio = 2;
    }
    
    width = img.width * ratio;
    height = img.height * ratio;
    globalWidth = width;
    globalHeight = height;
    
    ctx.save();
    ctx.setTransform(100100);
    ctx.clearRect(00, img.width, img.height);
    ctx.drawImage(img, 00, img.width, img.height, 00, width, height);
    
    redraw();
}
//이미지 비율을 캔버스 너비에 맞춤
function setImgForCanvas() {
    var ratio = 0;
    var width = 0;
    var height = 0;
    
    //세로가 긴 광고는 캔버스 높이에 맞추고, 그 반대는 가로에 맞춤.
    if(img.width < img.height) {
        ratio = img.height / canvas.height;
        
        if(ratio < 1) {
            width = (ratio * img.width) + (img.width / 2);
        } else {
            width = (img.width / ratio);
        }
        
        globalWidth = width;
        globalHeight = canvas.height;
    } else {
        ratio = img.width / canvas.width;
        
        if(ratio < 1) {
            height = (ratio * img.height) + (img.height / 2);
        } else {
            height = (img.height / ratio);
        }
        
        globalWidth = canvas.width;
        globalHeight = height;
    }
    
    chkRatioPercent();
    
    ctx.setTransform(100100);
    ctx.clearRect(00, img.width, img.height);
    ctx.drawImage(img, 00, img.width, img.height, 00, globalWidth, globalHeight);
    
    redraw();    
}
//clicks = -1 : 축소, clicks = 1 : 확대
function zoom(clicks, ratio){
    var pt = ctx.transformedPoint(lastX, lastY);
    
    if(clicks < 0) {
        width = globalWidth - (globalWidth * (ratio - 1));
        height = globalHeight - (globalHeight * (ratio - 1));
    } else {
        width = globalWidth * ratio;
        height = globalHeight * ratio;
    }
    
    globalWidth = width;
    globalHeight = height;
    
    ctx.save();
    ctx.clearRect(00, img.width, img.height);
    ctx.drawImage(img, 00, img.width, img.height, 00, width, height);
    
    chkRatioPercent();
    redraw();
}
//마우스 스크롤로 확대 & 축소
function handleScroll(evt){
    var delta = evt.wheelDelta ? evt.wheelDelta/40 : evt.detail ? -evt.detail : 0;
    
    if (delta) zoom(delta, scaleFactor);
    return evt.preventDefault() && false;
};
//svg
function trackTransforms(ctx){
    var svg = document.createElementNS("http://www.w3.org/2000/svg",'svg');
    var xform = svg.createSVGMatrix();
    ctx.getTransform = function(){ 
        return xform; 
    };
    
    var savedTransforms = [];
    var save = ctx.save;
    ctx.save = function(){
        savedTransforms.push(xform.translate(0,0));
        return save.call(ctx);
    };
    
    var restore = ctx.restore;
    ctx.restore = function(){
        xform = savedTransforms.pop();
        return restore.call(ctx);
    };
    
    var scale = ctx.scale;
    ctx.scale = function(sx,sy){
        xform = xform.scaleNonUniform(sx,sy);
        return scale.call(ctx,sx,sy);
    };
    
    var translate = ctx.translate;
    ctx.translate = function(dx,dy){
        xform = xform.translate(dx,dy);
        return translate.call(ctx,dx,dy);
    };
    
    var transform = ctx.transform;
    ctx.transform = function(a,b,c,d,e,f){
        var m2 = svg.createSVGMatrix();
        m2.a=a; m2.b=b; m2.c=c; m2.d=d; m2.e=e; m2.f=f;
        xform = xform.multiply(m2);
        return transform.call(ctx,a,b,c,d,e,f);
    };
    
    var setTransform = ctx.setTransform;
    ctx.setTransform = function(a,b,c,d,e,f){
        xform.a = a;
        xform.b = b;
        xform.c = c;
        xform.d = d;
        xform.e = e;
        xform.f = f;
        return setTransform.call(ctx,a,b,c,d,e,f);
    };
    
    var pt  = svg.createSVGPoint();
    ctx.transformedPoint = function(x,y){
        pt.x=x; pt.y=y;
        return pt.matrixTransform(xform.inverse());
    }
}
canvas.addEventListener('mousedown',function(evt){
    document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none';
    
    lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft);
    lastY = evt.offsetY || (evt.pageY - canvas.offsetTop);
    
    //드래그 시작점 좌표
    dragStart = ctx.transformedPoint(lastX, lastY);
},false);
canvas.addEventListener('mousemove',function(evt){
    lastX = evt.offsetX || (evt.pageX - canvas.offsetLeft);
    lastY = evt.offsetY || (evt.pageY - canvas.offsetTop);
    
    if (dragStart){
        var pt = ctx.transformedPoint(lastX, lastY);
        ctx.translate(pt.x-dragStart.x, pt.y-dragStart.y);
        redraw();
    }
},false);
canvas.addEventListener('mouseup',function(evt){
    dragStart = null;
},false);



2. pixi.js (based on webGL)

HTML5 엔진 라이브러리인 pixi.js를 이용해 만든 이미지 제어 코드다. new PIXI.Application로 객체를 생성하면 webGL방식으로 화면에 렌더링하게 된다.

var img = "https://cdn.pixabay.com/photo/2017/02/11/14/19/valentines-day-2057745_960_720.jpg";
var pixiContainer = new PIXI.Sprite(img);
var pixiObj = new PIXI.Application($("#previewImgBox").width(), $("#previewImgBox").height(), { backgroundColor: 13882323 });
$("#previewImgBox02").append(pixiObj.view);
pixiStart();
//이미지 제어 영역 세팅
function pixiStart() {
    pixiContainer.interactive = true;
    pixiContainer.buttonMode = true;
    
    pixiContainer.anchor.set(0.5);
    pixiContainer.x = pixiObj.renderer.width / 2;
    pixiContainer.y = pixiObj.renderer.height / 2;
    
    pixiContainer.on('pointerdown', onDragStart)
    .on('pointerup', onDragEnd)
    .on('pointerupoutside', onDragEnd)
    .on('pointermove', onDragMove);
    
    document.addEventListener("mousewheel", mouseWheelHandler, false);
    pixiObj.stage.addChild(pixiContainer);
}
function chkRatioPercent() {
    var percent = parseInt(pixiContainer.scale.x * 100);
    
    //원본크기인 경우
    if(percent > 200) {
        percent = 200;
    } else if(percent == 0) {
        percent = 100;
    } 
    
    $("#previewRatioBox").val(percent);
}
function setImgForRatio() {    
    var ratio = parseInt($("#previewRatioBox").val()) / 100;
    
    //원본크기의 10% ~ 200% 까지만 제어 
    if(ratio >= 0.1) {
        if(ratio <= 2) {
            ratio = ratio;
        } else if(ratio == 0) {
            $("#previewRatioBox").val(100);
            ratio = 1;
        } else {
            $("#previewRatioBox").val(200);
            ratio = 2;
        }
        pixiContainer.scale.x = ratio;
        pixiContainer.scale.y = ratio;
    } else {
        pixiContainer.scale.x = 0.1;
        pixiContainer.scale.y = 0.1;
    }
}
//이미지를 이미지 영역 너비에 맞춤
function setImgForCanvas() {
    var originalWidth = pixiContainer.texture.width;
    var originalHeight = pixiContainer.texture.height;
    if(originalWidth > originalHeight) {
        pixiContainer.width = $("#previewImgBox").width();
        pixiContainer.scale.y = pixiContainer.scale.x;
    } else {
        pixiContainer.height = $("#previewImgBox").height();
        pixiContainer.scale.x = pixiContainer.scale.y;
    }
    
    chkRatioPercent();
}
//x:event.clientX, y:event.clientY, isZoomin:-1이면 축소, 1이면 확대
function zoom(x, y, isZoomIn) {
    var nowScale = pixiContainer.scale.x;
    var direction = isZoomIn ? 1 : -1;
    var factor = (1 + direction * 0.1);
    
    //원본크기의 200%를 넘어가면 10%로 줄임
    if(nowScale >= 0.1 && nowScale < 2) {
        pixiContainer.scale.x *= factor;
        pixiContainer.scale.y *= factor;
    } else {
        pixiContainer.scale.x = 0.1;
        pixiContainer.scale.y = 0.1;
    }
    
    chkRatioPercent();
}
//회전
function rotateTo(angle) {
    pixiContainer.rotation += angle * Math.PI/180;
}
//스크롤바로 확대 & 축소
function mouseWheelHandler(e) {
    zoom(e.clientX, e.clientY, e.deltaY < 0);
}
function onDragStart(event) {
    if (!this.dragging) {
        this.data = event.data;
        this.dragging = true;
        this.dragPoint = event.data.getLocalPosition(this.parent);
        this.dragPoint.x -= this.x;
        this.dragPoint.y -= this.y;
    }
}
function onDragEnd() {
    if (this.dragging) {
        this.dragging = false;
        
        // set the interaction data to null
        this.data = null;
    }
}
function onDragMove() {
    if(this.dragging) {
        var newPosition = this.data.getLocalPosition(this.parent);
        document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none';
        
        this.x = newPosition.x - this.dragPoint.x;
        this.y = newPosition.y - this.dragPoint.y;
    }
}


3. pixi.js (based on HTML5 canvas)

PIXI.Application이 아니라 CanvasRenderer()로 객체를 생성했다. 그래서 해당 스테이지의 속성이 바뀔 때마다 pixiObj.render(stage)라는 코드 한 줄을 추가하여 rendering해야 한다.


- 이미지를 바꾸고 싶을 때 (to change image)


img = PIXI.Texture.fromImage(url);
stage.removeChild(pixiContainer);
pixiContainer = new PIXI.Sprite(img);
                        
$("#loading").show();
$("#loading_img").show();
                        
loader.reset();
loader.add('urlName', url);
           
loader.load(function(loader, resources) {
    pixiStart();
    pixiObj.render(stage);
                            
    $("#loading").hide();
    $("#loading_img").hide();
});
cs

: img 객체에 바꾸고 싶은 이미지의 url을 넣고, stage에서 만들어두었던 container노드를 제거한다. 그리고 바뀐 url을 넣은 img로 만든 container객체를 재생성한다. 전역에서 만들었던 loader 객체를 비운 후 add함수로 url을 넣는다. 이는 이미지가 load된 후에 이벤트를 주려는 의도로 쓰는 것이다. 참고로 $("#loading")은 개인 프로젝트에서 쓴 로딩바를 의미하며 이미지 로드가 끝나면 숨긴다.

Put changed image url to img object and remove stage object's child, pixiContainer. then re-creating new pixiContainer, put url in loader object that made for global. this process is for setting event after loading image. Plus, $("#loading") is just a progress bar that I use in my project to prevent controlling image before loaded.



- 드래그할 때 다른 요소가 선택되는 문제 (selecting problem when dragging)

1
document.body.style.mozUserSelect = document.body.style.webkitUserSelect = document.body.style.userSelect = 'none';
cs

: 각 브라우저에서 지원하는 selecting style 요소가 모두 다르므로, 모두 체크하여 none으로 표시하면 된다.

댓글 없음:

댓글 쓰기