1 /*
2  * Copyright (c) 2024 the ThorVG project. All rights reserved.
3 
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10 
11  * The above copyright notice and this permission notice shall be included in all
12  * copies or substantial portions of the Software.
13 
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20  * SOFTWARE.
21  */
22 
23 #include "../../lv_conf_internal.h"
24 #if LV_USE_THORVG_INTERNAL
25 
26 #include "tvgSwCommon.h"
27 
28 /************************************************************************/
29 /* Gaussian Filter Implementation                                       */
30 /************************************************************************/
31 
32 struct SwGaussianBlur
33 {
34     static constexpr int MAX_LEVEL = 3;
35     int level;
36     int kernel[MAX_LEVEL];
37 };
38 
39 
_gaussianExtendRegion(RenderRegion & region,int extra,int8_t direction)40 static void _gaussianExtendRegion(RenderRegion& region, int extra, int8_t direction)
41 {
42     //bbox region expansion for feathering
43     if (direction != 2) {
44         region.x = -extra;
45         region.w = extra * 2;
46     }
47     if (direction != 1) {
48         region.y = -extra;
49         region.h = extra * 2;
50     }
51 }
52 
53 
_gaussianRemap(int end,int idx,int border)54 static int _gaussianRemap(int end, int idx, int border)
55 {
56     //wrap
57     if (border == 1) return idx % end;
58 
59     //duplicate
60     if (idx < 0) return 0;
61     else if (idx >= end) return end - 1;
62     return idx;
63 }
64 
65 
66 //TODO: SIMD OPTIMIZATION?
_gaussianBlur(uint8_t * src,uint8_t * dst,int32_t stride,int32_t w,int32_t h,const SwBBox & bbox,int32_t dimension,int border,bool flipped)67 static void _gaussianBlur(uint8_t* src, uint8_t* dst, int32_t stride, int32_t w, int32_t h, const SwBBox& bbox, int32_t dimension, int border, bool flipped)
68 {
69     if (flipped) {
70         src += ((bbox.min.x * stride) + bbox.min.y) << 2;
71         dst += ((bbox.min.x * stride) + bbox.min.y) << 2;
72     } else {
73         src += ((bbox.min.y * stride) + bbox.min.x) << 2;
74         dst += ((bbox.min.y * stride) + bbox.min.x) << 2;
75     }
76 
77     auto iarr = 1.0f / (dimension + dimension + 1);
78 
79     for (int x = 0; x < h; x++) {
80         auto p = x * stride;
81         auto i = p * 4;                 //current index
82         auto l = -(dimension + 1);      //left index
83         auto r = dimension;             //right index
84         int acc[4] = {0, 0, 0, 0};      //sliding accumulator
85 
86         //initial acucmulation
87         for (int x2 = l; x2 < r; ++x2) {
88             auto id = (_gaussianRemap(w, x2, border) + p) * 4;
89             acc[0] += src[id++];
90             acc[1] += src[id++];
91             acc[2] += src[id++];
92             acc[3] += src[id];
93         }
94         //perform filtering
95         for (int x2 = 0; x2 < w; ++x2, ++r, ++l) {
96             auto rid = (_gaussianRemap(w, r, border) + p) * 4;
97             auto lid = (_gaussianRemap(w, l, border) + p) * 4;
98             acc[0] += src[rid++] - src[lid++];
99             acc[1] += src[rid++] - src[lid++];
100             acc[2] += src[rid++] - src[lid++];
101             acc[3] += src[rid] - src[lid];
102             dst[i++] = static_cast<uint8_t>(acc[0] * iarr + 0.5f);
103             dst[i++] = static_cast<uint8_t>(acc[1] * iarr + 0.5f);
104             dst[i++] = static_cast<uint8_t>(acc[2] * iarr + 0.5f);
105             dst[i++] = static_cast<uint8_t>(acc[3] * iarr + 0.5f);
106         }
107     }
108 }
109 
110 
_gaussianInit(int * kernel,float sigma,int level)111 static int _gaussianInit(int* kernel, float sigma, int level)
112 {
113     const auto MAX_LEVEL = SwGaussianBlur::MAX_LEVEL;
114 
115     //compute the kernel
116     auto wl = (int) sqrt((12 * sigma / MAX_LEVEL) + 1);
117     if (wl % 2 == 0) --wl;
118     auto wu = wl + 2;
119     auto mi = (12 * sigma - MAX_LEVEL * wl * wl - 4 * MAX_LEVEL * wl - 3 * MAX_LEVEL) / (-4 * wl - 4);
120     auto m = int(mi + 0.5f);
121     auto extends = 0;
122 
123     for (int i = 0; i < level; i++) {
124         kernel[i] = ((i < m ? wl : wu) - 1) / 2;
125         extends += kernel[i];
126     }
127 
128     return extends;
129 }
130 
131 
effectGaussianPrepare(RenderEffectGaussian * params)132 bool effectGaussianPrepare(RenderEffectGaussian* params)
133 {
134     auto data = (SwGaussianBlur*)malloc(sizeof(SwGaussianBlur));
135 
136     //compute box kernel sizes
137     data->level = int(SwGaussianBlur::MAX_LEVEL * ((params->quality - 1) * 0.01f)) + 1;
138     auto extends = _gaussianInit(data->kernel, params->sigma * params->sigma, data->level);
139 
140     //skip, if the parameters are invalid.
141     if (extends == 0) {
142         params->invalid = true;
143         free(data);
144         return false;
145     }
146 
147     _gaussianExtendRegion(params->extend, extends, params->direction);
148 
149     params->rd = data;
150 
151     return true;
152 }
153 
154 
155 /* It is best to take advantage of the Gaussian blur’s separable property
156    by dividing the process into two passes. horizontal and vertical.
157    We can expect fewer calculations. */
effectGaussianBlur(SwImage & image,SwImage & buffer,const SwBBox & bbox,const RenderEffectGaussian * params)158 bool effectGaussianBlur(SwImage& image, SwImage& buffer, const SwBBox& bbox, const RenderEffectGaussian* params)
159 {
160     if (params->invalid) return false;
161 
162     if (image.channelSize != sizeof(uint32_t)) {
163         TVGERR("SW_ENGINE", "Not supported grayscale Gaussian Blur!");
164         return false;
165     }
166 
167     auto data = static_cast<SwGaussianBlur*>(params->rd);
168     auto w = (bbox.max.x - bbox.min.x);
169     auto h = (bbox.max.y - bbox.min.y);
170     auto stride = image.stride;
171     auto front = image.buf8;
172     auto back = buffer.buf8;
173     auto swapped = false;
174 
175     //fine-tuning for low-quality (experimental)
176     auto threshold = (std::min(w, h) < 300) ? 2 : 1;
177 
178     TVGLOG("SW_ENGINE", "GaussianFilter region(%ld, %ld, %ld, %ld) params(%f %d %d), level(%d)", bbox.min.x, bbox.min.y, bbox.max.x, bbox.max.y, params->sigma, params->direction, params->border, data->level);
179 
180     //horizontal
181     if (params->direction == 0 || params->direction == 1) {
182         for (int i = 0; i < data->level; ++i) {
183             auto k = data->kernel[i] / threshold;
184             if (k == 0) continue;
185             _gaussianBlur(front, back, stride, w, h, bbox, k, params->border, false);
186             std::swap(front, back);
187             swapped = !swapped;
188         }
189     }
190 
191     //vertical. x/y flipping and horionztal access is pretty compatible with the memory architecture.
192     if (params->direction == 0 || params->direction == 2) {
193         rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, w, h, bbox, false);
194         std::swap(front, back);
195 
196         for (int i = 0; i < data->level; ++i) {
197             auto k = data->kernel[i] / threshold;
198             if (k == 0) continue;
199             _gaussianBlur(front, back, stride, h, w, bbox, k, params->border, true);
200             std::swap(front, back);
201             swapped = !swapped;
202         }
203 
204         rasterXYFlip(reinterpret_cast<uint32_t*>(front), reinterpret_cast<uint32_t*>(back), stride, h, w, bbox, true);
205         std::swap(front, back);
206     }
207 
208     if (swapped) std::swap(image.buf8, buffer.buf8);
209 
210     return true;
211 }
212 
213 #endif /* LV_USE_THORVG_INTERNAL */
214 
215