go.charczuk.com@v0.0.0-20240327042549-bc490516bd1a/projects/blogctl/pkg/resize/nearest.go (about) 1 /* 2 3 Copyright (c) 2023 - Present. Will Charczuk. All rights reserved. 4 Use of this source code is governed by a MIT license that can be found in the LICENSE file at the root of the repository. 5 6 */ 7 8 package resize 9 10 import "image" 11 12 func floatToUint8(x float32) uint8 { 13 // Nearest-neighbor values are always 14 // positive no need to check lower-bound. 15 if x > 0xfe { 16 return 0xff 17 } 18 return uint8(x) 19 } 20 21 func floatToUint16(x float32) uint16 { 22 if x > 0xfffe { 23 return 0xffff 24 } 25 return uint16(x) 26 } 27 28 func nearestGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { 29 newBounds := out.Bounds() 30 maxX := in.Bounds().Dx() - 1 31 32 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 33 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 34 var rgba [4]float32 35 var sum float32 36 start := offset[y] 37 ci := y * filterLength 38 for i := 0; i < filterLength; i++ { 39 if coeffs[ci+i] { 40 xi := start + i 41 switch { 42 case xi < 0: 43 xi = 0 44 case xi >= maxX: 45 xi = maxX 46 } 47 r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA() 48 rgba[0] += float32(r) 49 rgba[1] += float32(g) 50 rgba[2] += float32(b) 51 rgba[3] += float32(a) 52 sum++ 53 } 54 } 55 56 offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 57 value := floatToUint16(rgba[0] / sum) 58 out.Pix[offset+0] = uint8(value >> 8) 59 out.Pix[offset+1] = uint8(value) 60 value = floatToUint16(rgba[1] / sum) 61 out.Pix[offset+2] = uint8(value >> 8) 62 out.Pix[offset+3] = uint8(value) 63 value = floatToUint16(rgba[2] / sum) 64 out.Pix[offset+4] = uint8(value >> 8) 65 out.Pix[offset+5] = uint8(value) 66 value = floatToUint16(rgba[3] / sum) 67 out.Pix[offset+6] = uint8(value >> 8) 68 out.Pix[offset+7] = uint8(value) 69 } 70 } 71 } 72 73 func nearestRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []bool, offset []int, filterLength int) { 74 newBounds := out.Bounds() 75 maxX := in.Bounds().Dx() - 1 76 77 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 78 row := in.Pix[x*in.Stride:] 79 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 80 var rgba [4]float32 81 var sum float32 82 start := offset[y] 83 ci := y * filterLength 84 for i := 0; i < filterLength; i++ { 85 if coeffs[ci+i] { 86 xi := start + i 87 switch { 88 case uint(xi) < uint(maxX): 89 xi *= 4 90 case xi >= maxX: 91 xi = 4 * maxX 92 default: 93 xi = 0 94 } 95 rgba[0] += float32(row[xi+0]) 96 rgba[1] += float32(row[xi+1]) 97 rgba[2] += float32(row[xi+2]) 98 rgba[3] += float32(row[xi+3]) 99 sum++ 100 } 101 } 102 103 xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 104 out.Pix[xo+0] = floatToUint8(rgba[0] / sum) 105 out.Pix[xo+1] = floatToUint8(rgba[1] / sum) 106 out.Pix[xo+2] = floatToUint8(rgba[2] / sum) 107 out.Pix[xo+3] = floatToUint8(rgba[3] / sum) 108 } 109 } 110 } 111 112 func nearestNRGBA(in *image.NRGBA, out *image.NRGBA, scale float64, coeffs []bool, offset []int, filterLength int) { 113 newBounds := out.Bounds() 114 maxX := in.Bounds().Dx() - 1 115 116 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 117 row := in.Pix[x*in.Stride:] 118 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 119 var rgba [4]float32 120 var sum float32 121 start := offset[y] 122 ci := y * filterLength 123 for i := 0; i < filterLength; i++ { 124 if coeffs[ci+i] { 125 xi := start + i 126 switch { 127 case uint(xi) < uint(maxX): 128 xi *= 4 129 case xi >= maxX: 130 xi = 4 * maxX 131 default: 132 xi = 0 133 } 134 rgba[0] += float32(row[xi+0]) 135 rgba[1] += float32(row[xi+1]) 136 rgba[2] += float32(row[xi+2]) 137 rgba[3] += float32(row[xi+3]) 138 sum++ 139 } 140 } 141 142 xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4 143 out.Pix[xo+0] = floatToUint8(rgba[0] / sum) 144 out.Pix[xo+1] = floatToUint8(rgba[1] / sum) 145 out.Pix[xo+2] = floatToUint8(rgba[2] / sum) 146 out.Pix[xo+3] = floatToUint8(rgba[3] / sum) 147 } 148 } 149 } 150 151 func nearestRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { 152 newBounds := out.Bounds() 153 maxX := in.Bounds().Dx() - 1 154 155 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 156 row := in.Pix[x*in.Stride:] 157 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 158 var rgba [4]float32 159 var sum float32 160 start := offset[y] 161 ci := y * filterLength 162 for i := 0; i < filterLength; i++ { 163 if coeffs[ci+i] { 164 xi := start + i 165 switch { 166 case uint(xi) < uint(maxX): 167 xi *= 8 168 case xi >= maxX: 169 xi = 8 * maxX 170 default: 171 xi = 0 172 } 173 rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) 174 rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) 175 rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) 176 rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) 177 sum++ 178 } 179 } 180 181 xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 182 value := floatToUint16(rgba[0] / sum) 183 out.Pix[xo+0] = uint8(value >> 8) 184 out.Pix[xo+1] = uint8(value) 185 value = floatToUint16(rgba[1] / sum) 186 out.Pix[xo+2] = uint8(value >> 8) 187 out.Pix[xo+3] = uint8(value) 188 value = floatToUint16(rgba[2] / sum) 189 out.Pix[xo+4] = uint8(value >> 8) 190 out.Pix[xo+5] = uint8(value) 191 value = floatToUint16(rgba[3] / sum) 192 out.Pix[xo+6] = uint8(value >> 8) 193 out.Pix[xo+7] = uint8(value) 194 } 195 } 196 } 197 198 func nearestNRGBA64(in *image.NRGBA64, out *image.NRGBA64, scale float64, coeffs []bool, offset []int, filterLength int) { 199 newBounds := out.Bounds() 200 maxX := in.Bounds().Dx() - 1 201 202 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 203 row := in.Pix[x*in.Stride:] 204 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 205 var rgba [4]float32 206 var sum float32 207 start := offset[y] 208 ci := y * filterLength 209 for i := 0; i < filterLength; i++ { 210 if coeffs[ci+i] { 211 xi := start + i 212 switch { 213 case uint(xi) < uint(maxX): 214 xi *= 8 215 case xi >= maxX: 216 xi = 8 * maxX 217 default: 218 xi = 0 219 } 220 rgba[0] += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) 221 rgba[1] += float32(uint16(row[xi+2])<<8 | uint16(row[xi+3])) 222 rgba[2] += float32(uint16(row[xi+4])<<8 | uint16(row[xi+5])) 223 rgba[3] += float32(uint16(row[xi+6])<<8 | uint16(row[xi+7])) 224 sum++ 225 } 226 } 227 228 xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8 229 value := floatToUint16(rgba[0] / sum) 230 out.Pix[xo+0] = uint8(value >> 8) 231 out.Pix[xo+1] = uint8(value) 232 value = floatToUint16(rgba[1] / sum) 233 out.Pix[xo+2] = uint8(value >> 8) 234 out.Pix[xo+3] = uint8(value) 235 value = floatToUint16(rgba[2] / sum) 236 out.Pix[xo+4] = uint8(value >> 8) 237 out.Pix[xo+5] = uint8(value) 238 value = floatToUint16(rgba[3] / sum) 239 out.Pix[xo+6] = uint8(value >> 8) 240 out.Pix[xo+7] = uint8(value) 241 } 242 } 243 } 244 245 func nearestGray(in *image.Gray, out *image.Gray, scale float64, coeffs []bool, offset []int, filterLength int) { 246 newBounds := out.Bounds() 247 maxX := in.Bounds().Dx() - 1 248 249 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 250 row := in.Pix[x*in.Stride:] 251 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 252 var gray float32 253 var sum float32 254 start := offset[y] 255 ci := y * filterLength 256 for i := 0; i < filterLength; i++ { 257 if coeffs[ci+i] { 258 xi := start + i 259 switch { 260 case xi < 0: 261 xi = 0 262 case xi >= maxX: 263 xi = maxX 264 } 265 gray += float32(row[xi]) 266 sum++ 267 } 268 } 269 270 offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X) 271 out.Pix[offset] = floatToUint8(gray / sum) 272 } 273 } 274 } 275 276 func nearestGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []bool, offset []int, filterLength int) { 277 newBounds := out.Bounds() 278 maxX := in.Bounds().Dx() - 1 279 280 for x := newBounds.Min.X; x < newBounds.Max.X; x++ { 281 row := in.Pix[x*in.Stride:] 282 for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ { 283 var gray float32 284 var sum float32 285 start := offset[y] 286 ci := y * filterLength 287 for i := 0; i < filterLength; i++ { 288 if coeffs[ci+i] { 289 xi := start + i 290 switch { 291 case uint(xi) < uint(maxX): 292 xi *= 2 293 case xi >= maxX: 294 xi = 2 * maxX 295 default: 296 xi = 0 297 } 298 gray += float32(uint16(row[xi+0])<<8 | uint16(row[xi+1])) 299 sum++ 300 } 301 } 302 303 offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2 304 value := floatToUint16(gray / sum) 305 out.Pix[offset+0] = uint8(value >> 8) 306 out.Pix[offset+1] = uint8(value) 307 } 308 } 309 }