Html实现图片上传/裁剪/马赛克/压缩/旋转/缩放

发布于:2025-06-13 ⋅ 阅读:(24) ⋅ 点赞:(0)

cropper下载   https://download.csdn.net/download/dongyan3595/90970115

前端代码

<!doctype html>
<html lang="en">
<head>
    <base href="/aishop/">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta charset="UTF-8"/>
    <link rel="stylesheet" type="text/css" href="assets/fonts/font-awesome/css/font-awesome.css"/>
    <link rel="stylesheet" type="text/css" href="assets/js/vendor/bootstrap/css/bootstrap.min.css"/>
    <link rel="stylesheet" type="text/css" href="assets/js/vendor/cropper/cropper.min.css"/>
    <style>
        .upload-image {height: 100%; opacity: 0;}
        .upload-image-preview {width: 200px;height: 200px;overflow: hidden;margin: 0 44px;display: inline-block;}
        #imageBox {height: 451px;overflow: auto;background-color: #F1F1F1;}
        #imagePreviewBox .upload-image-preview {background-color: #F1F1F1;}
        .cropper-container {
            height:432px!important;
            width: 529px!important;
        }
        .cropper-crop-box{
            max-height:430px!important;
            max-width:527px!important;
        }
        .mosaic-canvas {
            position: absolute;
            top: 0;
            left: 0;
            pointer-events: auto; /* 改为auto,允许鼠标事件 */
            z-index: 1000;
        }

        .mosaic-active {
            cursor: crosshair !important;
        }
    </style>
</head>
<div>
    <div id="content" class="edit-content" style="padding: 5px; opacity: 0;">
        <div id="imageBox" class="col-xs-8" style="position: relative;">
            <img id="image" class="upload-image" src="assets/images/ruanzhu.jpg">
            <canvas id="mosaicCanvas" class="mosaic-canvas" style="display:none;"></canvas>
        </div>

        <div id="imagePreviewBox" class="col-xs-4">
            <div class="upload-image-preview"></div>
        </div>
        <div class="col-xs-4" style="text-align: center;">
            <button id="dragBtn" type="button" class="btn btn-danger fa fa-arrows" onclick="dragImage()" title="移动"></button>
            <button type="button" class="btn btn-danger fa fa-search-plus" onclick="zoomPlus()" title="放大图片"></button>
            <button type="button" class="btn btn-danger fa fa-search-minus" onclick="zoomMinus()" title="缩小图片"></button>
            <button type="button" class="btn btn-danger fa fa-refresh" onclick="reset()" title="重置图片"></button>
            <button id="mosaicBtn" type="button" class="btn btn-warning fa fa-th" onclick="toggleMosaic()" title="马赛克"></button>
            <button type="button" class="btn btn-info fa fa-reply" onclick="undoEdit()" title="撤销" id="undoBtn" disabled></button>
        </div>
        <div class="col-xs-4" style="margin-top: 5px; text-align: center;">
            <div class="btn-group">
                <button class="btn btn-danger fa fa-undo" onclick="rateLeft()" type="button" title="向左旋转90°"> 向左旋转
                </button>
            </div>
            <div class="btn-group">
                <button class="btn  btn-danger fa fa-repeat" onclick="rateRight()" type="button" title="向右旋转90°"> 向右旋转
                </button>
            </div>
        </div>
        <div class="col-xs-4" style="margin-top: 5px; text-align: center;">
            <div class="btn-group">
                <form id="uploadFileForm" action="#" style="display: none;">
                    <input type="file" id="uploadImage" name="image" accept="image/jpeg,image/gif,image/png,image/jpg" onchange="doUploadFile()"/>
                </form>
                <button class="btn btn-primary btn-block fa fa-upload" type="button" onclick="openUploadImage()"> 上传图片
                </button>
            </div>
            <div class="btn-group">
                <button class="btn btn-primary btn-block fa fa-save" type="button" onclick="doCrop()"> 裁剪上传</button>
            </div>
        </div>
        <div class="col-xs-12" style="margin-top: 5px; text-align: center;">
            <div class="form-group">
                <label for="mosaicSize">马赛克大小:</label>
                <input type="range" id="mosaicSize" min="5" max="30" value="10" class="form-control">
                <span id="mosaicSizeValue">10px</span>
            </div>
        </div>
        <div class="col-xs-12" style="margin-top: 5px; text-align: center;">
            <div class="form-group">
                <label for="imageSize">图片压缩:</label>
                <input type="range" id="imageSize" min="10" max="100" value="100" class="form-control">
                <span id="imageSizeValue">100%</span>
            </div>
        </div>
    </div>
</div>
<script type="text/javascript" src="assets/js/jquery-3.5.1.min.js"></script>
<script type="text/javascript" src="assets/js/vendor/bootstrap/js/bootstrap.min.js"></script>
<script type="text/javascript" src="assets/js/vendor/cropper/cropper.min.js"></script>
<script type="text/javascript" src="assets/js/common.js"></script>
<script src="assets/layuiadmin/layui/layui.js"></script>
<script type="text/javascript">
    var toggleMosaic = function() {}
    var undoEdit = function() {}
    var doCrop = function() {}
    var zoomPlus= function() {}
    var zoomMinus= function() {}
    var reset= function() {}
    var rateLeft= function() {}
    var rateRight= function() {}
    var dragImage= function() {}

    var openUploadImage= function() {}
    var  doUploadFile= function() {}
    layui.config({
        base: 'assets/layuiadmin/'
    }).extend({
        index: 'lib/index'
    }).use(['index', 'restajax', 'datamessage', 'dialog'], function() {
        var restAjax = layui.restajax;
        var dialog = layui.dialog;
        var dataMessage = layui.datamessage;

        function doSubmitForm() {
            return false;
        }

        var cropper;

        var isMosaicMode = false;
        var mosaicCanvas = document.getElementById('mosaicCanvas');
        var mosaicCtx = mosaicCanvas.getContext('2d');
        var isDrawing = false;
        var lastX, lastY;
        var editHistory = [];
        var currentHistoryIndex = -1;
        var maxHistorySteps = 10;


        // 放大
        zoomPlus = function () {
            cropper.zoom(0.1);
        }

        // 缩小
        zoomMinus = function () {
            cropper.zoom(-0.1);

        }

        // 逆时针90
        rateLeft = function () {
            cropper.rotate(-90);
        }

        // 顺时针90
        rateRight = function () {
            cropper.rotate(90);
        }

        // 重置
        reset = function () {
            cropper.reset();
        }

        // 图片移动
        var isDrag = false;

        dragImage = function () {
            if (!isDrag) {
                cropper.setDragMode('move');
                isDrag = true;
                $('#dragBtn').addClass('btn-default');
                $('#dragBtn').removeClass('btn-danger');
            } else {
                cropper.setDragMode('crop');
                isDrag = false;
                $('#dragBtn').addClass('btn-danger');
                $('#dragBtn').removeClass('btn-default');
            }
        }

        // 切换马赛克模式
        toggleMosaic = function() {
            isMosaicMode = !isMosaicMode;

            if (isMosaicMode) {
                // 进入马赛克模式
                $('#mosaicBtn').addClass('btn-success');
                $('#mosaicBtn').removeClass('btn-warning');

                // 保存当前状态到历史记录
                saveToHistory();

                // 显示马赛克画布
                setupMosaicCanvas();
                $('#mosaicCanvas').show();

                // 重要:将画布设置为可接收鼠标事件
                mosaicCanvas.style.pointerEvents = 'auto';

                // 添加鼠标事件
                $(mosaicCanvas).on('mousedown', startMosaic);
                $(document).on('mousemove', drawMosaic);
                $(document).on('mouseup mouseleave', stopMosaic);

                // 添加触摸事件
                $(mosaicCanvas).on('touchstart', handleTouchStart);
                $(document).on('touchmove', handleTouchMove);
                $(document).on('touchend touchcancel', handleTouchEnd);

                // 添加马赛克模式样式
                $('#imageBox').addClass('mosaic-active');
            } else {
                // 退出马赛克模式
                $('#mosaicBtn').addClass('btn-warning');
                $('#mosaicBtn').removeClass('btn-success');

                // 重要:将画布设置为不可接收鼠标事件
                mosaicCanvas.style.pointerEvents = 'none';

                // 移除事件监听
                $(mosaicCanvas).off('mousedown');
                $(document).off('mousemove', drawMosaic);
                $(document).off('mouseup mouseleave', stopMosaic);
                $(mosaicCanvas).off('touchstart');
                $(document).off('touchmove', handleTouchMove);
                $(document).off('touchend touchcancel', handleTouchEnd);

                // 移除马赛克模式样式
                $('#imageBox').removeClass('mosaic-active');
            }
        }


        // 设置马赛克画布
        function setupMosaicCanvas() {
            var cropperCanvas = $('.cropper-canvas')[0];
            var cropperImage = $('.cropper-canvas img')[0];

            if (cropperCanvas && cropperImage) {
                var containerRect = cropperCanvas.getBoundingClientRect();
                var imageBoxRect = $('#imageBox')[0].getBoundingClientRect();

                // 设置画布尺寸
                mosaicCanvas.width = containerRect.width;
                mosaicCanvas.height = containerRect.height;
                mosaicCanvas.style.width = containerRect.width + 'px';
                mosaicCanvas.style.height = containerRect.height + 'px';

                // 设置画布位置 - 相对于imageBox计算偏移
                mosaicCanvas.style.position = 'absolute';
                mosaicCanvas.style.top = (containerRect.top - imageBoxRect.top) + 'px';
                mosaicCanvas.style.left = (containerRect.left - imageBoxRect.left) + 'px';

                // 清除画布
                mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
            }
        }



        // 开始绘制马赛克
        function startMosaic(e) {
            e.preventDefault(); // 阻止默认行为
            isDrawing = true;
            var rect = mosaicCanvas.getBoundingClientRect();
            lastX = e.clientX - rect.left;
            lastY = e.clientY - rect.top;

            // 立即开始绘制一个点
            drawMosaicEffect(lastX, lastY, lastX, lastY);
        }


        // 处理触摸开始事件
        function handleTouchStart(e) {
            e.preventDefault();
            var touch = e.originalEvent.touches[0];
            var rect = mosaicCanvas.getBoundingClientRect();
            lastX = touch.clientX - rect.left;
            lastY = touch.clientY - rect.top;
            isDrawing = true;
        }

        // 处理触摸移动事件
        function handleTouchMove(e) {
            if (!isDrawing || !isMosaicMode) return;
            e.preventDefault();

            var touch = e.originalEvent.touches[0];
            var rect = mosaicCanvas.getBoundingClientRect();
            var x = touch.clientX - rect.left;
            var y = touch.clientY - rect.top;

            drawMosaicEffect(lastX, lastY, x, y);

            lastX = x;
            lastY = y;
        }

        // 处理触摸结束事件
        function handleTouchEnd(e) {
            isDrawing = false;
        }

        // 绘制马赛克
        function drawMosaic(e) {
            if (!isDrawing || !isMosaicMode) return;

            var rect = mosaicCanvas.getBoundingClientRect();
            var x = e.clientX - rect.left;
            var y = e.clientY - rect.top;

            drawMosaicEffect(lastX, lastY, x, y);

            lastX = x;
            lastY = y;
        }

        // 绘制马赛克效果
        function drawMosaicEffect(startX, startY, endX, endY) {
            var cropperImage = $('.cropper-canvas img')[0];
            if (!cropperImage) return;

            var mosaicSize = parseInt($('#mosaicSize').val());
            var dx = endX - startX;
            var dy = endY - startY;
            var distance = Math.sqrt(dx * dx + dy * dy);
            var steps = Math.max(Math.floor(distance), 1);

            for (var i = 0; i <= steps; i++) {
                var x = startX + (dx * i / steps);
                var y = startY + (dy * i / steps);

                // 计算马赛克块的左上角坐标
                var mosaicX = Math.floor(x / mosaicSize) * mosaicSize;
                var mosaicY = Math.floor(y / mosaicSize) * mosaicSize;

                // 从原始图像获取像素数据
                var cropperRect = cropperImage.getBoundingClientRect();
                var cropperCanvas = document.createElement('canvas');
                cropperCanvas.width = mosaicSize;
                cropperCanvas.height = mosaicSize;
                var cropperCtx = cropperCanvas.getContext('2d');

                try {
                    // 获取马赛克区域的图像数据
                    cropperCtx.drawImage(
                        cropperImage,
                        mosaicX, mosaicY, mosaicSize, mosaicSize,
                        0, 0, mosaicSize, mosaicSize
                    );

                    // 计算平均颜色
                    var imageData = cropperCtx.getImageData(0, 0, mosaicSize, mosaicSize);
                    var data = imageData.data;
                    var r = 0, g = 0, b = 0, a = 0, count = 0;

                    for (var j = 0; j < data.length; j += 4) {
                        r += data[j];
                        g += data[j + 1];
                        b += data[j + 2];
                        a += data[j + 3];
                        count++;
                    }

                    // 计算平均值
                    r = Math.round(r / count);
                    g = Math.round(g / count);
                    b = Math.round(b / count);
                    a = Math.round(a / count);

                    // 绘制马赛克块
                    mosaicCtx.fillStyle = `rgba(${r}, ${g}, ${b}, ${a / 255})`;
                    mosaicCtx.fillRect(mosaicX, mosaicY, mosaicSize, mosaicSize);
                } catch (error) {
                    console.error('马赛克绘制错误:', error);
                }
            }
        }

        // 停止绘制马赛克
        function stopMosaic() {
            if (isDrawing) {
                isDrawing = false;
                saveToHistory();
            }
        }

        // 保存到历史记录
        function saveToHistory() {
            // 如果当前不是最新状态,删除后面的历史
            if (currentHistoryIndex >= 0 && currentHistoryIndex < editHistory.length - 1) {
                editHistory = editHistory.slice(0, currentHistoryIndex + 1);
            }

            // 获取当前画布状态
            var imageData = null;
            if (mosaicCanvas.width > 0 && mosaicCanvas.height > 0) {
                try {
                    imageData = mosaicCtx.getImageData(0, 0, mosaicCanvas.width, mosaicCanvas.height);
                } catch (e) {
                    console.error('获取画布数据失败:', e);
                }
            }

            // 添加到历史记录
            if (imageData) {
                editHistory.push(imageData);

                // 限制历史记录数量
                if (editHistory.length > maxHistorySteps) {
                    editHistory.shift();
                }

                currentHistoryIndex = editHistory.length - 1;

                // 启用撤销按钮
                $('#undoBtn').prop('disabled', false);
            }
        }

        // 撤销编辑
        undoEdit = function () {
            if (currentHistoryIndex > 0) {
                currentHistoryIndex--;
                var imageData = editHistory[currentHistoryIndex];

                // 恢复到之前的状态
                mosaicCtx.putImageData(imageData, 0, 0);
            } else if (currentHistoryIndex === 0) {
                // 恢复到初始状态(清空画布)
                mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
                currentHistoryIndex = -1;
                $('#undoBtn').prop('disabled', true);
            }
        }

        // 重置马赛克画布和历史记录
        function resetMosaicCanvas() {
            if (mosaicCanvas) {
                mosaicCtx.clearRect(0, 0, mosaicCanvas.width, mosaicCanvas.height);
            }
            editHistory = [];
            currentHistoryIndex = -1;
            $('#undoBtn').prop('disabled', true);
        }


        // 打开上传图片
        openUploadImage = function () {
            $('#uploadImage').click();
        }

        // 添加键盘快捷键支持
        $(document).on('keydown', function(e) {
            // 检测 CTRL + Z
            if (e.ctrlKey && e.keyCode === 90) {
                // 阻止浏览器默认的撤销行为
                e.preventDefault();
                // 调用撤销函数
                undoEdit();
            }
        });

        // 上传文件
        doUploadFile = function () {
            var loadLayerIndex;
            var formData = new FormData($('#uploadFileForm')[0]);
            restAjax.postFile('api/goods/upload-image', formData, {}, function (code, data) {
                dialog.msg('上传成功');
                cropper.replace('route/file/download/false/' + data.data, false);
                localStorage.setItem('uploadImage', data.data);
                // 重置马赛克画布和历史记录
                resetMosaicCanvas();
            }, function(code, data) {
                dialog.msg(data.msg);
            }, function() {
                loadLayerIndex = dialog.msg(dataMessage.uploading, {icon: 16, time: 0, shade: 0.3});
            }, function() {
                dialog.close(loadLayerIndex);
            });
        }

        // 裁剪
        doCrop = function () {
            // 获取裁剪后的画布
            var croppedCanvas = cropper.getCroppedCanvas();

            // 如果有马赛克效果,将马赛克合并到裁剪后的图像上
            if (mosaicCanvas.width > 0 && mosaicCanvas.height > 0) {
                var cropperCanvas = $('.cropper-canvas')[0];
                var cropBox = $('.cropper-crop-box')[0];

                if (cropperCanvas && cropBox) {
                    var cropBoxRect = cropBox.getBoundingClientRect();
                    var cropperRect = cropperCanvas.getBoundingClientRect();

                    // 计算裁剪框相对于画布的位置
                    var left = cropBoxRect.left - cropperRect.left;
                    var top = cropBoxRect.top - cropperRect.top;

                    // 获取裁剪框的尺寸
                    var width = cropBoxRect.width;
                    var height = cropBoxRect.height;

                    // 在裁剪后的画布上绘制马赛克
                    var ctx = croppedCanvas.getContext('2d');
                    ctx.drawImage(
                        mosaicCanvas,
                        left, top, width, height,
                        0, 0, croppedCanvas.width, croppedCanvas.height
                    );
                }
            }

            croppedCanvas.toBlob(function (cropBlob) {
                var formData = new FormData();
                formData.append("file", cropBlob);
                formData.append("picturesThumbnails", $('#imageSize').val());
                var loadLayerIndex;
                restAjax.postFile('api/goods/upload-image', formData, {}, function (code, data) {
                    dialog.msg('裁剪成功');

                    // 完全重置马赛克画布和历史记录
                    resetMosaicCanvas();
                    // 替换图像
                    cropper.replace('route/file/download/false/' + data.data, false);
                    localStorage.setItem('uploadImage', data.data);

                    // 在图像加载完成后重新设置马赛克画布
                    setTimeout(function() {
                        setupMosaicCanvas();
                    }, 500);
                }, function(code, data) {
                    dialog.msg(data.msg);
                }, function() {
                    loadLayerIndex = dialog.msg(dataMessage.uploading, {icon: 16, time: 0, shade: 0.3});
                }, function() {
                    dialog.close(loadLayerIndex);
                });
            });
        }

        $(function () {
            var image = document.getElementById('image');
            cropper = new Cropper(image, {
                aspectRatio: 1.5 / 2,
                viewMode: 1,
                minContainerHeight: 430,
                maxContainerHeight: 430,
                preview: '.upload-image-preview',
                crop(event) {
                    // 如果在马赛克模式下,实时更新马赛克画布位置
                    if (isMosaicMode) {
                        setupMosaicCanvas();
                    }
                },
                ready() {
                    // 初始化马赛克画布
                    setTimeout(function() {
                        setupMosaicCanvas();
                    }, 500);
                }
            });

            $('#content').fadeTo(1000, 1);
            var fileId = restAjax.params(window.location.href).fileId;
            if (fileId != 'undefined' && fileId != undefined && fileId.length > 1) {
                localStorage.setItem('uploadImage', fileId);
                cropper.replace('route/file/download/false/' + fileId, false);
            }

            // 马赛克大小滑块事件
            $('#mosaicSize').on('input', function() {
                var size = $(this).val();
                $('#mosaicSizeValue').text(size + 'px');
            });

            $('#imageSize').on('input', function() {
                var size = $(this).val();
                $('#imageSizeValue').text(size + '%');
            });

            // 确保马赛克画布初始化
            setTimeout(function() {
                setupMosaicCanvas();
            }, 1000); // 延迟1秒,确保cropper已完全初始化
        })
    });
</script>
</body>
</html>

后台代码

    @PostMapping({"upload-image"})
    public SuccessResultData<String> uploadFile(@RequestParam("file") MultipartFile file, Double picturesThumbnails) throws IOException {
        // 参数校验
        if (file.isEmpty()) {
            throw new SaveException("上传文件不能为空");
        }
        InputStream inputStream = file.getInputStream();
        BufferedImage bufferedImage = ImageIO.read(inputStream);
        if (bufferedImage == null) {
            throw new SaveException("无效的图片文件");
        }
        BufferedImage image = bufferedImage;
        if (picturesThumbnails != null) {
            try {
                image = Thumbnails.of(new BufferedImage[]{bufferedImage}).scale(1).outputQuality(picturesThumbnails / 100).outputFormat("jpg").asBufferedImage();
            } catch (IOException e) {
                throw new SaveException("图片压缩出现异常");
            }
        }
        MultipartFile file1 = new MockMultipartFile(
                "file",            // 参数名(表单中的name)
                UUIDUtil.get32UUID() + ".jpg",        // 原始文件名
                "image/jpeg",
                convertToInputStream(image, "jpg")        // 文件输入流
        );
        Map<String, Object> params1 = new HashMap<>();
        String fileId1 = iFileService.uploadSingleByUserId(securityComponent.getCurrentUser().getUserId(), file1, UploadTypeEnum.IMAGE, params1);
        return new SuccessResultData(fileId1);
    }