webp_server_go/encoder/process.go

301 lines
8.9 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package encoder
import (
"errors"
"os"
"path"
"slices"
"strings"
"webp_server_go/config"
"webp_server_go/helper"
"github.com/davidbyttow/govips/v2/vips"
log "github.com/sirupsen/logrus"
)
func resizeImage(img *vips.ImageRef, extraParams config.ExtraParams) error {
imageHeight := img.Height()
imageWidth := img.Width()
imgHeightWidthRatio := float32(imageHeight) / float32(imageWidth)
//这里我们有宽度、高度和 max_width、max_height
//两对不能同时使用
//max_height 和 max_width 用于确保将更大的图像调整为 max_height 和 max_width
//例如max_width=200,max_height=100 的 500x500px 图像将调整为 100x100
//而较小的图像则保持不变
//如果两者都使用,我们将使用宽度和高度
if extraParams.MaxHeight > 0 && extraParams.MaxWidth > 0 {
// If any of it exceeds
if imageHeight > extraParams.MaxHeight || imageWidth > extraParams.MaxWidth {
// Check which dimension exceeds most
heightExceedRatio := float32(imageHeight) / float32(extraParams.MaxHeight)
widthExceedRatio := float32(imageWidth) / float32(extraParams.MaxWidth)
// 如果高度超过更多,例如 500x500 -> 200x100 (2.5 < 5)
// 以max_height为新高度调整大小并保留比例
if heightExceedRatio > widthExceedRatio {
err := img.Thumbnail(int(float32(extraParams.MaxHeight)/imgHeightWidthRatio), extraParams.MaxHeight, 0)
if err != nil {
return err
}
} else {
err := img.Thumbnail(extraParams.MaxWidth, int(float32(extraParams.MaxWidth)*imgHeightWidthRatio), 0)
if err != nil {
return err
}
}
}
}
if extraParams.MaxHeight > 0 && imageHeight > extraParams.MaxHeight && extraParams.MaxWidth == 0 {
err := img.Thumbnail(int(float32(extraParams.MaxHeight)/imgHeightWidthRatio), extraParams.MaxHeight, 0)
if err != nil {
return err
}
}
if extraParams.MaxWidth > 0 && imageWidth > extraParams.MaxWidth && extraParams.MaxHeight == 0 {
err := img.Thumbnail(extraParams.MaxWidth, int(float32(extraParams.MaxWidth)*imgHeightWidthRatio), 0)
if err != nil {
return err
}
}
if extraParams.Width > 0 && extraParams.Height > 0 {
var cropInteresting vips.Interesting
switch config.Config.ExtraParamsCropInteresting {
case "InterestingNone":
cropInteresting = vips.InterestingNone
case "InterestingCentre":
cropInteresting = vips.InterestingCentre
case "InterestingEntropy":
cropInteresting = vips.InterestingEntropy
case "InterestingAttention":
cropInteresting = vips.InterestingAttention
case "InterestingLow":
cropInteresting = vips.InterestingLow
case "InterestingHigh":
cropInteresting = vips.InterestingHigh
case "InterestingAll":
cropInteresting = vips.InterestingAll
default:
cropInteresting = vips.InterestingAttention
}
err := img.Thumbnail(extraParams.Width, extraParams.Height, cropInteresting)
if err != nil {
return err
}
}
if extraParams.Width > 0 && extraParams.Height == 0 {
err := img.Thumbnail(extraParams.Width, int(float32(extraParams.Width)*imgHeightWidthRatio), 0)
if err != nil {
return err
}
}
if extraParams.Height > 0 && extraParams.Width == 0 {
err := img.Thumbnail(int(float32(extraParams.Height)/imgHeightWidthRatio), extraParams.Height, 0)
if err != nil {
return err
}
}
return nil
}
func ResizeItself(raw, dest string, extraParams config.ExtraParams) {
log.Infof("开始调整图像大小: 源文件=%s, 目标文件=%s", raw, dest)
// 创建目标目录
if err := os.MkdirAll(path.Dir(dest), 0755); err != nil {
log.Errorf("创建目标目录失败: %v", err)
return
}
// 加载图像
img, err := vips.LoadImageFromFile(raw, &vips.ImportParams{
FailOnError: boolFalse,
})
if err != nil {
log.Warnf("加载图像失败: 文件=%s, 错误=%v", raw, err)
return
}
defer img.Close()
// 调整图像大小
if err := resizeImage(img, extraParams); err != nil {
log.Warnf("调整图像大小失败: %v", err)
return
}
// 移除元数据(如果配置要求)
if config.Config.StripMetadata {
log.Debug("正在移除图像元数据")
img.RemoveMetadata()
}
// 导出图像
buf, _, err := img.ExportNative()
if err != nil {
log.Errorf("导出图像失败: %v", err)
return
}
// 写入文件
if err := os.WriteFile(dest, buf, 0600); err != nil {
log.Errorf("写入目标文件失败: 文件=%s, 错误=%v", dest, err)
return
}
log.Infof("图像大小调整成功: 目标文件=%s", dest)
}
// Pre-process image(auto rotate, resize, etc.)
func preProcessImage(img *vips.ImageRef, imageType string, extraParams config.ExtraParams) error {
log.Debugf("开始预处理图像: 类型=%s, 宽度=%d, 高度=%d", imageType, img.Metadata().Width, img.Metadata().Height)
// 检查宽度/高度并忽略特定图像格式
switch imageType {
case "webp":
if img.Metadata().Width > config.WebpMax || img.Metadata().Height > config.WebpMax {
log.Warnf("WebP图像尺寸超限: 宽度=%d, 高度=%d, 最大限制=%d", img.Metadata().Width, img.Metadata().Height, config.WebpMax)
return errors.New("WebP图像太大")
}
if slices.Contains(webpIgnore, img.Format()) {
log.Infof("WebP编码器忽略图像类型: %s", img.Format())
return errors.New("WebP 编码器:忽略图像类型")
}
case "avif":
if img.Metadata().Width > config.AvifMax || img.Metadata().Height > config.AvifMax {
log.Warnf("AVIF图像尺寸超限: 宽度=%d, 高度=%d, 最大限制=%d", img.Metadata().Width, img.Metadata().Height, config.AvifMax)
return errors.New("AVIF图像太大")
}
if slices.Contains(avifIgnore, img.Format()) {
log.Infof("AVIF编码器忽略图像类型: %s", img.Format())
return errors.New("AVIF 编码器:忽略图像类型")
}
}
// 自动旋转
if err := img.AutoRotate(); err != nil {
log.Errorf("图像自动旋转失败: %v", err)
return err
}
log.Debug("图像自动旋转完成")
// 额外参数处理
if config.Config.EnableExtraParams {
log.Debug("开始应用额外图像处理参数")
if err := resizeImage(img, extraParams); err != nil {
log.Errorf("应用额外图像处理参数失败: %v", err)
return err
}
log.Debug("额外图像处理参数应用完成")
}
log.Debug("图像预处理完成")
return nil
}
func ProcessAndSaveImage(rawImageAbs, exhaustFilename string, extraParams config.ExtraParams) error {
log.Infof("开始处理图像: 源文件=%s, 目标文件=%s", rawImageAbs, exhaustFilename)
// 创建目标目录
if err := os.MkdirAll(path.Dir(exhaustFilename), 0755); err != nil {
log.Errorf("创建目标目录失败: %v", err)
return err
}
// 获取原图文件大小
originalInfo, err := os.Stat(rawImageAbs)
if err != nil {
log.Errorf("获取原图文件信息失败: %v", err)
return err
}
originalSize := originalInfo.Size()
// 如果原始图像是 NEF 格式,先转换为 JPG
if strings.HasSuffix(strings.ToLower(rawImageAbs), ".nef") {
tempJPG, converted := ConvertRawToJPG(rawImageAbs, exhaustFilename)
if converted {
defer func() {
log.Infoln("移除中间转换文件:", tempJPG)
if err := os.Remove(tempJPG); err != nil {
log.Warnln("删除转换文件失败", err)
}
}()
rawImageAbs = tempJPG
}
}
// 加载图像
img, err := vips.LoadImageFromFile(rawImageAbs, &vips.ImportParams{
FailOnError: boolFalse,
NumPages: intMinusOne,
})
if err != nil {
log.Warnf("无法打开源图像: %v", err)
return err
}
defer img.Close()
// 确定输出格式
var imageType string
switch {
case strings.HasSuffix(exhaustFilename, ".avif"):
imageType = "avif"
case strings.HasSuffix(exhaustFilename, ".jxl"):
imageType = "jxl"
default:
imageType = "webp"
}
// 预处理图像(自动旋转、调整大小等)
if err := preProcessImage(img, imageType, extraParams); err != nil {
log.Warnf("无法预处理源图像: %v", err)
return err
}
// 根据图像类型进行编码
var encoderErr error
switch imageType {
case "webp":
encoderErr = webpEncoder(img, rawImageAbs, exhaustFilename)
case "avif":
encoderErr = avifEncoder(img, rawImageAbs, exhaustFilename)
case "jxl":
encoderErr = jxlEncoder(img, rawImageAbs, exhaustFilename)
}
if encoderErr != nil {
log.Errorf("图像编码失败: %v", encoderErr)
return encoderErr
}
// 比较转换后的文件大小
convertedInfo, err := os.Stat(exhaustFilename)
if err != nil {
log.Errorf("获取转换后文件信息失败: %v", err)
return err
}
if convertedInfo.Size() > originalSize {
log.Infof("转换后的图片大于原图,使用原图: %s", rawImageAbs)
// 删除转换后的大文件
if err := os.Remove(exhaustFilename); err != nil {
log.Warnf("删除大的转换文件失败: %v", err)
}
// 将原图复制到 EXHAUST_PATH
if err := helper.CopyFile(rawImageAbs, exhaustFilename); err != nil {
log.Errorf("复制原图到 EXHAUST_PATH 失败: %v", err)
return err
}
} else {
log.Infof("图像处理成功: 目标文件=%s", exhaustFilename)
}
return nil
}