使用go照片合并工具

文章目录

概要

` 使用go开发桌面exe程序,方便照片合并压缩操作,身份证正反面,居住证正反面,照片全部压缩到1mb以下大小
目前存在小部分bug但是不影响正常使用!不多说直接上代码!


package main

import (
	"fmt"
	"image"
	"image/draw"
	"image/jpeg"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"
	"sync"
	"time"
)

const (
	frontKeyword   = "身份证人像面"
	backKeyword    = "身份证国徽面"
	newKeyword1    = "居住证正面"
	newKeyword2    = "居住证反面"
	newKeyword3    = "户口本户主页"
	newKeyword4    = "户口本本人页"
	maxImageSize   = 1024 * 1024  // 1MB
	expirationDate = "2023-09-20" // 设置软件过期日期
	maxConcurrency = 5            // 最大并发处理数
)

var (
	daysRemaining int
	visitedDirs   = make(map[string]struct{})
	mergeMutex    sync.Mutex
)

func main() {
	// 检查软件是否过期
	if isExpired(expirationDate) {
		fmt.Println("软件已过期,请联系开发者获取新版本。联系方式:xxxxxxx")
		return
	}

	// 获取剩余天数
	daysRemaining = calculateDaysRemaining(expirationDate)
	var dirPath string
	fmt.Print("请输入目录路径: ")
	fmt.Scan(&dirPath)

	processDirectory(dirPath)

	fmt.Println("所有操作已完成")
	//不关闭窗口操作
	for {
	}
}

func isExpired(expirationDate string) bool {
	expirationTime, err := time.Parse("2006-01-02", expirationDate)
	if err != nil {
		fmt.Println("无法解析过期日期:", err)
		return true // 如果无法解析过期日期,则默认为过期
	}

	currentTime := time.Now()
	return currentTime.After(expirationTime)
}

func calculateDaysRemaining(expirationDate string) int {
	expirationTime, err := time.Parse("2006-01-02", expirationDate)
	if err != nil {
		fmt.Println("无法解析过期日期:", err)
		return -1 // 如果无法解析过期日期,则返回-1表示错误
	}

	currentTime := time.Now()
	remainingDays := int(expirationTime.Sub(currentTime).Hours() / 24)
	return remainingDays
}

func processDirectory(dirPath string) {
	// 加锁以避免重复访问目录
	dirMutex := sync.Mutex{}
	dirMutex.Lock()
	defer dirMutex.Unlock()

	// 检查是否已经访问过此目录
	if _, exists := visitedDirs[dirPath]; exists {
		return
	}
	visitedDirs[dirPath] = struct{}{}

	// 检查是否是 "修改后" 文件夹或其子目录
	if isModifiedFolder(dirPath) {
		return
	}

	//获取当前文件夹
	currentDirName := filepath.Base(dirPath)

	// 创建 "修改后" 子目录
	modifiedFolderPath := filepath.Join(dirPath, currentDirName+"-修改后")
	if err := os.Mkdir(modifiedFolderPath, 0755); err != nil && !os.IsExist(err) {
		fmt.Println("无法创建 '修改后' 文件夹:", err)
		return
	}

	// 获取目录下的图片文件
	imageFiles, err := getImageFiles(dirPath)
	if err != nil {
		fmt.Println("无法获取图片文件列表:", err)
		return
	}

	// 创建一个通道来协调压缩任务
	compressChan := make(chan string)

	// 创建一个等待组以等待所有压缩任务完成
	var wg sync.WaitGroup

	// 启动多个协程进行图片压缩
	for i := 0; i < maxConcurrency; i++ {
		wg.Add(1)
		go func() {
			defer wg.Done()
			for imagePath := range compressChan {
				compressAndSaveImage(imagePath, maxImageSize, modifiedFolderPath)
			}
		}()
	}

	// 将图片路径发送到通道以进行压缩
	for _, imagePath := range imageFiles {
		compressChan <- imagePath
	}
	close(compressChan)

	// 加载身份证照片并合成
	frontIDCardImg, backIDCardImg, err := loadIDCardImages(dirPath)
	if err != nil {
		fmt.Println("无法加载身份证照片:", err)
		return
	}

	// 如果找到了正面和背面照片,进行合成和保存
	if frontIDCardImg != nil && backIDCardImg != nil {
		mergedImg := mergeImages(frontIDCardImg, backIDCardImg)

		savePath := filepath.Join(modifiedFolderPath, "合成照片.jpg")
		if err := saveCompressedImageJPEG(mergedImg, savePath, maxImageSize); err != nil {
			fmt.Println("无法保存合成照片:", err)
			return
		}

		fmt.Println("合成照片已保存至:", savePath)
	}

	// 新照片类型处理代码块开始
	processNewImageType(imageFiles, dirPath, newKeyword1, newKeyword2, modifiedFolderPath)
	processNewImageType(imageFiles, dirPath, newKeyword3, newKeyword4, modifiedFolderPath)
	processNewImageType(imageFiles, dirPath, "户口本首页", newKeyword4, modifiedFolderPath)
	// 新照片类型处理代码块结束

	// 处理子目录
	subDirs, err := getSubDirectories(dirPath)
	if err != nil {
		fmt.Println("无法获取子目录列表:", err)
		return
	}

	for _, subDir := range subDirs {
		processDirectory(subDir)
	}
}

func isModifiedFolder(dirPath string) bool {
	return strings.HasSuffix(dirPath, "修改后")
}

func processNewImageType(imageFiles []string, dirPath string, newKeyword1, newKeyword2, modifiedFolderPath string) {
	var newImage1 image.Image
	var newImage2 image.Image

	// 对于每个图像文件,检查文件名是否包含新关键字
	for _, imagePath := range imageFiles {
		if strings.Contains(imagePath, newKeyword1) {
			img, err := loadImage(imagePath)
			if err != nil {
				fmt.Printf("无法加载图像文件:%s,当前目录:%s\n", imagePath, getCurrentDirectory())
				continue
			}
			newImage1 = img
		} else if strings.Contains(imagePath, newKeyword2) {
			img, err := loadImage(imagePath)
			if err != nil {
				fmt.Printf("无法加载图像文件:%s,当前目录:%s\n", imagePath, getCurrentDirectory())
				continue
			}
			newImage2 = img
		}
	}

	// 如果找到了新照片类型的图像,进行合并和保存
	if newImage1 != nil && newImage2 != nil {
		mergedImg := mergeImages(newImage1, newImage2)

		savePath := filepath.Join(modifiedFolderPath, "新照片类型合成照片.jpg")
		if err := saveCompressedImageJPEG(mergedImg, savePath, maxImageSize); err != nil {
			fmt.Println("无法保存新照片类型合成照片:", err)
		} else {
			fmt.Println("新照片类型合成照片已保存至:", savePath)
		}
	}
}

func getImageFiles(dirPath string) ([]string, error) {
	var imageFiles []string

	fileList, err := getFileList(dirPath)
	if err != nil {
		return nil, err
	}

	for _, fileInfo := range fileList {
		if !fileInfo.IsDir() && isImageFile(fileInfo.Name()) {
			imageFiles = append(imageFiles, filepath.Join(dirPath, fileInfo.Name()))
		}
	}

	return imageFiles, nil
}

func isImageFile(fileName string) bool {
	extensions := []string{".jpg", ".jpeg", ".png", ".gif"}
	lowerFileName := strings.ToLower(fileName)
	for _, ext := range extensions {
		if strings.HasSuffix(lowerFileName, ext) {
			return true
		}
	}
	return false
}

func compressAndSaveImage(imagePath string, maxSize int, outputPath string) {
	img, err := loadImage(imagePath)
	if err != nil {
		fmt.Printf("无法加载图像文件:%s,当前目录:%s\n", imagePath, getCurrentDirectory())
		return
	}

	// 获取图像的文件名和扩展名
	_, fileName := filepath.Split(imagePath)

	// 构建保存压缩图像的路径
	savePath := filepath.Join(outputPath, getCompressedImagePath(fileName))
	if err := saveCompressedImageJPEG(img, savePath, maxSize); err != nil {
		fmt.Println("无法保存压缩图像:", err)
	}
}

func getCompressedImagePath(imagePath string) string {
	ext := filepath.Ext(imagePath)
	base := strings.TrimSuffix(imagePath, ext)
	return base + "_compressed" + ext
}

func getFileList(dirPath string) ([]os.FileInfo, error) {
	dirInfo, err := os.Stat(dirPath)
	if os.IsNotExist(err) {
		return nil, err
	}

	if !dirInfo.IsDir() {
		return nil, fmt.Errorf("指定路径不是目录")
	}

	fileList, err := ioutil.ReadDir(dirPath)
	if err != nil {
		return nil, err
	}

	return fileList, nil
}

func loadIDCardImages(dirPath string) (image.Image, image.Image, error) {
	var frontIDCardImg image.Image
	var backIDCardImg image.Image

	fileList, err := getFileList(dirPath)
	if err != nil {
		return nil, nil, err
	}

	for _, fileInfo := range fileList {
		if !fileInfo.IsDir() {
			fileName := fileInfo.Name()

			if strings.Contains(fileName, frontKeyword) {
				img, err := loadImage(filepath.Join(dirPath, fileName))
				if err != nil {
					return nil, nil, err
				}
				frontIDCardImg = img
			} else if strings.Contains(fileName, backKeyword) {
				img, err := loadImage(filepath.Join(dirPath, fileName))
				if err != nil {
					return nil, nil, err
				}
				backIDCardImg = img
			}
		}
	}

	return frontIDCardImg, backIDCardImg, nil
}

func mergeImages(frontImg, backImg image.Image) image.Image {
	frontWidth := frontImg.Bounds().Dx()
	frontHeight := frontImg.Bounds().Dy()
	backWidth := backImg.Bounds().Dx()
	backHeight := backImg.Bounds().Dy()

	mergedWidth := frontWidth + backWidth
	mergedHeight := frontHeight
	if backHeight > frontHeight {
		mergedHeight = backHeight
	}

	mergedImg := image.NewRGBA(image.Rect(0, 0, mergedWidth, mergedHeight))
	draw.Draw(mergedImg, image.Rect(0, 0, frontWidth, frontHeight), frontImg, image.Point{}, draw.Over)
	draw.Draw(mergedImg, image.Rect(frontWidth, 0, mergedWidth, backHeight), backImg, image.Point{}, draw.Over)

	return mergedImg
}

// 保存经过压缩的图像为JPEG格式
func saveCompressedImageJPEG(img image.Image, filePath string, maxSize int) error {
	// 尝试不同的压缩质量,直到图像大小满足要求
	quality := 100
	for {
		outputFile, err := os.Create(filePath)
		if err != nil {
			return err
		}
		defer outputFile.Close()

		// 将图像直接保存为压缩后的 JPEG 格式
		err = jpeg.Encode(outputFile, img, &jpeg.Options{Quality: quality})
		if err != nil {
			return err
		}

		// 检查图像大小是否满足要求
		stat, err := outputFile.Stat()
		if err != nil {
			return err
		}
		if stat.Size() <= int64(maxSize) {
			break
		}

		// 降低压缩质量再次尝试
		quality -= 10
		if quality <= 0 {
			return fmt.Errorf("无法满足目标图像大小")
		}
	}

	return nil
}

func loadImage(filePath string) (image.Image, error) {
	imgFile, err := os.Open(filePath)
	if err != nil {
		fmt.Printf("无法加载图像文件:%s,当前目录:%s\n", filePath, getCurrentDirectory())
		return nil, err
	}
	defer imgFile.Close()

	img, _, err := image.Decode(imgFile)
	if err != nil {
		return nil, err
	}

	return img, nil
}

func getCurrentDirectory() string {
	dir, err := os.Getwd()
	if err != nil {
		return ""
	}
	return dir
}

func getSubDirectories(dirPath string) ([]string, error) {
	var subDirs []string

	fileList, err := getFileList(dirPath)
	if err != nil {
		return nil, err
	}

	for _, fileInfo := range fileList {
		if fileInfo.IsDir() {
			subDirs = append(subDirs, filepath.Join(dirPath, fileInfo.Name()))
		}
	}

	return subDirs, nil
}

#程序打包命令

CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -o 重生之我是压缩工具3.2.exe test6.go

在这里插入图片描述

ps:写完之后反正我也看不懂!有能力的自己优化修改吧!图片我就不放了,自己测试看效果就好了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值