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