function clampTo8(i: number) { return i < 0 ? 0 : (i > 255 ? 255 : i) } function clampNegative(i: number) { return i >= 0 ? i : 0 } // Convolve image data in horizontal direction. Can be used for: // // 1. bitmap with premultiplied alpha // 2. bitmap without alpha (all values 255) // // Notes: // // - output is transposed // - output resolution is ~15 bits per channel (for better precision) // export function convolveHor( src: Uint8ClampedArray, dest: Uint8Array | Uint16Array, srcW: number, srcH: number, destW: number, filters: Int16Array, ) { let r, g, b, a let filterPtr, filterShift, filterSize let srcPtr, srcY, destX, filterVal let srcOffset = 0, destOffset = 0 // For each row for (srcY = 0; srcY < srcH; srcY++) { filterPtr = 0 // Apply precomputed filters to each destination row point for (destX = 0; destX < destW; destX++) { // Get the filter that determines the current output pixel filterShift = filters[filterPtr++] filterSize = filters[filterPtr++] srcPtr = (srcOffset + (filterShift * 4)) | 0 r = g = b = a = 0 // Apply the filter to the row to get the destination pixel r, g, b, a for (; filterSize > 0; filterSize--) { filterVal = filters[filterPtr++] // Use reverse order to workaround deopts in old v8 (node v.10) // Big thanks to @mraleph (Vyacheslav Egorov) for the tip a = (a + filterVal * src[srcPtr + 3]) | 0 b = (b + filterVal * src[srcPtr + 2]) | 0 g = (g + filterVal * src[srcPtr + 1]) | 0 r = (r + filterVal * src[srcPtr]) | 0 srcPtr = (srcPtr + 4) | 0 } // Store 15 bits between passes for better precision // Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only // dest[destOffset + 3] = clampNegative(a >> 7) dest[destOffset + 2] = clampNegative(b >> 7) dest[destOffset + 1] = clampNegative(g >> 7) dest[destOffset] = clampNegative(r >> 7) destOffset = (destOffset + srcH * 4) | 0 } destOffset = ((srcY + 1) * 4) | 0 srcOffset = ((srcY + 1) * srcW * 4) | 0 } } // Supplementary method for `convolveHor()` // export function convolveVert( src: Uint8ClampedArray | Uint16Array, dest: Uint8Array, srcW: number, srcH: number, destW: number, filters: Int16Array, ) { let r, g, b, a let filterPtr, filterShift, filterSize let srcPtr, srcY, destX, filterVal let srcOffset = 0, destOffset = 0 // For each row for (srcY = 0; srcY < srcH; srcY++) { filterPtr = 0 // Apply precomputed filters to each destination row point for (destX = 0; destX < destW; destX++) { // Get the filter that determines the current output pixel filterShift = filters[filterPtr++] filterSize = filters[filterPtr++] srcPtr = (srcOffset + (filterShift * 4)) | 0 r = g = b = a = 0 // Apply the filter to the row to get the destination pixel r, g, b, a for (; filterSize > 0; filterSize--) { filterVal = filters[filterPtr++] // Use reverse order to workaround deopts in old v8 (node v.10) // Big thanks to @mraleph (Vyacheslav Egorov) for the tip a = (a + filterVal * src[srcPtr + 3]) | 0 b = (b + filterVal * src[srcPtr + 2]) | 0 g = (g + filterVal * src[srcPtr + 1]) | 0 r = (r + filterVal * src[srcPtr]) | 0 srcPtr = (srcPtr + 4) | 0 } // Sync with premultiplied version for exact result match r >>= 7 g >>= 7 b >>= 7 a >>= 7 // Bring this value back in range + round result // dest[destOffset + 3] = clampTo8((a + (1 << 13)) >> 14) dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14) dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14) dest[destOffset] = clampTo8((r + (1 << 13)) >> 14) destOffset = (destOffset + srcH * 4) | 0 } destOffset = ((srcY + 1) * 4) | 0 srcOffset = ((srcY + 1) * srcW * 4) | 0 } } // Premultiply & convolve image data in horizontal direction. Can be used for: // // - Any bitmap data, extracted with `.getImageData()` method (with // non-premultiplied alpha) // // For images without alpha channel this method is slower than `convolveHor()` // export function convolveHorWithPre( src: Uint8ClampedArray, dest: Uint8Array | Uint16Array, srcW: number, srcH: number, destW: number, filters: Int16Array, ) { let r, g, b, a, alpha let filterPtr, filterShift, filterSize let srcPtr, srcY, destX, filterVal let srcOffset = 0, destOffset = 0 // For each row for (srcY = 0; srcY < srcH; srcY++) { filterPtr = 0 // Apply precomputed filters to each destination row point for (destX = 0; destX < destW; destX++) { // Get the filter that determines the current output pixel filterShift = filters[filterPtr++] filterSize = filters[filterPtr++] srcPtr = (srcOffset + (filterShift * 4)) | 0 r = g = b = a = 0 // Apply the filter to the row to get the destination pixel r, g, b, a for (; filterSize > 0; filterSize--) { filterVal = filters[filterPtr++] // Use reverse order to workaround deopts in old v8 (node v.10) // Big thanks to @mraleph (Vyacheslav Egorov) for the tip alpha = src[srcPtr + 3] a = (a + filterVal * alpha) | 0 b = (b + filterVal * src[srcPtr + 2] * alpha) | 0 g = (g + filterVal * src[srcPtr + 1] * alpha) | 0 r = (r + filterVal * src[srcPtr] * alpha) | 0 srcPtr = (srcPtr + 4) | 0 } // Premultiply is (* alpha / 255) // Postpone division for better performance b = (b / 255) | 0 g = (g / 255) | 0 r = (r / 255) | 0 // Store 15 bits between passes for better precision // Instead of shift to 14 (FIXED_FRAC_BITS), shift to 7 only // dest[destOffset + 3] = clampNegative(a >> 7) dest[destOffset + 2] = clampNegative(b >> 7) dest[destOffset + 1] = clampNegative(g >> 7) dest[destOffset] = clampNegative(r >> 7) destOffset = (destOffset + srcH * 4) | 0 } destOffset = ((srcY + 1) * 4) | 0 srcOffset = ((srcY + 1) * srcW * 4) | 0 } } // Supplementary method for `convolveHorWithPre()` // export function convolveVertWithPre( src: Uint8ClampedArray | Uint16Array, dest: Uint8Array, srcW: number, srcH: number, destW: number, filters: Int16Array, ) { let r, g, b, a let filterPtr, filterShift, filterSize let srcPtr, srcY, destX, filterVal let srcOffset = 0, destOffset = 0 // For each row for (srcY = 0; srcY < srcH; srcY++) { filterPtr = 0 // Apply precomputed filters to each destination row point for (destX = 0; destX < destW; destX++) { // Get the filter that determines the current output pixel filterShift = filters[filterPtr++] filterSize = filters[filterPtr++] srcPtr = (srcOffset + (filterShift * 4)) | 0 r = g = b = a = 0 // Apply the filter to the row to get the destination pixel r, g, b, a for (; filterSize > 0; filterSize--) { filterVal = filters[filterPtr++] // Use reverse order to workaround deopts in old v8 (node v.10) // Big thanks to @mraleph (Vyacheslav Egorov) for the tip a = (a + filterVal * src[srcPtr + 3]) | 0 b = (b + filterVal * src[srcPtr + 2]) | 0 g = (g + filterVal * src[srcPtr + 1]) | 0 r = (r + filterVal * src[srcPtr]) | 0 srcPtr = (srcPtr + 4) | 0 } // Downscale to leave room for un-premultiply r >>= 7 g >>= 7 b >>= 7 a >>= 7 // Un-premultiply a = clampTo8((a + (1 << 13)) >> 14) if (a > 0) { r = (r * 255 / a) | 0 g = (g * 255 / a) | 0 b = (b * 255 / a) | 0 } // Bring this value back in range + round result // Shift value = FIXED_FRAC_BITS + 7 // dest[destOffset + 3] = a dest[destOffset + 2] = clampTo8((b + (1 << 13)) >> 14) dest[destOffset + 1] = clampTo8((g + (1 << 13)) >> 14) dest[destOffset] = clampTo8((r + (1 << 13)) >> 14) destOffset = (destOffset + srcH * 4) | 0 } destOffset = ((srcY + 1) * 4) | 0 srcOffset = ((srcY + 1) * srcW * 4) | 0 } }