converter.go

  1/*
  2Copyright (c) 2012, Jan Schlicht <jan.schlicht@gmail.com>
  3
  4Permission to use, copy, modify, and/or distribute this software for any purpose
  5with or without fee is hereby granted, provided that the above copyright notice
  6and this permission notice appear in all copies.
  7
  8THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
  9REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
 10FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
 11INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
 12OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 13TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
 14THIS SOFTWARE.
 15*/
 16
 17package resize
 18
 19import "image"
 20
 21// Keep value in [0,255] range.
 22func clampUint8(in int32) uint8 {
 23	// casting a negative int to an uint will result in an overflown
 24	// large uint. this behavior will be exploited here and in other functions
 25	// to achieve a higher performance.
 26	if uint32(in) < 256 {
 27		return uint8(in)
 28	}
 29	if in > 255 {
 30		return 255
 31	}
 32	return 0
 33}
 34
 35// Keep value in [0,65535] range.
 36func clampUint16(in int64) uint16 {
 37	if uint64(in) < 65536 {
 38		return uint16(in)
 39	}
 40	if in > 65535 {
 41		return 65535
 42	}
 43	return 0
 44}
 45
 46func resizeGeneric(in image.Image, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
 47	newBounds := out.Bounds()
 48	maxX := in.Bounds().Dx() - 1
 49
 50	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
 51		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
 52			var rgba [4]int64
 53			var sum int64
 54			start := offset[y]
 55			ci := y * filterLength
 56			for i := 0; i < filterLength; i++ {
 57				coeff := coeffs[ci+i]
 58				if coeff != 0 {
 59					xi := start + i
 60					switch {
 61					case xi < 0:
 62						xi = 0
 63					case xi >= maxX:
 64						xi = maxX
 65					}
 66
 67					r, g, b, a := in.At(xi+in.Bounds().Min.X, x+in.Bounds().Min.Y).RGBA()
 68
 69					rgba[0] += int64(coeff) * int64(r)
 70					rgba[1] += int64(coeff) * int64(g)
 71					rgba[2] += int64(coeff) * int64(b)
 72					rgba[3] += int64(coeff) * int64(a)
 73					sum += int64(coeff)
 74				}
 75			}
 76
 77			offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
 78
 79			value := clampUint16(rgba[0] / sum)
 80			out.Pix[offset+0] = uint8(value >> 8)
 81			out.Pix[offset+1] = uint8(value)
 82			value = clampUint16(rgba[1] / sum)
 83			out.Pix[offset+2] = uint8(value >> 8)
 84			out.Pix[offset+3] = uint8(value)
 85			value = clampUint16(rgba[2] / sum)
 86			out.Pix[offset+4] = uint8(value >> 8)
 87			out.Pix[offset+5] = uint8(value)
 88			value = clampUint16(rgba[3] / sum)
 89			out.Pix[offset+6] = uint8(value >> 8)
 90			out.Pix[offset+7] = uint8(value)
 91		}
 92	}
 93}
 94
 95func resizeRGBA(in *image.RGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
 96	newBounds := out.Bounds()
 97	maxX := in.Bounds().Dx() - 1
 98
 99	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
100		row := in.Pix[x*in.Stride:]
101		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
102			var rgba [4]int32
103			var sum int32
104			start := offset[y]
105			ci := y * filterLength
106			for i := 0; i < filterLength; i++ {
107				coeff := coeffs[ci+i]
108				if coeff != 0 {
109					xi := start + i
110					switch {
111					case uint(xi) < uint(maxX):
112						xi *= 4
113					case xi >= maxX:
114						xi = 4 * maxX
115					default:
116						xi = 0
117					}
118
119					rgba[0] += int32(coeff) * int32(row[xi+0])
120					rgba[1] += int32(coeff) * int32(row[xi+1])
121					rgba[2] += int32(coeff) * int32(row[xi+2])
122					rgba[3] += int32(coeff) * int32(row[xi+3])
123					sum += int32(coeff)
124				}
125			}
126
127			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
128
129			out.Pix[xo+0] = clampUint8(rgba[0] / sum)
130			out.Pix[xo+1] = clampUint8(rgba[1] / sum)
131			out.Pix[xo+2] = clampUint8(rgba[2] / sum)
132			out.Pix[xo+3] = clampUint8(rgba[3] / sum)
133		}
134	}
135}
136
137func resizeNRGBA(in *image.NRGBA, out *image.RGBA, scale float64, coeffs []int16, offset []int, filterLength int) {
138	newBounds := out.Bounds()
139	maxX := in.Bounds().Dx() - 1
140
141	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
142		row := in.Pix[x*in.Stride:]
143		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
144			var rgba [4]int32
145			var sum int32
146			start := offset[y]
147			ci := y * filterLength
148			for i := 0; i < filterLength; i++ {
149				coeff := coeffs[ci+i]
150				if coeff != 0 {
151					xi := start + i
152					switch {
153					case uint(xi) < uint(maxX):
154						xi *= 4
155					case xi >= maxX:
156						xi = 4 * maxX
157					default:
158						xi = 0
159					}
160
161					// Forward alpha-premultiplication
162					a := int32(row[xi+3])
163					r := int32(row[xi+0]) * a
164					r /= 0xff
165					g := int32(row[xi+1]) * a
166					g /= 0xff
167					b := int32(row[xi+2]) * a
168					b /= 0xff
169
170					rgba[0] += int32(coeff) * r
171					rgba[1] += int32(coeff) * g
172					rgba[2] += int32(coeff) * b
173					rgba[3] += int32(coeff) * a
174					sum += int32(coeff)
175				}
176			}
177
178			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*4
179
180			out.Pix[xo+0] = clampUint8(rgba[0] / sum)
181			out.Pix[xo+1] = clampUint8(rgba[1] / sum)
182			out.Pix[xo+2] = clampUint8(rgba[2] / sum)
183			out.Pix[xo+3] = clampUint8(rgba[3] / sum)
184		}
185	}
186}
187
188func resizeRGBA64(in *image.RGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
189	newBounds := out.Bounds()
190	maxX := in.Bounds().Dx() - 1
191
192	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
193		row := in.Pix[x*in.Stride:]
194		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
195			var rgba [4]int64
196			var sum int64
197			start := offset[y]
198			ci := y * filterLength
199			for i := 0; i < filterLength; i++ {
200				coeff := coeffs[ci+i]
201				if coeff != 0 {
202					xi := start + i
203					switch {
204					case uint(xi) < uint(maxX):
205						xi *= 8
206					case xi >= maxX:
207						xi = 8 * maxX
208					default:
209						xi = 0
210					}
211
212					rgba[0] += int64(coeff) * (int64(row[xi+0])<<8 | int64(row[xi+1]))
213					rgba[1] += int64(coeff) * (int64(row[xi+2])<<8 | int64(row[xi+3]))
214					rgba[2] += int64(coeff) * (int64(row[xi+4])<<8 | int64(row[xi+5]))
215					rgba[3] += int64(coeff) * (int64(row[xi+6])<<8 | int64(row[xi+7]))
216					sum += int64(coeff)
217				}
218			}
219
220			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
221
222			value := clampUint16(rgba[0] / sum)
223			out.Pix[xo+0] = uint8(value >> 8)
224			out.Pix[xo+1] = uint8(value)
225			value = clampUint16(rgba[1] / sum)
226			out.Pix[xo+2] = uint8(value >> 8)
227			out.Pix[xo+3] = uint8(value)
228			value = clampUint16(rgba[2] / sum)
229			out.Pix[xo+4] = uint8(value >> 8)
230			out.Pix[xo+5] = uint8(value)
231			value = clampUint16(rgba[3] / sum)
232			out.Pix[xo+6] = uint8(value >> 8)
233			out.Pix[xo+7] = uint8(value)
234		}
235	}
236}
237
238func resizeNRGBA64(in *image.NRGBA64, out *image.RGBA64, scale float64, coeffs []int32, offset []int, filterLength int) {
239	newBounds := out.Bounds()
240	maxX := in.Bounds().Dx() - 1
241
242	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
243		row := in.Pix[x*in.Stride:]
244		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
245			var rgba [4]int64
246			var sum int64
247			start := offset[y]
248			ci := y * filterLength
249			for i := 0; i < filterLength; i++ {
250				coeff := coeffs[ci+i]
251				if coeff != 0 {
252					xi := start + i
253					switch {
254					case uint(xi) < uint(maxX):
255						xi *= 8
256					case xi >= maxX:
257						xi = 8 * maxX
258					default:
259						xi = 0
260					}
261
262					// Forward alpha-premultiplication
263					a := int64(uint16(row[xi+6])<<8 | uint16(row[xi+7]))
264					r := int64(uint16(row[xi+0])<<8|uint16(row[xi+1])) * a
265					r /= 0xffff
266					g := int64(uint16(row[xi+2])<<8|uint16(row[xi+3])) * a
267					g /= 0xffff
268					b := int64(uint16(row[xi+4])<<8|uint16(row[xi+5])) * a
269					b /= 0xffff
270
271					rgba[0] += int64(coeff) * r
272					rgba[1] += int64(coeff) * g
273					rgba[2] += int64(coeff) * b
274					rgba[3] += int64(coeff) * a
275					sum += int64(coeff)
276				}
277			}
278
279			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*8
280
281			value := clampUint16(rgba[0] / sum)
282			out.Pix[xo+0] = uint8(value >> 8)
283			out.Pix[xo+1] = uint8(value)
284			value = clampUint16(rgba[1] / sum)
285			out.Pix[xo+2] = uint8(value >> 8)
286			out.Pix[xo+3] = uint8(value)
287			value = clampUint16(rgba[2] / sum)
288			out.Pix[xo+4] = uint8(value >> 8)
289			out.Pix[xo+5] = uint8(value)
290			value = clampUint16(rgba[3] / sum)
291			out.Pix[xo+6] = uint8(value >> 8)
292			out.Pix[xo+7] = uint8(value)
293		}
294	}
295}
296
297func resizeGray(in *image.Gray, out *image.Gray, scale float64, coeffs []int16, offset []int, filterLength int) {
298	newBounds := out.Bounds()
299	maxX := in.Bounds().Dx() - 1
300
301	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
302		row := in.Pix[(x-newBounds.Min.X)*in.Stride:]
303		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
304			var gray int32
305			var sum int32
306			start := offset[y]
307			ci := y * filterLength
308			for i := 0; i < filterLength; i++ {
309				coeff := coeffs[ci+i]
310				if coeff != 0 {
311					xi := start + i
312					switch {
313					case xi < 0:
314						xi = 0
315					case xi >= maxX:
316						xi = maxX
317					}
318					gray += int32(coeff) * int32(row[xi])
319					sum += int32(coeff)
320				}
321			}
322
323			offset := (y-newBounds.Min.Y)*out.Stride + (x - newBounds.Min.X)
324			out.Pix[offset] = clampUint8(gray / sum)
325		}
326	}
327}
328
329func resizeGray16(in *image.Gray16, out *image.Gray16, scale float64, coeffs []int32, offset []int, filterLength int) {
330	newBounds := out.Bounds()
331	maxX := in.Bounds().Dx() - 1
332
333	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
334		row := in.Pix[x*in.Stride:]
335		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
336			var gray int64
337			var sum int64
338			start := offset[y]
339			ci := y * filterLength
340			for i := 0; i < filterLength; i++ {
341				coeff := coeffs[ci+i]
342				if coeff != 0 {
343					xi := start + i
344					switch {
345					case uint(xi) < uint(maxX):
346						xi *= 2
347					case xi >= maxX:
348						xi = 2 * maxX
349					default:
350						xi = 0
351					}
352					gray += int64(coeff) * int64(uint16(row[xi+0])<<8|uint16(row[xi+1]))
353					sum += int64(coeff)
354				}
355			}
356
357			offset := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*2
358			value := clampUint16(gray / sum)
359			out.Pix[offset+0] = uint8(value >> 8)
360			out.Pix[offset+1] = uint8(value)
361		}
362	}
363}
364
365func resizeYCbCr(in *ycc, out *ycc, scale float64, coeffs []int16, offset []int, filterLength int) {
366	newBounds := out.Bounds()
367	maxX := in.Bounds().Dx() - 1
368
369	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
370		row := in.Pix[x*in.Stride:]
371		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
372			var p [3]int32
373			var sum int32
374			start := offset[y]
375			ci := y * filterLength
376			for i := 0; i < filterLength; i++ {
377				coeff := coeffs[ci+i]
378				if coeff != 0 {
379					xi := start + i
380					switch {
381					case uint(xi) < uint(maxX):
382						xi *= 3
383					case xi >= maxX:
384						xi = 3 * maxX
385					default:
386						xi = 0
387					}
388					p[0] += int32(coeff) * int32(row[xi+0])
389					p[1] += int32(coeff) * int32(row[xi+1])
390					p[2] += int32(coeff) * int32(row[xi+2])
391					sum += int32(coeff)
392				}
393			}
394
395			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
396			out.Pix[xo+0] = clampUint8(p[0] / sum)
397			out.Pix[xo+1] = clampUint8(p[1] / sum)
398			out.Pix[xo+2] = clampUint8(p[2] / sum)
399		}
400	}
401}
402
403func nearestYCbCr(in *ycc, out *ycc, scale float64, coeffs []bool, offset []int, filterLength int) {
404	newBounds := out.Bounds()
405	maxX := in.Bounds().Dx() - 1
406
407	for x := newBounds.Min.X; x < newBounds.Max.X; x++ {
408		row := in.Pix[x*in.Stride:]
409		for y := newBounds.Min.Y; y < newBounds.Max.Y; y++ {
410			var p [3]float32
411			var sum float32
412			start := offset[y]
413			ci := y * filterLength
414			for i := 0; i < filterLength; i++ {
415				if coeffs[ci+i] {
416					xi := start + i
417					switch {
418					case uint(xi) < uint(maxX):
419						xi *= 3
420					case xi >= maxX:
421						xi = 3 * maxX
422					default:
423						xi = 0
424					}
425					p[0] += float32(row[xi+0])
426					p[1] += float32(row[xi+1])
427					p[2] += float32(row[xi+2])
428					sum++
429				}
430			}
431
432			xo := (y-newBounds.Min.Y)*out.Stride + (x-newBounds.Min.X)*3
433			out.Pix[xo+0] = floatToUint8(p[0] / sum)
434			out.Pix[xo+1] = floatToUint8(p[1] / sum)
435			out.Pix[xo+2] = floatToUint8(p[2] / sum)
436		}
437	}
438}