【2024.2.26更新】基于SANE实现OpenWrt打印服务器的移动扫描功能

0. 前言

最近春节放假回家时,感觉之前实现用路由器实现的移动扫描功能界面有点太难看了.......因此重新覆写了代码,在此做一总结。

1. 之前的相关文章

基于Sane成功解决路由器改OpenWrt打印扫描服务器的手机移动端(IOS、Android)扫描功能实现问题

 【2024.1.2更新】windows10环境下vscode配置go开发环境及热加载Air框架设置记录

实现openwrt打印扫描服务器移动端扫描的配置服务文件

TP-LINK-TL-WR703N(原装)制作打印服务器过程记录整理

2. SANE协议简介 

为了让新读者更快了解本篇文章的作用及意义,这里引用一下博主FDWMin对SANE协议的简要介绍:

Linux下通用扫描仪API——SANE( Scanner Access Now Easy)

SANE( Scanner Access Now Easy),是一个应用程序编程接口(API),它提供给任何光栅图像扫描仪硬件标准化的访问(平板扫描仪,手持式扫描仪,视频和静止相机,图像采集卡等。 )。该api是公共领域,它的讨论和发展,对所有人开放。目前的源代码是UNIX(包括GNU / Linux)的和GNU通用公共许可证(下可用SANE API可用于专有应用程序和后端为好)。

SANE是一种通用扫描仪接口。这样的通用接口的价值在于,它允许写入每个图像采集装置,而不是为每个设备和应用的一个驱动器只有一个驱动器。所以,如果你有三个应用程序和四个设备,传统上你不得不写12次不同的程序(3*4种驱动程序); 有了 SANE,这个数字减少到7(3+4种驱动程序)。当然,储蓄获得的越来越多的驱动程序和/或应用程序被添加更大。

不仅SANE减少开发时间和代码重复,这也引发了在哪些应用程序可以工作的水平。这样,就会使这在以前是闻所未闻的,在UNIX世界的应用。虽然SANE主要是针对UNIX环境中,该标准已被精心设计,使之可以实现在几乎任何硬件或操作系统的API。

虽然SANE是“Scanner Access Now Easy”的首字母缩写的希望当然是SANE在某种意义上确实是明智的,这将是很容易实现的API,同时适应今天的扫描仪硬件和应用程序所需的所有功能。具体而言,SANE应足够宽,以适应装置,例如扫描仪,数码相机和摄像机,以及如图像文件的过滤器的虚拟设备。

关于SANE更详细的介绍和使用建议直接参考SANE官网:

SANE - Scanner Access Now Easy

简介中的重点是SANE支持Unix系统,Unix系统在我的认知中与嵌入式系统相近(非该专业,个人观点),这意味着SANE可以在路由器这样的小型设备中运行。

3. 代码重构

之前扫描服务器的移动端实现是通过对github上gyscos编写的RemoteScanViewer项目进行修改后实现的。

 修改后实现的移动扫描端界面:

尽管项目能够正常运行,但是界面确实有点太难看了,而且HTML前端采用的CDN 是外网链接,如果采用本地引入js文件会莫名其妙报错,导致页面没有办法显示,虽然后面采用国内BootCDN替换了原来的CDN链接,但这并不能解决网络问题,意味着每次运行服务必须处于联网状态。

BootCDN - Bootstrap 中文网开源项目免费 CDN 加速服务

除此之外, 该项目对移动端的支持也存在问题,使用移动端进行访问时,会出现页面组件混乱的现象。

鉴于此,我决定对该项目进行重写。

3.1 代码结构(或者说技术栈,不知道这么说对不对)

前端——由于不打算采用Node.js(因为这意味着又得在OpenWrt上装包),所以没法使用Vue,尝试过在原生HTML上通过本地文件引入Vue.js,可以使用部分Vue方法,但UI组件库使用时有问题,遂放弃,采用原生HTML

后端——采用Golang编写,具体Web框架采用gin框架,相比gframe的庞大繁杂,gin简单上手快,更符合项目需求。

通信——采用本地文件引入的方式引入axios.js,使用axios进行请求和响应。

3.2 部分代码展示及解释

执行扫描的主要函数

// 执行SANE扫描cmd命令并返回实时扫描进度
func ScanProgress(device string, dpi string, mode string, fileName string) (float64, error) {
	ScanEnd = "0"
	//声明TIF_DEST变量
	TIF_DEST := DEST_DIR + "/" + fileName + ".tiff"
	//声明PDF_DEST变量
	PDF_DEST := DEST_DIR + "/" + fileName + ".pdf"
	//声明TBN_DEST变量
	TBN_DEST := DEST_DIR + "/thumb/" + fileName + ".png"
	cmdName := "scanimage"
	cmdArgs := []string{"--progress", "--device-name", device, "--resolution", dpi, "--mode", mode, "--format", "tiff", "--output-file", TIF_DEST} // --device-name设备名称 --resolution分辨率 --mode扫描模式 --progress命令用于显示扫描的进度

	// 打印即将执行的命令
	fmt.Printf("将要执行的命令:%s %s\n", cmdName, strings.Join(cmdArgs, " "))
	ScanProgressValue = 60.00
	cmd := exec.Command(cmdName, cmdArgs...)
	var stderr bytes.Buffer
	cmd.Stderr = &stderr
	stdout, err := cmd.StdoutPipe()
	if err != nil {
		fmt.Printf("执行cmd.StdoutPipe()出错:%s\n", err)
		fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
		return 0, err
	}

	if err := cmd.Start(); err != nil {
		fmt.Printf("执行cmd.Start()出错:%s\n", err)
		fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
		return 0, err
	}

	go func() {
		reader := bufio.NewReader(stdout)
		for {
			line, err := reader.ReadString('\n')
			fmt.Printf("当前行内容:%s\n", line) // 添加调试输出

			if err != nil || err == io.EOF {
				break
			}

			if strings.Contains(line, "Progress:") {
				fmt.Printf("发现进度信息:%s\n", line) // 添加调试输出

				progressStr := strings.Split(line, ":")[1]
				// 去除百分号
				progressStr = strings.TrimSpace(progressStr[:len(progressStr)-1])

				progress, err := strconv.ParseFloat(progressStr, 64)
				if err != nil {
					fmt.Println("解析进度信息时出现错误:", err) // 添加调试输出
					// 如果解析出错,可以尝试打印 progressStr 看看原始内容
					continue
				}

				fmt.Printf("当前 ScanProgressValue:%f\n", progress)
				// 将进度保存到全局变量
				ScanProgressValue = progress
			}
		}
	}()

	if err := cmd.Wait(); err != nil {
		fmt.Printf("执行cmd.Wait()出错:%s\n", err)
		fmt.Println(fmt.Sprint(err) + ": " + stderr.String())
		return 0, err
	}
	ScanProgressValue = 100.00
	fmt.Printf("tiff格式转换...")
	//将获得的tiff转换
	//转为pdf
	command1 := "convert"
	args1 := []string{TIF_DEST, PDF_DEST}
	cmd1 := exec.Command(command1, args1...)
	err1 := cmd1.Run()
	if err1 != nil {
		fmt.Printf("执行tiff转pdf出错: %s", err1)
		fmt.Println("尝试使用golang转换pdf...")
		// 读取 TIF 文件
		tifData, err := os.ReadFile(TIF_DEST)
		if err != nil {
			fmt.Println("Error reading TIF file:", err)

		}

		// 解码 TIF 数据
		img, err := tiff.Decode(bytes.NewReader(tifData))
		if err != nil {
			fmt.Println("Error decoding TIF image:", err)

		}

		// 获取 TIF 图像的宽度和高度
		bounds := img.Bounds()
		imageWidth := float64(bounds.Dx())
		imageHeight := float64(bounds.Dy())

		// 创建 PDF 文件
		pdf := gofpdf.New("P", "mm", "A4", "")
		pdf.AddPage()

		// 将 TIF 图像转换为 PDF 图像
		imageIndex := pdf.RegisterImageOptions(TIF_DEST, gofpdf.ImageOptions{ImageType: "TIFF", ReadDpi: true})
		if imageIndex == nil {
			fmt.Println("Error registering image for PDF:", pdf.Error())

		}

		pdf.ImageOptions(TIF_DEST, 0, 0, imageWidth, imageHeight, false, gofpdf.ImageOptions{ImageType: "TIFF"}, 0, "")

		// 保存 PDF 文件
		err = pdf.OutputFileAndClose(PDF_DEST)
		if err != nil {
			fmt.Println("Error saving PDF file:", err)

		}

		fmt.Println("Conversion from TIF to PDF successful!")
	}
	//转为png
	command2 := "convert"
	args2 := []string{TIF_DEST, TBN_DEST}
	cmd2 := exec.Command(command2, args2...)
	err2 := cmd2.Run()
	if err2 != nil {
		fmt.Printf("执行tiff转png出错: %s\n", err2)
		fmt.Println("尝试使用golang转png...")
		// 读取 TIF 文件
		tifData, err := os.ReadFile(TIF_DEST)
		if err != nil {
			fmt.Println("Error reading TIF file:", err)
		}

		// 解码 TIF 数据
		img, err := tiff.Decode(bytes.NewReader(tifData))
		if err != nil {
			fmt.Println("Error decoding TIF image:", err)
		}

		// 创建输出 PNG 文件
		outfile, err := os.Create(TBN_DEST)
		if err != nil {
			fmt.Println("Error creating PNG file:", err)
		}
		defer outfile.Close()

		// 将图像以 PNG 格式写入文件
		if err := png.Encode(outfile, img); err != nil {
			fmt.Println("Error encoding PNG image:", err)
		}

		fmt.Println("Conversion from TIF to PNG successful!")
	}
	fmt.Printf("tiff转换完成")
	ScanEnd = "1"
	return 0, nil
}

这里采用了和之前gyscos那个项目的同样方法,即采用SANE的命令行工具scanimage,通过Go执行scanimage命令,实现扫描功能的控制。

scanimage - flexible command-line-frontend including support for pnm and tiff output (included in sane-backends).

对于Go,其实我们还有另一种方法来实现SANE的扫描功能,即go-sane包:

go-sane包封装了针对go语言的SANE协议实现函数,但是这个包有一个缺陷,其采用了cgo

这意味着我们不能直接通过go的跨平台编译功能,要通过其他方法才能实现跨平台编译:

含有CGO代码的项目如何实现跨平台编译

这过于麻烦了,因此我们采用scanimage命令行工具实现扫描功能即可。 

3.3 编译工具

 编译采用的是liteide这款IDE:

IDE的界面: 

​ 界面比较简朴,但是它最显眼的地方是编译环境的设置非常方便:

注意跨平台编译时cgo要关掉,因此CGO_ENABLED等于0

 3.4 前端组件库

前端这回由于采用原生HTML,所以可选择的库不多。经过寻找终于找到一个基于原生HTML实现的UI组件库:
ICEUI-HTML5前后端框架

组件丰富度还行,够用了(当然后面还是发现了一点Bug…),最主要是它声称支持移动端显示。 

在使用它的导航菜单组件时,我发现在PC端时显示正常: 

变成移动端时出现问题: 

可以看到移动端显示时,并菜单项目并没有按竖向排布,同时点击右边的三角后里面的内容并没有显示出来…难绷

但是除了这个小问题之外没有再遇到更多的问题,因此还行。

 4. 最终效果

4.1 首页

空白是因为里面的东西还没搞,我计划做成一个打印和扫描一体的一个项目,还没搞完。

4.2 扫描页 

 4.3 扫描文件效果

4.4 扫描文件详情页 

4.5 文件预览(基于PDF.js) 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值