github.com/astrogo/fitsio@v0.3.0/fltimg/image.go (about) 1 // Copyright 2016 The astrogo Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // fltimg provides image.Image implementations for the float32- and float64-image encodings of FITS. 6 package fltimg 7 8 import ( 9 "encoding/binary" 10 "image" 11 "image/color" 12 "math" 13 ) 14 15 const ( 16 gamma = 1 / 2.2 17 ) 18 19 type f32Gray float32 20 21 func (c f32Gray) RGBA() (r, g, b, a uint32) { 22 f := math.Pow(float64(c), gamma) 23 switch { 24 case f > 1: 25 f = 1 26 case f < 0: 27 f = 0 28 } 29 i := uint32(f * 0xffff) 30 return i, i, i, 0xffff 31 } 32 33 // Gray32 represents an image.Image encoded in 32b IEEE floating-point values 34 type Gray32 struct { 35 Pix []uint8 36 Stride int 37 Rect image.Rectangle 38 Min float32 39 Max float32 40 } 41 42 // NewGray32 creates a new Gray32 image with the given bounds. 43 func NewGray32(rect image.Rectangle, pix []byte) *Gray32 { 44 w, h := rect.Dx(), rect.Dy() 45 switch { 46 case pix == nil: 47 panic("fltimg: nil pixel buffer") 48 case len(pix) != 4*w*h: 49 panic("fltimg: inconsistent pixels size") 50 } 51 img := &Gray32{Pix: pix, Stride: 4 * w, Rect: rect} 52 min := float32(+math.MaxFloat32) 53 max := float32(-math.MaxFloat32) 54 for i := 0; i < len(img.Pix); i += 4 { 55 v := img.at(i) 56 if v > max { 57 max = v 58 } 59 if v < min { 60 min = v 61 } 62 } 63 img.Min = min 64 img.Max = max 65 return img 66 } 67 68 func (p *Gray32) at(i int) float32 { 69 bits := binary.BigEndian.Uint32(p.Pix[i : i+4]) 70 return math.Float32frombits(bits) 71 } 72 73 func (p *Gray32) setf(i int, v float32) { 74 binary.BigEndian.PutUint32(p.Pix[i:i+4], math.Float32bits(v)) 75 } 76 77 func (p *Gray32) ColorModel() color.Model { return Gray32Model } 78 func (p *Gray32) Bounds() image.Rectangle { return p.Rect } 79 func (p *Gray32) At(x, y int) color.Color { 80 i := p.PixOffset(x, y) 81 v := p.at(i) 82 f := (v - p.Min) / (p.Max - p.Min) 83 switch { 84 case f < 0: 85 f = 0 86 case f > 1: 87 f = 1 88 } 89 return f32Gray(f) 90 } 91 92 func (p *Gray32) Set(x, y int, c color.Color) { 93 i := p.PixOffset(x, y) 94 r, _, _, _ := Gray32Model.Convert(c).RGBA() 95 v := math.Exp(math.Log(float64(r)/float64(0xffff)) / gamma) 96 p.setf(i, float32(v)) 97 } 98 99 func (p *Gray32) PixOffset(x, y int) int { 100 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*4 101 } 102 103 type f64Gray float64 104 105 func (c f64Gray) RGBA() (r, g, b, a uint32) { 106 f := math.Pow(float64(c), gamma) 107 switch { 108 case f > 1: 109 f = 1 110 case f < 0: 111 f = 0 112 } 113 i := uint32(f * 0xffff) 114 return i, i, i, 0xffff 115 } 116 117 // Gray64 represents an image.Image encoded in 64b IEEE floating-point values 118 type Gray64 struct { 119 Pix []uint8 120 Stride int 121 Rect image.Rectangle 122 Min float64 123 Max float64 124 } 125 126 // NewGray64 creates a new Gray64 image with the given bounds. 127 func NewGray64(rect image.Rectangle, pix []byte) *Gray64 { 128 w, h := rect.Dx(), rect.Dy() 129 switch { 130 case pix == nil: 131 panic("fltimg: nil pixel buffer") 132 case len(pix) != 8*w*h: 133 panic("fltimg: inconsistent pixels size") 134 } 135 img := &Gray64{pix, 8 * w, rect, 0, 0} 136 min := +math.MaxFloat64 137 max := -math.MaxFloat64 138 for i := 0; i < len(img.Pix); i += 8 { 139 v := img.at(i) 140 if v > max { 141 max = v 142 } 143 if v < min { 144 min = v 145 } 146 } 147 img.Min = min 148 img.Max = max 149 return img 150 151 } 152 153 func (p *Gray64) at(i int) float64 { 154 bits := binary.BigEndian.Uint64(p.Pix[i : i+8]) 155 return math.Float64frombits(bits) 156 } 157 158 func (p *Gray64) setf(i int, v float64) { 159 binary.BigEndian.PutUint64(p.Pix[i:i+8], math.Float64bits(v)) 160 } 161 162 func (p *Gray64) ColorModel() color.Model { return Gray64Model } 163 func (p *Gray64) Bounds() image.Rectangle { return p.Rect } 164 func (p *Gray64) At(x, y int) color.Color { 165 i := p.PixOffset(x, y) 166 v := p.at(i) 167 f := (1 - (v-p.Min)/(p.Max-p.Min)) 168 switch { 169 case f < 0: 170 f = 0 171 case f > 1: 172 f = 1 173 } 174 return f64Gray(f) 175 } 176 177 func (p *Gray64) Set(x, y int, c color.Color) { 178 i := p.PixOffset(x, y) 179 r, _, _, _ := Gray64Model.Convert(c).RGBA() 180 v := math.Exp(math.Log(float64(r)/float64(0xffff)) / gamma) 181 p.setf(i, v) 182 } 183 184 func (p *Gray64) PixOffset(x, y int) int { 185 return (y-p.Rect.Min.Y)*p.Stride + (x-p.Rect.Min.X)*8 186 } 187 188 // Models for the fltimg color types. 189 var ( 190 Gray32Model color.Model = color.ModelFunc(gray32Model) 191 Gray64Model color.Model = color.ModelFunc(gray64Model) 192 ) 193 194 func gray32Model(c color.Color) color.Color { 195 if _, ok := c.(f32Gray); ok { 196 return c 197 } 198 r, g, b, _ := c.RGBA() 199 y := (19595*r + 38470*g + 7471*b + 1<<15) >> 16 200 v := math.Exp(math.Log(float64(y)/float64(0xffff)) / gamma) 201 return f32Gray(v) 202 } 203 204 func gray64Model(c color.Color) color.Color { 205 if _, ok := c.(f64Gray); ok { 206 return c 207 } 208 r, g, b, _ := c.RGBA() 209 y := (19595*r + 38470*g + 7471*b + 1<<15) >> 16 210 v := math.Exp(math.Log(float64(y)/float64(0xffff)) / gamma) 211 return f64Gray(v) 212 }