{"id":36656866,"url":"https://github.com/mandykoh/prism","last_synced_at":"2026-01-12T10:20:50.417Z","repository":{"id":44576765,"uuid":"282759042","full_name":"mandykoh/prism","owner":"mandykoh","description":"Colour management for Go","archived":false,"fork":false,"pushed_at":"2024-06-27T04:12:00.000Z","size":3351,"stargazers_count":47,"open_issues_count":4,"forks_count":7,"subscribers_count":4,"default_branch":"main","last_synced_at":"2024-06-27T05:49:01.555Z","etag":null,"topics":["color","colour-management","image-processing"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mandykoh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2020-07-27T00:41:42.000Z","updated_at":"2024-06-27T04:09:46.000Z","dependencies_parsed_at":"2022-09-11T19:10:49.952Z","dependency_job_id":"b82fc29f-856d-486e-9086-2385fc548635","html_url":"https://github.com/mandykoh/prism","commit_stats":null,"previous_names":[],"tags_count":69,"template":false,"template_full_name":null,"purl":"pkg:github/mandykoh/prism","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mandykoh%2Fprism","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mandykoh%2Fprism/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mandykoh%2Fprism/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mandykoh%2Fprism/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mandykoh","download_url":"https://codeload.github.com/mandykoh/prism/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mandykoh%2Fprism/sbom","scorecard":{"id":614855,"data":{"date":"2025-08-11","repo":{"name":"github.com/mandykoh/prism","commit":"4f1db6f00119efb2a16f70807663e150dbd8f517"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.5,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":1,"reason":"Found 3/17 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/test.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/mandykoh/prism/test.yml/main?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/test.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/mandykoh/prism/test.yml/main?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 18 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-21T03:37:54.661Z","repository_id":44576765,"created_at":"2025-08-21T03:37:54.661Z","updated_at":"2025-08-21T03:37:54.661Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28338165,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-12T06:09:07.588Z","status":"ssl_error","status_checked_at":"2026-01-12T06:05:18.301Z","response_time":98,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["color","colour-management","image-processing"],"created_at":"2026-01-12T10:20:47.788Z","updated_at":"2026-01-12T10:20:50.235Z","avatar_url":"https://github.com/mandykoh.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# prism\n\n[![PkgGoDev](https://pkg.go.dev/badge/github.com/mandykoh/prism)](https://pkg.go.dev/github.com/mandykoh/prism)\n[![Go Report Card](https://goreportcard.com/badge/github.com/mandykoh/prism)](https://goreportcard.com/report/github.com/mandykoh/prism)\n[![Build Status](https://travis-ci.org/mandykoh/prism.svg?branch=main)](https://travis-ci.org/mandykoh/prism)\n\n`prism` aims to become a set of utilities for practical colour management and conversion in pure Go.\n\n`prism` currently implements:\n\n* Encoding/decoding linear colour from sRGB, Adobe RGB, Pro Photo RGB, and Display P3 encodings\n* Fast LUT-based tonal response encoding/decoding\n* Conversion to and from CIE xyY, CIE XYZ, and CIE Lab\n* Chromatic adaptation in XYZ space between different white points\n* Extracting metadata (including ICC profile) from PNG, JPEG, and WebP files\n\nStill missing:\n\n* Embedding of tagged colour profiles in image\n* Exposing colour data from ICC profiles (to enable conversions between arbitrary profiles)\n* Rendering intent support\n* CMYK support\n\nSee the [API documentation](https://pkg.go.dev/github.com/mandykoh/prism) for more details.\n\nThis software is made available under an [MIT license](LICENSE).\n\nMuch of this implementation is based on information provided by [Bruce Lindbloom](http://www.brucelindbloom.com) and [Charles Poynton](http://poynton.ca), among many others who generously contribute to public edification on the esoteric science of colour.\n\n\n## Rationale\n\nUsing the analogy of working with strings, a _colour space_ is to _colour encoding_ what a character set is to character encoding.\n\nImage data provided by the standard [`image`](https://golang.org/pkg/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).\n\n`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.\n\n\n### sRGB is the Web standard; why do I still need to worry about this?\n\nThis 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:\n\n\n#### 1. Wide gamut imaging is becoming commonplace\n\nsRGB 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.\n\nThe 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:\n\n![Example of incorrectly interpreting an Adobe RGB image as sRGB](doc-images/example-bad-conversion.png)\n\n_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).\n\nAnother 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.\n\n\n#### 2. sRGB uses a non-linear tonal response curve\n\nFor 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](https://blog.johnnovak.net/2016/09/21/what-every-coder-should-know-about-gamma/), so sRGB(127, 127, 127) is not actually half as bright as sRGB(255, 255, 255).\n\nThe 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:\n\n![Example of correct and incorrect resampling](doc-images/example-resampling.png)\n\nSince 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.\n\nAnother 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.\n\n\n## Example usage\n\n\n### Metadata extraction\n\nImage 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`](https://pkg.go.dev/github.com/mandykoh/prism/meta/autometa?tab=doc#Load):\n\n```go\n// Get a meta.Data instance containing image details and a stream to the full image\nmd, imgStream, err := autometa.Load(inFile)\nif err != nil {\n    panic(err)\n}\n\n// The metadata specifies the image format\nimgFormat := md.Format // eg. jpeg.Format\n\n// Load the full image after extracting metadata\nimg, err = jpeg.Decode(imgStream)\nif err != nil {\n    panic(err)\n}\n```\n\nIncluded in the metadata are basic details about the image such as the pixel dimensions and colour depth:\n\n```go\nfmt.Printf(\"Image format: %s\\n\", md.Format)\nfmt.Printf(\"Image height in pixels: %d\\n\", md.PixelHeight)\nfmt.Printf(\"Image width in pixels: %d\\n\", md.PixelWidth)\nfmt.Printf(\"Bits per component: %d\\n\", md.BitsPerComponent)\n```\n\nThe 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.\n\nIf the image contained an ICC profile, it can be retrieved from the metadata:\n\n```go\niccProfile, err := md.ICCProfile()\ndescription, err := iccProfile.Description()  // eg. \"sRGB IEC61966-2.1\"\n```\n\nIf no profile exists, `nil` is returned without an error.\n\n`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.\n\n\n### Colour linearisation\n\nAn 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:\n\n```go\nimg = prism.ConvertImageToRGBA64(img, parallelism)\n```\n\nThen the image can be linearised (here, using itself as both source and destination):\n\n```go\nsrgb.LineariseImage(img, img, parallelism)\n```\n\nAlternatively a new blank 16-bit image can be created and the original image linearised into it:\n\n```go\nlinearisedImg := image.NewRGBA64(img.Bounds())\nsrgb.LineariseImage(linearisedImg, img, parallelism)\n```\n\nThe 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:\n\n```go\nresampled := image.NewRGBA64(image.Rect(0, 0, img.Rect.Dx()/2, img.Rect.Dy()/2))\ndraw.BiLinear.Scale(resampled, resampled.Rect, img, img.Bounds(), draw.Src, nil)\n```\n\nNote 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:\n\n```go\nencodedImg := image.NewRGBA(resampled.Bounds())\nsrgb.EncodeImage(encodedImg, resampled, parallelism)\n```\n\n\n### Colour conversion\n\nConversions between RGB colour spaces are performed via the CIE XYZ intermediate colour space (using the `ToXYZ` and `ColorFromXYZ` functions).\n\nThe following example converts Adobe RGB (1998) pixel data to sRGB. It retrieves a pixel from an [NRGBA image](https://golang.org/pkg/image/#NRGBA), 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`:\n\n```go\nc := inputImg.NRGBAAt(x, y)                 // Take input colour value\nac, alpha := adobergb.ColorFromNRGBA(c)     // Interpret image pixel as Adobe RGB and convert to linear representation\nsc := srgb.ColorFromXYZ(ac.ToXYZ())         // Convert to XYZ, then from XYZ to sRGB linear representation\noutputImg.SetNRGBA(x, y, sc.ToNRGBA(alpha)) // Write sRGB-encoded value to output image\n```\n\n\n### Chromatic adaptation\n\nAdobe 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.\n\nThe following example prepares such a chromatic adaptation (using the [`AdaptBetweenXYYWhitePoints`](https://pkg.go.dev/github.com/mandykoh/prism/ciexyz?tab=doc#AdaptBetweenXYYWhitePoints) function), then uses it in converting from Pro Photo RGB to sRGB:\n\n```go\nadaptation := ciexyz.AdaptBetweenXYYWhitePoints(\n    prophotorgb.StandardWhitePoint,         // From D50\n    srgb.StandardWhitePoint,                // To D65\n)\n\nc := inputImg.NRGBAAt(x, y)                 // Take input colour value\npc, alpha := prophotorgb.ColorFromNRGBA(c)  // Interpret image pixel as Pro Photo RGB and convert to linear representation\n\nxyz := pc.ToXYZ()                           // Convert from Pro Photo RGB to CIE XYZ\nxyz = adaptation.Apply(xyz)                 // Apply chromatic adaptation from D50 to D65\n\nsc := srgb.ColorFromXYZ(xyz)                // Convert from CIE XYZ to sRGB linear representation\noutputImg.SetNRGBA(x, y, sc.ToNRGBA(alpha)) // Write sRGB-encoded value to output image\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmandykoh%2Fprism","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmandykoh%2Fprism","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmandykoh%2Fprism/lists"}