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}