prism

package
v1.7.2 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2025 License: MIT Imports: 4 Imported by: 0

README

This code is based on code from two other projects, https://github.com/mandykoh/prism (mainly image file format parsing, MIT license) and http://github.com/go-andiamo/iccarus (mainly ICC file parsing, Apache 2.0 license)

The code from these is modified and optimised and fixed with a large number of changes.

It is imported so that optimised conversion routes for imaging.NRGB can be written, which would cause a circular dependency otherwise.

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/kovidgoyal/imaging/prism"
	"github.com/kovidgoyal/imaging/prism/adobergb"
	"github.com/kovidgoyal/imaging/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-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))
		}
	}

	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/kovidgoyal/imaging/prism"
	"github.com/kovidgoyal/imaging/prism/displayp3"
	"github.com/kovidgoyal/imaging/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-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))
		}
	}

	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/kovidgoyal/imaging/prism"
	"github.com/kovidgoyal/imaging/prism/ciexyz"
	"github.com/kovidgoyal/imaging/prism/prophotorgb"
	"github.com/kovidgoyal/imaging/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-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))
		}
	}

	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/kovidgoyal/imaging/prism"
	"github.com/kovidgoyal/imaging/prism/adobergb"
	"github.com/kovidgoyal/imaging/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).

	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")
	}
}
Example (LinearisedResampling)
package main

import (
	"github.com/kovidgoyal/imaging/prism"
	"github.com/kovidgoyal/imaging/prism/srgb"
	"golang.org/x/image/draw"
	"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 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())
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ConvertImageToNRGBA

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

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

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.
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