Proposal
Currently the standard image libraries provide no way to read or write most of the metadata associated with an image. I'd like to change that so that the standard image loading libraries surface the metadata in images they read and allow code to annotate images with metadata.
Each image format has its own metadata. Additionally there are two common image metadata formats (eXif and XMP) which are each used in multiple image formats. (XMP is used in GIF, JPEG, and PNG files, while eXif is used in PNG and JPEG files) The formats themselves also have some interesting quirks which make excessive cleverness unwise. (There are three(!) separate key/value stores attached to PNG files, for example, and the format doesn't disallow a key being used multiple times in the different, or even the same, kv store)
With this in mind, the three guiding principles behind this proposal are:
- Metadata should be minimally processed on read and write
- It should be possible to read an image and immediately write it out in the same format, with the output having semantically identical metadata to the original.
- It should be drop-in compatible with the existing image interfaces.
Point 1 means that PNG's three key/value stores will be exposed as three separate stores, and can't be a plain string/string map. It also means that code which wishes to pull information out of a PNG needs to know to look in both the XMP and eXif data.
Point 2 means we need to at least record any metadata that we don't currently know how to interpret so it can be written back out, and the images that are returned from decoding an image need to have its metadata silently attached.
Point 3 means we can't add methods to existing interfaces, since it's possible there are other packages that implement them. It also means we can't alter the calling conventions of RegisterFormat() in image.
This proposal should make it possible for third-party image libraries to participate in the new standard if they choose.
This proposal has some decisions which are either potentially controversial or potentially fragile. The ones I can think of are noted in the Potential Issues section
NOTE: "Metadata" means any data not directly related to the pixels in an image on disk. For example PPI and rotation, for example, are metadata, while alpha channel is not. (Rotation is arguably pixel data the way that alpha channel is, but since it massively affects the translation of disk pixels to image pixels we'll classify it as metadata for now)
Changes to current packages
Some of the standard packages will be changed. The changes are backwards-compatible.
image
- New registration function
RegisterFormatExtended
type Format struct {
Name string
MagicString string
Decode func(io.Reader) (Image, error)
DecodeConfig func(io.Reader) (Image, error)
DecodeMetadata func(io.Reader) (*metadata.Metadata, error)
GetMetadata func(Image) (applies bool, *metadata.Metadata, error)
}
RegisterFormatExtended(*Format)
The new registration format includes a function to decode the metatada, as well as a format-specific function to return the parsed metadata for an image.
RegisterFormat marked deprecated
It will still work, but internally will just construct a Format struct and call RegisterFormatExtended.
GetMetadata added as registered function
This function will be passed an Image. Because there's no way to tell what type the image is, the metadata getting code must iterate through each format's get function. The function sets applies to false if it doesn't apply. Iteration short-circuits at the first function that says it does apply.
image/gif
- Metadata type, and supporting types, added
type Metadata struct {
Comment []string
XMPMetadata *xmp.XMP
AppBlocks []AppBlock
}
type AppBlock struct {
AppID [8]byte
AppAuthCode [3]byte
BlockData []byte
}
- Metadata added to
Options
type Options struct {
NumColors int
Quantizer draw.Quantizer
Drawer draw.Drawer
Metadata *Metadata // Added
}
image/png
- Metadata added. Note that for the initial proposal not all the chunks are proposed to be exported. Any chunk the library can't process is stored in the
unexported field, which is only accessible to the library's reader.
type Metadata struct {
KVText []struct{string, string}
CompressedKVText []struct{string, string}
UnicodeKVText []struct{string, string}
ChangeTime *time.Time
XMPMetadata *xmp.XMP
ExifMetadata *exif.Exif
unexported map[string][]byte // For currently uninterpreted chunks
}
Encode function gets optional Options parameter:
func Encode(w io.Writer, m image.Image, o *Options) error
type Options {
Metadata *Metadata
}
image/jpeg
type Metadata struct {
ExifMetadata *exif.Exif
XMPMetadata *xmp.XMP
unexported []byte // For other uninterpreted data
}
- Metadata added to
Options struct
type Options struct {
Quality int
Metadata *Metadata
}
New packages
We add a new general-purpose metadata package to hold the metadata get function. We also add packages for the two shared metadata formats, XMP and eXif.
images/metadata
interface Metadata {
// Make sure the Metadata struct is actually valid, as each format has
// its own quirks and restrictions.
Validate() error
// Here mostly to disambiguate from all the other interfaces with just
// Validate in them.
IsImageLibraryMetadata()
}
// Returns the filetype-specific metadata. Type-switch to tell what kind.
Get(image.Image) (*Metadata, error)
Get will internally iterate through the registered GetMetadata functions until it finds one that matches.
The Validate method allows code to make sure the metadata struct is valid for the format before writing it out. This is important since many of the fields have quirks and limitations -- for example the key in png's key/value store must be ISO 8859-1 and between 1 and 79 characters.
images/metadata/exif
The exif package decodes metadata in eXif format.
type Exif {
// Handwave on the internals until the proposal is generally deemed OK
}
// Decode a binary blob, without any leading or trailing markers, as exif
func Decode([]byte) (*Exif, error)
images/metadata/xmp
The xmp package decodes metadata in XMP format. (Or, if you prefer, ISO 16684-1:2019 as it's an official ISO standard)
type XMP {
// Handwave on the internals until the proposal is generally deemed OK
}
// Decode a binary blob, without leading or trailing markers, as xmp
func Decode([]byte) (*XMP, error)
Potential Issues
The proposal has some decisions which are either potentially controversial or potentially fragile. They are noted
Iterating through metadata get functions is fragile
I can't think of a better way, without having the libraries tag the returned Image with a type, which the API doesn't support that I can see.
Adding an optional *Options parameter to png.Encode
This has to be done by actually adding a ...*Options parameter to the signature and then complaining if multiple ones are passed. This requires runtime signature validation, which isn't great
Adding *Metadata to existing Options parameters may cause unexpected behaviour
jpeg.Options has an integer quality parameter, so it can't actually be left off. We'd have to interpret 0 == default. gif.Options has an integer color count parameter, and we'd have to interpret 0 == default.
No access to uninterpreted metadata fields and chunks
This is intentional. It means that writers can't add their own chunks to the output, and it means that readers can't implement their own interpretation code. This is definitely inconvenient, but it means that when the additional chunks are interpreted by the standard library there won't potentially be two ways to access the same data.
Once the implementation is deemed complete then read/write access to these additional chunks shoud be added.
Optional metadata entites are stored as pointers to things.
This allows us to interpret nil pointers as elided entities and not require a HasFoo functions for each element. Not everyone is fond of this style.
No translation between image format metadata
If you read a jpeg in, then write it as a gif, the metadata isn't translated. This is intentional, since there's currently no clear 1:1 translation between file format metadata information.
Proposal
Currently the standard image libraries provide no way to read or write most of the metadata associated with an image. I'd like to change that so that the standard image loading libraries surface the metadata in images they read and allow code to annotate images with metadata.
Each image format has its own metadata. Additionally there are two common image metadata formats (eXif and XMP) which are each used in multiple image formats. (XMP is used in GIF, JPEG, and PNG files, while eXif is used in PNG and JPEG files) The formats themselves also have some interesting quirks which make excessive cleverness unwise. (There are three(!) separate key/value stores attached to PNG files, for example, and the format doesn't disallow a key being used multiple times in the different, or even the same, kv store)
With this in mind, the three guiding principles behind this proposal are:
Point 1 means that PNG's three key/value stores will be exposed as three separate stores, and can't be a plain string/string map. It also means that code which wishes to pull information out of a PNG needs to know to look in both the XMP and eXif data.
Point 2 means we need to at least record any metadata that we don't currently know how to interpret so it can be written back out, and the images that are returned from decoding an image need to have its metadata silently attached.
Point 3 means we can't add methods to existing interfaces, since it's possible there are other packages that implement them. It also means we can't alter the calling conventions of
RegisterFormat()in image.This proposal should make it possible for third-party image libraries to participate in the new standard if they choose.
This proposal has some decisions which are either potentially controversial or potentially fragile. The ones I can think of are noted in the Potential Issues section
NOTE: "Metadata" means any data not directly related to the pixels in an image on disk. For example PPI and rotation, for example, are metadata, while alpha channel is not. (Rotation is arguably pixel data the way that alpha channel is, but since it massively affects the translation of disk pixels to image pixels we'll classify it as metadata for now)
Changes to current packages
Some of the standard packages will be changed. The changes are backwards-compatible.
image
RegisterFormatExtendedThe new registration format includes a function to decode the metatada, as well as a format-specific function to return the parsed metadata for an image.
RegisterFormatmarked deprecatedIt will still work, but internally will just construct a
Formatstruct and callRegisterFormatExtended.GetMetadataadded as registered functionThis function will be passed an Image. Because there's no way to tell what type the image is, the metadata getting code must iterate through each format's get function. The function sets
appliesto false if it doesn't apply. Iteration short-circuits at the first function that says it does apply.image/gif
Optionsimage/png
unexportedfield, which is only accessible to the library's reader.Encodefunction gets optionalOptionsparameter:image/jpeg
OptionsstructNew packages
We add a new general-purpose metadata package to hold the metadata get function. We also add packages for the two shared metadata formats, XMP and eXif.
images/metadata
Getwill internally iterate through the registeredGetMetadatafunctions until it finds one that matches.The
Validatemethod allows code to make sure the metadata struct is valid for the format before writing it out. This is important since many of the fields have quirks and limitations -- for example the key in png's key/value store must be ISO 8859-1 and between 1 and 79 characters.images/metadata/exif
The exif package decodes metadata in eXif format.
images/metadata/xmp
The xmp package decodes metadata in XMP format. (Or, if you prefer, ISO 16684-1:2019 as it's an official ISO standard)
Potential Issues
The proposal has some decisions which are either potentially controversial or potentially fragile. They are noted
Iterating through metadata get functions is fragile
I can't think of a better way, without having the libraries tag the returned Image with a type, which the API doesn't support that I can see.
Adding an optional
*Optionsparameter topng.EncodeThis has to be done by actually adding a ...*Options parameter to the signature and then complaining if multiple ones are passed. This requires runtime signature validation, which isn't great
Adding
*Metadatato existingOptionsparameters may cause unexpected behaviourjpeg.Optionshas an integer quality parameter, so it can't actually be left off. We'd have to interpret 0 == default.gif.Optionshas an integer color count parameter, and we'd have to interpret 0 == default.No access to uninterpreted metadata fields and chunks
This is intentional. It means that writers can't add their own chunks to the output, and it means that readers can't implement their own interpretation code. This is definitely inconvenient, but it means that when the additional chunks are interpreted by the standard library there won't potentially be two ways to access the same data.
Once the implementation is deemed complete then read/write access to these additional chunks shoud be added.
Optional metadata entites are stored as pointers to things.
This allows us to interpret
nilpointers as elided entities and not require aHasFoofunctions for each element. Not everyone is fond of this style.No translation between image format metadata
If you read a jpeg in, then write it as a gif, the metadata isn't translated. This is intentional, since there's currently no clear 1:1 translation between file format metadata information.