prism

package module
v0.35.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 3, 2023 License: MIT Imports: 4 Imported by: 5

README

prism

PkgGoDev Go Report Card Build Status

prism aims to become a set of utilities for practical colour management and conversion in pure Go.

prism currently implements:

  • Encoding/decoding linear colour from sRGB, Adobe RGB, Pro Photo RGB, and Display P3 encodings
  • Fast LUT-based tonal response encoding/decoding
  • Conversion to and from CIE xyY, CIE XYZ, and CIE Lab
  • Chromatic adaptation in XYZ space between different white points
  • Extracting metadata (including ICC profile) from PNG, JPEG, and WebP files

Still missing:

  • Embedding of tagged colour profiles in image
  • Exposing colour data from ICC profiles (to enable conversions between arbitrary profiles)
  • Rendering intent support
  • CMYK support

See the API documentation for more details.

This software is made available under an MIT license.

Much of this implementation is based on information provided by Bruce Lindbloom and Charles Poynton, among many others who generously contribute to public edification on the esoteric science of colour.

Rationale

Using the analogy of working with strings, a colour space is to colour encoding what a character set is to character encoding.

Image data provided by the standard image package doesn’t come with colour space information, yet colour values from the images are invariably encoded in some scheme specific to the colour space that the image is targeting. And as with character encodings, programmers then routinely take these RGB (for example) values and make incorrect assumptions about how they’ve been encoded, leading to software that sort of mostly works in common cases but fails in important ways (like supporting non-English languages or Emoji).

prism can be used to decode these colour values into a normalised, linear representation more suitable for image processing, and subsequently converting those back to encoded colour values in (potentially) other colour spaces.

sRGB is the Web standard; why do I still need to worry about this?

This is like asking why we need to worry about UTF-8 when ASCII is the standard, or why we need to worry about other fonts when Times New Roman is the standard. However, there are two prominent reasons:

1. Wide gamut imaging is becoming commonplace

sRGB is a narrow gamut colour space. Smartphones, computing devices, and other displays (including everything marketed under “HDR” consumer labels) increasingly use wider gamuts, and are capable of reproducing much more saturation than sRGB can represent. Images produced on these displays, taking advantage of the wider gamuts, will look incorrect when naively interpreted as sRGB.

The following example shows an image targeting Adobe RGB (a wide gamut colour space commonly used by artists and photographers; left) and what happens when the same image is incorrectly assumed to be sRGB (right). Note the loss of saturation—a common complaint with images uploaded to social media or other sites. The bright, saturated topping has become much more dull and unappetising, and the whole image has gained an unpleasant greenish cast:

Example of incorrectly interpreting an Adobe RGB image as sRGB

This is not a deficiency of sRGB. This image is well within the sRGB gamut, and a correct interpretation will look just like the version on the left (indeed, this example figure itself is actually sRGB).

Another way of stating this problem is that sRGB being the Web standard makes it the default, but doesn’t preclude other, significantly different colour spaces from common use.

2. sRGB uses a non-linear tonal response curve

For efficiency and fidelity, nearly all colour encoding schemes (sRGB or otherwise) are set up to be perceptually linear. But because our eyes don’t perceive brightness linearly, this means the colour values are not linear in intensity, so sRGB(127, 127, 127) is not actually half as bright as sRGB(255, 255, 255).

The following example shows an image being resampled to half size, without and with corrected linear colour. The corrected example shows that the checkerboard patches are consistent in brightness with the solid patches after resizing, while the resized patches in the incorrect example end up as different colours:

Example of correct and incorrect resampling

Since many image manipulation operations (such as scaling, sharpening, or blending) rely on colour values having linear intensity, applying them to non-linear colour data produces visual artefacts and generally incorrect results.

Another way of stating this problem is that colour values in images are encoded (sometimes referred to as “gamma encoding” or “gamma correction”), and need to be decoded rather than used directly.

Example usage

Metadata extraction

Image metadata can be extracted from images without needing to consume the entire image stream. Currently, this is supported for JPEG and PNG data. The following example demonstrates this using autometa.Load:

// Get a meta.Data instance containing image details and a stream to the full image
md, imgStream, err := autometa.Load(inFile)
if err != nil {
    panic(err)
}

// The metadata specifies the image format
imgFormat := md.Format // eg. jpeg.Format

// Load the full image after extracting metadata
img, err = jpeg.Decode(imgStream)
if err != nil {
    panic(err)
}

Included in the metadata are basic details about the image such as the pixel dimensions and colour depth:

fmt.Printf("Image format: %s\n", md.Format)
fmt.Printf("Image height in pixels: %d\n", md.PixelHeight)
fmt.Printf("Image width in pixels: %d\n", md.PixelWidth)
fmt.Printf("Bits per component: %d\n", md.BitsPerComponent)

The stream returned by autometa.Load reproduces the full image stream, so that it can be later passed to (for example) jpeg.Decode to load the rest of the image. This allows information like the size of the image to be known before having to load an extremely large image.

If the image contained an ICC profile, it can be retrieved from the metadata:

iccProfile, err := md.ICCProfile()
description, err := iccProfile.Description()  // eg. "sRGB IEC61966-2.1"

If no profile exists, nil is returned without an error.

autometa.Load delegates to format-specific loaders like jpegmeta.Load and pngmeta.Load; these can be used instead if you know the format of image.

Colour linearisation

An image can be easily converted from its colour space encoding (eg sRGB) to a linear encoding. Because this operation can be lossy in 8-bit colour depths, it’s a good idea to first convert images to 16-bit colour (eg instances of image.NRGBA64 or image.RGBA64). prism provides utility functions for such conversions:

img = prism.ConvertImageToRGBA64(img, parallelism)

Then the image can be linearised (here, using itself as both source and destination):

srgb.LineariseImage(img, img, parallelism)

Alternatively a new blank 16-bit image can be created and the original image linearised into it:

linearisedImg := image.NewRGBA64(img.Bounds())
srgb.LineariseImage(linearisedImg, img, parallelism)

The image can then be passed to operations that expect an image.Image but assume linear colour. Here we pass it to the BiLinear rescaler to reduce the image to half its original size, which will now produce a correct result in linear space:

resampled := image.NewRGBA64(image.Rect(0, 0, img.Rect.Dx()/2, img.Rect.Dy()/2))
draw.BiLinear.Scale(resampled, resampled.Rect, img, img.Bounds(), draw.Src, nil)

Note that the output is still linearised, so before writing the image to an output file (eg in PNG or JPEG format), we need to re-encode it back to sRGB space, and probably also want to convert it back to 8-bit colour:

encodedImg := image.NewRGBA(resampled.Bounds())
srgb.EncodeImage(encodedImg, resampled, parallelism)
Colour conversion

Conversions between RGB colour spaces are performed via the CIE XYZ intermediate colour space (using the ToXYZ and ColorFromXYZ functions).

The following example converts Adobe RGB (1998) pixel data to sRGB. It retrieves a pixel from an NRGBA image, decodes it to an Adobe RGB (1998) linearised colour value, then converts that to an sRGB colour value via CIE XYZ, before finally encoding the result as an 8-bit sRGB value suitable for writing back to an image.NRGBA:

c := inputImg.NRGBAAt(x, y)                 // Take input colour value
ac, alpha := adobergb.ColorFromNRGBA(c)     // Interpret image pixel as Adobe RGB and convert to linear representation
sc := srgb.ColorFromXYZ(ac.ToXYZ())         // Convert to XYZ, then from XYZ to sRGB linear representation
outputImg.SetNRGBA(x, y, sc.ToNRGBA(alpha)) // Write sRGB-encoded value to output image
Chromatic adaptation

Adobe RGB (1998) and sRGB are both specified referring to a standard D65 white point. However, Pro Photo RGB references a D50 white point. When converting between white points, a chromatic adaptation is required to compensate for a shift in warmness/coolness that would otherwise occur.

The following example prepares such a chromatic adaptation (using the AdaptBetweenXYYWhitePoints function), then uses it in converting from Pro Photo RGB to sRGB:

adaptation := ciexyz.AdaptBetweenXYYWhitePoints(
    prophotorgb.StandardWhitePoint,         // From D50
    srgb.StandardWhitePoint,                // To D65
)

c := inputImg.NRGBAAt(x, y)                 // Take input colour value
pc, alpha := prophotorgb.ColorFromNRGBA(c)  // Interpret image pixel as Pro Photo RGB and convert to linear representation

xyz := pc.ToXYZ()                           // Convert from Pro Photo RGB to CIE XYZ
xyz = adaptation.Apply(xyz)                 // Apply chromatic adaptation from D50 to D65

sc := srgb.ColorFromXYZ(xyz)                // Convert from CIE XYZ to sRGB linear representation
outputImg.SetNRGBA(x, y, sc.ToNRGBA(alpha)) // Write sRGB-encoded value to output image

Documentation

Overview

Package prism provides a set of tools for colour management and conversion. Subpackages provide support for encoding/decoding image pixel data in specific colour spaces, and conversions between those spaces.

Example (ConvertAdobeRGBToSRGB)
package main

import (
	"fmt"
	"github.com/mandykoh/prism"
	"github.com/mandykoh/prism/adobergb"
	"github.com/mandykoh/prism/srgb"
	"image"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"runtime"

	_ "image/jpeg"

	_ "image/png"
)

func loadImage(path string) *image.NRGBA {
	imgFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

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

	return prism.ConvertImageToNRGBA(img, runtime.NumCPU())
}

func compare(img1, img2 *image.NRGBA, threshold int) float64 {
	diffCount := 0

	for i := img1.Rect.Min.Y; i < img1.Rect.Max.Y; i++ {
		for j := img1.Rect.Min.X; j < img1.Rect.Max.X; j++ {
			c1 := img1.NRGBAAt(j, i)
			d1 := [4]int{int(c1.R), int(c1.G), int(c1.B), int(c1.A)}

			c2 := img2.NRGBAAt(j, i)
			d2 := [4]int{int(c2.R), int(c2.G), int(c2.B), int(c2.A)}

			diff := 0
			for k := range d1 {
				if d1[k] > d2[k] {
					diff += d1[k] - d2[k]
				} else {
					diff += d2[k] - d1[k]
				}
			}

			if diff > threshold {
				diffCount++
			}
		}
	}

	return float64(diffCount) / float64(img1.Rect.Dx()*img1.Rect.Dy())
}

func writeImage(path string, img image.Image) {
	_ = os.MkdirAll(filepath.Dir(path), os.ModePerm)

	imgFile, err := os.Create(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

	err = png.Encode(imgFile, img)
	if err != nil {
		panic(err)
	}

	log.Printf("Output written to %s", path)
}

func main() {
	referenceImg := loadImage("test-images/pizza-rgb8-srgb.jpg")
	inputImg := loadImage("test-images/pizza-rgb8-adobergb.jpg")

	convertedImg := image.NewNRGBA(inputImg.Rect)
	for i := inputImg.Rect.Min.Y; i < inputImg.Rect.Max.Y; i++ {
		for j := inputImg.Rect.Min.X; j < inputImg.Rect.Max.X; j++ {
			inCol, alpha := adobergb.ColorFromNRGBA(inputImg.NRGBAAt(j, i))
			outCol := srgb.ColorFromXYZ(inCol.ToXYZ())
			convertedImg.SetNRGBA(j, i, outCol.ToNRGBA(alpha))
		}
	}

	writeImage("example-output/adobergb-to-srgb.png", convertedImg)

	if difference := compare(convertedImg, referenceImg, 5); difference > 0.01 {
		fmt.Printf("Images differ by %.2f%% of pixels exceeding difference threshold", difference*100)
	} else {
		fmt.Printf("Images match")
	}

}
Output:

Images match
Example (ConvertDisplayP3ToSRGB)
package main

import (
	"fmt"
	"github.com/mandykoh/prism"
	"github.com/mandykoh/prism/displayp3"
	"github.com/mandykoh/prism/srgb"
	"image"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"runtime"

	_ "image/jpeg"

	_ "image/png"
)

func loadImage(path string) *image.NRGBA {
	imgFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

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

	return prism.ConvertImageToNRGBA(img, runtime.NumCPU())
}

func compare(img1, img2 *image.NRGBA, threshold int) float64 {
	diffCount := 0

	for i := img1.Rect.Min.Y; i < img1.Rect.Max.Y; i++ {
		for j := img1.Rect.Min.X; j < img1.Rect.Max.X; j++ {
			c1 := img1.NRGBAAt(j, i)
			d1 := [4]int{int(c1.R), int(c1.G), int(c1.B), int(c1.A)}

			c2 := img2.NRGBAAt(j, i)
			d2 := [4]int{int(c2.R), int(c2.G), int(c2.B), int(c2.A)}

			diff := 0
			for k := range d1 {
				if d1[k] > d2[k] {
					diff += d1[k] - d2[k]
				} else {
					diff += d2[k] - d1[k]
				}
			}

			if diff > threshold {
				diffCount++
			}
		}
	}

	return float64(diffCount) / float64(img1.Rect.Dx()*img1.Rect.Dy())
}

func writeImage(path string, img image.Image) {
	_ = os.MkdirAll(filepath.Dir(path), os.ModePerm)

	imgFile, err := os.Create(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

	err = png.Encode(imgFile, img)
	if err != nil {
		panic(err)
	}

	log.Printf("Output written to %s", path)
}

func main() {
	referenceImg := loadImage("test-images/pizza-rgb8-srgb.jpg")
	inputImg := loadImage("test-images/pizza-rgb8-displayp3.jpg")

	convertedImg := image.NewNRGBA(inputImg.Rect)
	for i := inputImg.Rect.Min.Y; i < inputImg.Rect.Max.Y; i++ {
		for j := inputImg.Rect.Min.X; j < inputImg.Rect.Max.X; j++ {
			inCol, alpha := displayp3.ColorFromNRGBA(inputImg.NRGBAAt(j, i))
			outCol := srgb.ColorFromXYZ(inCol.ToXYZ())
			convertedImg.SetNRGBA(j, i, outCol.ToNRGBA(alpha))
		}
	}

	writeImage("example-output/displayp3-to-srgb.png", convertedImg)

	if difference := compare(convertedImg, referenceImg, 5); difference > 0.005 {
		fmt.Printf("Images differ by %.2f%% of pixels exceeding difference threshold", difference*100)
	} else {
		fmt.Printf("Images match")
	}

}
Output:

Images match
Example (ConvertProPhotoRGBToSRGB)
package main

import (
	"fmt"
	"github.com/mandykoh/prism"
	"github.com/mandykoh/prism/ciexyz"
	"github.com/mandykoh/prism/prophotorgb"
	"github.com/mandykoh/prism/srgb"
	"image"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"runtime"

	_ "image/jpeg"

	_ "image/png"
)

func loadImage(path string) *image.NRGBA {
	imgFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

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

	return prism.ConvertImageToNRGBA(img, runtime.NumCPU())
}

func compare(img1, img2 *image.NRGBA, threshold int) float64 {
	diffCount := 0

	for i := img1.Rect.Min.Y; i < img1.Rect.Max.Y; i++ {
		for j := img1.Rect.Min.X; j < img1.Rect.Max.X; j++ {
			c1 := img1.NRGBAAt(j, i)
			d1 := [4]int{int(c1.R), int(c1.G), int(c1.B), int(c1.A)}

			c2 := img2.NRGBAAt(j, i)
			d2 := [4]int{int(c2.R), int(c2.G), int(c2.B), int(c2.A)}

			diff := 0
			for k := range d1 {
				if d1[k] > d2[k] {
					diff += d1[k] - d2[k]
				} else {
					diff += d2[k] - d1[k]
				}
			}

			if diff > threshold {
				diffCount++
			}
		}
	}

	return float64(diffCount) / float64(img1.Rect.Dx()*img1.Rect.Dy())
}

func writeImage(path string, img image.Image) {
	_ = os.MkdirAll(filepath.Dir(path), os.ModePerm)

	imgFile, err := os.Create(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

	err = png.Encode(imgFile, img)
	if err != nil {
		panic(err)
	}

	log.Printf("Output written to %s", path)
}

func main() {
	referenceImg := loadImage("test-images/pizza-rgb8-srgb.jpg")
	inputImg := loadImage("test-images/pizza-rgb8-prophotorgb.jpg")

	adaptation := ciexyz.AdaptBetweenXYYWhitePoints(
		prophotorgb.StandardWhitePoint,
		srgb.StandardWhitePoint,
	)

	convertedImg := image.NewNRGBA(inputImg.Rect)
	for i := inputImg.Rect.Min.Y; i < inputImg.Rect.Max.Y; i++ {
		for j := inputImg.Rect.Min.X; j < inputImg.Rect.Max.X; j++ {
			inCol, alpha := prophotorgb.ColorFromNRGBA(inputImg.NRGBAAt(j, i))

			xyz := inCol.ToXYZ()
			xyz = adaptation.Apply(xyz)

			outCol := srgb.ColorFromXYZ(xyz)
			convertedImg.SetNRGBA(j, i, outCol.ToNRGBA(alpha))
		}
	}

	writeImage("example-output/prophotorgb-to-srgb.png", convertedImg)

	if difference := compare(convertedImg, referenceImg, 5); difference > 0.015 {
		fmt.Printf("Images differ by %.2f%% of pixels exceeding difference threshold", difference*100)
	} else {
		fmt.Printf("Images match")
	}

}
Output:

Images match
Example (ConvertSRGBToAdobeRGB)
package main

import (
	"fmt"
	"github.com/mandykoh/prism"
	"github.com/mandykoh/prism/adobergb"
	"github.com/mandykoh/prism/srgb"
	"image"
	"os"
	"runtime"

	_ "image/jpeg"

	_ "image/png"
)

func loadImage(path string) *image.NRGBA {
	imgFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

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

	return prism.ConvertImageToNRGBA(img, runtime.NumCPU())
}

func compare(img1, img2 *image.NRGBA, threshold int) float64 {
	diffCount := 0

	for i := img1.Rect.Min.Y; i < img1.Rect.Max.Y; i++ {
		for j := img1.Rect.Min.X; j < img1.Rect.Max.X; j++ {
			c1 := img1.NRGBAAt(j, i)
			d1 := [4]int{int(c1.R), int(c1.G), int(c1.B), int(c1.A)}

			c2 := img2.NRGBAAt(j, i)
			d2 := [4]int{int(c2.R), int(c2.G), int(c2.B), int(c2.A)}

			diff := 0
			for k := range d1 {
				if d1[k] > d2[k] {
					diff += d1[k] - d2[k]
				} else {
					diff += d2[k] - d1[k]
				}
			}

			if diff > threshold {
				diffCount++
			}
		}
	}

	return float64(diffCount) / float64(img1.Rect.Dx()*img1.Rect.Dy())
}

func main() {
	referenceImg := loadImage("test-images/pizza-rgb8-adobergb.jpg")
	inputImg := loadImage("test-images/pizza-rgb8-srgb.jpg")

	convertedImg := image.NewNRGBA(inputImg.Rect)
	for i := inputImg.Rect.Min.Y; i < inputImg.Rect.Max.Y; i++ {
		for j := inputImg.Rect.Min.X; j < inputImg.Rect.Max.X; j++ {
			inCol, alpha := srgb.ColorFromNRGBA(inputImg.NRGBAAt(j, i))
			outCol := adobergb.ColorFromXYZ(inCol.ToXYZ())
			convertedImg.SetNRGBA(j, i, outCol.ToNRGBA(alpha))
		}
	}

	// Output will be written without an embedded colour profile (software used
	// to examine this image will assume sRGB unless told otherwise).
	//writeImage("example-output/srgb-to-adobergb.png", convertedImg)

	if difference := compare(convertedImg, referenceImg, 4); difference > 0.01 {
		fmt.Printf("Images differ by %.2f%% of pixels exceeding difference threshold", difference*100)
	} else {
		fmt.Printf("Images match")
	}

}
Output:

Images match
Example (LinearisedResampling)
package main

import (
	"github.com/mandykoh/prism"
	"github.com/mandykoh/prism/srgb"
	"golang.org/x/image/draw"
	"image"
	"image/png"
	"log"
	"os"
	"path/filepath"
	"runtime"

	_ "image/jpeg"

	_ "image/png"
)

func loadImage(path string) *image.NRGBA {
	imgFile, err := os.Open(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

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

	return prism.ConvertImageToNRGBA(img, runtime.NumCPU())
}

func writeImage(path string, img image.Image) {
	_ = os.MkdirAll(filepath.Dir(path), os.ModePerm)

	imgFile, err := os.Create(path)
	if err != nil {
		panic(err)
	}
	defer imgFile.Close()

	err = png.Encode(imgFile, img)
	if err != nil {
		panic(err)
	}

	log.Printf("Output written to %s", path)
}

func main() {
	img := loadImage("test-images/checkerboard-srgb.png")

	rgba64 := image.NewRGBA64(img.Bounds())
	srgb.LineariseImage(rgba64, img, runtime.NumCPU())

	resampled := image.NewNRGBA64(image.Rect(0, 0, rgba64.Rect.Dx()/2, rgba64.Rect.Dy()/2))
	draw.BiLinear.Scale(resampled, resampled.Rect, rgba64, rgba64.Rect, draw.Src, nil)

	rgba := image.NewRGBA(resampled.Rect)
	srgb.EncodeImage(rgba, resampled, runtime.NumCPU())

	writeImage("example-output/checkerboard-resampled.png", rgba)

}
Output:

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConvertImageToNRGBA added in v0.27.0

func ConvertImageToNRGBA(img image.Image, parallelism int) *image.NRGBA

ConvertImageToNRGBA is a convenience function for getting an NRGBA image from any image. If the specified image isn’t already NRGBA, a conversion is performed.

parallelism specifies the maximum degree of parallel processing; a value of 4 indicates that processing may be spread across up to four threads. However, this is not guaranteed as not all conversions are parallelised.

func ConvertImageToRGBA added in v0.27.0

func ConvertImageToRGBA(img image.Image, parallelism int) *image.RGBA

ConvertImageToRGBA is a convenience function for getting an RGBA image from any image. If the specified image isn’t already RGBA, a conversion is performed.

parallelism specifies the maximum degree of parallel processing; a value of 4 indicates that processing may be spread across up to four threads. However, this is not guaranteed as not all conversions are parallelised.

func ConvertImageToRGBA64 added in v0.27.0

func ConvertImageToRGBA64(img image.Image, parallelism int) *image.RGBA64

ConvertImageToRGBA64 is a convenience function for getting an RGBA64 image from any image. If the specified image isn’t already RGBA64, a conversion is performed.

parallelism specifies the maximum degree of parallel processing; a value of 4 indicates that processing may be spread across up to four threads. However, this is not guaranteed as not all conversions are parallelised.

Types

This section is empty.

Directories

Path Synopsis
Package adobergb provides support for the Adobe RGB (1998) colour space.
Package adobergb provides support for the Adobe RGB (1998) colour space.
Package cielab provides support for the CIE Lab colour space.
Package cielab provides support for the CIE Lab colour space.
Package ciexyy provides support for the CIE xyY colour space.
Package ciexyy provides support for the CIE xyY colour space.
Package ciexyz provides support for the CIE XYZ colour space.
Package ciexyz provides support for the CIE XYZ colour space.
Package displayp3 provides support for the Display P3 colour space.
Package displayp3 provides support for the Display P3 colour space.
Package linear provides support for working with linearised colour.
Package linear provides support for working with linearised colour.
lut
Package matrix provides support for common matrix maths operations.
Package matrix provides support for common matrix maths operations.
Package meta and its subpackages provide support for embedded image metadata.
Package meta and its subpackages provide support for embedded image metadata.
autometa
Package autometa provides support for embedded metadata and automatic detection of image formats.
Package autometa provides support for embedded metadata and automatic detection of image formats.
binary
Package binary provides common utilities for working with binary data streams.
Package binary provides common utilities for working with binary data streams.
icc
Package icc provides support for working with ICC colour profile data.
Package icc provides support for working with ICC colour profile data.
jpegmeta
Package jpegmeta provides support for working with embedded JPEG metadata.
Package jpegmeta provides support for working with embedded JPEG metadata.
pngmeta
Package pngmeta provides support for working with embedded PNG metadata.
Package pngmeta provides support for working with embedded PNG metadata.
webpmeta
Package webpmeta provides support for working with embedded WebP metadata.
Package webpmeta provides support for working with embedded WebP metadata.
Package prophotorgb provides support for the Pro Photo RGB colour space.
Package prophotorgb provides support for the Pro Photo RGB colour space.
Package srgb provides support for the sRGB colour space.
Package srgb provides support for the sRGB colour space.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL