前端vue后端springboot如何实现图片格式转换

大家都知道,我们直接修改图片后缀名实际上图片格式还是原来的格式,并没有正确转换图片格式,那么我们应该怎么去做呢,下面我用代码来教大家

先看效果,喜欢使用的小伙伴也可以收藏一下网站,免费使用;

需要功能完整代码的可以点击网站联系我,API对接里面有本人联系方式:https://office.zjdiante.com/imageTool/imageConvertFormat

网站地址:点透办公-免费图片格式转换

实现思路:前端vue写一下样式上传文件到后端,后端接收文件流;调用安装的 ImageMagick ,使用命名转换一下即可,ImageMagick是一款免费开源的图片处理工具

java后端代码如下:

java 复制代码
package com.ruoyi.basicTool.imageTool.service;

import com.ruoyi.basicTool.imageTool.utils.FfmpegImageConvertUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

/**
 * 图片格式转换:统一走本机 ImageMagick(与开放平台 URL 转换同源)。
 */
@Service
public class ImageConvertFormatService {

    /**
     * 转换图片格式
     *
     * @param file         原始图片文件
     * @param targetFormat 目标格式(小写),须为 {@link FfmpegImageConvertUtils#supportedTargetFormats()} 之一
     */
    public byte[] convertFormat(MultipartFile file, String targetFormat) throws Exception {
        if (file == null || file.isEmpty()) {
            throw new IllegalArgumentException("请上传图片文件");
        }
        String normalized = targetFormat == null ? "" : targetFormat.toLowerCase().trim();
        if (!FfmpegImageConvertUtils.isValidTargetFormat(normalized)) {
            throw new IllegalArgumentException("不支持的目标格式: " + targetFormat);
        }
        return FfmpegImageConvertUtils.convertBytes(file.getBytes(), file.getOriginalFilename(), normalized);
    }

    public boolean isValidFormat(String format) {
        return format != null && FfmpegImageConvertUtils.isValidTargetFormat(format.toLowerCase().trim());
    }

    public String generateFileName(String originalFileName, String targetFormat) {
        if (originalFileName == null || originalFileName.isEmpty()) {
            return "converted_image." + targetFormat;
        }
        int lastDotIndex = originalFileName.lastIndexOf('.');
        String nameWithoutExt = (lastDotIndex > 0)
                ? originalFileName.substring(0, lastDotIndex)
                : originalFileName;
        return nameWithoutExt + "." + targetFormat;
    }

    public MediaType getMediaType(String format) {
        if (format == null) {
            return MediaType.APPLICATION_OCTET_STREAM;
        }
        switch (format.toLowerCase()) {
            case "jpg":
            case "jpeg":
            case "jfif":
                return MediaType.IMAGE_JPEG;
            case "png":
            case "apng":
                return format.equalsIgnoreCase("apng")
                        ? MediaType.parseMediaType("image/apng")
                        : MediaType.IMAGE_PNG;
            case "gif":
                return MediaType.IMAGE_GIF;
            case "webp":
                return MediaType.parseMediaType("image/webp");
            case "bmp":
                return MediaType.parseMediaType("image/bmp");
            case "tif":
            case "tiff":
                return MediaType.parseMediaType("image/tiff");
            case "avif":
                return MediaType.parseMediaType("image/avif");
            case "heic":
                return MediaType.parseMediaType("image/heic");
            case "heif":
                return MediaType.parseMediaType("image/heif");
            case "ico":
                return MediaType.parseMediaType("image/x-icon");
            case "ppm":
                return MediaType.parseMediaType("image/x-portable-pixmap");
            case "pgm":
                return MediaType.parseMediaType("image/x-portable-graymap");
            case "pbm":
                return MediaType.parseMediaType("image/x-portable-bitmap");
            case "pam":
                return MediaType.parseMediaType("image/x-portable-arbitrarymap");
            case "tga":
                return MediaType.parseMediaType("image/x-tga");
            case "xbm":
                return MediaType.parseMediaType("image/x-xbitmap");
            case "xpm":
                return MediaType.parseMediaType("image/x-xpixmap");
            case "jp2":
            case "j2k":
                return MediaType.parseMediaType("image/jp2");
            case "pcx":
                return MediaType.parseMediaType("image/x-pcx");
            case "sgi":
                return MediaType.parseMediaType("image/sgi");
            case "sun":
                return MediaType.parseMediaType("image/x-sun-raster");
            case "wbmp":
                return MediaType.parseMediaType("image/vnd.wap.wbmp");
            case "fits":
            case "fts":
                return MediaType.parseMediaType("image/fits");
            case "miff":
                return MediaType.parseMediaType("image/x-miff");
            case "psd":
                return MediaType.parseMediaType("image/vnd.adobe.photoshop");
            case "pdf":
                return MediaType.parseMediaType("application/pdf");
            case "dds":
                return MediaType.parseMediaType("image/vnd.ms-dds");
            case "svg":
                return MediaType.parseMediaType("image/svg+xml");
            case "eps":
                return MediaType.parseMediaType("application/postscript");
            case "ai":
                return MediaType.parseMediaType("application/postscript");
            case "ps":
                return MediaType.parseMediaType("application/postscript");
            case "jxl":
                return MediaType.parseMediaType("image/jxl");
            case "exr":
                return MediaType.parseMediaType("image/x-exr");
            case "dpx":
                return MediaType.parseMediaType("image/x-dpx");
            case "jpe":
                return MediaType.IMAGE_JPEG;
            case "pict":
                return MediaType.parseMediaType("image/x-pict");
            case "dcm":
                return MediaType.parseMediaType("application/dicom");
            default:
                return MediaType.APPLICATION_OCTET_STREAM;
        }
    }
}
java 复制代码
package com.ruoyi.basicTool.imageTool.utils;



import org.apache.commons.lang3.StringUtils;



import java.io.IOException;

import java.nio.file.Files;

import java.nio.file.Path;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.HashSet;

import java.util.List;

import java.util.Locale;

import java.util.Set;



/**

 * 使用本机 ImageMagick({@code magick})做静态图格式互转(与站内 {@code /app/image/convertFormat} 一致)。

 * <p>目标格式取决于当前 ImageMagick 策略与已启用的编码器;HEIC/HEIF 等需对应 delegate。若某格式未编译进 ImageMagick,执行会失败。</p>

 */

public final class FfmpegImageConvertUtils {



    /**
     * 允许的目标格式(小写,不含点)。
     * <p>jpeg/jpe 与 jpg 等价(输出统一 .jpg)。相机 RAW、视频容器等不作为通用位图导出目标,未列入。</p>
     * <p>具体能否写出取决于本机 ImageMagick 编译项与策略;未启用的编码器会转换失败。</p>
     */
    private static final Set<String> TARGET_FORMATS = new HashSet<>(Arrays.asList(
            /* 一、日常与通用(jpeg/jpe 请求会规范为 jpg) */
            "jpg", "jfif", "png", "gif", "webp", "bmp", "tif", "tiff",
            "heic", "heif", "ico", "svg", "pdf", "psd", "eps", "ai",
            /* 二、Windows / 系统 */
            "cur", "emf", "wmf", "pcx", "pbm", "pgm", "ppm", "pam", "xbm", "xpm",
            /* 三、Web / 现代编码 */
            "apng", "jxl", "avif",
            /* 五、印刷 / 设计 */
            "ps", "dds", "exr", "dpx", "hdr", "pict", "tga",
            /* 六、医学 / 专业容器 */
            "dcm", "miff", "mpc", "fits", "fts",
            /* 七、动图相关 */
            "mng", "jng",
            /* 八、其它(ImageMagick 常见可写后缀;未列全则由策略决定) */
            "jp2", "j2k", "j2c", "ras", "rgb", "rgba", "sgi", "sun", "wbmp", "wpg", "xwd",
            "yuv", "ycbcr", "gray", "cals", "cin", "cut", "dib", "djvu", "hrz", "jbig",
            "otb", "p7", "mtv", "palm", "tpic", "uyvy", "vicar", "viff"
    ));



    /** 允许的源文件后缀(小写,带点):常见位图 + 相机 RAW 等(由 ImageMagick 解码)。 */

    private static final Set<String> SOURCE_EXTENSIONS = new HashSet<>(Arrays.asList(
            ".jpg", ".jpeg", ".jfif", ".jpe", ".png", ".apng", ".webp", ".gif", ".bmp",
            ".tif", ".tiff", ".avif", ".heic", ".heif", ".jp2", ".j2k", ".j2c",
            ".ppm", ".pgm", ".pbm", ".pam", ".tga", ".pcx", ".xwd", ".sgi", ".sun",
            ".ico", ".xbm", ".xpm", ".svg", ".pdf", ".psd", ".eps", ".ai",
            ".cr2", ".cr3", ".nef", ".nrw", ".arw", ".orf", ".rw2", ".raf", ".dng",
            ".pef", ".srw", ".sr2", ".kdc", ".mrw", ".3fr", ".raw"
    ));



    private FfmpegImageConvertUtils() {

    }



    /** 对外文档/校验:支持的目标格式列表(小写)。 */

    public static Set<String> supportedTargetFormats() {

        return Collections.unmodifiableSet(TARGET_FORMATS);

    }



    public static boolean isValidTargetFormat(String format) {

        if (format == null) {

            return false;

        }

        String t = format.trim().toLowerCase(Locale.ROOT);

        if ("jpeg".equals(t) || "jpe".equals(t)) {

            t = "jpg";

        }

        return TARGET_FORMATS.contains(t);

    }



    public static boolean isAllowedSourceFilename(String filename) {

        if (StringUtils.isBlank(filename)) {

            return true;

        }

        String ext = suffixOf(filename);

        if (ext.isEmpty()) {

            return true;

        }

        return SOURCE_EXTENSIONS.contains(ext);

    }



    /**

     * @param inputBytes       原图字节

     * @param originalFilename 原始文件名(决定解码器线索)

     * @param targetFormat     目标格式,如 png、jpg、webp(小写)

     */

    public static byte[] convertBytes(byte[] inputBytes, String originalFilename, String targetFormat)

            throws Exception {

        if (inputBytes == null || inputBytes.length == 0) {

            throw new IllegalArgumentException("图片数据为空");

        }

        String tf = targetFormat == null ? "" : targetFormat.trim().toLowerCase(Locale.ROOT);

        if ("jpeg".equals(tf) || "jpe".equals(tf)) {

            tf = "jpg";

        }

        if (!TARGET_FORMATS.contains(tf)) {

            throw new IllegalArgumentException("不支持的目标格式: " + targetFormat);

        }

        String inName = StringUtils.isNotBlank(originalFilename) ? originalFilename : "image.jpg";

        if (!isAllowedSourceFilename(inName)) {

            throw new IllegalArgumentException("不支持的源图片格式,请使用常见图片后缀(如 JPG/PNG/WebP/TIFF/AVIF/HEIC 等)");

        }

        String inExt = suffixOf(inName);

        if (inExt.isEmpty()) {

            inExt = ".jpg";

        }

        String outExt = tempFileSuffixForOutput(tf);



        Path tempIn = null;

        Path tempOut = null;

        try {

            tempIn = Files.createTempFile("imc_cv_in_", inExt);

            Files.write(tempIn, inputBytes);

            tempOut = Files.createTempFile("imc_cv_out_", outExt);

            List<String> cmd = buildConvertCommand(tempIn, tempOut, outExt);

            ImageMagickRunUtils.runMagick(tempOut, cmd);

            return Files.readAllBytes(tempOut);

        } finally {

            deleteQuietly(tempIn);

            deleteQuietly(tempOut);

        }

    }



    /**

     * 临时输出文件后缀。JFIF 与 JPEG 等价,统一用 .jpg。

     */

    private static String tempFileSuffixForOutput(String tf) {

        if ("jpg".equals(tf) || "jpeg".equals(tf) || "jfif".equals(tf) || "jpe".equals(tf)) {

            return ".jpg";

        }

        if ("j2k".equals(tf)) {

            return ".jp2";

        }

        return "." + tf;

    }



    private static List<String> buildConvertCommand(Path in, Path out, String outExt) {

        String lower = outExt.toLowerCase(Locale.ROOT);

        List<String> c = new ArrayList<>();

        c.add(ImageMagickRunUtils.magickBinary());

        c.add(in.toAbsolutePath().toString());



        if (".jpg".equals(lower) || ".jpeg".equals(lower) || ".jfif".equals(lower) || ".jpe".equals(lower)) {

            c.add("-quality");

            c.add("92");

        } else if (".png".equals(lower)) {

            c.add("-define");

            c.add("png:compression-level=9");

        } else if (".apng".equals(lower)) {

            c.add("-define");

            c.add("png:compression-level=9");

        } else if (".webp".equals(lower)) {

            c.add("-quality");

            c.add("92");

        } else if (".jxl".equals(lower)) {

            c.add("-quality");

            c.add("90");

        } else if (".heic".equals(lower) || ".heif".equals(lower)) {

            c.add("-quality");

            c.add("85");

        } else if (".tif".equals(lower) || ".tiff".equals(lower)) {

            c.add("-compress");

            c.add("Zip");

        } else if (".jp2".equals(lower) || ".j2k".equals(lower) || ".j2c".equals(lower)) {

            c.add("-quality");

            c.add("80");

        } else if (".psd".equals(lower)) {

            c.add("-compress");

            c.add("Zip");

        }



        c.add(out.toAbsolutePath().toString());

        return c;

    }



    private static void deleteQuietly(Path p) {

        if (p == null) {

            return;

        }

        try {

            Files.deleteIfExists(p);

        } catch (IOException ignored) {

            // ignore

        }

    }



    static String suffixOf(String filename) {

        int i = filename.lastIndexOf('.');

        return i >= 0 ? filename.substring(i).toLowerCase(Locale.ROOT) : "";

    }

}
相关推荐
代码煮茶1 小时前
Vue3 项目规范实战 | ESLint+Prettier+Git Hooks 搭建前端代码规范体系
前端·javascript·vue.js
米丘1 小时前
新一代代码格式化工具 Oxfmt/Oxlint
前端·rust·前端工程化
韭菜炒大葱1 小时前
讲讲 浏览器的缓存机制
前端·面试·浏览器
AI砖家1 小时前
DeepSeek TUI 保姆级安装配置全指南 -Windows||macOS双平台全覆盖
服务器·前端·人工智能·windows·macos·ai编程·策略模式
Apache0121 小时前
chrome调试打开,让AI来操作浏览器
前端·chrome
lbaihao1 小时前
LLVM Cpu0 调用规则解析
开发语言·前端·python·llvm
hexu_blog2 小时前
前端vue 后端springboot如何实现图片去水印
前端·javascript·vue.js
whuhewei2 小时前
React搜索框组件
前端·javascript·react.js
姓王者2 小时前
Cloudflare Pages自定义依赖安装实践 | 姓王者的博客
前端