Golang中jpeg.Decode解码多张图片失败问题探讨

Golang中jpeg.Decode解码多张图片失败问题探讨 我已成功在我的网页应用中实现了图片下载功能。图片在浏览器中显示正常;然而,当我尝试解码它们以进行尺寸调整时,却遇到了错误。GitHub上有一个关于此问题的帖子: https://github.com/golang/go/issues/45902 该问题在那里尚未解决。对我来说,这个问题似乎在处理较大图片时更频繁地出现。这些图片在大多数应用程序中都能正常工作,例如:Affinity Photo 和 Mac Photos。我在 Affinity 中编辑了其中一个文件并减小了其尺寸。当我把宽度降到800像素后,我就能成功解码并调整其大小了。原图是4000像素宽,从 Mac Photos 导出的。解码失败并报错:unexpected EOF。这张照片是用 iPhone 12 拍摄的。我在大多数照片上都看到了这个错误。在处理了相当多的照片后,我发现只有当我把宽度降到大约800像素左右时,才能稳定地解码它们。在每一个解码失败的案例中,图片在浏览器中都能正常显示。 我尝试下载这张图片: https://hiconsumption.com/fastest-cars-in-the-world/ 它的宽度是1000像素,解码时遇到了同样的错误。 除非是我的代码哪里做错了,否则我觉得这个问题应该会被广泛报告才对。

var decodedImage image.Image
var err error
if fileType == "jpg" {
	buf := bytes.NewBufferString(imageData)
	reader := base64.NewDecoder(base64.StdEncoding, buf)

	config, err := jpeg.DecodeConfig(reader)

	if err != nil {
		log.Println("error jpeg.DecodeConfig: ", err)
		return err
	} else {
		log.Println("Width:", config.Width, "Height:", config.Height)
	}
	buf = bytes.NewBufferString(imageData)
	reader = base64.NewDecoder(base64.StdEncoding, buf)
	decodedImage, err = jpeg.Decode(reader)
	if err != nil {
		log.Println("jpeg.Decode failed error: ", err)
		return err
	}
}

更多关于Golang中jpeg.Decode解码多张图片失败问题探讨的实战教程也可以访问 https://www.itying.com/category-94-b0.html

15 回复

哦,好的,那你推荐用什么替代呢?

更多关于Golang中jpeg.Decode解码多张图片失败问题探讨的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


没关系,我理解错了问题。我以为是浏览器无法读取你处理后的图像。

好的,没问题。上面的代码中出现了错误,具体在 jpeg.DecodeConfig()jpeg.Decode() 处。

rschluet:

ioutil

很高兴你取得了一些进展!顺便提一下,ioutil 已被弃用。

我也不知道这一点!不过看起来修复起来很简单:ioutil.ReadAll 变成了 io.ReadAllioutil.ReadFileos.ReadFile,等等。我刚才还担心了一下!

我并未使用多部分表单,但今天早上我一直在研究它,并且已经成功地通过多部分表单获取了文件类型元素的数据。我现在正在修改我的 processImage() 函数。随着我取得进一步进展,我将更新这个帖子。

“如果请求体的尺寸尚未被 MaxBytesReader 限制,则其大小上限为 10MB。”

网站图标

http package - net/http - pkg.go.dev

Package http provides HTTP client and server implementations.

除了隔离之外,我没有更好的建议。您能否将大图像下载为.jpg文件,并编写直接读取和解码该文件的Go代码?如果可以,您能否编写Go代码对下载的文件进行base-64编码,并将结果的大小与您在服务中接收到的数据大小进行比较?您可能在某个地方有截断请求数据的情况。

func main() {
    fmt.Println("hello world")
}

感谢,了解限制是件好事,但我的图片都没有那么大。我目前正在处理的那张图片(在浏览器中显示正常)不到4MB。当它到达服务器时,大小为524288字节。这就解释了为什么当它被发送回浏览器时,只有大约顶部10%的部分显示出来。我没有使用MaxBytesReader,所以限制应该是10MB。我想知道浏览器是否限制了发送的数据量。我使用的是Mac上的Safari,还有iPad上的。

啊,好建议。在浏览器中,base64编码的图片显示正常。但是,如果我只是将该字符串保存到数据库,再取出来发送回浏览器,它就被截断了。我添加了代码来对从浏览器发送的数据进行校验和,它也因意外的EOF而失败。它以POST请求的形式到达服务器,我使用

request.ParseForm()
p.DataBodyMap = request.Form

来填充url.Values,它是一个map[string][]string。在浏览器提交和request.ParseForm()之间的某个环节,base64字符串被截断了。

是的——据我所知,没有计划进行破坏性变更。如果你查看 ioutil.ReadAll,它只是调用了现在更推荐的 io 版本:

// ReadAll reads from r until an error or EOF and returns the data it read.
// A successful call returns err == nil, not err == EOF. Because ReadAll is
// defined to read from src until EOF, it does not treat an EOF from Read
// as an error to be reported.
//
// As of Go 1.16, this function simply calls io.ReadAll.
func ReadAll(r io.Reader) ([]byte, error) {
	return io.ReadAll(r)
}

根据文档(强调部分是我的):

自 Go 1.16 起,相同的功能现在由 io 包或 os 包提供,在新代码中应优先使用这些实现。详情请参阅具体函数的文档。

我在项目中遇到 ioutil 时会慢慢将其重构掉,但我并没有将其作为优先事项,因为没有迫切的需要(甚至没有非迫切的需要!只是轻微的偏好)。

好的,现在可以正常工作了。以下是代码:

case http.MethodPost:
		contentType := request.Header.Get("Content-Type")
		if strings.HasPrefix(contentType, "multipart/form-data") {
			p.DataBodyMap = make(url.Values, 0)
			request.ParseMultipartForm(1000)
			for key, value := range request.MultipartForm.Value {
				p.DataBodyMap[key] = value
			}
			photoFile, header, err := request.FormFile("photoBytesString")
			if err == nil {
				log.Println(header, err)
				photoContentType := header.Header["Content-Type"][0]
				log.Println(photoContentType)

				photoBytes, _ := ioutil.ReadAll(photoFile)
				log.Println("len(photoBytes): ", len(photoBytes))
				encodedImage64 := base64.StdEncoding.EncodeToString(photoBytes)
				encodedImage64WithPrefix := "data:" + photoContentType + ";base64," + encodedImage64
				p.DataBodyMap["photoBytesString"] = []string{encodedImage64WithPrefix}
			}

这段代码工作正常,没有截断任何数据。

啊,好建议。在浏览器中,base64编码的图片显示正常。但是,如果我只是将该字符串保存到数据库,再取出来发送回浏览器,它就被截断了。我添加了代码来对从浏览器发送的数据进行校验和,它也因意外的EOF而失败。它是作为POST请求到达服务器的,我使用了…

现在,当你说“将字符串保存到数据库”时,你指的是什么?你用的是哪种数据库?你试图保存base64编码图片的列的数据类型是什么?你确定问题出在请求上,而不是你的数据库里吗?

谢谢,很高兴知道限制是多少,但我的图片都没有那么大。我目前正在测试的图片(在浏览器中显示正常)不到4MB。当它到达服务器时,大小是524288字节。这就解释了为什么当它被发送回浏览器时,只显示了大约顶部10%的部分。我没有使用MaxBytesReader,所以限制应该是10MB。我在想是不是浏览器限制了发送的数据量。我使用的是Mac和iPad上的Safari。

我有一个处理图片上传/调整大小的生产应用程序,我们经常处理更大的图片。浏览器限制上传大小的可能性也极小。在我作为Web开发人员的这些年里,我遇到过很多奇怪的浏览器问题,但这对我来说是新的。

你能展示一下你的HTML表单以及服务器端处理上传的代码吗?你使用的是multipart/form-data吗?

这是一个已知的 JPEG 解码问题,主要出现在某些特定编码的 JPEG 图片上。问题通常与 JPEG 文件的渐进式编码或某些元数据格式有关。以下是解决方案和示例代码:

解决方案1:使用 image.Decode 替代 jpeg.Decode

import (
    "bytes"
    "encoding/base64"
    "image"
    _ "image/jpeg" // 注册JPEG解码器
    "io"
)

func decodeImage(imageData string) (image.Image, error) {
    buf := bytes.NewBufferString(imageData)
    reader := base64.NewDecoder(base64.StdEncoding, buf)
    
    // 先获取配置信息
    config, format, err := image.DecodeConfig(reader)
    if err != nil {
        return nil, err
    }
    log.Printf("Format: %s, Width: %d, Height: %d", format, config.Width, config.Height)
    
    // 重新创建reader进行完整解码
    buf = bytes.NewBufferString(imageData)
    reader = base64.NewDecoder(base64.StdEncoding, buf)
    
    img, _, err := image.Decode(reader)
    return img, err
}

解决方案2:使用 io.ReadAll 读取完整数据

import (
    "bytes"
    "encoding/base64"
    "image"
    "image/jpeg"
    "io"
)

func decodeImageRobust(imageData string) (image.Image, error) {
    // 解码base64
    data, err := base64.StdEncoding.DecodeString(imageData)
    if err != nil {
        return nil, err
    }
    
    // 使用bytes.Reader而不是bytes.Buffer
    reader := bytes.NewReader(data)
    
    // 尝试解码配置
    config, err := jpeg.DecodeConfig(reader)
    if err != nil {
        return nil, err
    }
    
    // 重置reader位置
    reader.Seek(0, io.SeekStart)
    
    // 解码完整图片
    return jpeg.Decode(reader)
}

解决方案3:添加错误恢复机制

import (
    "bytes"
    "encoding/base64"
    "image"
    "image/jpeg"
    "io"
)

func decodeImageWithRetry(imageData string, maxRetries int) (image.Image, error) {
    data, err := base64.StdEncoding.DecodeString(imageData)
    if err != nil {
        return nil, err
    }
    
    var img image.Image
    var decodeErr error
    
    for i := 0; i < maxRetries; i++ {
        reader := bytes.NewReader(data)
        img, decodeErr = jpeg.Decode(reader)
        if decodeErr == nil {
            return img, nil
        }
        
        // 如果是EOF错误,尝试不同的方法
        if decodeErr == io.EOF || decodeErr == io.ErrUnexpectedEOF {
            // 尝试使用image.Decode
            reader.Seek(0, io.SeekStart)
            img, _, decodeErr = image.Decode(reader)
            if decodeErr == nil {
                return img, nil
            }
        }
    }
    
    return nil, decodeErr
}

解决方案4:使用第三方库处理问题图片

import (
    "bytes"
    "encoding/base64"
    "image"
    "github.com/disintegration/imaging" // 第三方图像处理库
)

func decodeWithImaging(imageData string) (image.Image, error) {
    data, err := base64.StdEncoding.DecodeString(imageData)
    if err != nil {
        return nil, err
    }
    
    // 使用imaging库解码,它内部有更好的错误处理
    img, err := imaging.Decode(bytes.NewReader(data))
    if err != nil {
        return nil, err
    }
    
    return img, nil
}

关键点说明:

  1. image.Decodejpeg.Decode 更健壮,因为它会尝试所有注册的解码器
  2. 使用 bytes.Reader 而不是 bytes.Buffer 可以更好地控制读取位置
  3. 某些JPEG文件可能包含损坏的元数据或使用非标准的编码方式
  4. 渐进式JPEG在某些情况下可能导致解码问题

对于你的具体代码,建议修改为:

var decodedImage image.Image
var err error
if fileType == "jpg" {
    // 解码base64数据
    data, err := base64.StdEncoding.DecodeString(imageData)
    if err != nil {
        log.Println("base64 decode error: ", err)
        return err
    }
    
    reader := bytes.NewReader(data)
    
    // 使用image.DecodeConfig替代jpeg.DecodeConfig
    config, format, err := image.DecodeConfig(reader)
    if err != nil {
        log.Println("image.DecodeConfig error: ", err)
        return err
    }
    log.Printf("Format: %s, Width: %d, Height: %d", format, config.Width, config.Height)
    
    // 重置reader并解码
    reader.Seek(0, io.SeekStart)
    decodedImage, _, err = image.Decode(reader)
    if err != nil {
        log.Println("image.Decode failed error: ", err)
        return err
    }
}

这个问题确实在Go社区中被多次报告,主要影响某些特定编码的JPEG文件。使用上述方法可以显著提高解码成功率。

回到顶部