Initial commit.
This commit is contained in:
		
							
								
								
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								LICENSE
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,202 @@ | ||||
|  | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|    You may obtain a copy of the License at | ||||
|  | ||||
|        http://www.apache.org/licenses/LICENSE-2.0 | ||||
|  | ||||
|    Unless required by applicable law or agreed to in writing, software | ||||
|    distributed under the License is distributed on an "AS IS" BASIS, | ||||
|    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|    See the License for the specific language governing permissions and | ||||
|    limitations under the License. | ||||
							
								
								
									
										174
									
								
								oklab.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								oklab.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| // Based on: https://bottosson.github.io/posts/oklab/ | ||||
| // | ||||
| // OkLab first defines a transform from xyz, and multiplies by this matrix: | ||||
| // [+0.8189330101, +0.3618667424, -0.1288597137] | ||||
| // [+0.0329845436, +0.9293118715, +0.0361456387] | ||||
| // [+0.0482003018, +0.2643662691, +0.633851707 ] | ||||
| // | ||||
| // Wikipedia lists this matrix for converting from linear sRGB to D65 CIE XYZ, so | ||||
| // I'm considering it canonical: | ||||
| // [+0.4124, +0.3576, +0.1805] | ||||
| // [+0.2126, +0.7152, +0.0722] | ||||
| // [+0.0193, +0.1192, +0.9505] | ||||
| // | ||||
| // Combined, we get: | ||||
| // [+0.41217385032507, +0.5362974607032, +0.05146302925248] | ||||
| // [+0.21187214048845, +0.6807476834212, +0.10740645682645] | ||||
| // [+0.08831541121808, +0.2818663070584, +0.63026344660742] | ||||
| // | ||||
| // And its inverse: | ||||
| // [+4.0767584135565013494237930518854,    -3.3072279873944731418619352916485,  +0.230721459944885632473018834049 ] | ||||
| // [-1.2681810851624033989047813181437,    +2.6092932102856398573991970933594,  -0.3411121165477535569679616041822] | ||||
| // [-0.0040984077180314400491332639337372, -0.70350366010241732765095902557887, +1.7068604529788013559365593912662] | ||||
| // | ||||
| // After a non-linear transformation (cube root), OkLab applies a second matrix: | ||||
| // [+0.2104542553, +0.793617785,  -0.0040720468] | ||||
| // [+1.9779984951, -2.428592205,  +0.4505937099] | ||||
| // [+0.0259040371, +0.7827717662, -0.808675766 ] | ||||
| // | ||||
| // And its inverse: | ||||
| // [+0.99999999845051981426207542502031, +0.39633779217376785682345989261573,  +0.21580375806075880342314146183004 ] | ||||
| // [+1.0000000088817607767160752456705,  -0.10556134232365634941095687705472,  -0.063854174771705903405254198817796] | ||||
| // [+1.0000000546724109177012928651534,  -0.089484182094965759689052745863391, -1.2914855378640917399489287529148  ] | ||||
| // | ||||
| // It should be noted that the numbers in the first column were supposed to be 1, and other implementations skip | ||||
| // multiplying by that column altogether. I however am insane, and shall continue barging forward. | ||||
|  | ||||
| package oklab | ||||
|  | ||||
| import ( | ||||
| 	"image/color" | ||||
| 	"math" | ||||
| ) | ||||
|  | ||||
| type Color struct { | ||||
| 	Lightness, ChromaA, ChromaB, A float64 | ||||
| } | ||||
|  | ||||
| func linearize(c uint32) float64 { | ||||
| 	l := float64(c) / 0xffff | ||||
|  | ||||
| 	if l <= 0.039285714285714285714285714285714 { | ||||
| 		return l / 12.923210180787861094641554898407 | ||||
| 	} | ||||
|  | ||||
| 	return math.Pow((l+0.055)/1.055, 2.4) | ||||
| } | ||||
|  | ||||
| func delinearize(l float64) uint32 { | ||||
| 	switch { | ||||
| 	case l <= 0: | ||||
| 		return 0 | ||||
| 	case l <= 0.0030399346397784299969770436366690: | ||||
| 		return uint32(l*846922.57919793247683733430026710 + 0.5) | ||||
| 	case l >= 1: | ||||
| 		return 0xffff | ||||
| 	default: | ||||
| 		return uint32(69139.425*math.Pow(l, 1/2.4) - 3603.925) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FromNRGBA create an OkLab color from non-pre-multiplied RGBA. | ||||
| func FromNRGBA(r, g, b, a uint32) Color { | ||||
| 	rLin, gLin, bLin := linearize(r), linearize(g), linearize(b) | ||||
|  | ||||
| 	l := math.Cbrt(0.41217385032507*rLin + 0.5362974607032*gLin + 0.05146302925248*bLin) | ||||
| 	m := math.Cbrt(0.21187214048845*rLin + 0.6807476834212*gLin + 0.10740645682645*bLin) | ||||
| 	s := math.Cbrt(0.08831541121808*rLin + 0.2818663070584*gLin + 0.63026344660742*bLin) | ||||
|  | ||||
| 	return Color{ | ||||
| 		0.2104542553*l + 0.793617785*m - 0.0040720468*s, | ||||
| 		1.9779984951*l - 2.428592205*m + 0.4505937099*s, | ||||
| 		0.0259040371*l + 0.7827717662*m - 0.808675766*s, | ||||
| 		float64(a) / float64(0xffff), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FromRGBA create an OkLab color from pre-multiplied RGBA. | ||||
| func FromRGBA(r, g, b, a uint32) Color { | ||||
| 	switch a { | ||||
| 	case 0xffff: | ||||
| 		// do nothing. | ||||
| 	case 0: | ||||
| 		// completely transparent, color information was lost. | ||||
| 		// pretend it was gray. | ||||
| 		r, g, b = 0x7fff, 0x7fff, 0x7fff | ||||
| 	default: | ||||
| 		// un-premultiply rgb. | ||||
| 		// | ||||
| 		// Note that I'm rounding up here, which is the opposite of what the NRGBA/NRGBA64 colors do, | ||||
| 		// which may be a bug as RGBA64->NRGBA64->RGBA64 is lossy. | ||||
| 		r = (r*0xffff + a - 1) / a | ||||
| 		g = (g*0xffff + a - 1) / a | ||||
| 		b = (b*0xffff + a - 1) / a | ||||
| 	} | ||||
|  | ||||
| 	return FromNRGBA(r, g, b, a) | ||||
| } | ||||
|  | ||||
| func cube(v float64) float64 { | ||||
| 	return v * v * v | ||||
| } | ||||
|  | ||||
| // NRGBA converts to non-premultiplied RGBA. | ||||
| func (c Color) NRGBA() (r, g, b, a uint32) { | ||||
| 	l := cube(0.99999999845051981426207542502031*c.Lightness + 0.39633779217376785682345989261573*c.ChromaA + 0.21580375806075880342314146183004*c.ChromaB) | ||||
| 	m := cube(1.0000000088817607767160752456705*c.Lightness - 0.10556134232365634941095687705472*c.ChromaA - 0.063854174771705903405254198817796*c.ChromaB) | ||||
| 	s := cube(1.0000000546724109177012928651534*c.Lightness - 0.089484182094965759689052745863391*c.ChromaA - 1.2914855378640917399489287529148*c.ChromaB) | ||||
|  | ||||
| 	r = delinearize(4.0767584135565013494237930518854*l - 3.3072279873944731418619352916485*m + 0.230721459944885632473018834049*s) | ||||
| 	g = delinearize(-1.2681810851624033989047813181437*l + 2.6092932102856398573991970933594*m - 0.3411121165477535569679616041822*s) | ||||
| 	b = delinearize(-0.0040984077180314400491332639337372*l - 0.70350366010241732765095902557887*m + 1.7068604529788013559365593912662*s) | ||||
| 	a = uint32(c.A*0xffff + 0.5) | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| // RGBA converts to premultiplied RGBA. | ||||
| func (c Color) RGBA() (r, g, b, a uint32) { | ||||
| 	r, g, b, a = c.NRGBA() | ||||
|  | ||||
| 	r = r * a / 0xffff | ||||
| 	g = g * a / 0xffff | ||||
| 	b = b * a / 0xffff | ||||
|  | ||||
| 	return | ||||
| } | ||||
|  | ||||
| func sqr(a float64) float64 { | ||||
| 	return a * a | ||||
| } | ||||
|  | ||||
| func Distance(a, b Color) float64 { | ||||
| 	dL := a.Lightness*a.A - b.Lightness*b.A | ||||
| 	da := a.ChromaA*a.A - b.ChromaA*b.A | ||||
| 	db := a.ChromaB*a.A - b.ChromaB*b.A | ||||
| 	dA := a.A - b.A | ||||
|  | ||||
| 	return math.Sqrt(max(sqr(dL), sqr(dL+dA)) + max(sqr(da), sqr(da+dA)) + max(sqr(db), sqr(db+dA))) | ||||
| 	//return math.Sqrt((sqr(dL)+sqr(da)+sqr(db))*(a.A*b.A) + sqr(dA)) | ||||
| } | ||||
|  | ||||
| func okLabModel(c color.Color) color.Color { | ||||
| 	switch c := c.(type) { | ||||
| 	case Color: | ||||
| 		return c | ||||
|  | ||||
| 	// Special handling for color.NRGBA and color.NRGBA64 | ||||
| 	case color.NRGBA: | ||||
| 		return FromNRGBA(uint32(c.R)*0x101, uint32(c.G)*0x101, uint32(c.B)*0x101, uint32(c.A)*0x101) | ||||
|  | ||||
| 	case color.NRGBA64: | ||||
| 		return FromNRGBA(uint32(c.R), uint32(c.G), uint32(c.B), uint32(c.A)) | ||||
|  | ||||
| 	// This isn't a standard interface, but I'm going to check for it regardless. | ||||
| 	case interface{ NRGBA() (r, g, b, a uint32) }: | ||||
| 		return FromNRGBA(c.NRGBA()) | ||||
|  | ||||
| 	default: | ||||
| 		return FromRGBA(c.RGBA()) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Implements a color model for converting arbitrary colors to OKLab. | ||||
| var Model = color.ModelFunc(okLabModel) | ||||
							
								
								
									
										195
									
								
								oklab_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										195
									
								
								oklab_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,195 @@ | ||||
| package oklab | ||||
|  | ||||
| import ( | ||||
| 	"image/color" | ||||
| 	"math" | ||||
| 	"reflect" | ||||
| 	"testing" | ||||
| ) | ||||
|  | ||||
| func TestColor_NRGBA(t *testing.T) { | ||||
| 	// Not checking the whole space because that would be slow, and alpha is given less resolution in this test. | ||||
| 	// The increments were chosen because they're factors of 0xffff, so we'll end on the maximum value. | ||||
| 	for a := uint32(0); a < 0x10000; a += 4369 { | ||||
| 		for b := uint32(0); b < 0x10000; b += 1285 { | ||||
| 			for g := uint32(0); g < 0x10000; g += 1285 { | ||||
| 				for r := uint32(0); r < 0x10000; r += 1285 { | ||||
| 					_r, _g, _b, _a := FromNRGBA(r, g, b, a).NRGBA() | ||||
|  | ||||
| 					if r != _r || g != _g || b != _b || a != _a { | ||||
| 						t.Errorf("NRGBA(0x%04x,0x%04x,0x%04x,0x%04x) -> OkLab -> NRGBA(0x%04x,0x%04x,0x%04x,0x%04x)", r, g, b, a, _r, _g, _b, _a) | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestColor_RGBA(t *testing.T) { | ||||
| 	// This is similar to the NRGBA test, except the components step by a/17 instead of a fixed 1285. | ||||
| 	// a/17 was chosen because 17 is a factor of 4369, so the components will end equalling a. | ||||
| 	for a := uint32(0); a < 0x10000; a += 4369 { | ||||
| 		step := max(1, a/17) | ||||
| 		for b := uint32(0); b <= a; b += step { | ||||
| 			for g := uint32(0); g <= a; g += step { | ||||
| 				for r := uint32(0); r <= a; r += step { | ||||
| 					_r, _g, _b, _a := FromRGBA(r, g, b, a).RGBA() | ||||
|  | ||||
| 					if r != _r || g != _g || b != _b || a != _a { | ||||
| 						t.Errorf("RGBA(0x%04x,0x%04x,0x%04x,0x%04x) -> OkLab -> RGBA(0x%04x,0x%04x,0x%04x,0x%04x)", r, g, b, a, _r, _g, _b, _a) | ||||
| 						return | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func Test_delinearize(t *testing.T) { | ||||
| 	for c := uint32(0); c < 0x10000; c++ { | ||||
| 		got := delinearize(linearize(c)) | ||||
| 		if got != c { | ||||
| 			t.Errorf("delinearize(linearize(0x%04x)) != 0x%04x", c, got) | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // The NRGBA and NRGBA64 models don't have sensible ways to recover transparent colors from types it | ||||
| // doesn't know about, so I'm going to help them out. | ||||
| func fixedNRGBAModel(c color.Color) color.Color { | ||||
| 	if c, ok := c.(interface{ NRGBA() (r, g, b, a uint32) }); ok { | ||||
| 		r, g, b, a := c.NRGBA() | ||||
| 		return color.NRGBA{R: uint8(r >> 8), G: uint8(g >> 8), B: uint8(b >> 8), A: uint8(a >> 8)} | ||||
| 	} | ||||
|  | ||||
| 	return color.NRGBAModel.Convert(c) | ||||
| } | ||||
|  | ||||
| func fixedNRGBA64Model(c color.Color) color.Color { | ||||
| 	if c, ok := c.(interface{ NRGBA() (r, g, b, a uint32) }); ok { | ||||
| 		r, g, b, a := c.NRGBA() | ||||
| 		return color.NRGBA64{R: uint16(r), G: uint16(g), B: uint16(b), A: uint16(a)} | ||||
| 	} | ||||
|  | ||||
| 	return color.NRGBA64Model.Convert(c) | ||||
| } | ||||
|  | ||||
| func Test_Model(t *testing.T) { | ||||
| 	// test to make sure we can reproduce some test colors. | ||||
| 	// In particular, I want to make sure the colors can be recovered from transparent NRGBA and NRGBA64. | ||||
| 	for _, tt := range [...]struct { | ||||
| 		name  string | ||||
| 		color color.Color | ||||
| 		model color.Model | ||||
| 	}{ | ||||
| 		{ | ||||
| 			"OkLab: nop", | ||||
| 			Color{math.Inf(1), math.Inf(-1), math.MaxFloat64, 0}, | ||||
| 			Model, | ||||
| 		}, { | ||||
| 			"NRGBA: opaque Sapphire", | ||||
| 			color.NRGBA{R: 0x0f, G: 0x52, B: 0xba, A: 0xff}, | ||||
| 			color.ModelFunc(fixedNRGBAModel), | ||||
| 		}, { | ||||
| 			"NRGBA: translucent Orchid", | ||||
| 			color.NRGBA{R: 0xda, G: 0x70, B: 0xd6, A: 0x7f}, | ||||
| 			color.ModelFunc(fixedNRGBAModel), | ||||
| 		}, { | ||||
| 			"NRGBA: invisible Floral White", | ||||
| 			color.NRGBA{R: 0xff, G: 0xfa, B: 0xf0, A: 0x00}, | ||||
| 			color.ModelFunc(fixedNRGBAModel), | ||||
| 		}, { | ||||
| 			"NRGBA64: opaque Amethyst", | ||||
| 			color.NRGBA64{R: 0x9999, G: 0x6666, B: 0xcccc, A: 0xffff}, | ||||
| 			color.ModelFunc(fixedNRGBA64Model), | ||||
| 		}, { | ||||
| 			"NRGBA64: translucent Smoke", | ||||
| 			color.NRGBA64{R: 0xf5f5, G: 0xf5f5, B: 0xf5f5, A: 0x7fff}, | ||||
| 			color.ModelFunc(fixedNRGBA64Model), | ||||
| 		}, { | ||||
| 			"NRGBA64: invisible Emerald", | ||||
| 			color.NRGBA64{R: 0x5050, G: 0xC8C8, B: 0x7878, A: 0x0000}, | ||||
| 			color.ModelFunc(fixedNRGBA64Model), | ||||
| 		}, { | ||||
| 			"RGBA64: opaque Chartreuse", | ||||
| 			color.RGBA64{R: 0xb2b2, G: 0xd6d6, B: 0x3f3f, A: 0xffff}, | ||||
| 			color.RGBA64Model, | ||||
| 		}, { | ||||
| 			"RGBA64: transparent Mauveine", | ||||
| 			color.RGBA64{R: 0x577c, G: 0x013e, B: 0x602b, A: 0x9e37}, | ||||
| 			color.RGBA64Model, | ||||
| 		}, { | ||||
| 			"RGBA64: invisible nothing", | ||||
| 			color.RGBA64{R: 0x0, G: 0x0, B: 0x0, A: 0x0}, | ||||
| 			color.RGBA64Model, | ||||
| 		}, { | ||||
| 			"RGBA: opaque Chartreuse", | ||||
| 			color.RGBA{R: 0xb2, G: 0xd6, B: 0x3f, A: 0xff}, | ||||
| 			color.RGBAModel, | ||||
| 		}, { | ||||
| 			"RGBA: transparent Mauveine", | ||||
| 			color.RGBA{R: 0x57, G: 0x01, B: 0x60, A: 0x9e}, | ||||
| 			color.RGBAModel, | ||||
| 		}, { | ||||
| 			"RGBA: invisible nothing", | ||||
| 			color.RGBA{R: 0x0, G: 0x0, B: 0x0, A: 0x0}, | ||||
| 			color.RGBAModel, | ||||
| 		}, | ||||
| 	} { | ||||
| 		t.Run(tt.name, func(t *testing.T) { | ||||
| 			c := Model.Convert(tt.color).(Color) | ||||
|  | ||||
| 			if got := tt.model.Convert(c); !reflect.DeepEqual(got, tt.color) { | ||||
| 				t.Errorf("%#+v -> %#+v -> %#+v, want %#+v", tt.color, c, got, tt.color) | ||||
| 				return | ||||
| 			} | ||||
| 		}) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func TestDistance(t *testing.T) { | ||||
| 	colours := []Color{ | ||||
| 		FromNRGBA(0xffff, 0xffff, 0xffff, 0xffff), | ||||
| 		FromNRGBA(0xffff, 0xffff, 0xffff, 0x7fff), | ||||
| 		FromNRGBA(0xffff, 0xffff, 0xffff, 0x0000), | ||||
| 		FromNRGBA(0x0000, 0x0000, 0x0000, 0xffff), | ||||
| 		FromNRGBA(0x0000, 0x0000, 0x0000, 0x7fff), | ||||
| 		FromNRGBA(0x0000, 0x0000, 0x0000, 0x0000), | ||||
| 	} | ||||
|  | ||||
| 	for i, c0 := range colours { | ||||
| 		for j, c1 := range colours { | ||||
| 			d := Distance(c0, c1) | ||||
|  | ||||
| 			if i == j || (c0.A == 0 && c1.A == 0) { | ||||
| 				// if they're the same color, or both are completely transparent, | ||||
| 				// they should be perceived as identical. | ||||
|  | ||||
| 				if d != 0 { | ||||
| 					t.Errorf("Distance(%v, %v) = %v, want 0", c0, c1, d) | ||||
| 				} | ||||
| 			} else { | ||||
| 				// otherwise, there should be some kind of difference between them. | ||||
| 				if d <= 0 { | ||||
| 					t.Errorf("Distance(%v, %v) = %v, want > 0", c0, c1, d) | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| 			mid := Color{ | ||||
| 				Lightness: (c0.Lightness + c1.Lightness) * 0.5, | ||||
| 				ChromaA:   (c0.ChromaA + c1.ChromaA) * 0.5, | ||||
| 				ChromaB:   (c0.ChromaB + c1.ChromaB) * 0.5, | ||||
| 				A:         (c0.A + c1.A) * 0.5, | ||||
| 			} | ||||
|  | ||||
| 			// traveling from c0 to c1 via mid can't possibly be | ||||
| 			// shorter than traveling from c0 to c1 directly. | ||||
| 			d2 := Distance(c0, mid) + Distance(mid, c1) | ||||
| 			if d2 < d { | ||||
| 				t.Errorf("Distance(%v, %v)+Distance(%v, %v) < Distance(%v, %v), want %f >= %f", c0, mid, mid, c1, c0, c1, d2, d) | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Reference in New Issue
	
	Block a user