cropImage16x9.ts 3.39 KB
import type { ComponentPublicInstance } from "vue";

/** 较小分辨率 + 二次 compressImage,适配常见 php.ini 默认 2M 上限 */
const DEST_W = 640;
const DEST_H = 360;

/**
 * 将图片按中心裁剪为 16:9,输出约 640×360 JPEG(仅接受横向原图:宽大于高)。
 * - H5:canvas + Blob URL(供 uni.uploadFile)
 * - 微信小程序:依赖页面内 canvas-id="crop16" 与传入的组件实例
 * - 其他端:原图返回
 */
export function cropImageTo16x9(
  src: string,
  mpInstance?: ComponentPublicInstance | null
): Promise<string> {
  return new Promise((resolve, reject) => {
    uni.getImageInfo({
      src,
      success: (info) => {
        const iw = info.width;
        const ih = info.height;
        if (iw <= ih) {
          reject(new Error("请上传横向照片(宽需大于高),竖图或方图无法使用"));
          return;
        }
        const ar = 16 / 9;
        let sx = 0;
        let sy = 0;
        let sw = iw;
        let sh = ih;
        if (iw / ih > ar) {
          sw = Math.round(ih * ar);
          sx = Math.round((iw - sw) / 2);
        } else {
          sh = Math.round(iw / ar);
          sy = Math.round((ih - sh) / 2);
        }

        const plat = String(
          (import.meta as unknown as { env?: { UNI_PLATFORM?: string } }).env
            ?.UNI_PLATFORM ?? ""
        );
        if (plat === "h5" || plat === "web") {
          cropH5(src, sx, sy, sw, sh).then(resolve).catch(reject);
          return;
        }
        if (plat === "mp-weixin" && mpInstance) {
          cropMpWx(src, sx, sy, sw, sh, mpInstance).then(resolve).catch(reject);
          return;
        }
        resolve(src);
      },
      fail: (e) => reject(e),
    });
  });
}

function cropH5(
  src: string,
  sx: number,
  sy: number,
  sw: number,
  sh: number
): Promise<string> {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      try {
        const canvas = document.createElement("canvas");
        canvas.width = DEST_W;
        canvas.height = DEST_H;
        const ctx = canvas.getContext("2d");
        if (!ctx) {
          reject(new Error("canvas"));
          return;
        }
        ctx.drawImage(img, sx, sy, sw, sh, 0, 0, DEST_W, DEST_H);
        canvas.toBlob(
          (blob) => {
            if (!blob) {
              reject(new Error("toBlob"));
              return;
            }
            resolve(URL.createObjectURL(blob));
          },
          "image/jpeg",
          0.68
        );
      } catch (e) {
        reject(e);
      }
    };
    img.onerror = () => reject(new Error("图片加载失败"));
    img.src = src;
  });
}

function cropMpWx(
  src: string,
  sx: number,
  sy: number,
  sw: number,
  sh: number,
  instance: ComponentPublicInstance
): Promise<string> {
  return new Promise((resolve, reject) => {
    const ctx = uni.createCanvasContext("crop16", instance as any);
    ctx.drawImage(src, sx, sy, sw, sh, 0, 0, DEST_W, DEST_H);
    ctx.draw(false, () => {
      setTimeout(() => {
        uni.canvasToTempFilePath(
          {
            canvasId: "crop16",
            destWidth: DEST_W,
            destHeight: DEST_H,
            fileType: "jpg",
            quality: 0.68,
            success: (r) => resolve(r.tempFilePath),
            fail: (e) => reject(e),
          },
          instance as any
        );
      }, 150);
    });
  });
}