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  }