【问题标题】:How to resize then crop an image with canvas如何调整大小然后用画布裁剪图像
【发布时间】:2014-11-18 20:44:03
【问题描述】:

我已经知道怎么做

-> 调整图像大小:

var image = document.getElementById('myImage'),
    canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');
ctx.drawImage(image,0,0,400,300);

-> 或裁剪图像:

var image = document.getElementById('myImage'),
    canvas = document.createElement('canvas'),
    ctx = canvas.getContext('2d');
ctx.drawImage(image,50,50,image.width,image.height,0,0,50,50);

但我不知道如何调整大小然后裁剪图像。我怎么能这样做?谢谢。

【问题讨论】:

    标签: javascript html5-canvas


    【解决方案1】:

    来自documentation,这些是drawImage的参数:

    drawImage(image,
    
        sx, sy, sw, sh,
        dx, dy, dw, dh);
    

    因此,从源图像中裁剪外部 10 个像素(假设它是 100 * 50),然后将其缩放到 160*60

    ctx.drawImage(image,
        10, 10,   // Start at 10 pixels from the left and the top of the image (crop),
        80, 30,   // "Get" a `80 * 30` (w * h) area from the source image (crop),
        0, 0,     // Place the result at 0, 0 in the canvas,
        160, 60); // With as width / height: 160 * 60 (scale)
    

    例子:

    const image = new Image(),
          canvas = document.getElementById('canvas'),
          ctx = canvas.getContext('2d');
    
    image.src = 'https://i.stack.imgur.com/I4jXc.png';
    
    image.addEventListener('load', () => {
        ctx.drawImage(image,
            70, 20,   // Start at 70/20 pixels from the left and the top of the image (crop),
            50, 50,   // "Get" a `50 * 50` (w * h) area from the source image (crop),
            0, 0,     // Place the result at 0, 0 in the canvas,
            100, 100); // With as width / height: 100 * 100 (scale)
    });
    Image: <br/>
    <img src="https://i.stack.imgur.com/I4jXc.png" /><br/>
    Canvas: <br/>
    <canvas id="canvas" width="275px" height="95px"></canvas>

    【讨论】:

    • 非常感谢您的回答。顺便说一句,我有一个小问题。裁剪后如何将O 画布调整为 20x20 大小?
    • @Orion:实际的画布(如在 HTML 元素中)?还是图片本身?
    • 我的意思是图像本身。如何调整它的大小?
    • 您将大小设置为drawImage 调用中的最后两个参数(在我的最后一个示例中,100, 100 是所需的宽度/高度)。
    • 您按照文档的说明进行了解释。很好的例子!
    【解决方案2】:

    在画布上绘制之前,使用 canvas.width 和 canvas.height 指定最终的画布尺寸,否则最终将始终为 300x150

    【讨论】:

    • 请注意,不应使用 CSS 来调整画布的大小,因为这会拉伸画布。按照 Jim 的建议,使用 html 属性正确设置画布的实际大小。
    【解决方案3】:

    我以本教程为例 http://tympanus.net/codrops/2014/10/30/resizing-cropping-images-canvas/ 并在 vanilla js 中做了同样的事情。它可能需要一些重构,但它正在工作(至少在我的 chrome 中的 windows labtop 上)

    一个html文件:

    <!DOCTYPE html>
    <html lang="en">
     <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }
        /* styles for resize container and image */
        .resize-container {
            position: relative;
            display: inline-block;
            cursor: move;
            margin: 0 auto;
            top: 0;
            left: 0;
        }
    
        .resize-container img {
            display: block
        }
    
        .resize-container:hover img,
        .resize-container:active img {
            outline: 2px dashed rgba(222,60,80,.9);
        }
        /* styles for resize handles */
        .resize-handle-ne,
        .resize-handle-se,
        .resize-handle-nw,
        .resize-handle-sw {
            position: absolute;
            display: block;
            width: 10px;
            height: 10px;
            background: rgba(222, 60, 80, .9);
            z-index: 999;
        }
    
        .resize-handle-nw {
            top: -5px;
            left: -5px;
            cursor: nw-resize;
        }
    
        .resize-handle-sw {
            bottom: -5px;
            left: -5px;
            cursor: sw-resize;
        }
    
        .resize-handle-ne {
            top: -5px;
            right: -5px;
            cursor: ne-resize;
        }
    
        .resize-handle-se {
            bottom: -5px;
            right: -5px;
            cursor: se-resize;
        }
        .overlay {
            position: absolute;
            left: 50%;
            top: 50%;
            margin-left: -100px;
            margin-top: -100px;
            z-index: 999;
            width: 200px;
            height: 200px;
            border: solid 2px rgba(222,60,80,.9);
            box-sizing: content-box;
            pointer-events: none;
        }
    
        .overlay:after,
        .overlay:before {
            content: '';
            position: absolute;
            display: block;
            width: 204px;
            height: 40px;
            border-left: dashed 2px rgba(222,60,80,.9);
            border-right: dashed 2px rgba(222,60,80,.9);
        }
    
        .overlay:before {
            top: 0;
            margin-left: -2px;
            margin-top: -40px;
        }
    
        .overlay:after {
            bottom: 0;
            margin-left: -2px;
            margin-bottom: -40px;
        }
    
        .overlay-inner:after,
        .overlay-inner:before {
            content: '';
            position: absolute;
            display: block;
            width: 40px;
            height: 204px;
            border-top: dashed 2px rgba(222,60,80,.9);
            border-bottom: dashed 2px rgba(222,60,80,.9);
        }
    
        .overlay-inner:before {
            left: 0;
            margin-left: -40px;
            margin-top: -2px;
        }
    
        .overlay-inner:after {
            right: 0;
            margin-right: -40px;
            margin-top: -2px;
        }
    
        .btn-crop {
            position: absolute;
            vertical-align: bottom;
            right: 5px;
            bottom: 5px;
            padding: 6px 10px;
            z-index: 999;
            background-color: rgb(222,60,80);
            border: none;
            border-radius: 5px;
            color: #FFF;
        }
    
        #result {
            position: absolute;
            top: 0;
            left: 0;
            width: 100vw;
            height: 150vh;
            z-index: -1;
            display: flex;
            align-items: flex-end;
        }
      </style>
     </head>
      <body>
     <div id="resize-container" class="resize-container">
        <span class="resize-handle resize-handle-nw"></span>
        <span class="resize-handle resize-handle-ne"></span>
        <span class="resize-handle resize-handle-sw"></span>
        <span class="resize-handle resize-handle-se"></span>
        <img id="img" class="resize-image" src="image.png" alt="Image" />
    </div>
    <div id="overlay" class="overlay">
        <div class="overlay-inner">
        </div>
    </div>
    <button id="js-crop" class="btn-crop">Crop</button>
    <div id="result"></div>
    
    <script>
        const resizeableImage = image_target => {
            // defining the variables 
            let constrain = false
    
            const overlay = document.getElementById('overlay')
            const cropBtn = document.getElementById('js-crop')
            const container = document.getElementById('resize-container')
            const orig_src = new Image()
            const event_state = {}
            const min_width = 60
            const min_height = 60
            const max_width = 800
            const max_height = 900
            
            const resize_canvas = document.createElement('canvas')
    
            const resizeImage = (width, height) => {
                resize_canvas.width = width 
                resize_canvas.height = height 
                resize_canvas.getContext('2d').drawImage(orig_src, 0, 0, width, height)
                image_target.src = resize_canvas.toDataURL("image/png")
            }
    
            // the resizing function is where most of the action happens. This function is contansly invoked 
            // while the user is dragging one of the resize handles. Every time this function is called we
            // work out the new with and height by taking the current position of the mouse relative to the
            // initial position of the corner we are dragging
            const resizing = e => {
                let width, height, left, top 
    
                const rec = container.getBoundingClientRect()
                const offset = {
                    top: rec.top + window.scrollY,
                    left: rec.left + window.scrollX
                }
                const mouse = {
                    x: (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + window.screenLeft,
                    y: (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + window.screenTop
                }
    
                const eTargetClass = event_state.evnt.target.classList[1]
                // if statements for being able to resize from each corner
                if (eTargetClass === 'resize-handle-se') { // south-east (bottom-right)
                    width = mouse.x - event_state.container_left
                    height = mouse.y  - event_state.container_top
                    left = event_state.container_left
                    top = event_state.container_top
                } else if (eTargetClass === 'resize-handle-sw') { // south-west (bottom-left)
                    width = event_state.container_width - (mouse.x - event_state.container_left)
                    height = mouse.y  - event_state.container_top
                    left = mouse.x
                    top = event_state.container_top
                } else if (eTargetClass === 'resize-handle-ne') { // north-east (top-right)
                    width = mouse.x - event_state.container_left
                    height = event_state.container_height - (mouse.y - event_state.container_top)
                    left = event_state.container_left
                    top = mouse.y
    
                    if(constrain || e.shiftKey){
                        top = mouse.y - ((width / orig_src.width * orig_src.height) - height)
                    }
                } else if (eTargetClass === 'resize-handle-nw') { // north-west (top-left)
                    width = event_state.container_width - (mouse.x - event_state.container_left)
                    height = event_state.container_height - (mouse.y - event_state.container_top)
                    left = mouse.x
                    top = mouse.y
    
                    if(constrain || e.shiftKey){
                        top = mouse.y - ((width / orig_src.width * orig_src.height) - height)
                    }
                }
    
    
                if (constrain || e.shiftKey) {
                    height = width / orig_src.width * orig_src.height 
                }
    
                if (width > min_width && height > min_height && width < max_width && height < max_height) {
                    container.style.top = `${top}px`
                    container.style.left = `${left}px`
                    resizeImage(width, height)
                }
            }
    
            // before we start tracking the mouse position we want to take
            // a snapshot of the container dimensions and other key data
            // points. We store these in a variable named event_state and
            // use them later as a point of reference while resizing to
            // work out the change in height and width
            const saveEventState = e => {
                event_state.container_width = container.getBoundingClientRect().width 
                event_state.container_height = container.getBoundingClientRect().height 
                event_state.container_left = container.offsetLeft
                event_state.container_top = container.offsetTop 
                // mouse coordinates
                event_state.mouse_x = (e.clientX || e.pageX || e.originalEvent.touches[0].clientX) + window.screenLeft,
                event_state.mouse_y = (e.clientY || e.pageY || e.originalEvent.touches[0].clientY) + window.screenTop,
    
                event_state.evnt = e
            }
    
            // this two functions do very little other that tell the 
            // browser to start paying attention to where the mouse
            // is moving and when to stop paying attention
            const startResize = e => {
                e.preventDefault()
                e.stopPropagation()
                saveEventState(e)
                
                document.addEventListener('mousemove', resizing)
                document.addEventListener('mouseup', endResize)
            }
    
            const endResize = e => {
                e.preventDefault()
                //  The touchend event fires when one or more touch points are
                // removed from the touch surface.
                document.removeEventListener('mousemove', resizing)
                document.removeEventListener('touchend', resizing)
                document.removeEventListener('mouseup', endResize)
                document.removeEventListener('touchmove', endResize)
            }
    
            // in this function we need to work out the new position of the top left edge of the 
            // container. This will be equal to the current position of the mouse, offset by the
            // distance the mouse was from the top left corner when we started dragging the image
            const moving = e => {
                const mouse = {
                    x: (e.clientX || e.pageX) + window.screenLeft,
                    y: (e.clientY || e.pageY) + window.screenTop
                }
    
                container.style.left = `${mouse.x - (event_state.mouse_y - event_state.container_top)}px`
                container.style.top = `${mouse.y - (event_state.mouse_y - event_state.container_top)}px`
            }
    
            // function for moving the image around
            const startMoving = e => {
                e.preventDefault()
                e.stopPropagation()
                saveEventState(e)
    
                document.addEventListener('mousemove', moving)
                document.addEventListener('mouseup', endMoving)
            }
    
            const endMoving = e => {
                e.preventDefault()
    
                document.removeEventListener('mouseup', endMoving)
                document.removeEventListener('mousemove', moving)
            }
    
            const crop = e => {
                const left = overlay.offsetLeft - container.offsetLeft 
                const top = overlay.offsetTop - container.offsetTop 
                const width = overlay.getBoundingClientRect().width
                const height = overlay.getBoundingClientRect().height
    
                const crop_canvas = document.createElement('canvas')
                crop_canvas.width = width 
                crop_canvas.height = height 
    
                crop_canvas.getContext('2d').drawImage(image_target, left, top, width, height, 0, 0, width, height)
    
                const croppedImage = document.createElement('img')
                croppedImage.src = crop_canvas.toDataURL("image/png")
                document.querySelector('#result').appendChild(croppedImage)
            }
    
            // this function is called immediately and makes a copy of the
            // original image used for resizing
            const init = () => {
                // create a new image with a copy of the original src
                // When resizing, we will always use this original copy
                // as the base
                orig_src.src = image_target.src
    
                // add events
                container.addEventListener('mousedown', startResize)
                image_target.addEventListener('mousedown', startMoving)
                cropBtn.addEventListener('click', crop)
            }
    
            init()
        }
    
        // initializing the canvas and the target image
        const image = document.getElementById('img')
        resizeableImage(image)
    </script>
    

    【讨论】: