{"version":3,"file":"index.cjs","names":["SUPPORTED_FORMATS: Format[]"],"sources":["../src/index.ts"],"sourcesContent":["import { IPicGo, IPluginConfig } from 'picgo'\nimport sharp from 'sharp'\n\n// 统一日志封装，优先使用 ctx.log，受 enableLogging 控制\nfunction createLogger(ctx: IPicGo, enable: boolean) {\n    const base = ctx.log || console\n    return {\n        info: (...args: any[]) => enable && base.info?.('[optimization]', ...args),\n        warn: (...args: any[]) => {\n            if (!enable) {\n                return\n            }\n            if (base.warn) {\n                base.warn('[optimization]', ...args)\n            } else if (base.info) {\n                base.info('[optimization][WARN]', ...args)\n            } else {\n                console.warn('[optimization]', ...args)\n            }\n        },\n        error: (...args: any[]) => {\n            if (base.error) {\n                base.error('[optimization]', ...args)\n            } else if (base.info) {\n                base.info('[optimization][ERR]', ...args)\n            } else {\n                console.error('[optimization]', ...args)\n            }\n        }, // error 总是输出\n    }\n}\n\nexport interface IPicGoOutputItem {\n    buffer?: Buffer\n    fileName: string\n    extname?: string\n    [k: string]: any\n}\n\n// 插件用户配置类型\nexport interface OptimizationConfig {\n    format?: string // 若为空或未指定，则保持原格式；填写支持格式则强制转换\n    quality?: number // 质量 1-100\n    maxWidth?: number // 最大宽度\n    maxHeight?: number // 最大高度\n    enableLogging?: boolean // 是否输出详细日志\n    skipIfLarger?: boolean // 如果转换后体积更大则回退原始\n}\n\n// 从 PicGo 配置读取当前插件配置\nfunction getUserConfig(ctx: IPicGo): OptimizationConfig {\n    const cfg = ctx.getConfig<OptimizationConfig>('picgo-plugin-optimization') || {}\n    return cfg\n}\n\n// 生成可视化配置 Schema（GUI / CLI config）\nfunction config(ctx: IPicGo): IPluginConfig[] {\n    const userConfig = getUserConfig(ctx)\n    return [\n        {\n            name: 'format',\n            type: 'input',\n            default: userConfig.format ?? '',\n            alias: '输出格式(留空保持原格式)',\n            message: '可填写: jpeg|jpg|png|webp|jp2|tiff|avif|heif|jxl|svg|gif；留空表示不转换',\n            required: false,\n        },\n        {\n            name: 'quality',\n            type: 'input',\n            default: userConfig.quality ?? 80,\n            alias: '质量(1-100)',\n            message: '输出图像质量（有损格式生效）',\n            required: false,\n        },\n        {\n            name: 'maxWidth',\n            type: 'input',\n            default: userConfig.maxWidth ?? 0,\n            alias: '最大宽度(0 表示不限制)',\n            message: '超过该宽度会等比缩放',\n            required: false,\n        },\n        {\n            name: 'maxHeight',\n            type: 'input',\n            default: userConfig.maxHeight ?? 0,\n            alias: '最大高度(0 表示不限制)',\n            message: '超过该高度会等比缩放',\n            required: false,\n        },\n        {\n            name: 'skipIfLarger',\n            type: 'confirm',\n            default: userConfig.skipIfLarger ?? true,\n            alias: '若变大则回退',\n            message: '若压缩/转换后文件体积更大则回退原图',\n            required: false,\n        },\n        {\n            name: 'enableLogging',\n            type: 'confirm',\n            default: userConfig.enableLogging ?? false,\n            alias: '启用详细日志',\n            message: '输出调试信息（在 PicGo 日志中）',\n            required: false,\n        },\n    ]\n}\n\n\n// GUI 菜单 (PicGo GUI 调用)\nfunction guiMenu(ctx: IPicGo) {\n    const userConfig = getUserConfig(ctx)\n    const logger = createLogger(ctx, !!userConfig.enableLogging)\n    return [\n        {\n            label: '查看当前json配置',\n            async handle(innerCtx: IPicGo, guiApi: any) {\n                const cfg = innerCtx.getConfig('picgo-plugin-optimization') || {}\n                const text = JSON.stringify(cfg, null, 2)\n                if (guiApi?.showMessageBox) {\n                    await guiApi.showMessageBox({ title: 'picgo-plugin-optimization 配置', message: text, type: 'info' })\n                } else {\n                    logger.info('[optimization] 当前配置', text)\n                }\n            },\n        },\n    ]\n}\n\n// 核心处理逻辑\nasync function handle(ctx: IPicGo): Promise<void> {\n    const userConfig = getUserConfig(ctx)\n    const logger = createLogger(ctx, !!userConfig.enableLogging)\n    logger.info('用户配置', userConfig)\n    const output = ctx.output || []\n    for (const item of output) {\n        try {\n            if (!item.buffer) {\n                continue\n            }\n            const beforeSize = item.buffer.length\n            const originalExt = (item.extname || item.fileName.split('.').pop() || '').toLowerCase().replace(/^\\./, '')\n            const targetFormat = resolveTargetFormat(userConfig.format || '', originalExt)\n            const quality = normalizeQuality(userConfig.quality)\n            const maxWidth = userConfig.maxWidth || 0\n            const maxHeight = userConfig.maxHeight || 0\n            const skipIfLarger = userConfig.skipIfLarger !== false // 默认 true\n\n            logger.info('处理文件', {\n                file: item.fileName,\n                originalExt,\n                size: beforeSize,\n                plan: { format: targetFormat, quality, resize: (maxWidth || maxHeight) ? `${maxWidth || 'auto'}x${maxHeight || 'auto'}` : 'no' },\n            })\n\n            // 若无需转换且无需缩放则跳过\n            // const needResize = !!(maxWidth || maxHeight)\n            // if (!needResize && targetFormat === originalExt) {\n            //     continue\n            // }\n\n            const { buffer: newBuffer, widthChanged } = await optimizeBuffer(item.buffer, {\n                targetFormat,\n                quality,\n                maxWidth,\n                maxHeight,\n            })\n\n            if (skipIfLarger && newBuffer.length > beforeSize) {\n                logger.warn('回退: 转换后更大', { file: item.fileName, before: beforeSize, after: newBuffer.length })\n                continue\n            }\n\n            item.buffer = newBuffer\n            if (targetFormat !== originalExt) {\n                item.extname = `.${targetFormat}`\n                item.fileName = replaceFileExt(item.fileName, targetFormat)\n            }\n            const saved = ((1 - newBuffer.length / beforeSize) * 100).toFixed(2)\n            logger.info('完成优化', { file: item.fileName, widthChanged, savedPercent: saved, newSize: newBuffer.length })\n        } catch (err) {\n            logger.error('处理失败', item.fileName, err)\n        }\n    }\n}\n\n// ---------- 辅助逻辑实现 ----------\n\ninterface OptimizeOptions {\n    targetFormat: string\n    quality: number\n    maxWidth: number\n    maxHeight: number\n}\n\nfunction normalizeQuality(q?: number): number {\n    if (typeof q !== 'number') {\n        return 80\n    }\n    if (q < 1) {\n        return 1\n    }\n    if (q > 100) {\n        return 100\n    }\n    return Math.round(q)\n}\n\n/**\n * 计算 effort 参数，根据不同的范围，将 effort 映射到规定的 effort 范围内\n * @param effort 输入的 effort 值\n * @returns 归一化后的 effort 值\n */\nfunction normalizeEffort(effort?: number, min = 1, max = 10): number {\n    if (typeof effort !== 'number') {\n        return Math.round((min + max) / 2)\n    }\n    if (effort < min) {\n        return min\n    }\n    if (effort > max) {\n        return max\n    }\n    return Math.round(effort)\n}\n\nexport type Format = 'jpeg' | 'jpg' | 'png' | 'webp' | 'jp2' | 'tiff' | 'avif' | 'heif' | 'jxl' | 'svg' | 'gif'\nconst SUPPORTED_FORMATS: Format[] = ['jpeg', 'jpg', 'png', 'webp', 'jp2', 'tiff', 'avif', 'heif', 'jxl', 'svg', 'gif']\n\nfunction resolveTargetFormat(target: string, original: string): string {\n    if (!target) {\n        // 未配置 => 保持原格式\n        return original\n    }\n    const lower = target.toLowerCase()\n    if (SUPPORTED_FORMATS.includes(lower as Format)) {\n        // 若与原格式相同则等同于不转换\n        return lower\n    }\n    // 不支持的目标 => 保持原格式\n    return original\n}\n\nfunction replaceFileExt(name: string, ext: string): string {\n    return `${name.replace(/\\.[^.]+$/, '')}.${ext}`\n}\n\ninterface OptimizeResult {\n    buffer: Buffer\n    widthChanged: boolean\n}\n\nasync function optimizeBuffer(input: Buffer, opt: OptimizeOptions): Promise<OptimizeResult> {\n    const { targetFormat, quality, maxWidth, maxHeight } = opt\n    const image = sharp(input, { sequentialRead: true })\n    const meta = await image.metadata()\n    let widthChanged = false\n    let resized = image\n    // resize 逻辑\n    if ((maxWidth || maxHeight) && meta.width && meta.height) {\n        const { width, height } = computeResize(meta.width, meta.height, maxWidth, maxHeight)\n        if (width !== meta.width || height !== meta.height) {\n            resized = resized.resize(width, height)\n            widthChanged = true\n        }\n    }\n    const encoded = applyFormat(resized, targetFormat, quality)\n    const buffer = await encoded.toBuffer()\n    return { buffer, widthChanged }\n}\n\ninterface Size {\n    width: number\n    height: number\n}\nfunction computeResize(w: number, h: number, maxW: number, maxH: number): Size {\n    let width = w\n    let height = h\n    if (maxW && width > maxW) {\n        const ratio = maxW / width\n        width = maxW\n        height = Math.round(height * ratio)\n    }\n    if (maxH && height > maxH) {\n        const ratio = maxH / height\n        height = maxH\n        width = Math.round(width * ratio)\n    }\n    return { width, height }\n}\n\nfunction getEffort(quality: number, divisor: number = 10, offset: number = 0): number {\n    return Math.floor(quality / divisor) + offset\n}\n\nfunction applyFormat(instance: sharp.Sharp, fmt: string, quality: number): sharp.Sharp {\n    switch (fmt) {\n        case 'jpg':\n        case 'jpeg':\n            return instance.jpeg({ quality, progressive: true, mozjpeg: true })\n        case 'png':\n            // compressionLevel 应该始终设置为较高的值以获得更好的压缩效果\n            return instance.png({ quality, compressionLevel: 9, palette: false, effort: normalizeEffort(getEffort(quality, 10), 1, 10) })\n        case 'webp':\n            return instance.webp({ quality, alphaQuality: quality, effort: normalizeEffort(getEffort(quality, 15), 0, 6) })\n        case 'avif':\n            return instance.avif({ quality, effort: normalizeEffort(getEffort(quality, 10, -1), 0, 9) })\n        case 'tiff':\n            return instance.tiff({ quality, compression: 'lzw' })\n        case 'gif':\n            return instance.gif({ effort: normalizeEffort(getEffort(quality, 10), 1, 10) })\n        case 'heif':\n            return instance.heif({ quality, effort: normalizeEffort(getEffort(quality, 10, -1), 0, 9) })\n        case 'jp2':\n            return instance.jp2({ quality })\n        case 'jxl':\n            return instance.jxl({ quality, effort: normalizeEffort(getEffort(quality, 10, -1), 3, 9) })\n        default:\n            return instance\n    }\n}\n\n// 注册 beforeUpload 插件（替代 transformer）\nconst register = (ctx: IPicGo): void => {\n    ctx.helper.beforeUploadPlugins.register('optimization', {\n        handle,\n        config,\n        name: '图片优化 (beforeUpload)',\n    })\n}\n\nexport { register }\nexport const beforeUploadPlugins = 'optimization'\n\nmodule.exports = (ctx: IPicGo) => ({\n    register,\n    beforeUploadPlugins,\n    guiMenu,\n    config,\n})\n"],"mappings":"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"}