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