go 建议:图像:解码选项

xfyts7mz  于 4个月前  发布在  Go
关注(0)|答案(4)|浏览(104)

动机
当前的 Decode 函数在 image 包中提供了一种配置底层格式解码器的方法。image 使用的注册系统使得扩展已注册格式处理程序的解码行为变得困难。然而,有许多可以由少量解码选项解决的开放问题。本提案描述了一种扩展现有的格式注册系统的方法,以便将选项传递给各个格式解码器。它引入了一个 DecodeOptions 类型作为扩展点。
有两个广泛考虑的配置领域。本提案解决了这两个领域,但它们是正交的,可以独立评估和实现。这些领域是:

避免无法处理的图像上的大分配

这类问题通常是由图像文件中的错误或恶意头信息引起的。通常这可能是一个非常大的 x 或 y 维度,导致在准备读取一系列像素数据流之前分配大量缓冲区。目前可以通过首先解码图像配置来检查尺寸,然后再进行完整解码来缓解这个问题。但是,这有一个缺点,即要么需要两次读取输入,要么缓冲并重新读取以进行解码。相关问题的建议表明,使用 16k 缓冲区是合适的。以下问题与此用例相关(#5050 是总体问题)

  • 5050 - image/gif:解码不受信任(非常大的)图像可能会导致巨大的内存分配
  • 10790 - x/image/webp:过度消耗内存
  • 12512 - image/png:限制内存 Decode 在解码过程中可以使用多少内存
  • 10399 - x/image/bmp:内存不足

对无效输入更加宽容

标准库中的图像解码器严格且对无效输入失败。有一些类型的无效输入可能对于某些用途是可以接受的。以下问题建议采用宽松的解码模式会有所帮助:

  • 10447 - image/jpeg:向部分解码或容忍性解码无效图像添加选项?
  • 20804 - image/gif:解码 gif 返回 unknown block type: 0x01 错误
  • 20899 - image/png:解码位图失败
  • 20856 - image/gif:解码 gif 返回 frame bounds larger than image bounds 错误

其他相关选项问题

此外,以下问题建议其他领域可以从解码选项中受益,但在本提案中未进一步考虑:

  • 8055 - image:将图像解码/调整大小到现有缓冲区
  • 18098 - proposal:为 image/jpeg、image/png 等添加验证函数
  • 4341 - image/jpeg:纠正 EXIF 方向?
  • 18365 - image/png:不支持设置和检索 PPI/DPI

包更改
主要的可扩展点是一个新类型。其字段将在本节末尾讨论。

type DecodeOptions struct

格式解码器通过一个新的包级函数 RegisterFormatDecoder 进行注册。

func RegisterFormatDecoder(f FormatDecoder)

任何提供图像格式解码的包都需要实现 FormatDecoder 接口:

type FormatDecoder interface {
	// Name is the name of the format, like "jpeg" or "png".
	Name() string

	// Prefix is the magic prefix that identifies the format's encoding. The magic
	// string can contain "?" wildcards that each match any one byte.
	Prefix() string

	// Decode decodes an image using the supplied options.
	Decode(r io.Reader, o *DecodeOptions) (Image, string, error)

	// DecodeConfig decodes the color model and dimensions of an image using the supplied options.
	DecodeConfig(r io.Reader, o *DecodeOptions) (Config, string, error)
}

引入了一个带有基本构造函数函数的新导出 Decoder 类型:

type Decoder struct {
	DecodeOptions
}

这些 Decode 方法将从阅读器的类型中嗅探图像,并查找相应的已注册 FormatDecoder。如果有可用的,则调用其相应的 Decode 方法,传入 Reader 和 Decoder 的选项。这要求 Decode 具有访问包级注册的格式解码器的能力。
要配置解码,开发人员将创建一个新的 Decoder,然后在调用 Decode 或 DecodeConfig 之前设置适当的字段。
以下是提议的选项。

MaxHeight 和 MaxWidth

MaxHeight 和 MaxWidth 是 DecodeOptions 字段,用于设置解码图像的最大允许尺寸。这些字段限制了为故障或恶意图像分配大量内存的可能性。
这些选项仅由 Decode 方法使用。当一个解码器尝试解码具有超过 MaxHeight 或 MaxWidth 的尺寸的图像时,应停止处理并返回错误。可以在图像包 ErrDimensionsExceeded 中定义一个新的错误标准,或者让解码器返回其自己的错误。DecodeConfig 应根据图像数据流描述的宽度和高度返回一个包含配置信息的 Config,而不考虑配置选项。

ReturnPartial

ReturnPartial 是 DecodeOptions 字段,指示解码器在遇到错误时可能返回部分解码的图像。
当前的 Decode 行为是在出现错误时不返回任何图像数据。当此选项为 true 时,当解码器在解码过程中遇到与格式相关的错误时,调用者可能会从解码过程中接收到部分图像。
现有的包级 DecodeDecodeConfig 函数将被重写,以创建一个解码器并调用它,而不使用任何选项:

func Decode(r io.Reader) (Image, string, error) {
    return NewDecoder().Decode(r)
}

func DecodeConfig(r io.Reader) (Config, string, error) {
    return NewDecoder().DecodeConfig(r)
}

RegisterFormat 的弃用

RegisterFormat 将被标记为弃用,以便使用 RegisterFormatDecoder。

tjvv9vkg

tjvv9vkg2#

整体目标似乎还不错。以下是一些主要和次要的想法:
我建议从 image/* 开始分叉,并在向标准库的 API 更改之前证明概念。例如,API 提案仍然不允许我们在解码渐进式 JPEG 时看到中间阶段,当压缩字节缓慢到来时。有一个新的 ReturnPartial 选项被提议,但据我所知,只有在我们看到错误时才会触发,而不会在我们只是被 io.Reader 阻塞时触发。
我们现在不必解决渐进式 JPEG 的情况,但我犹豫是否要在 Go 1.12 中承诺一个使 Go 1.16 更难解决该情况的 API。一种可能的设计方法是使用更像 text.Transformer 而不是 io.Reader 的东西。
这就是我在我的 Wuffs 项目中采用的方法。虽然有点难以理解,但这就是 Wuffs io_buffer_meta 类型的样子。没有阻塞 I/O,协程会将部分解码的图像yield给调用者。
这种 Transformer 类似的方法,允许 读取字节,也可能导致显著的速度提升。有一些优化技术,例如 C 的 libjpeg 可以这样做,而 Go 的 image/jpeg 不能这样做,因为 C 代码可以乐观地读取太多字节,稍后取消这些它不需要的字节。相比之下,Go 的 io.Reader 接口和 Decode(r io.Reader) (image.Image, string, error) 函数类型没有地方说“对不起,我从 r 读取了太多字节,这里是更改”。
但是使用类似于 Transformer 而不是 io.Reader 的方法是一个重大的变化,也比 Go 的习惯用法“使用一个 io.Reader 并阻塞在 I/O 上,因为 goroutines 是廉价的”更复杂。尝试使用这样的方法应该真的开始在 stdlib 之外进行,但也可以说保持在 stdlib 之外,作为 x/image2std2/imageiand/image 或其他什么。打破向后兼容性的约束也可能意味着能够将默认字节顺序从 RGBA 更改为 BGRA,或者以其他方式更适合 BGRA,这将使与 Windows 和 X11 GUI 的原生格式集成更容易,而无需例如每帧都相当昂贵的 swizzling
最后的一个小问题。我认为如果客户端可以使用 Decoder{} 结构体字面量,我们就不需要 NewDecoder 函数。

mwg9r5ms

mwg9r5ms3#

感谢nigeltao的评论。我会对读者的观点做更多的研究。看起来@dsnet关于#23154的评论是相关的,尤其是他提到的BufferedReader类型。

von4xj4u

von4xj4u4#

请将此标记为待定,直到@nigeltao和@iand完成更多研究。一旦准备好进行更多审查,请随时删除标签。

相关问题