├── CMakeLists.txt
├── LICENSE
├── README.md
├── browse.h
├── cpuimage.c
├── cpuimage.h
├── demo
├── demo.c
├── fastmath.h
├── stb_image.h
└── tiny_jpeg.h
/CMakeLists.txt:
--------------------------------------------------------------------------------
1 | cmake_minimum_required(VERSION 3.9)
2 | project(demo)
3 |
4 | set(CMAKE_CXX_STANDARD 11)
5 |
6 | add_executable(demo
7 | cpuimage.c
8 | demo.c browse.h)
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2018 Zhihan Gao
2 |
3 | MIT License
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # cpuimage
2 | An open source cpu-based image processing
3 |
4 | # Donating
5 |
6 | If you found this project useful, consider buying me a coffee
7 |
8 |
9 |
--------------------------------------------------------------------------------
/browse.h:
--------------------------------------------------------------------------------
1 | #ifndef DEMO_BROWSE_H
2 | #define DEMO_BROWSE_H
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | #if _WIN32
9 |
10 | #include
11 |
12 | #pragma comment(lib,"shell32.lib")
13 |
14 | static void browse(const char *url)
15 | {
16 | ShellExecuteA(NULL,"open", url, NULL, NULL, SW_SHOWNORMAL);
17 | }
18 |
19 | #endif
20 |
21 | #if linux || __FreeBSD__ || __sun&&__SVR4
22 |
23 | #include
24 | #include
25 | #include
26 |
27 | static void browse(const char *url)
28 | {
29 | pid_t childpid;
30 | const char *args[3];
31 |
32 | const char *browser = getenv("BROWSER");
33 | if (browser)
34 | browser = strdup(browser);
35 | else
36 | browser = "x-www-browser";
37 |
38 | args[0] = browser;
39 | args[1] = url;
40 | args[2] = NULL;
41 |
42 | childpid = fork();
43 | if (childpid == 0)
44 | {
45 | execvp(args[0], (char**)args);
46 | perror(args[0]); // failed to execute
47 | return;
48 | }
49 | }
50 |
51 | #endif
52 |
53 | #if __APPLE__
54 |
55 | #include
56 | #include
57 | #include
58 |
59 | static void browse(const char *url)
60 | {
61 | pid_t childpid;
62 | const char *args[5];
63 |
64 | char *browser = getenv("BROWSER");
65 | if (browser)
66 | { browser = strdup(browser);
67 | args[0] = browser;
68 | args[1] = url;
69 | args[2] = NULL;
70 | }
71 | else
72 | {
73 | //browser = "/Applications/Safari.app/Contents/MacOS/Safari";
74 | args[0] = "open";
75 | args[1] = "-a";
76 | args[2] = "/Applications/Safari.app";
77 | args[3] = url;
78 | args[4] = NULL;
79 | }
80 |
81 | childpid = fork();
82 | if (childpid == 0)
83 | {
84 | execvp(args[0], (char**)args);
85 | perror(args[0]); // failed to execute
86 | return;
87 | }
88 | }
89 |
90 | #endif
91 | #endif //DEMO_BROWSE_H
92 |
--------------------------------------------------------------------------------
/cpuimage.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 | #include "cpuimage.h"
8 | /*
9 |
10 | LevelParams redLevelParams = {
11 | //色阶最小值
12 | 0,
13 | //色阶中间值
14 | 127,
15 | //色阶最大值
16 | 255,
17 | //最小输出值
18 | 0,
19 | //最大输出值
20 | 255,
21 | //是否应用
22 | true,
23 | };
24 | LevelParams greenLevelParams = {
25 | //色阶最小值
26 | 0,
27 | //色阶中间值
28 | 127,
29 | //色阶最大值
30 | 255,
31 | //最小输出值
32 | 0,
33 | //最大输出值
34 | 255,
35 | //是否应用
36 | true,
37 | };
38 |
39 | LevelParams blueLevelParams = {
40 | //色阶最小值
41 | 0,
42 | //色阶中间值
43 | 127,
44 | //色阶最大值
45 | 255,
46 | //最小输出值
47 | 0,
48 | //最大输出值
49 | 255,
50 | //是否应用
51 | true,
52 | };*/
53 |
54 | static inline unsigned long byteswap_ulong(unsigned long i)
55 | {
56 | unsigned long j;
57 | j = (i << 24);
58 | j += (i << 8) & 0x00FF0000;
59 | j += (i >> 8) & 0x0000FF00;
60 | j += (i >> 24);
61 | return j;
62 | }
63 |
64 | static inline unsigned short byteswap_ushort(unsigned short i)
65 | {
66 | unsigned short j;
67 | j = (i << 8);
68 | j += (i >> 8);
69 | return j;
70 | }
71 |
72 | static inline unsigned char step(unsigned char edge, unsigned char x)
73 | {
74 | return (unsigned char)(x < edge ? 0 : 255);
75 | }
76 |
77 | static inline int Abs(int x)
78 | {
79 | return (x ^ (x >> 31)) - (x >> 31);
80 | }
81 |
82 | unsigned char mix_u8(unsigned char a, unsigned char b, float alpha)
83 | {
84 | return (unsigned char)ClampToByte(a * (1.0f - alpha) + b * alpha);
85 | }
86 |
87 | float dot(unsigned char R, unsigned char G, unsigned char B, float fR, float fG, float fB)
88 | {
89 | return (float)(R * fR + G * fG + B * fB);
90 | }
91 |
92 | static inline float mix(float a, float b, float alpha) { return (a * (1.0f - alpha) + b * alpha); }
93 |
94 | unsigned char degree(unsigned char InputColor, unsigned char OutputColor, float intensity)
95 | {
96 | return (unsigned char)ClampToByte(((intensity * OutputColor) + (1.0f - intensity) * InputColor));
97 | }
98 |
99 | float smoothstep(float edgeMin, float edgeMax, float x)
100 | {
101 | return clamp((x - edgeMin) / (edgeMax - edgeMin), 0.0f, 1.0f);
102 | }
103 |
104 | float vec2_distance(float vecX, float vecY, float otherX, float otherY)
105 | {
106 | float dx = vecX - otherX;
107 | float dy = vecY - otherY;
108 | return sqrtf(dx * dx + dy * dy);
109 | }
110 |
111 | void CPUImageGrayscaleFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride)
112 | {
113 | int Channels = Stride / Width;
114 |
115 | const int B_WT = (int)(0.114 * 256 + 0.5);
116 | const int G_WT = (int)(0.587 * 256 + 0.5);
117 | const int R_WT = 256 - B_WT - G_WT; // int(0.299 * 256 + 0.5);
118 | int Channel = Stride / Width;
119 | if (Channel == 3)
120 | {
121 | for (int Y = 0; Y < Height; Y++)
122 | {
123 | unsigned char *LinePS = Input + Y * Stride;
124 | unsigned char *LinePD = Output + Y * Width;
125 | int X = 0;
126 | for (; X < Width - 4; X += 4, LinePS += Channel * 4) {
127 | LinePD[X + 0] = (unsigned char) ((B_WT * LinePS[0] + G_WT * LinePS[1] + R_WT * LinePS[2]) >> 8);
128 | LinePD[X + 1] = (unsigned char) ((B_WT * LinePS[3] + G_WT * LinePS[4] + R_WT * LinePS[5]) >> 8);
129 | LinePD[X + 2] = (unsigned char) ((B_WT * LinePS[6] + G_WT * LinePS[7] + R_WT * LinePS[8]) >> 8);
130 | LinePD[X + 3] = (unsigned char) ((B_WT * LinePS[9] + G_WT * LinePS[10] + R_WT * LinePS[11]) >> 8);
131 | }
132 | for (; X < Width; X++, LinePS += Channel) {
133 | LinePD[X] = (unsigned char) (B_WT * LinePS[0] + G_WT * LinePS[1] + R_WT * LinePS[2]) >> 8;
134 | }
135 | }
136 | } else if (Channel == 4) {
137 | for (int Y = 0; Y < Height; Y++) {
138 | unsigned char *LinePS = Input + Y * Stride;
139 | unsigned char *LinePD = Output + Y * Width;
140 | int X = 0;
141 | for (; X < Width - 4; X += 4, LinePS += Channel * 4) {
142 | LinePD[X + 0] = (unsigned char) ((B_WT * LinePS[0] + G_WT * LinePS[1] + R_WT * LinePS[2]) >> 8);
143 | LinePD[X + 1] = (unsigned char) ((B_WT * LinePS[4] + G_WT * LinePS[5] + R_WT * LinePS[6]) >> 8);
144 | LinePD[X + 2] = (unsigned char) ((B_WT * LinePS[8] + G_WT * LinePS[9] + R_WT * LinePS[10]) >> 8);
145 | LinePD[X + 3] = (unsigned char) ((B_WT * LinePS[12] + G_WT * LinePS[13] + R_WT * LinePS[14]) >> 8);
146 | }
147 | for (; X < Width; X++, LinePS += Channel) {
148 | LinePD[X] = (unsigned char) ((B_WT * LinePS[0] + G_WT * LinePS[1] + R_WT * LinePS[2]) >> 8);
149 | }
150 | }
151 | }
152 | else if (Channel == 1)
153 | {
154 | if (Output != Input)
155 | {
156 | memcpy(Output, Input, Height * Stride);
157 | }
158 | }
159 | }
160 |
161 | /*
162 | CPUImageRGBFilter: Adjusts the individual RGB channels of an image
163 | red,green,blue : Normalized values by which each color channel is multiplied.The range is from 0.0 up, with 1.0 as the default.
164 | */
165 | void CPUImageRGBFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float redAdjustment, float greenAdjustment, float blueAdjustment)
166 | {
167 | int Channels = Stride / Width;
168 | if (Channels == 1)
169 | return;
170 | unsigned char AdjustMapR[256] = {0};
171 | unsigned char AdjustMapG[256] = {0};
172 | unsigned char AdjustMapB[256] = {0};
173 | for (int pixel = 0; pixel < 256; pixel++)
174 | {
175 | AdjustMapR[pixel] = (unsigned char)(pixel * redAdjustment);
176 | AdjustMapG[pixel] = (unsigned char)(pixel * greenAdjustment);
177 | AdjustMapB[pixel] = (unsigned char)(pixel * blueAdjustment);
178 | }
179 | for (int Y = 0; Y < Height; Y++)
180 | {
181 | unsigned char *pOutput = Output + (Y * Stride);
182 | unsigned char *pInput = Input + (Y * Stride);
183 | for (int X = 0; X < Width; X++)
184 | {
185 | pOutput[0] = AdjustMapR[pInput[0]];
186 | pOutput[1] = AdjustMapG[pInput[1]];
187 | pOutput[2] = AdjustMapB[pInput[2]];
188 | pInput += Channels;
189 | pOutput += Channels;
190 | }
191 | }
192 | }
193 | /*
194 | CPUImageAverageLuminanceThresholdFilter: This applies a thresholding operation where the threshold is continually adjusted based on the average luminance of the scene.
195 | thresholdMultiplier : This is a factor that the average luminance will be multiplied by in order to arrive at the final threshold to use.By default, this is 1.0.
196 | */
197 |
198 | void CPUImageAverageLuminanceThresholdFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float thresholdMultiplier)
199 | {
200 | int Channels = Stride / Width;
201 | unsigned char Luminance = 0;
202 | if (Channels == 1)
203 | {
204 | int numberOfPixels = Width * Height;
205 | unsigned int histogramGray[256] = {0};
206 | for (int Y = 0; Y < Height; Y++)
207 | {
208 | unsigned char *pInput = Input + (Y * Stride);
209 | for (int X = 0; X < Width; X++)
210 | {
211 | histogramGray[pInput[0]]++;
212 | pInput += Channels;
213 | }
214 | }
215 | unsigned int SumPix = 0;
216 | for (unsigned int i = 0; i < 256; i++)
217 | {
218 | SumPix += histogramGray[i] * i;
219 | }
220 | Luminance = (unsigned char)((SumPix / numberOfPixels) * thresholdMultiplier);
221 |
222 | for (int Y = 0; Y < Height; Y++)
223 | {
224 | unsigned char *pOutput = Output + (Y * Stride);
225 | unsigned char *pInput = Input + (Y * Stride);
226 | for (int X = 0; X < Width; X++)
227 | {
228 | pOutput[0] = step(Luminance, pInput[0]);
229 | pInput++;
230 | pOutput++;
231 | }
232 | }
233 | }
234 | else if (Channels == 3 || Channels == 4)
235 | {
236 |
237 | int numberOfPixels = Width * Height;
238 | unsigned int histogramLum[256] = {0};
239 | for (int Y = 0; Y < Height; Y++)
240 | {
241 | unsigned char *pInput = Input + (Y * Stride);
242 | for (int X = 0; X < Width; X++)
243 | {
244 | const unsigned char R = pInput[0];
245 | const unsigned char G = pInput[1];
246 | const unsigned char B = pInput[2];
247 | histogramLum[((13926 * R + 46884 * G + 4725 * B) >> 16)]++;
248 | pInput += Channels;
249 | }
250 | }
251 | unsigned int Sum = 0;
252 | for (unsigned int i = 0; i < 256; i++)
253 | {
254 | Sum += histogramLum[i] * i;
255 | }
256 | Luminance = (unsigned char)((Sum / numberOfPixels) * thresholdMultiplier);
257 | for (int Y = 0; Y < Height; Y++)
258 | {
259 | unsigned char *pOutput = Output + (Y * Stride);
260 | unsigned char *pInput = Input + (Y * Stride);
261 | for (int X = 0; X < Width; X++)
262 | {
263 | unsigned char luminance =(unsigned char)((13926 * pInput[0] + 46884 * pInput[1] + 4725 * pInput[2]) >> 16);
264 | pOutput[2] = pOutput[1] = pOutput[0] = step(Luminance, luminance);
265 | pInput += Channels;
266 | pOutput += Channels;
267 | }
268 | }
269 | }
270 | }
271 | /*
272 | CPUImageAverageColor: This processes an Input image and determines the average color of the scene, by averaging the RGBA components for each pixel in the image.A reduction process is used to progressively downsample the source image on the GPU, followed by a short averaging calculation on the CPU.The Output from this filter is meaningless, but you need to set the colorAverageProcessingFinishedBlock property to a block that takes in four color components and a frame time and does something with them.
273 | Shader :
274 | ------------------------------------------------------------------------
275 |
276 | GPUImageColorAveragingVertexShaderString = SHADER_STRING
277 | (
278 | attribute vec4 position;
279 | attribute vec4 InputTextureCoordinate;
280 |
281 | uniform float texelWidth;
282 | uniform float texelHeight;
283 |
284 | varying vec2 upperLeftInputTextureCoordinate;
285 | varying vec2 upperRightInputTextureCoordinate;
286 | varying vec2 lowerLeftInputTextureCoordinate;
287 | varying vec2 lowerRightInputTextureCoordinate;
288 |
289 | void main()
290 | {
291 | gl_Position = position;
292 |
293 | upperLeftInputTextureCoordinate = InputTextureCoordinate.xy + vec2(-texelWidth, -texelHeight);
294 | upperRightInputTextureCoordinate = InputTextureCoordinate.xy + vec2(texelWidth, -texelHeight);
295 | lowerLeftInputTextureCoordinate = InputTextureCoordinate.xy + vec2(-texelWidth, texelHeight);
296 | lowerRightInputTextureCoordinate = InputTextureCoordinate.xy + vec2(texelWidth, texelHeight);
297 | }
298 | );
299 |
300 |
301 | GPUImageColorAveragingFragmentShaderString = SHADER_STRING
302 | (
303 | precision highp float;
304 |
305 | uniform sampler2D InputImageTexture;
306 |
307 | varying highp vec2 OutputTextureCoordinate;
308 |
309 | varying highp vec2 upperLeftInputTextureCoordinate;
310 | varying highp vec2 upperRightInputTextureCoordinate;
311 | varying highp vec2 lowerLeftInputTextureCoordinate;
312 | varying highp vec2 lowerRightInputTextureCoordinate;
313 |
314 | void main()
315 | {
316 | highp vec4 upperLeftColor = texture2D(InputImageTexture, upperLeftInputTextureCoordinate);
317 | highp vec4 upperRightColor = texture2D(InputImageTexture, upperRightInputTextureCoordinate);
318 | highp vec4 lowerLeftColor = texture2D(InputImageTexture, lowerLeftInputTextureCoordinate);
319 | highp vec4 lowerRightColor = texture2D(InputImageTexture, lowerRightInputTextureCoordinate);
320 |
321 | gl_FragColor = 0.25 * (upperLeftColor + upperRightColor + lowerLeftColor + lowerRightColor);
322 | }
323 | );
324 | ------------------------------------------------------------------------
325 | */
326 | //与gpuimage算法不一致
327 | void CPUImageAverageColor(unsigned char *Input, int Width, int Height, int Stride, unsigned char *AverageR, unsigned char *AverageG, unsigned char *AverageB, unsigned char *AverageA)
328 | {
329 | int Channels = Stride / Width;
330 | if (Channels == 1)
331 | {
332 | int numberOfPixels = Width * Height;
333 | unsigned int histogramGray[256] = {0};
334 | for (int Y = 0; Y < Height; Y++)
335 | {
336 | unsigned char *pInput = Input + (Y * Stride);
337 | for (int X = 0; X < Width; X++)
338 | {
339 | histogramGray[pInput[0]]++;
340 | pInput += Channels;
341 | }
342 | }
343 | unsigned int Sum = 0;
344 | for (unsigned int i = 0; i < 256; i++)
345 | {
346 | Sum += histogramGray[i] * i;
347 | }
348 | *AverageR = (unsigned char)(Sum / numberOfPixels);
349 | *AverageG = *AverageR;
350 | *AverageB = *AverageR;
351 | *AverageA = *AverageR;
352 | }
353 | else if (Channels == 3)
354 | {
355 |
356 | int numberOfPixels = Width * Height;
357 | unsigned int histogramRGB[768] = {0};
358 | unsigned int *histogramR = &histogramRGB[0];
359 | unsigned int *histogramG = &histogramRGB[256];
360 | unsigned int *histogramB = &histogramRGB[512];
361 | for (int Y = 0; Y < Height; Y++)
362 | {
363 | unsigned char *pInput = Input + (Y * Stride);
364 | for (int X = 0; X < Width; X++)
365 | {
366 | const unsigned char R = pInput[0];
367 | const unsigned char G = pInput[1];
368 | const unsigned char B = pInput[2];
369 | histogramR[R]++;
370 | histogramG[G]++;
371 | histogramB[B]++;
372 | pInput += Channels;
373 | }
374 | }
375 | unsigned int SumPixR = 0, SumPixG = 0, SumPixB = 0;
376 | for (unsigned int i = 0; i < 256; i++)
377 | {
378 | SumPixR += histogramR[i] * i;
379 | SumPixG += histogramG[i] * i;
380 | SumPixB += histogramB[i] * i;
381 | }
382 | *AverageR =(unsigned char) (SumPixR / numberOfPixels);
383 | *AverageG =(unsigned char)(SumPixG / numberOfPixels);
384 | *AverageB =(unsigned char) (SumPixB / numberOfPixels);
385 | *AverageA = 255;
386 | }
387 | else if (Channels == 4)
388 | {
389 |
390 | int numberOfPixels = Width * Height;
391 | unsigned int histogramRGB[768 + 256] = {0};
392 | unsigned int *histogramR = &histogramRGB[0];
393 | unsigned int *histogramG = &histogramRGB[256];
394 | unsigned int *histogramB = &histogramRGB[512];
395 | unsigned int *histogramA = &histogramRGB[768];
396 | for (int Y = 0; Y < Height; Y++)
397 | {
398 | unsigned char *pInput = Input + (Y * Stride);
399 | for (int X = 0; X < Width; X++)
400 | {
401 | const unsigned char R = pInput[0];
402 | const unsigned char G = pInput[1];
403 | const unsigned char B = pInput[2];
404 | const unsigned char A = pInput[3];
405 | histogramR[R]++;
406 | histogramG[G]++;
407 | histogramB[B]++;
408 | histogramA[A]++;
409 | pInput += Channels;
410 | }
411 | }
412 | unsigned int SumPixR = 0, SumPixG = 0, SumPixB = 0, SumPixA = 0;
413 | for (unsigned int i = 0; i < 256; i++)
414 | {
415 | SumPixR += histogramR[i] * i;
416 | SumPixG += histogramG[i] * i;
417 | SumPixB += histogramB[i] * i;
418 | SumPixA += histogramA[i] * i;
419 | }
420 | *AverageR =(unsigned char) (SumPixR / numberOfPixels);
421 | *AverageG =(unsigned char) (SumPixG / numberOfPixels);
422 | *AverageB =(unsigned char) (SumPixB / numberOfPixels);
423 | *AverageA =(unsigned char) (SumPixA / numberOfPixels);
424 | }
425 | }
426 |
427 | /*
428 | CPUImageLuminosity: Like the CPUImageAverageColor, this reduces an image to its average luminosity.You need to set the luminosityProcessingFinishedBlock to handle the Output of this filter, which just returns a luminosity value and a frame time.
429 |
430 |
431 | Shader :
432 | ------------------------------------------------------------------------
433 | precision highp float;
434 |
435 | uniform sampler2D InputImageTexture;
436 |
437 | varying highp vec2 OutputTextureCoordinate;
438 |
439 | varying highp vec2 upperLeftInputTextureCoordinate;
440 | varying highp vec2 upperRightInputTextureCoordinate;
441 | varying highp vec2 lowerLeftInputTextureCoordinate;
442 | varying highp vec2 lowerRightInputTextureCoordinate;
443 |
444 | const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
445 |
446 | void main()
447 | {
448 | highp float upperLeftLuminance = dot(texture2D(InputImageTexture, upperLeftInputTextureCoordinate).rgb, W);
449 | highp float upperRightLuminance = dot(texture2D(InputImageTexture, upperRightInputTextureCoordinate).rgb, W);
450 | highp float lowerLeftLuminance = dot(texture2D(InputImageTexture, lowerLeftInputTextureCoordinate).rgb, W);
451 | highp float lowerRightLuminance = dot(texture2D(InputImageTexture, lowerRightInputTextureCoordinate).rgb, W);
452 |
453 | highp float luminosity = 0.25 * (upperLeftLuminance + upperRightLuminance + lowerLeftLuminance + lowerRightLuminance);
454 | gl_FragColor = vec4(luminosity, luminosity, luminosity, 1.0);
455 | }
456 |
457 |
458 | precision highp float;
459 |
460 | uniform sampler2D InputImageTexture;
461 |
462 | varying highp vec2 OutputTextureCoordinate;
463 |
464 | varying highp vec2 upperLeftInputTextureCoordinate;
465 | varying highp vec2 upperRightInputTextureCoordinate;
466 | varying highp vec2 lowerLeftInputTextureCoordinate;
467 | varying highp vec2 lowerRightInputTextureCoordinate;
468 |
469 | void main()
470 | {
471 | highp float upperLeftLuminance = texture2D(InputImageTexture, upperLeftInputTextureCoordinate).r;
472 | highp float upperRightLuminance = texture2D(InputImageTexture, upperRightInputTextureCoordinate).r;
473 | highp float lowerLeftLuminance = texture2D(InputImageTexture, lowerLeftInputTextureCoordinate).r;
474 | highp float lowerRightLuminance = texture2D(InputImageTexture, lowerRightInputTextureCoordinate).r;
475 |
476 | highp float luminosity = 0.25 * (upperLeftLuminance + upperRightLuminance + lowerLeftLuminance + lowerRightLuminance);
477 | gl_FragColor = vec4(luminosity, luminosity, luminosity, 1.0);
478 | }
479 | ------------------------------------------------------------------------
480 | */
481 |
482 | void CPUImageLuminosity(unsigned char *Input, int Width, int Height, int Stride, unsigned char *Luminance)
483 | {
484 | int Channels = Stride / Width;
485 | if (Channels == 1)
486 | {
487 | int numberOfPixels = Width * Height;
488 | unsigned int histogramGray[256] = {0};
489 | for (int Y = 0; Y < Height; Y++)
490 | {
491 | unsigned char *pInput = Input + (Y * Stride);
492 | for (int X = 0; X < Width; X++)
493 | {
494 | histogramGray[pInput[0]]++;
495 | pInput++;
496 | }
497 | }
498 | unsigned int Sum = 0;
499 | for (int i = 0; i < 256; i++)
500 | {
501 | Sum += histogramGray[i] * i;
502 | }
503 | *Luminance = (unsigned char)(Sum / numberOfPixels);
504 | }
505 | else if (Channels == 3 || Channels == 4)
506 | {
507 |
508 | int numberOfPixels = Width * Height;
509 | unsigned int histogramLum[256] = {0};
510 | for (int Y = 0; Y < Height; Y++)
511 | {
512 | unsigned char *pInput = Input + (Y * Stride);
513 | for (int X = 0; X < Width; X++)
514 | {
515 | const unsigned char R = pInput[0];
516 | const unsigned char G = pInput[1];
517 | const unsigned char B = pInput[2];
518 | histogramLum[((13926 * R + 46884 * G + 4725 * B) >> 16)]++;
519 | pInput += Channels;
520 | }
521 | }
522 | unsigned int Sum = 0;
523 | for (unsigned int i = 0; i < 256; i++)
524 | {
525 | Sum += histogramLum[i] * i;
526 | }
527 | *Luminance = (unsigned char)(Sum / numberOfPixels);
528 | }
529 | }
530 | /*
531 | CPUImageColorMatrixFilter: Transforms the colors of an image by applying a matrix to them
532 | colorMatrix : A 4x4 matrix used to transform each color in an image
533 | intensity : The degree to which the new transformed color replaces the original color for each pixel
534 |
535 |
536 | Shader :
537 | ------------------------------------------------------------------------
538 |
539 | varying highp vec2 textureCoordinate;
540 |
541 | uniform sampler2D InputImageTexture;
542 |
543 | uniform lowp mat4 colorMatrix;
544 | uniform lowp float intensity;
545 |
546 | void main()
547 | {
548 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
549 | lowp vec4 OutputColor = textureColor * colorMatrix;
550 |
551 | gl_FragColor = (intensity * OutputColor) + ((1.0 - intensity) * textureColor);
552 | }
553 | ------------------------------------------------------------------------
554 | */
555 |
556 | void CPUImageColorMatrixFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float *colorMatrix, float intensity)
557 | {
558 | int Channels = Stride / Width;
559 | if (Channels == 1)
560 | return;
561 | unsigned char degreeMap[256 * 256] = {0};
562 | for (int pixel = 0; pixel < 256; pixel++)
563 | {
564 | unsigned char *pDegreeMap = degreeMap + pixel * 256;
565 | for (int out = 0; out < 256; out++)
566 | {
567 | pDegreeMap[0] = degree((unsigned char)pixel, (unsigned char)out, intensity);
568 | pDegreeMap++;
569 | }
570 | }
571 | unsigned char colorMatrixMap[256 * 16] = {0};
572 | unsigned char *colorMatrix0 = &colorMatrixMap[0];
573 | unsigned char *colorMatrix1 = &colorMatrixMap[256];
574 | unsigned char *colorMatrix2 = &colorMatrixMap[256 * 2];
575 | unsigned char *colorMatrix3 = &colorMatrixMap[256 * 3];
576 | unsigned char *colorMatrix4 = &colorMatrixMap[256 * 4];
577 | unsigned char *colorMatrix5 = &colorMatrixMap[256 * 5];
578 | unsigned char *colorMatrix6 = &colorMatrixMap[256 * 6];
579 | unsigned char *colorMatrix7 = &colorMatrixMap[256 * 7];
580 | unsigned char *colorMatrix8 = &colorMatrixMap[256 * 8];
581 | unsigned char *colorMatrix9 = &colorMatrixMap[256 * 9];
582 | unsigned char *colorMatrix10 = &colorMatrixMap[256 * 10];
583 | unsigned char *colorMatrix11 = &colorMatrixMap[256 * 11];
584 | unsigned char *colorMatrix12 = &colorMatrixMap[256 * 12];
585 | unsigned char *colorMatrix13 = &colorMatrixMap[256 * 13];
586 | unsigned char *colorMatrix14 = &colorMatrixMap[256 * 14];
587 | unsigned char *colorMatrix15 = &colorMatrixMap[256 * 15];
588 | for (int pixel = 0; pixel < 256; pixel++)
589 | {
590 | colorMatrix0[pixel] = ClampToByte(pixel * colorMatrix[0]);
591 | colorMatrix1[pixel] = ClampToByte(pixel * colorMatrix[1]);
592 | colorMatrix2[pixel] = ClampToByte(pixel * colorMatrix[2]);
593 | colorMatrix3[pixel] = ClampToByte(pixel * colorMatrix[3]);
594 | colorMatrix4[pixel] = ClampToByte(pixel * colorMatrix[4]);
595 | colorMatrix5[pixel] = ClampToByte(pixel * colorMatrix[5]);
596 | colorMatrix6[pixel] = ClampToByte(pixel * colorMatrix[6]);
597 | colorMatrix7[pixel] = ClampToByte(pixel * colorMatrix[7]);
598 | colorMatrix8[pixel] = ClampToByte(pixel * colorMatrix[8]);
599 | colorMatrix9[pixel] = ClampToByte(pixel * colorMatrix[9]);
600 | colorMatrix10[pixel] = ClampToByte(pixel * colorMatrix[10]);
601 | colorMatrix11[pixel] = ClampToByte(pixel * colorMatrix[11]);
602 | colorMatrix12[pixel] = ClampToByte(pixel * colorMatrix[12]);
603 | colorMatrix13[pixel] = ClampToByte(pixel * colorMatrix[13]);
604 | colorMatrix14[pixel] = ClampToByte(pixel * colorMatrix[14]);
605 | colorMatrix15[pixel] = ClampToByte(pixel * colorMatrix[15]);
606 | }
607 | if (Channels == 4)
608 | {
609 | unsigned char outR, outG, outB, outA;
610 | int WidthStep = Width * Channels;
611 | for (int Y = 0; Y < Height; Y++)
612 | {
613 | unsigned char *pOutput = Output + (Y * WidthStep);
614 | unsigned char *pInput = Input + (Y * WidthStep);
615 | for (int X = 0; X < Width; X++)
616 | {
617 | const unsigned char r = pInput[0];
618 | const unsigned char g = pInput[1];
619 | const unsigned char b = pInput[2];
620 | const unsigned char a = pInput[3];
621 | outR = ClampToByte(colorMatrix0[r] + colorMatrix1[g] + colorMatrix2[b] + colorMatrix3[a]);
622 | unsigned char *pDegreeMapR = degreeMap + (r << 8);
623 | pOutput[0] = pDegreeMapR[outR];
624 | outG = ClampToByte(colorMatrix4[r] + colorMatrix5[g] + colorMatrix6[b] + colorMatrix7[a]);
625 | unsigned char *pDegreeMapG = degreeMap + (g << 8);
626 | pOutput[1] = pDegreeMapG[outG];
627 | outB = ClampToByte(colorMatrix8[r] + colorMatrix9[g] + colorMatrix10[b] + colorMatrix11[a]);
628 | unsigned char *pDegreeMapB = degreeMap + (b << 8);
629 | pOutput[2] = pDegreeMapB[outB];
630 | outA = ClampToByte(colorMatrix12[r] + colorMatrix13[g] + colorMatrix14[b] + colorMatrix15[a]);
631 | unsigned char *pDegreeMapA = degreeMap + (a << 8);
632 | pOutput[3] = pDegreeMapA[outA];
633 | pInput += Channels;
634 | pOutput += Channels;
635 | }
636 | }
637 | }
638 | else if (Channels == 3)
639 | {
640 | //三个通道则,a为255不透明
641 | unsigned char outR, outG, outB;
642 | int WidthStep = Width * Channels;
643 | for (int Y = 0; Y < Height; Y++)
644 | {
645 | unsigned char *pOutput = Output + (Y * WidthStep);
646 | unsigned char *pInput = Input + (Y * WidthStep);
647 | for (int X = 0; X < Width; X++)
648 | {
649 | const unsigned char r = pInput[0];
650 | const unsigned char g = pInput[1];
651 | const unsigned char b = pInput[2];
652 | outR = ClampToByte(colorMatrix0[r] + colorMatrix1[g] + colorMatrix2[b]);
653 | unsigned char *pDegreeMapR = degreeMap + (r << 8);
654 | pOutput[0] = pDegreeMapR[outR];
655 | outG = ClampToByte(colorMatrix4[r] + colorMatrix5[g] + colorMatrix6[b]);
656 | unsigned char *pDegreeMapG = degreeMap + (g << 8);
657 | pOutput[1] = pDegreeMapG[outG];
658 | outB = ClampToByte(colorMatrix8[r] + colorMatrix9[g] + colorMatrix10[b]);
659 | unsigned char *pDegreeMapB = degreeMap + (b << 8);
660 | pOutput[2] = pDegreeMapB[outB];
661 | pInput += Channels;
662 | pOutput += Channels;
663 | }
664 | }
665 | }
666 | }
667 |
668 | /*
669 | CPUImageSepiaFilter: Simple sepia tone filter
670 | intensity : The degree to which the sepia tone replaces the normal image color(0.0 - 1.0, with 1.0 as the default)
671 | */
672 |
673 | void CPUImageSepiaFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int intensity)
674 | {
675 | int Channels = Stride / Width;
676 | if (Channels == 1)
677 | return;
678 | float fIntensity = intensity / 100.0f;
679 |
680 | float colorMatrix[4 * 4] = {
681 | 0.3588f, 0.7044f, 0.1368f, 0.0f,
682 | 0.2990f, 0.5870f, 0.1140f, 0.0f,
683 | 0.2392f, 0.4696f, 0.0912f, 0.0f,
684 | 0.f, 0.f, 0.f, 1.f};
685 | CPUImageColorMatrixFilter(Input, Output, Width, Height, Stride, colorMatrix, fIntensity);
686 | }
687 | /*
688 |
689 | CPUImageChromaKeyFilter: For a given color in the image, sets the alpha channel to 0. This is similar to the GPUImageChromaKeyBlendFilter, only instead of blending in a second image for a matching color this doesn't take in a second image and just turns a given color transparent.
690 | thresholdSensitivity : How close a color match needs to exist to the target color to be replaced(default of 0.4)
691 | smoothing : How smoothly to blend for the color match(default of 0.1)
692 |
693 |
694 | Shader :
695 | ------------------------------------------------------------------------
696 | // Shader code based on Apple's CIChromaKeyFilter example: https://developer.apple.com/library/mac/#samplecode/CIChromaKeyFilter/Introduction/Intro.html
697 | (
698 | precision highp float;
699 |
700 | varying highp vec2 textureCoordinate;
701 |
702 | uniform float thresholdSensitivity;
703 | uniform float smoothing;
704 | uniform vec3 colorToReplace;
705 | uniform sampler2D InputImageTexture;
706 | uniform sampler2D InputImageTexture2;
707 |
708 | void main()
709 | {
710 | vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
711 |
712 | float maskY = 0.2989 * colorToReplace.r + 0.5866 * colorToReplace.g + 0.1145 * colorToReplace.b;
713 | float maskCr = 0.7132 * (colorToReplace.r - maskY);
714 | float maskCb = 0.5647 * (colorToReplace.b - maskY);
715 |
716 | float Y = 0.2989 * textureColor.r + 0.5866 * textureColor.g + 0.1145 * textureColor.b;
717 | float Cr = 0.7132 * (textureColor.r - Y);
718 | float Cb = 0.5647 * (textureColor.b - Y);
719 |
720 | // float blendValue = 1.0 - smoothstep(thresholdSensitivity - smoothing, thresholdSensitivity , abs(Cr - maskCr) + abs(Cb - maskCb));
721 | float blendValue = smoothstep(thresholdSensitivity, thresholdSensitivity + smoothing, distance(vec2(Cr, Cb), vec2(maskCr, maskCb)));
722 | gl_FragColor = vec4(textureColor.rgb, textureColor.a * blendValue);
723 | }
724 | );
725 | ------------------------------------------------------------------------
726 |
727 | */
728 |
729 | void CPUImageChromaKeyFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, unsigned char colorToReplaceR, unsigned char colorToReplaceG, unsigned char colorToReplaceB, float thresholdSensitivity, float smoothing)
730 | {
731 | int Channels = Stride / Width;
732 | if (Channels == 1)
733 | {
734 | return;
735 | }
736 |
737 | unsigned char maskY =(unsigned char) ((19589 * colorToReplaceR + 38443 * colorToReplaceG + 7504 * colorToReplaceB) >> 16);
738 |
739 | unsigned char maskCr =(unsigned char) ((46740 * (colorToReplaceR - maskY) >> 16) + 128);
740 |
741 | unsigned char maskCb =(unsigned char) ((37008 * (colorToReplaceB - maskY) >> 16) + 128);
742 | int iThresholdSensitivity = (int)(thresholdSensitivity * 255.0f);
743 | int iSmoothing = (int)(smoothing * 256);
744 | if (Channels == 3)
745 | {
746 | short blendMap[256 * 256] = {0};
747 | for (int Cr = 0; Cr < 256; Cr++)
748 | {
749 | short *pBlendMap = blendMap + (Cr << 8);
750 | for (int Cb = 0; Cb < 256; Cb++)
751 | {
752 | pBlendMap[Cb] = (short)(255.0f * smoothstep((float)iThresholdSensitivity, (float)iThresholdSensitivity + iSmoothing, vec2_distance((float)Cr, (float)Cb, (float)maskCr, (float)maskCb)));
753 | }
754 | }
755 | for (int Y = 0; Y < Height; Y++)
756 | {
757 | unsigned char *pOutput = Output + (Y * Stride);
758 | unsigned char *pInput = Input + (Y * Stride);
759 | for (int X = 0; X < Width; X++)
760 | {
761 | const unsigned char R = pInput[0];
762 | const unsigned char G = pInput[1];
763 | const unsigned char B = pInput[2];
764 | unsigned char y = (unsigned char)((19589 * R + 38443 * G + 7504 * B) >> 16);
765 | unsigned char Cr = (unsigned char)((46740 * (R - y) >> 16) + 128);
766 | unsigned char Cb = (unsigned char)((37008 * (B - y) >> 16) + 128);
767 | //乘以255取得mask,不乘以255则排除mask
768 | short *pBlendMap = blendMap + (Cr << 8);
769 | const short blendValue = pBlendMap[Cb];
770 | pOutput[0] =(unsigned char) (255 - (R * blendValue));
771 | pOutput[1] =(unsigned char) (255 - (G * blendValue));
772 | pOutput[2] =(unsigned char) (255 - (B * blendValue));
773 | pInput += Channels;
774 | pOutput += Channels;
775 | }
776 | }
777 | }
778 | else if (Channels == 4)
779 | {
780 | unsigned char blendMap[256 * 256] = {0};
781 | for (int Cr = 0; Cr < 256; Cr++)
782 | {
783 | unsigned char *pBlendMap = blendMap + (Cr << 8);
784 | for (int Cb = 0; Cb < 256; Cb++)
785 | {
786 | pBlendMap[Cb] = (unsigned char)(255.0f * smoothstep((float)iThresholdSensitivity, (float)iThresholdSensitivity + iSmoothing, vec2_distance((float)Cr, (float)Cb, (float)maskCr, (float)maskCb)));
787 | }
788 | }
789 | for (int Y = 0; Y < Height; Y++)
790 | {
791 | unsigned char *pOutput = Output + (Y * Stride);
792 | unsigned char *pInput = Input + (Y * Stride);
793 | for (int X = 0; X < Width; X++)
794 | {
795 | const unsigned char R = pInput[0];
796 | const unsigned char G = pInput[1];
797 | const unsigned char B = pInput[2];
798 | const unsigned char A = pInput[3];
799 | unsigned char y = (unsigned char)((19589 * R + 38443 * G + 7504 * B) >> 16);
800 | unsigned char Cr =(unsigned char)((46740 * (R - y) >> 16) + 128);
801 | unsigned char Cb = (unsigned char)((37008 * (B - y) >> 16) + 128);
802 | //直接处理透明通道
803 | unsigned char *pBlendMap = blendMap + (Cr << 8);
804 | const unsigned char blendValue = pBlendMap[Cb];
805 | pOutput[3] = ClampToByte(A * blendValue);
806 | pInput += Channels;
807 | pOutput += Channels;
808 | }
809 | }
810 | }
811 | } /*
812 | CPUImageLookupFilter: Uses an RGB color lookup image to remap the colors in an image.First, use your favourite photo editing application to apply a filter to lookup.png from GPUImage / framework / Resources.For this to work properly each pixel color must not depend on other pixels(e.g.blur will not work).If you need a more complex filter you can create as many lookup tables as required.Once ready, use your new lookup.png file as a second Input for GPUImageLookupFilter.
813 |
814 | Shader :
815 | ------------------------------------------------------------------------
816 | varying highp vec2 textureCoordinate;
817 | varying highp vec2 textureCoordinate2; // TODO: This is not used
818 |
819 | uniform sampler2D InputImageTexture;
820 | uniform sampler2D InputImageTexture2; // lookup texture
821 |
822 | uniform lowp float intensity;
823 |
824 | void main()
825 | {
826 | highp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
827 |
828 | highp float blueColor = textureColor.b * 63.0;
829 |
830 | highp vec2 quad1;
831 | quad1.y = floor(floor(blueColor) / 8.0);
832 | quad1.x = floor(blueColor) - (quad1.y * 8.0);
833 |
834 | highp vec2 quad2;
835 | quad2.y = floor(ceil(blueColor) / 8.0);
836 | quad2.x = ceil(blueColor) - (quad2.y * 8.0);
837 |
838 | highp vec2 texPos1;
839 | texPos1.x = (quad1.x * 0.125) + 0.5 / 512.0 + ((0.125 - 1.0 / 512.0) * textureColor.r);
840 | texPos1.y = (quad1.y * 0.125) + 0.5 / 512.0 + ((0.125 - 1.0 / 512.0) * textureColor.g);
841 |
842 | highp vec2 texPos2;
843 | texPos2.x = (quad2.x * 0.125) + 0.5 / 512.0 + ((0.125 - 1.0 / 512.0) * textureColor.r);
844 | texPos2.y = (quad2.y * 0.125) + 0.5 / 512.0 + ((0.125 - 1.0 / 512.0) * textureColor.g);
845 |
846 | lowp vec4 newColor1 = texture2D(InputImageTexture2, texPos1);
847 | lowp vec4 newColor2 = texture2D(InputImageTexture2, texPos2);
848 |
849 | lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));
850 | gl_FragColor = mix(textureColor, vec4(newColor.rgb, textureColor.w), intensity);
851 | }
852 | ------------------------------------------------------------------------
853 | */
854 |
855 | //lookup 512*512*3
856 | void CPUImageLookupFilter(unsigned char *Input, unsigned char *Output, unsigned char *lookupTable, int Width, int Height, int Stride, int intensity)
857 | {
858 | int Channels = Stride / Width;
859 | if (Channels == 1)
860 | return;
861 | float preMap[256 * 5] = {0};
862 | float *pixelColorMap = &preMap[0];
863 | float *quad1yMap = &preMap[256];
864 | float *quad1xMap = &preMap[256 + 256];
865 | float *quad2yMap = &preMap[256 + 256 + 256];
866 | float *quad2xMap = &preMap[256 + 256 + 256 + 256];
867 |
868 | unsigned short fractMap[256] = {0};
869 | unsigned short sIntensity =(unsigned short) max(min(intensity, 100), 0);
870 | int c1 = 256 * (100 - sIntensity) / 100;
871 | int c2 = 256 * (100 - (100 - sIntensity)) / 100;
872 |
873 | for (int b = 0; b < 256; b++)
874 | {
875 | pixelColorMap[b] = b * (63.0f / 255.0f);
876 | fractMap[b] = (unsigned short)(256 * (pixelColorMap[b] - truncf(pixelColorMap[b])));
877 | quad1yMap[b] = floorf(floorf(pixelColorMap[b]) * (1.0f / 8.0f));
878 | quad1xMap[b] = floorf(pixelColorMap[b]) - (quad1yMap[b] * 8.0f);
879 | quad2yMap[b] = floorf(ceilf(pixelColorMap[b]) * (1.0f / 8.0f));
880 | quad2xMap[b] = ceilf(pixelColorMap[b]) - (quad2yMap[b] * 8.0f);
881 | quad1yMap[b] = quad1yMap[b] * 64.0f + 0.5f;
882 | quad2yMap[b] = quad2yMap[b] * 64.0f + 0.5f;
883 | quad1xMap[b] = quad1xMap[b] * 64.0f + 0.5f;
884 | quad2xMap[b] = quad2xMap[b] * 64.0f + 0.5f;
885 | }
886 |
887 | int lookupChannels = 3;
888 | for (int Y = 0; Y < Height; Y++)
889 | {
890 | unsigned char *pOutput = Output + (Y * Stride);
891 | unsigned char *pInput = Input + (Y * Stride);
892 | for (int X = 0; X < Width; X++)
893 | {
894 | unsigned char R = pInput[0];
895 | unsigned char G = pInput[1];
896 | unsigned char B = pInput[2];
897 | float green = pixelColorMap[G];
898 | float red = pixelColorMap[R];
899 | unsigned char *pLineLookup1 = &lookupTable[(((int)(quad1yMap[B] + green) << 9) + (int)(quad1xMap[B] + red)) * lookupChannels];
900 | unsigned char *pLineLookup2 = &lookupTable[(((int)(quad2yMap[B] + green) << 9) + (int)(quad2xMap[B] + red)) * lookupChannels];
901 | unsigned short fractB = fractMap[B];
902 | pOutput[0] = (unsigned char) ((int)(R * c1 + ((*pLineLookup1++ * (256 - fractB) + *pLineLookup2++ * fractB) >> 8) * c2) >> 8);
903 | pOutput[1] = (unsigned char)((int)(G * c1 + ((*pLineLookup1++ * (256 - fractB) + *pLineLookup2++ * fractB) >> 8) * c2) >> 8);
904 | pOutput[2] = (unsigned char)((int)(B * c1 + ((*pLineLookup1++ * (256 - fractB) + *pLineLookup2++ * fractB) >> 8) * c2) >> 8);
905 | pInput += Channels;
906 | pOutput += Channels;
907 | }
908 | }
909 | }
910 |
911 | /*
912 | CPUImageSaturationFilter: Adjusts the saturation of an image
913 | saturation : The degree of saturation or desaturation to apply to the image(0.0 - 2.0, with 1.0 as the default)
914 |
915 | Shader :
916 | ------------------------------------------------------------------------------------------ -
917 | varying highp vec2 textureCoordinate;
918 |
919 | uniform sampler2D InputImageTexture;
920 | uniform lowp float saturation;
921 |
922 | // Values from "Graphics Shaders: Theory and Practice" by Bailey and Cunningham
923 | const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
924 |
925 | void main()
926 | {
927 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
928 | lowp float luminance = dot(textureColor.rgb, luminanceWeighting);
929 | lowp vec3 greyScaleColor = vec3(luminance);
930 |
931 | gl_FragColor = vec4(mix(greyScaleColor, textureColor.rgb, saturation), textureColor.w);
932 | }
933 | ------------------------------------------------------------------------------------------ -
934 | */
935 |
936 | void CPUImageSaturationFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float saturation)
937 | {
938 | int Channels = Stride / Width;
939 | if (Channels == 1)
940 | return;
941 | unsigned char SaturationMap[256 * 256] = {0};
942 | for (int grey = 0; grey < 256; grey++)
943 | {
944 | unsigned char *pSaturationMap = SaturationMap + (grey << 8);
945 | for (int input = 0; input < 256; input++)
946 | {
947 | pSaturationMap[0] = (unsigned char)((mix_u8((unsigned char)grey,(unsigned char) input, saturation) + input) * 0.5f);
948 | pSaturationMap++;
949 | }
950 | }
951 | //0.2125*256.0*256.0=13926.4
952 | //0.7154*256.0*256.0=46884.4544
953 | //0.0721*256.0*256.0=4725.1456
954 | for (int Y = 0; Y < Height; Y++)
955 | {
956 | unsigned char *pOutput = Output + (Y * Stride);
957 | unsigned char *pInput = Input + (Y * Stride);
958 | for (int X = 0; X < Width; X++)
959 | {
960 | unsigned char *pSaturationMap = SaturationMap + (((13926 * pInput[0] + 46884 * pInput[1] + 4725 * pInput[2]) >> 16) << 8);
961 | pOutput[0] = pSaturationMap[pInput[0]];
962 | pOutput[1] = pSaturationMap[pInput[1]];
963 | pOutput[2] = pSaturationMap[pInput[2]];
964 | pInput += Channels;
965 | pOutput += Channels;
966 | }
967 | }
968 | }
969 |
970 | /*
971 |
972 | CPUImageGammaFilter: Adjusts the gamma of an image
973 | gamma : The gamma adjustment to apply(0.0 - 3.0, with 1.0 as the default)
974 |
975 | Shader :
976 | ------------------------------------------------------------------------------------------ -
977 | varying highp vec2 textureCoordinate;
978 |
979 | uniform sampler2D InputImageTexture;
980 | uniform lowp float gamma;
981 |
982 | void main()
983 | {
984 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
985 |
986 | gl_FragColor = vec4(pow(textureColor.rgb, vec3(gamma)), textureColor.w);
987 | }
988 | ------------------------------------------------------------------------------------------ -
989 | */
990 |
991 | void CPUImageGammaFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float gamma)
992 | {
993 | int Channels = Stride / Width;
994 | if (Channels == 1)
995 | return;
996 | unsigned char GammasMap[256] = {0};
997 | for (int pixel = 0; pixel < 256; pixel++)
998 | {
999 | GammasMap[pixel] = ClampToByte(pow(pixel, gamma));
1000 | }
1001 | for (int Y = 0; Y < Height; Y++)
1002 | {
1003 | unsigned char *pOutput = Output + (Y * Stride);
1004 | unsigned char *pInput = Input + (Y * Stride);
1005 | for (int X = 0; X < Width; X++)
1006 | {
1007 | pOutput[0] = GammasMap[pInput[0]];
1008 | pOutput[1] = GammasMap[pInput[1]];
1009 | pOutput[2] = GammasMap[pInput[2]];
1010 | pInput += Channels;
1011 | pOutput += Channels;
1012 | }
1013 | }
1014 | }
1015 |
1016 | /*
1017 |
1018 | CPUImageContrastFilter: Adjusts the contrast of the image
1019 | contrast : The adjusted contrast(0.0 - 4.0, with 1.0 as the default)
1020 |
1021 |
1022 | Shader :
1023 | ------------------------------------------------------------------------------------------ -
1024 | varying highp vec2 textureCoordinate;
1025 |
1026 | uniform sampler2D InputImageTexture;
1027 | uniform lowp float contrast;
1028 |
1029 | void main()
1030 | {
1031 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1032 |
1033 | gl_FragColor = vec4(((textureColor.rgb - vec3(0.5)) * contrast + vec3(0.5)), textureColor.w);
1034 | }
1035 | ------------------------------------------------------------------------------------------ -
1036 | */
1037 | void CPUImageContrastFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float contrast)
1038 | {
1039 | int Channels = Stride / Width;
1040 | if (Channels == 1)
1041 | return;
1042 | unsigned char contrastMap[256] = {0};
1043 | for (int pixel = 0; pixel < 256; pixel++)
1044 | {
1045 | contrastMap[pixel] = ClampToByte((pixel - 127) * contrast + 127);
1046 | }
1047 | for (int Y = 0; Y < Height; Y++)
1048 | {
1049 | unsigned char *pOutput = Output + (Y * Stride);
1050 | unsigned char *pInput = Input + (Y * Stride);
1051 | for (int X = 0; X < Width; X++)
1052 | {
1053 | pOutput[0] = contrastMap[pInput[0]];
1054 | pOutput[1] = contrastMap[pInput[1]];
1055 | pOutput[2] = contrastMap[pInput[2]];
1056 | pInput += Channels;
1057 | pOutput += Channels;
1058 | }
1059 | }
1060 | }
1061 | /*
1062 |
1063 | CPUImageExposureFilter: Adjusts the exposure of the image
1064 | exposure : The adjusted exposure(-10.0 - 10.0, with 0.0 as the default)
1065 |
1066 | Shader :
1067 | ------------------------------------------------------------------------------------------ -
1068 | varying highp vec2 textureCoordinate;
1069 |
1070 | uniform sampler2D InputImageTexture;
1071 | uniform highp float exposure;
1072 |
1073 | void main()
1074 | {
1075 | highp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1076 |
1077 | gl_FragColor = vec4(textureColor.rgb * pow(2.0, exposure), textureColor.w);
1078 | }
1079 | ------------------------------------------------------------------------------------------ -
1080 | */
1081 | void CPUImageExposureFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float exposure)
1082 | {
1083 | int Channels = Stride / Width;
1084 | if (Channels == 1)
1085 | return;
1086 | unsigned char exposureMap[256] = {0};
1087 | for (int pixel = 0; pixel < 256; pixel++)
1088 | {
1089 | exposureMap[pixel] = ClampToByte(pixel * pow(2.0, exposure));
1090 | }
1091 | for (int Y = 0; Y < Height; Y++)
1092 | {
1093 | unsigned char *pOutput = Output + (Y * Stride);
1094 | unsigned char *pInput = Input + (Y * Stride);
1095 | for (int X = 0; X < Width; X++)
1096 | {
1097 | pOutput[0] = exposureMap[pInput[0]];
1098 | pOutput[1] = exposureMap[pInput[1]];
1099 | pOutput[2] = exposureMap[pInput[2]];
1100 | pInput += Channels;
1101 | pOutput += Channels;
1102 | }
1103 | }
1104 | }
1105 |
1106 | /*
1107 |
1108 | CPUImageBrightnessFilter: Adjusts the brightness of the image
1109 | brightness : The adjusted brightness(-1.0 - 1.0, with 0.0 as the default)
1110 |
1111 | Shader :
1112 | ------------------------------------------------------------------------------------------ -
1113 | varying highp vec2 textureCoordinate;
1114 |
1115 | uniform sampler2D InputImageTexture;
1116 | uniform lowp float brightness;
1117 |
1118 | void main()
1119 | {
1120 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1121 |
1122 | gl_FragColor = vec4((textureColor.rgb + vec3(brightness)), textureColor.w);
1123 | }
1124 | ------------------------------------------------------------------------------------------
1125 | */
1126 |
1127 | void CPUImageBrightnessFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int brightness)
1128 | {
1129 | int Channels = Stride / Width;
1130 | if (Channels == 1)
1131 | return;
1132 |
1133 | unsigned char BrightnessMap[256] = {0};
1134 | for (int pixel = 0; pixel < 256; pixel++)
1135 | {
1136 | BrightnessMap[pixel] = ClampToByte(pixel + brightness);
1137 | }
1138 | for (int Y = 0; Y < Height; Y++)
1139 | {
1140 | unsigned char *pOutput = Output + (Y * Stride);
1141 | unsigned char *pInput = Input + (Y * Stride);
1142 | for (int X = 0; X < Width; X++)
1143 | {
1144 | pOutput[0] = BrightnessMap[pInput[0]];
1145 | pOutput[1] = BrightnessMap[pInput[1]];
1146 | pOutput[2] = BrightnessMap[pInput[2]];
1147 | pInput += Channels;
1148 | pOutput += Channels;
1149 | }
1150 | }
1151 | }
1152 | /*
1153 |
1154 | CPUImageFalseColorFilter: Uses the luminance of the image to mix between two user - specified colors
1155 | firstColor : The first and second colors specify what colors replace the dark and light areas of the image, respectively.The defaults are(0.0, 0.0, 0.5) amd(1.0, 0.0, 0.0).
1156 | secondColor :
1157 |
1158 |
1159 | Shader :
1160 | ------------------------------------------------------------------------
1161 | precision lowp float;
1162 |
1163 | varying highp vec2 textureCoordinate;
1164 |
1165 | uniform sampler2D InputImageTexture;
1166 | uniform float intensity;
1167 | uniform vec3 firstColor;
1168 | uniform vec3 secondColor;
1169 |
1170 | const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
1171 |
1172 | void main()
1173 | {
1174 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1175 | float luminance = dot(textureColor.rgb, luminanceWeighting);
1176 |
1177 | gl_FragColor = vec4(mix(firstColor.rgb, secondColor.rgb, luminance), textureColor.a);
1178 | }
1179 | ------------------------------------------------------------------------
1180 |
1181 | */
1182 |
1183 | void CPUImageFalseColorFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, unsigned char firstColorR, unsigned char firstColorG, unsigned char firstColorB, unsigned char secondColorR, unsigned char secondColorG, unsigned char secondColorB, int intensity)
1184 | {
1185 | int Channels = Stride / Width;
1186 | if (Channels == 1)
1187 | return;
1188 | unsigned short sIntensity =(unsigned short) max(min(intensity, 100), 0);
1189 | int c1 = 256 * (100 - sIntensity) / 100;
1190 | int c2 = 256 * (100 - (100 - sIntensity)) / 100;
1191 |
1192 | unsigned char ColorMapR[256] = {0};
1193 | unsigned char ColorMapG[256] = {0};
1194 | unsigned char ColorMapB[256] = {0};
1195 | for (int pixel = 0; pixel < 256; pixel++)
1196 | {
1197 | float fPixel = pixel * (1.0f / 255.0f);
1198 | ColorMapR[pixel] = mix_u8(firstColorR, secondColorR, fPixel);
1199 | ColorMapG[pixel] = mix_u8(firstColorG, secondColorG, fPixel);
1200 | ColorMapB[pixel] = mix_u8(firstColorB, secondColorB, fPixel);
1201 | }
1202 | for (int Y = 0; Y < Height; Y++)
1203 | {
1204 | unsigned char *pOutput = Output + (Y * Stride);
1205 | unsigned char *pInput = Input + (Y * Stride);
1206 | for (int X = 0; X < Width; X++)
1207 | {
1208 | unsigned char luminanceWeighting =(unsigned char) ((13926 * pInput[0] + 46884 * pInput[1] + 4725 * pInput[2]) >> 16);
1209 |
1210 | pOutput[0] =(unsigned char) ((pInput[0] * c1 + ColorMapR[luminanceWeighting] * c2) >> 8);
1211 | pOutput[1] =(unsigned char) ((pInput[1] * c1 + ColorMapG[luminanceWeighting] * c2) >> 8);
1212 | pOutput[2] =(unsigned char)((pInput[2] * c1 + ColorMapB[luminanceWeighting] * c2) >> 8);
1213 | pInput += Channels;
1214 | pOutput += Channels;
1215 | }
1216 | }
1217 | }
1218 | /*
1219 |
1220 | CPUImageHazeFilter: Used to add or remove haze(similar to a UV filter)
1221 | distance : Strength of the color applied.Default 0. Values between - .3 and .3 are best.
1222 | slope : Amount of color change.Default 0. Values between - .3 and .3 are best.
1223 |
1224 | Shader :
1225 | ------------------------------------------------------------------------
1226 | varying highp vec2 textureCoordinate;
1227 |
1228 | uniform sampler2D InputImageTexture;
1229 |
1230 | uniform lowp float hazeDistance;
1231 | uniform highp float slope;
1232 |
1233 | void main()
1234 | {
1235 | //todo reconsider precision modifiers
1236 | highp vec4 color = vec4(1.0);//todo reimplement as a parameter
1237 |
1238 | highp float d = textureCoordinate.y * slope + hazeDistance;
1239 |
1240 | highp vec4 c = texture2D(InputImageTexture, textureCoordinate); // consider using unpremultiply
1241 |
1242 | c = (c - d * color) / (1.0 - d);
1243 |
1244 | gl_FragColor = c; //consider using premultiply(c);
1245 | }
1246 | ------------------------------------------------------------------------
1247 | */
1248 |
1249 | //distance: Strength of the color applied.Default 0. Values between - .3 and .3 are best.
1250 | // slope : Amount of color change.Default 0. Values between - .3 and .3 are best.
1251 | void CPUImageHazeFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float distance, float slope, int intensity)
1252 | {
1253 | int Channels = Stride / Width;
1254 | if (Channels == 1)
1255 | return;
1256 | unsigned short sIntensity = (unsigned short)max(min(intensity, 100), 0);
1257 | int c1 = 256 * (100 - sIntensity) / 100;
1258 | int c2 = 256 * (100 - (100 - sIntensity)) / 100;
1259 | short *distanceColorMap = (short *)malloc(Height * sizeof(short));
1260 | short *patchDistanceMap = (short *)malloc(Height * sizeof(short));
1261 | if (distanceColorMap == NULL || patchDistanceMap == NULL)
1262 | {
1263 | if (distanceColorMap)
1264 | {
1265 | free(distanceColorMap);
1266 | }
1267 | if (patchDistanceMap)
1268 | {
1269 | free(patchDistanceMap);
1270 | }
1271 | return;
1272 | }
1273 | float color = 1.0f;
1274 | for (int i = 0; i < Height; i++)
1275 | {
1276 | float d = i * (1.0f / Height) * slope + distance;
1277 | distanceColorMap[i] = ClampToByte(255.0 * d * color);
1278 | patchDistanceMap[i] = (short)(256 * clamp(1.0f / (1.0f - d), 0.0f, 1.0f));
1279 | }
1280 | for (int Y = 0; Y < Height; Y++)
1281 | {
1282 | unsigned char *pOutput = Output + (Y * Stride);
1283 | unsigned char *pInput = Input + (Y * Stride);
1284 | for (int X = 0; X < Width; X++)
1285 | {
1286 | pOutput[0] = (unsigned char)((int)(pInput[0] * c1 + ((ClampToByte(pInput[0] - distanceColorMap[Y]) * patchDistanceMap[Y]) >> 8) * c2) >> 8);
1287 | pOutput[1] = (unsigned char)((int)(pInput[1] * c1 + ((ClampToByte(pInput[1] - distanceColorMap[Y]) * patchDistanceMap[Y]) >> 8) * c2) >> 8);
1288 | pOutput[2] = (unsigned char)((int)(pInput[2] * c1 + ((ClampToByte(pInput[2] - distanceColorMap[Y]) * patchDistanceMap[Y]) >> 8) * c2) >> 8);
1289 | pInput += Channels;
1290 | pOutput += Channels;
1291 | }
1292 | }
1293 | free(distanceColorMap);
1294 | free(patchDistanceMap);
1295 | }
1296 |
1297 | /*
1298 |
1299 | CPUImageOpacityFilter: Adjusts the alpha channel of the incoming image
1300 | opacity : The value to multiply the incoming alpha channel for each pixel by(0.0 - 1.0, with 1.0 as the default)
1301 |
1302 | Shader :
1303 | ------------------------------------------------------------------------
1304 | varying highp vec2 textureCoordinate;
1305 |
1306 | uniform sampler2D InputImageTexture;
1307 | uniform lowp float opacity;
1308 |
1309 | void main()
1310 | {
1311 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1312 |
1313 | gl_FragColor = vec4(textureColor.rgb, textureColor.a * opacity);
1314 | }
1315 | ------------------------------------------------------------------------
1316 | */
1317 | void CPUImageOpacityFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float opacity)
1318 | {
1319 | int Channels = Stride / Width;
1320 | if (Channels == 1)
1321 | return;
1322 | if (Channels == 4)
1323 | {
1324 | unsigned char opacityMap[256] = {0};
1325 | for (unsigned int pixel = 0; pixel < 256; pixel++)
1326 | {
1327 | opacityMap[pixel] = (unsigned char)(pixel * opacity);
1328 | }
1329 | for (int Y = 0; Y < Height; Y++)
1330 | {
1331 | unsigned char *pOutput = Output + (Y * Stride);
1332 | unsigned char *pInput = Input + (Y * Stride);
1333 | for (int X = 0; X < Width; X++)
1334 | {
1335 | pOutput[3] = opacityMap[pInput[3]];
1336 | pInput += Channels;
1337 | pOutput += Channels;
1338 | }
1339 | }
1340 | }
1341 | }
1342 |
1343 | /*
1344 |
1345 | CPUImageLevelsFilter: Photoshop - like levels adjustment.The min, max, minOut and maxOut parameters are floats in the range[0, 1].If you have parameters from Photoshop in the range[0, 255] you must first convert them to be[0, 1].The gamma / mid parameter is a float >= 0. This matches the value from Photoshop.If you want to apply levels to RGB as well as individual channels you need to use this filter twice - first for the individual channels and then for all channels.
1346 | */
1347 |
1348 | /*
1349 | Shader :
1350 | ------------------------------------------------------------------------
1351 | IOS版本
1352 |
1353 | //Gamma correction
1354 | //Details: http://blog.mouaif.org/2009/01/22/photoshop-gamma-correction-shader/
1355 |
1356 |
1357 | #define GammaCorrection(color, gamma) pow(color, 1.0 / gamma)
1358 |
1359 |
1360 | ** Levels control (Input (+gamma), Output)
1361 | ** Details: http://blog.mouaif.org/2009/01/28/levels-control-shader/
1362 |
1363 |
1364 | #define LevelsControlInputRange(color, minInput, maxInput) min(max(color - minInput, vec3(0.0)) / (maxInput - minInput), vec3(1.0))
1365 | #define LevelsControlInput(color, minInput, gamma, maxInput) GammaCorrection(LevelsControlInputRange(color, minInput, maxInput), gamma)
1366 | #define LevelsControlOutputRange(color, minOutput, maxOutput) mix(minOutput, maxOutput, color)
1367 | #define LevelsControl(color, minInput, gamma, maxInput, minOutput, maxOutput) LevelsControlOutputRange(LevelsControlInput(color, minInput, gamma, maxInput), minOutput, maxOutput)
1368 | ------------------------------------------------------------------------
1369 | varying highp vec2 textureCoordinate;
1370 |
1371 | uniform sampler2D InputImageTexture;
1372 | uniform mediump vec3 levelMinimum;
1373 | uniform mediump vec3 levelMiddle;
1374 | uniform mediump vec3 levelMaximum;
1375 | uniform mediump vec3 minOutput;
1376 | uniform mediump vec3 maxOutput;
1377 |
1378 | void main()
1379 | {
1380 | mediump vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1381 |
1382 | gl_FragColor = vec4(LevelsControl(textureColor.rgb, levelMinimum, levelMiddle, levelMaximum, minOutput, maxOutput), textureColor.a);
1383 | }
1384 | ------------------------------------------------------------------------------------------ -
1385 | 安卓版本:
1386 | ------------------------------------------------------------------------------------------ -
1387 | varying highp vec2 textureCoordinate;
1388 |
1389 | uniform sampler2D InputImageTexture;
1390 | uniform mediump vec3 levelMinimum;
1391 | uniform mediump vec3 levelMiddle;
1392 | uniform mediump vec3 levelMaximum;
1393 | uniform mediump vec3 minOutput;
1394 | uniform mediump vec3 maxOutput;
1395 |
1396 | void main()
1397 | {
1398 | mediump vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1399 | gl_FragColor = vec4(mix(minOutput, maxOutput, pow(min(max(textureColor.rgb - levelMinimum, vec3(0.0)) / (levelMaximum - levelMinimum), vec3(1.0)), 1.0 / levelMiddle)), textureColor.a);
1400 | };
1401 | ------------------------------------------------------------------------------------------ -
1402 | */
1403 |
1404 | void CPUImageLevelsFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, cpuLevelParams *redLevelParams, cpuLevelParams *greenLevelParams, cpuLevelParams *blueLevelParams)
1405 | {
1406 | int Channels = Stride / Width;
1407 | unsigned char LevelMapR[256] = {0};
1408 | unsigned char LevelMapG[256] = {0};
1409 | unsigned char LevelMapB[256] = {0};
1410 | for (int pixel = 0; pixel < 256; pixel++)
1411 | {
1412 | if (redLevelParams->Enable)
1413 | {
1414 | LevelMapR[pixel] = (mix_u8( (unsigned char)redLevelParams->minOutput, (unsigned char)redLevelParams->maxOutput, (powf(min(max(pixel - redLevelParams->levelMinimum, (0.0f)) / (redLevelParams->levelMaximum - redLevelParams->levelMinimum), (255)), 1.0f / (redLevelParams->levelMiddle * (1.0f / 255.0f))))));
1415 | }
1416 | else
1417 | {
1418 | LevelMapR[pixel] = (unsigned char) pixel;
1419 | }
1420 | if (greenLevelParams->Enable)
1421 | {
1422 | LevelMapG[pixel] = (mix_u8( (unsigned char)greenLevelParams->minOutput, (unsigned char)greenLevelParams->maxOutput, (powf(min(max(pixel - greenLevelParams->levelMinimum, (0.0f)) / (greenLevelParams->levelMaximum - greenLevelParams->levelMinimum), (255)), 1.0f / (greenLevelParams->levelMiddle * (1.0f / 255.0f))))));
1423 | }
1424 | else
1425 | {
1426 | LevelMapG[pixel] = (unsigned char) pixel;
1427 | }
1428 | if (blueLevelParams->Enable)
1429 | {
1430 | LevelMapB[pixel] = (mix_u8( (unsigned char)blueLevelParams->minOutput, (unsigned char)blueLevelParams->maxOutput, (powf(min(max(pixel - blueLevelParams->levelMinimum, (0.0f)) / (blueLevelParams->levelMaximum - blueLevelParams->levelMinimum), (255)), 1.0f / (blueLevelParams->levelMiddle * (1.0f / 255.0f))))));
1431 | }
1432 | else
1433 | {
1434 | LevelMapB[pixel] = (unsigned char) pixel;
1435 | }
1436 | }
1437 |
1438 | for (int Y = 0; Y < Height; Y++)
1439 | {
1440 | unsigned char *pOutput = Output + (Y * Stride);
1441 | unsigned char *pInput = Input + (Y * Stride);
1442 | for (int X = 0; X < Width; X++)
1443 | {
1444 | pOutput[0] = LevelMapR[pInput[0]];
1445 | pOutput[1] = LevelMapG[pInput[1]];
1446 | pOutput[2] = LevelMapB[pInput[2]];
1447 | pInput += Channels;
1448 | pOutput += Channels;
1449 | }
1450 | }
1451 | }
1452 |
1453 | /*
1454 |
1455 | CPUImageHueFilter: Adjusts the hue of an image
1456 | hue : The hue angle, in degrees. 90 degrees by default
1457 |
1458 |
1459 | // Adapted from http://stackoverflow.com/questions/9234724/how-to-change-hue-of-a-texture-with-glsl - see for code and discussion
1460 |
1461 |
1462 | Shader:
1463 | ------------------------------------------------------------------------
1464 | precision highp float;
1465 | varying highp vec2 textureCoordinate;
1466 |
1467 | uniform sampler2D InputImageTexture;
1468 | uniform mediump float hueAdjust;
1469 | const highp vec4 kRGBToYPrime = vec4(0.299, 0.587, 0.114, 0.0);
1470 | const highp vec4 kRGBToI = vec4(0.595716, -0.274453, -0.321263, 0.0);
1471 | const highp vec4 kRGBToQ = vec4(0.211456, -0.522591, 0.31135, 0.0);
1472 |
1473 | const highp vec4 kYIQToR = vec4(1.0, 0.9563, 0.6210, 0.0);
1474 | const highp vec4 kYIQToG = vec4(1.0, -0.2721, -0.6474, 0.0);
1475 | const highp vec4 kYIQToB = vec4(1.0, -1.1070, 1.7046, 0.0);
1476 |
1477 | void main()
1478 | {
1479 | // Sample the Input pixel
1480 | highp vec4 color = texture2D(InputImageTexture, textureCoordinate);
1481 |
1482 | // Convert to YIQ
1483 | highp float YPrime = dot(color, kRGBToYPrime);
1484 | highp float I = dot(color, kRGBToI);
1485 | highp float Q = dot(color, kRGBToQ);
1486 |
1487 | // Calculate the hue and chroma
1488 | highp float hue = atan(Q, I);
1489 | highp float chroma = sqrt(I * I + Q * Q);
1490 |
1491 | // Make the user's adjustments
1492 | hue += (-hueAdjust); //why negative rotation?
1493 |
1494 | // Convert back to YIQ
1495 | Q = chroma * sin(hue);
1496 | I = chroma * cos(hue);
1497 |
1498 | // Convert back to RGB
1499 | highp vec4 yIQ = vec4(YPrime, I, Q, 0.0);
1500 | color.r = dot(yIQ, kYIQToR);
1501 | color.g = dot(yIQ, kYIQToG);
1502 | color.b = dot(yIQ, kYIQToB);
1503 |
1504 | // Save the result
1505 | gl_FragColor = color;
1506 | }
1507 | ------------------------------------------------------------------------ */
1508 |
1509 | /* RGB to YIQ */
1510 | void rgb2yiq(unsigned char *R, unsigned char *G, unsigned char *B, short *Y, short *I, short *Q)
1511 | {
1512 | *Y = (short) ((int)(0.299f * 65536) * *R + (int)(0.587f * 65536) * *G + (int)(0.114f * 65536) * *B) >> 16;
1513 | *I = (short) ((int)(0.595f * 65536) * *R - (int)(0.274453f * 65536) * *G - (int)(0.321263f * 65536) * *B) >> 16;
1514 | *Q = (short) ((int)(0.211456f * 65536) * *R - (int)(0.522591f * 65536) * *G + (int)(0.311135f * 65536) * *B) >> 16;
1515 | }
1516 |
1517 | /* YIQ to RGB */
1518 | void yiq2rgb(short *Y, short *I, short *Q, unsigned char *R, unsigned char *G, unsigned char *B)
1519 | {
1520 | *R = ClampToByte((int)(*Y + ((((int)(0.9563 * 65536)) * (*I)) + ((int)(0.6210 * 65536)) * (*Q))) >> 16);
1521 | *G = ClampToByte((int)(*Y - ((((int)(0.2721 * 65536)) * (*I)) + ((int)(0.6474 * 65536)) * (*Q))) >> 16);
1522 | *B = ClampToByte((int)(*Y + ((((int)(1.7046 * 65536)) * (*Q)) - ((int)(1.1070 * 65536)) * (*I))) >> 16);
1523 | }
1524 |
1525 | void CPUImageHueFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float hueAdjust)
1526 | {
1527 | int Channels = Stride / Width;
1528 | hueAdjust = fmodf(hueAdjust, 360.0f) * 3.14159265358979323846f / 180.0f;
1529 | float hueMap[256 * 256] = {0};
1530 | float ChromaMap[256 * 256] = {0};
1531 |
1532 | for (int Q = 0; Q < 256; Q++)
1533 | {
1534 | float *pChromaMap = ChromaMap + Q * 256;
1535 | for (int I = 0; I < 256; I++)
1536 | {
1537 | pChromaMap[0] = sqrtf((float)((I - 128) * (I - 128) + (Q - 128) * (Q - 128)));
1538 | pChromaMap++;
1539 | }
1540 | }
1541 | for (int Q = 0; Q < 256; Q++)
1542 | {
1543 | float *pHueMap = hueMap + Q * 256;
1544 | for (int I = 0; I < 256; I++)
1545 | {
1546 | pHueMap[0] = atan2f((float)Q - 128, (float)I - 128);
1547 | pHueMap++;
1548 | }
1549 | }
1550 | float hue = 0;
1551 | short YPrime = 0;
1552 | short I = 0;
1553 | short Q = 0;
1554 | for (int Y = 0; Y < Height; Y++)
1555 | {
1556 | unsigned char *pOutput = Output + (Y * Stride);
1557 | unsigned char *pInput = Input + (Y * Stride);
1558 | for (int X = 0; X < Width; X++)
1559 | {
1560 | // Convert to YIQ
1561 | rgb2yiq(&pInput[0], &pInput[1], &pInput[2], &YPrime, &I, &Q);
1562 | // Calculate the hue and chroma
1563 | float *pHueMap = hueMap + ((Q + 128) << 8);
1564 | hue = pHueMap[I + 128];
1565 | float *pChromaMap = ChromaMap + ((Q + 128) << 8);
1566 | float chroma = pChromaMap[I + 128];
1567 | // Make the user's adjustments
1568 | hue += (-hueAdjust); //why negative rotation?
1569 | // Convert back to YIQ
1570 | Q = (short)(chroma * sinf(hue));
1571 | I = (short)(chroma * cosf(hue));
1572 | yiq2rgb(&YPrime, &I, &Q, &pOutput[0], &pOutput[1], &pOutput[2]);
1573 | pInput += Channels;
1574 | pOutput += Channels;
1575 | }
1576 | }
1577 | }
1578 | /*
1579 |
1580 | CPUImageHighlightShadowTintFilter: Allows you to tint the shadows and highlights of an image independently using a color and intensity
1581 | shadowTintColor : Shadow tint RGB color(GPUVector4).Default : {1.0f, 0.0f, 0.0f, 1.0f} (red).
1582 | highlightTintColor : Highlight tint RGB color(GPUVector4).Default : {0.0f, 0.0f, 1.0f, 1.0f} (blue).
1583 | shadowTintIntensity : Shadow tint intensity, from 0.0 to 1.0.Default : 0.0
1584 | highlightTintIntensity : Highlight tint intensity, from 0.0 to 1.0, with 0.0 as the default.
1585 | Shader :
1586 | ------------------------------------------------------------------------
1587 |
1588 | precision lowp float;
1589 |
1590 | varying highp vec2 textureCoordinate;
1591 |
1592 | uniform sampler2D InputImageTexture;
1593 | uniform lowp float shadowTintIntensity;
1594 | uniform lowp float highlightTintIntensity;
1595 | uniform highp vec4 shadowTintColor;
1596 | uniform highp vec4 highlightTintColor;
1597 |
1598 | const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
1599 |
1600 | void main()
1601 | {
1602 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1603 | highp float luminance = dot(textureColor.rgb, luminanceWeighting);
1604 |
1605 | highp vec4 shadowResult = mix(textureColor, max(textureColor, vec4(mix(shadowTintColor.rgb, textureColor.rgb, luminance), textureColor.a)), shadowTintIntensity);
1606 | highp vec4 highlightResult = mix(textureColor, min(shadowResult, vec4(mix(shadowResult.rgb, highlightTintColor.rgb, luminance), textureColor.a)), highlightTintIntensity);
1607 |
1608 | gl_FragColor = vec4(mix(shadowResult.rgb, highlightResult.rgb, luminance), textureColor.a);
1609 | }
1610 | );
1611 | ------------------------------------------------------------------------
1612 |
1613 | */
1614 |
1615 | void CPUImageHighlightShadowTintFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float shadowTintR, float shadowTintG, float shadowTintB, float highlightTintR, float highlightTintG, float highlightTintB, float shadowTintIntensity, float highlightTintIntensity)
1616 | {
1617 | int Channels = Stride / Width;
1618 | if (Channels == 1)
1619 | {
1620 | return;
1621 | }
1622 |
1623 | unsigned char HighlightShadowMapR[256 * 256] = {0};
1624 | unsigned char HighlightShadowMapG[256 * 256] = {0};
1625 | unsigned char HighlightShadowMapB[256 * 256] = {0};
1626 | for (int lum = 0; lum < 256; lum++)
1627 | {
1628 | float luminance = (1.0f / 255.0f) * lum;
1629 | unsigned char *pHighlightShadowMapR = HighlightShadowMapR + (lum << 8);
1630 | unsigned char *pHighlightShadowMapG = HighlightShadowMapG + (lum << 8);
1631 | unsigned char *pHighlightShadowMapB = HighlightShadowMapB + (lum << 8);
1632 | for (int pixel = 0; pixel < 256; pixel++)
1633 | {
1634 | float fpixel = (1.0f / 255.0f) * pixel;
1635 | float shadowResultR = mix(fpixel, max(fpixel, mix(shadowTintR, fpixel, luminance)), shadowTintIntensity);
1636 | float shadowResultG = mix(fpixel, max(fpixel, mix(shadowTintG, fpixel, luminance)), shadowTintIntensity);
1637 | float shadowResultB = mix(fpixel, max(fpixel, mix(shadowTintB, fpixel, luminance)), shadowTintIntensity);
1638 | float highlightResultR = mix(fpixel, min(shadowResultR, mix(shadowResultR, highlightTintR, luminance)), highlightTintIntensity);
1639 | float highlightResultG = mix(fpixel, min(shadowResultG, mix(shadowResultG, highlightTintG, luminance)), highlightTintIntensity);
1640 | float highlightResultB = mix(fpixel, min(shadowResultB, mix(shadowResultB, highlightTintB, luminance)), highlightTintIntensity);
1641 | pHighlightShadowMapR[pixel] = ClampToByte(255.0f * mix(shadowResultR, highlightResultR, luminance));
1642 | pHighlightShadowMapG[pixel] = ClampToByte(255.0f * mix(shadowResultG, highlightResultG, luminance));
1643 | pHighlightShadowMapB[pixel] = ClampToByte(255.0f * mix(shadowResultB, highlightResultB, luminance));
1644 | }
1645 | }
1646 | for (int Y = 0; Y < Height; Y++)
1647 | {
1648 | unsigned char *pOutput = Output + (Y * Stride);
1649 | unsigned char *pInput = Input + (Y * Stride);
1650 | for (int X = 0; X < Width; X++)
1651 | {
1652 | const unsigned char R = pInput[0];
1653 | const unsigned char G = pInput[1];
1654 | const unsigned char B = pInput[2];
1655 | unsigned short lum = (unsigned short)((13926 * R + 46884 * G + 4725 * B) >> 16) << 8;
1656 | unsigned char *pHighlightShadowMapR = HighlightShadowMapR + (lum);
1657 | unsigned char *pHighlightShadowMapG = HighlightShadowMapG + (lum);
1658 | unsigned char *pHighlightShadowMapB = HighlightShadowMapB + (lum);
1659 | pOutput[0] = pHighlightShadowMapR[R];
1660 | pOutput[1] = pHighlightShadowMapG[G];
1661 | pOutput[2] = pHighlightShadowMapB[B];
1662 | pInput += Channels;
1663 | pOutput += Channels;
1664 | }
1665 | }
1666 | }
1667 | /*
1668 |
1669 | CPUImageHighlightShadowFilter: Adjusts the shadows and highlights of an image
1670 | shadows : Increase to lighten shadows, from 0.0 to 1.0, with 0.0 as the default.
1671 | highlights : Decrease to darken highlights, from 0.0 to 1.0, with 1.0 as the default.
1672 |
1673 | Shader :
1674 | ------------------------------------------------------------------------
1675 | uniform sampler2D InputImageTexture;
1676 | varying highp vec2 textureCoordinate;
1677 |
1678 | uniform lowp float shadows;
1679 | uniform lowp float highlights;
1680 |
1681 | const mediump vec3 luminanceWeighting = vec3(0.3, 0.3, 0.3);
1682 |
1683 | void main()
1684 | {
1685 | lowp vec4 source = texture2D(InputImageTexture, textureCoordinate);
1686 | mediump float luminance = dot(source.rgb, luminanceWeighting);
1687 |
1688 | mediump float shadow = clamp((pow(luminance, 1.0 / (shadows + 1.0)) + (-0.76)*pow(luminance, 2.0 / (shadows + 1.0))) - luminance, 0.0, 1.0);
1689 | mediump float highlight = clamp((1.0 - (pow(1.0 - luminance, 1.0 / (2.0 - highlights)) + (-0.8)*pow(1.0 - luminance, 2.0 / (2.0 - highlights)))) - luminance, -1.0, 0.0);
1690 | lowp vec3 result = vec3(0.0, 0.0, 0.0) + ((luminance + shadow + highlight) - 0.0) * ((source.rgb - vec3(0.0, 0.0, 0.0)) / (luminance - 0.0));
1691 |
1692 | gl_FragColor = vec4(result.rgb, source.a);
1693 | }
1694 | ------------------------------------------------------------------------
1695 | */
1696 | void CPUImageHighlightShadowFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float shadows, float highlights)
1697 | {
1698 | int Channels = Stride / Width;
1699 | if (Channels == 1)
1700 | {
1701 | return;
1702 | }
1703 | short luminanceWeightingMap[256] = {0};
1704 | short shadowMap[256] = {0};
1705 | short highlightMap[256] = {0};
1706 |
1707 | int divLuminance[256 * 256] = {0};
1708 | for (int pixel = 0; pixel < 256; pixel++)
1709 | {
1710 | luminanceWeightingMap[pixel] = (short)(pixel * 0.3f);
1711 | float luminance = (1.0f / 255.0f) * pixel;
1712 | shadowMap[pixel] = (short)(255.0f * clamp((powf(luminance, 1.0f / (shadows + 1.0f)) + (-0.76f) * powf(luminance, 2.0f / (shadows + 1.0f))) - luminance, 0.0f, 1.0f));
1713 | highlightMap[pixel] = (short)(255.0f * clamp((1.0f - (powf(1.0f - luminance, 1.0f / (2.0f - highlights)) + (-0.8f) * powf(1.0f - luminance, 2.0f / (2.0f - highlights)))) - luminance, -1.0f, 0.0f));
1714 | }
1715 | for (int luminance = 0; luminance < 256; luminance++)
1716 | {
1717 | int *pDivLuminance = divLuminance + luminance * 256;
1718 | for (int pixel = 0; pixel < 256; pixel++)
1719 | {
1720 | pDivLuminance[0] = (int)(255.0f * pixel * (1.0f / luminance));
1721 | pDivLuminance++;
1722 | }
1723 | }
1724 | for (int Y = 0; Y < Height; Y++)
1725 | {
1726 | unsigned char *pOutput = Output + (Y * Stride);
1727 | unsigned char *pInput = Input + (Y * Stride);
1728 | for (int X = 0; X < Width; X++)
1729 | {
1730 | const short luminance = luminanceWeightingMap[pInput[0]] + luminanceWeightingMap[pInput[1]] + luminanceWeightingMap[pInput[2]];
1731 | const short shadow = shadowMap[luminance];
1732 | const short highlight = highlightMap[luminance];
1733 | short lshpixel = (luminance + shadow + highlight);
1734 | int *pDivLuminance = divLuminance + (luminance << 8);
1735 | pOutput[0] =(unsigned char) ((lshpixel * pDivLuminance[pInput[0]]) >> 8);
1736 | pOutput[1] =(unsigned char) ((lshpixel * pDivLuminance[pInput[1]]) >> 8);
1737 | pOutput[2] =(unsigned char) ((lshpixel * pDivLuminance[pInput[2]]) >> 8);
1738 | pInput += Channels;
1739 | pOutput += Channels;
1740 | }
1741 | }
1742 | }
1743 |
1744 | /*
1745 |
1746 | CPUImageMonochromeFilter: Converts the image to a single - color version, based on the luminance of each pixel
1747 | intensity : The degree to which the specific color replaces the normal image color(0.0 - 1.0, with 1.0 as the default)
1748 | color : The color to use as the basis for the effect, with(0.6, 0.45, 0.3, 1.0) as the default.
1749 |
1750 | Shader :
1751 | ------------------------------------------------------------------------------------------ -
1752 | precision lowp float;
1753 |
1754 | varying highp vec2 textureCoordinate;
1755 |
1756 | uniform sampler2D InputImageTexture;
1757 | uniform float intensity;
1758 | uniform vec3 filterColor;
1759 |
1760 | const mediump vec3 luminanceWeighting = vec3(0.2125, 0.7154, 0.0721);
1761 |
1762 | void main()
1763 | {
1764 | //desat, then apply overlay blend
1765 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1766 | float luminance = dot(textureColor.rgb, luminanceWeighting);
1767 |
1768 | lowp vec4 desat = vec4(vec3(luminance), 1.0);
1769 |
1770 | //overlay
1771 | lowp vec4 OutputColor = vec4(
1772 | (desat.r < 0.5 ? (2.0 * desat.r * filterColor.r) : (1.0 - 2.0 * (1.0 - desat.r) * (1.0 - filterColor.r))),
1773 | (desat.g < 0.5 ? (2.0 * desat.g * filterColor.g) : (1.0 - 2.0 * (1.0 - desat.g) * (1.0 - filterColor.g))),
1774 | (desat.b < 0.5 ? (2.0 * desat.b * filterColor.b) : (1.0 - 2.0 * (1.0 - desat.b) * (1.0 - filterColor.b))),
1775 | 1.0
1776 | );
1777 |
1778 | //which is better, or are they equal?
1779 | gl_FragColor = vec4(mix(textureColor.rgb, OutputColor.rgb, intensity), textureColor.a);
1780 | }
1781 | ------------------------------------------------------------------------------------------ -
1782 | */
1783 | void CPUImageMonochromeFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, unsigned char filterColorR, unsigned char filterColorG, unsigned char filterColorB, int intensity)
1784 | {
1785 | int Channels = Stride / Width;
1786 | if (Channels == 1)
1787 | {
1788 | return;
1789 | }
1790 | unsigned short sIntensity = (unsigned short)max(min(intensity, 100), 0);
1791 | int c1 = 256 * (100 - sIntensity) / 100;
1792 | int c2 = 256 * (100 - (100 - sIntensity)) / 100;
1793 | float fColorR = (float)filterColorR * (1.0f / 255.0f);
1794 | float fColorG = (float)filterColorG * (1.0f / 255.0f);
1795 | float fColorB = (float)filterColorB * (1.0f / 255.0f);
1796 | unsigned char filterColorRMap[256] = {0};
1797 | unsigned char filterColorGMap[256] = {0};
1798 | unsigned char filterColorBMap[256] = {0};
1799 | for (int luminance = 0; luminance < 256; luminance++)
1800 | {
1801 | float lum = (1.0f / 255.0f) * luminance;
1802 | filterColorRMap[luminance] = (unsigned char)(255.0f * (lum < 0.5f ? (2.0f * lum * fColorR) : (1.0f - 2.0f * (1.0f - lum) * (1.0f - fColorR))));
1803 | filterColorGMap[luminance] = (unsigned char)(255.0f * (lum < 0.5f ? (2.0f * lum * fColorG) : (1.0f - 2.0f * (1.0f - lum) * (1.0f - fColorG))));
1804 | filterColorBMap[luminance] = (unsigned char)(255.0f * (lum < 0.5f ? (2.0f * lum * fColorB) : (1.0f - 2.0f * (1.0f - lum) * (1.0f - fColorB))));
1805 | }
1806 | for (int Y = 0; Y < Height; Y++)
1807 | {
1808 | unsigned char *pOutput = Output + (Y * Stride);
1809 | unsigned char *pInput = Input + (Y * Stride);
1810 | for (int X = 0; X < Width; X++)
1811 | {
1812 | unsigned char lum =(unsigned char) ((13926 * pInput[0] + 46884 * pInput[1] + 4725 * pInput[2]) >> 16);
1813 | pOutput[0] =(unsigned char)((int)(pInput[0] * c1 + filterColorRMap[lum] * c2) >> 8);
1814 | pOutput[1] =(unsigned char) ((int)(pInput[1] * c1 + filterColorGMap[lum] * c2) >> 8);
1815 | pOutput[2] =(unsigned char) ((int)(pInput[2] * c1 + filterColorBMap[lum] * c2) >> 8);
1816 | pInput += Channels;
1817 | pOutput += Channels;
1818 | }
1819 | }
1820 | }
1821 | /*
1822 |
1823 | CPUImageColorInvertFilter: Inverts the colors of an image
1824 |
1825 |
1826 | Shader :
1827 | ------------------------------------------------------------------------------------------ -
1828 | varying vec2 textureCoordinate;
1829 |
1830 | uniform sampler2D InputImageTexture;
1831 |
1832 | void main()
1833 | {
1834 | vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1835 |
1836 | gl_FragColor = vec4((1.0 - textureColor.rgb), textureColor.w);
1837 | }
1838 | ------------------------------------------------------------------------------------------ -
1839 | */
1840 | void CPUImageColorInvertFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride)
1841 | {
1842 | int Channels = Stride / Width;
1843 | if (Channels == 1)
1844 | {
1845 | return;
1846 | }
1847 | unsigned char invertMap[256] = {0};
1848 | for (int pixel = 0; pixel < 256; pixel++)
1849 | {
1850 | invertMap[pixel] =(unsigned char) (255 - pixel);
1851 | }
1852 | for (int Y = 0; Y < Height; Y++)
1853 | {
1854 | unsigned char *pOutput = Output + (Y * Stride);
1855 | unsigned char *pInput = Input + (Y * Stride);
1856 | for (int X = 0; X < Width; X++)
1857 | {
1858 | pOutput[0] = invertMap[pInput[0]];
1859 | pOutput[1] = invertMap[pInput[1]];
1860 | pOutput[2] = invertMap[pInput[2]];
1861 | pInput += Channels;
1862 | pOutput += Channels;
1863 | }
1864 | }
1865 | }
1866 |
1867 | /*
1868 |
1869 | CPUImageSolidColorGenerator: This Outputs a generated image with a solid color.You need to define the image size using - forceProcessingAtSize :
1870 | color : The color, in a four component format, that is used to fill the image.
1871 |
1872 | Shader :
1873 | ------------------------------------------------------------------------
1874 |
1875 | precision lowp float;
1876 |
1877 | varying highp vec2 textureCoordinate;
1878 | uniform sampler2D InputImageTexture;
1879 | uniform vec4 color;
1880 | uniform float useExistingAlpha;
1881 |
1882 | void main()
1883 | {
1884 | lowp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1885 | gl_FragColor = vec4(color.rgb, max(textureColor.a, 1.0 - useExistingAlpha));
1886 | }
1887 | ------------------------------------------------------------------------
1888 | */
1889 |
1890 | void CPUImageSolidColorGenerator(unsigned char *Output, int Width, int Height, int Stride, unsigned char colorR, unsigned char colorG, unsigned char colorB, unsigned char colorAlpha)
1891 | {
1892 | int Channels = Stride / Width;
1893 | if (Channels == 4)
1894 | {
1895 | for (int Y = 0; Y < Height; Y++)
1896 | {
1897 | unsigned char *pOutput = Output + (Y * Stride);
1898 | for (int X = 0; X < Width; X++)
1899 | {
1900 | pOutput[0] = colorR;
1901 | pOutput[1] = colorG;
1902 | pOutput[2] = colorB;
1903 | pOutput[3] = colorAlpha;
1904 | pOutput += Channels;
1905 | }
1906 | }
1907 | }
1908 | else if (Channels == 3)
1909 | {
1910 | for (int Y = 0; Y < Height; Y++)
1911 | {
1912 | unsigned char *pOutput = Output + (Y * Stride);
1913 | for (int X = 0; X < Width; X++)
1914 | {
1915 | pOutput[0] = colorR;
1916 | pOutput[1] = colorG;
1917 | pOutput[2] = colorB;
1918 | pOutput += Channels;
1919 | }
1920 | }
1921 | }
1922 | }
1923 |
1924 | /*
1925 |
1926 | CPUImageLuminanceThresholdFilter: Pixels with a luminance above the threshold will appear white, and those below will be black
1927 | threshold : The luminance threshold, from 0.0 to 1.0, with a default of 0.5
1928 |
1929 | Shader :
1930 | ------------------------------------------------------------------------
1931 | varying highp vec2 textureCoordinate;
1932 |
1933 | uniform sampler2D InputImageTexture;
1934 | uniform highp float threshold;
1935 |
1936 | const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);
1937 |
1938 | void main()
1939 | {
1940 | highp vec4 textureColor = texture2D(InputImageTexture, textureCoordinate);
1941 | highp float luminance = dot(textureColor.rgb, W);
1942 | highp float thresholdResult = step(threshold, luminance);
1943 |
1944 | gl_FragColor = vec4(vec3(thresholdResult), textureColor.w);
1945 | }
1946 | ------------------------------------------------------------------------
1947 | */
1948 |
1949 | void CPUImageLuminanceThresholdFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, unsigned char threshold)
1950 | {
1951 | int Channels = Stride / Width;
1952 | if (Channels == 1)
1953 | {
1954 |
1955 | for (int Y = 0; Y < Height; Y++)
1956 | {
1957 | unsigned char *pOutput = Output + (Y * Stride);
1958 | unsigned char *pInput = Input + (Y * Stride);
1959 | for (int X = 0; X < Width; X++)
1960 | {
1961 | pOutput[0] = step(threshold, pInput[0]);
1962 | pInput++;
1963 | pOutput++;
1964 | }
1965 | }
1966 | return;
1967 | }
1968 |
1969 | for (int Y = 0; Y < Height; Y++)
1970 | {
1971 | unsigned char *pOutput = Output + (Y * Stride);
1972 | unsigned char *pInput = Input + (Y * Stride);
1973 | for (int X = 0; X < Width; X++)
1974 | {
1975 | unsigned char luminance = (unsigned char)((13926 * pInput[0] + 46884 * pInput[1] + 4725 * pInput[2]) >> 16);
1976 | pOutput[2] = pOutput[1] = pOutput[0] = step(threshold, luminance);
1977 | pInput += Channels;
1978 | pOutput += Channels;
1979 | }
1980 | }
1981 | }
1982 | /*
1983 |
1984 | CPUImageWhiteBalanceFilter: Adjusts the white balance of an image.
1985 | temperature : The temperature to adjust the image by, in ºK.A value of 4000 is very cool and 7000 very warm.The default value is 5000. Note that the scale between 4000 and 5000 is nearly as visually significant as that between 5000 and 7000.
1986 | tint : The tint to adjust the image by.A value of - 200 is very green and 200 is very pink.The default value is 0.
1987 |
1988 |
1989 | Shader :
1990 | ------------------------------------------------------------------------
1991 | uniform sampler2D InputImageTexture;
1992 | varying highp vec2 textureCoordinate;
1993 |
1994 | uniform lowp float temperature;
1995 | uniform lowp float tint;
1996 |
1997 | const lowp vec3 warmFilter = vec3(0.93, 0.54, 0.0);
1998 |
1999 | const mediump mat3 RGBtoYIQ = mat3(0.299, 0.587, 0.114, 0.596, -0.274, -0.322, 0.212, -0.523, 0.311);
2000 | const mediump mat3 YIQtoRGB = mat3(1.0, 0.956, 0.621, 1.0, -0.272, -0.647, 1.0, -1.105, 1.702);
2001 |
2002 | void main()
2003 | {
2004 | lowp vec4 source = texture2D(InputImageTexture, textureCoordinate);
2005 |
2006 | mediump vec3 yiq = RGBtoYIQ * source.rgb; //adjusting tint
2007 | yiq.b = clamp(yiq.b + tint*0.5226*0.1, -0.5226, 0.5226);
2008 | lowp vec3 rgb = YIQtoRGB * yiq;
2009 |
2010 | lowp vec3 processed = vec3(
2011 | (rgb.r < 0.5 ? (2.0 * rgb.r * warmFilter.r) : (1.0 - 2.0 * (1.0 - rgb.r) * (1.0 - warmFilter.r))), //adjusting temperature
2012 | (rgb.g < 0.5 ? (2.0 * rgb.g * warmFilter.g) : (1.0 - 2.0 * (1.0 - rgb.g) * (1.0 - warmFilter.g))),
2013 | (rgb.b < 0.5 ? (2.0 * rgb.b * warmFilter.b) : (1.0 - 2.0 * (1.0 - rgb.b) * (1.0 - warmFilter.b))));
2014 |
2015 | gl_FragColor = vec4(mix(rgb, processed, temperature), source.a);
2016 | }
2017 | ------------------------------------------------------------------------
2018 | */
2019 |
2020 | void CPUImageWhiteBalanceFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float temperature, float tint)
2021 | {
2022 | int Channels = Stride / Width;
2023 | if (Channels == 1)
2024 | {
2025 | return;
2026 | }
2027 | float Temperature = temperature;
2028 | Temperature = Temperature < 5000 ? (float)(0.0004 * (Temperature - 5000.0)) : (float)(0.00006 * (Temperature - 5000.0));
2029 |
2030 | float Tint = tint;
2031 | Tint = (float)(Tint / 100.0f);
2032 |
2033 | short YPrime = 0;
2034 | short I = 0;
2035 | short Q = 0;
2036 |
2037 | float warmFilterR = 0.93f;
2038 | float warmFilterG = 0.54f;
2039 | float warmFilterB = 0;
2040 | int plusTint = (int)(Tint * 255.0f * 0.5226f * 0.1f);
2041 |
2042 | short QTint[256] = {0};
2043 | unsigned char processedMap[256 * 3] = {0};
2044 | unsigned char *processedRMap = &processedMap[0];
2045 | unsigned char *processedGMap = &processedMap[256];
2046 | unsigned char *processedBMap = &processedMap[512];
2047 | for (int pixel = 0; pixel < 256; pixel++)
2048 | {
2049 | float fpixel = pixel * (1.0f / 255.0f);
2050 | QTint[pixel] = (short)clamp((float)(pixel - 127 + plusTint), -127.0f, 127.0f);
2051 | float processedR = (fpixel < 0.5f ? (2.0f * fpixel * warmFilterR) : (1.0f - 2.0f * (1.0f - fpixel) * (1.0f - warmFilterR)));
2052 | float processedG = (fpixel < 0.5f ? (2.0f * fpixel * warmFilterG) : (1.0f - 2.0f * (1.0f - fpixel) * (1.0f - warmFilterG)));
2053 | float processedB = (fpixel < 0.5f ? (2.0f * fpixel * warmFilterB) : (1.0f - 2.0f * (1.0f - fpixel) * (1.0f - warmFilterB)));
2054 | processedRMap[pixel] = ClampToByte(255.0f * mix(fpixel, processedR, Temperature));
2055 | processedGMap[pixel] = ClampToByte(255.0f * mix(fpixel, processedG, Temperature));
2056 | processedBMap[pixel] = ClampToByte(255.0f * mix(fpixel, processedB, Temperature));
2057 | }
2058 | for (int Y = 0; Y < Height; Y++)
2059 | {
2060 | unsigned char *pOutput = Output + (Y * Stride);
2061 | unsigned char *pInput = Input + (Y * Stride);
2062 | for (int X = 0; X < Width; X++)
2063 | {
2064 | const unsigned char R = pInput[0];
2065 | const unsigned char G = pInput[1];
2066 | const unsigned char B = pInput[2];
2067 |
2068 | YPrime =(short) (((int)(0.299f * 65536) * R + (int)(0.587f * 65536) * G + (int)(0.114f * 65536) * B) >> 16);
2069 | I = (short)(((int)(0.595f * 65536) * R - (int)(0.274453f * 65536) * G - (int)(0.321263f * 65536) * B) >> 16);
2070 | Q = (short)(((int)(0.211456f * 65536) * R - (int)(0.522591f * 65536) * G + (int)(0.311135f * 65536) * B) >> 16);
2071 | //adjusting tint
2072 | Q = QTint[Q + 127];
2073 | //adjusting temperature
2074 | pOutput[0] = processedRMap[ClampToByte(YPrime + (((int)(0.9563 * 65536) * I + (int)(0.6210 * 65536) * Q) >> 16))];
2075 | pOutput[1] = processedGMap[ClampToByte(YPrime - (((int)(0.2721 * 65536) * I + (int)(0.6474 * 65536) * Q) >> 16))];
2076 | pOutput[2] = processedBMap[ClampToByte(YPrime + (((int)(1.7046 * 65536) * Q - (int)(1.1070 * 65536) * I) >> 16))];
2077 |
2078 | pInput += Channels;
2079 | pOutput += Channels;
2080 | }
2081 | }
2082 | }
2083 |
2084 | /*
2085 |
2086 | CPUImageVibranceFilter: Adjusts the vibrance of an image
2087 | vibrance : The vibrance adjustment to apply, using 0.0 as the default, and a suggested min / max of around - 1.2 and 1.2, respectively.
2088 |
2089 | Shader :
2090 | ------------------------------------------------------------------------
2091 | varying highp vec2 textureCoordinate;
2092 |
2093 | uniform sampler2D InputImageTexture;
2094 | uniform lowp float vibrance;
2095 |
2096 | void main() {
2097 | lowp vec4 color = texture2D(InputImageTexture, textureCoordinate);
2098 | lowp float average = (color.r + color.g + color.b) / 3.0;
2099 | lowp float mx = max(color.r, max(color.g, color.b));
2100 | lowp float amt = (mx - average) * (-vibrance * 3.0);
2101 | color.rgb = mix(color.rgb, vec3(mx), amt);
2102 | gl_FragColor = color;
2103 | }
2104 | ------------------------------------------------------------------------
2105 | */
2106 |
2107 | void CPUImageVibranceFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float vibrance)
2108 | {
2109 | int Channels = Stride / Width;
2110 | if (Channels == 1)
2111 | {
2112 | return;
2113 | }
2114 | int iVibrance = (int)(-(vibrance * 256));
2115 | unsigned char mx = 0;
2116 | int amt = 0;
2117 | for (int Y = 0; Y < Height; Y++)
2118 | {
2119 | unsigned char *pOutput = Output + (Y * Stride);
2120 | unsigned char *pInput = Input + (Y * Stride);
2121 | for (int X = 0; X < Width; X++)
2122 | {
2123 | const unsigned char r = pInput[0];
2124 | const unsigned char g = pInput[1];
2125 | const unsigned char b = pInput[2];
2126 | mx = max3(r, g, b);
2127 | amt = (3 * mx - (r + g + b)) * iVibrance;
2128 | pOutput[0] = ClampToByte((r * (255 * 256 - amt) + mx * amt) >> 16);
2129 | pOutput[1] = ClampToByte((g * (255 * 256 - amt) + mx * amt) >> 16);
2130 | pOutput[2] = ClampToByte((b * (255 * 256 - amt) + mx * amt) >> 16);
2131 |
2132 | pInput += Channels;
2133 | pOutput += Channels;
2134 | }
2135 | }
2136 | }
2137 |
2138 | /*
2139 |
2140 | CPUImageSkinToneFilter: A skin - tone adjustment filter that affects a unique range of light skin - tone colors and adjusts the pink / green or pink / orange range accordingly.Default values are targetted at fair caucasian skin, but can be adjusted as required.
2141 | skinToneAdjust : Amount to adjust skin tone.Default : 0.0, suggested min / max : -0.3 and 0.3 respectively.
2142 | skinHue : Skin hue to be detected.Default : 0.05 (fair caucasian to reddish skin).
2143 | skinHueThreshold : Amount of variance in skin hue.Default : 40.0.
2144 | maxHueShift : Maximum amount of hue shifting allowed.Default : 0.25.
2145 | maxSaturationShift = Maximum amount of saturation to be shifted(when using orange).Default : 0.4.
2146 | upperSkinToneColor = GPUImageSkinToneUpperColorGreen or GPUImageSkinToneUpperColorOrange
2147 |
2148 | //upperSkinToneColor is 0 or 1
2149 | Shader :
2150 | ------------------------------------------------------------------------
2151 |
2152 | varying highp vec2 textureCoordinate;
2153 |
2154 | uniform sampler2D InputImageTexture;
2155 |
2156 | // [-1;1] <=> [pink;orange]
2157 | uniform highp float skinToneAdjust; // will make reds more pink
2158 |
2159 | // Other parameters
2160 | uniform mediump float skinHue;
2161 | uniform mediump float skinHueThreshold;
2162 | uniform mediump float maxHueShift;
2163 | uniform mediump float maxSaturationShift;
2164 | uniform int upperSkinToneColor;
2165 |
2166 | // RGB <-> HSV conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
2167 | highp vec3 rgb2hsv(highp vec3 c)
2168 | {
2169 | highp vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
2170 | highp vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
2171 | highp vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
2172 |
2173 | highp float d = q.x - min(q.w, q.y);
2174 | highp float e = 1.0e-10;
2175 | return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
2176 | }
2177 |
2178 | // HSV <-> RGB conversion, thanks to http://lolengine.net/blog/2013/07/27/rgb-to-hsv-in-glsl
2179 | highp vec3 hsv2rgb(highp vec3 c)
2180 | {
2181 | highp vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
2182 | highp vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
2183 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
2184 | }
2185 |
2186 |
2187 | // Main
2188 | void main()
2189 | {
2190 |
2191 | // Sample the Input pixel
2192 | highp vec4 colorRGB = texture2D(InputImageTexture, textureCoordinate);
2193 |
2194 | // Convert color to HSV, extract hue
2195 | highp vec3 colorHSV = rgb2hsv(colorRGB.rgb);
2196 | highp float hue = colorHSV.x;
2197 |
2198 | // check how far from skin hue
2199 | highp float dist = hue - skinHue;
2200 | if (dist > 0.5)
2201 | dist -= 1.0;
2202 | if (dist < -0.5)
2203 | dist += 1.0;
2204 | dist = abs(dist) / 0.5; // normalized to [0,1]
2205 |
2206 | // Apply Gaussian like filter
2207 | highp float weight = exp(-dist*dist*skinHueThreshold);
2208 | weight = clamp(weight, 0.0, 1.0);
2209 |
2210 | // Using pink/green, so only adjust hue
2211 | if (upperSkinToneColor == 0) {
2212 | colorHSV.x += skinToneAdjust * weight * maxHueShift;
2213 | // Using pink/orange, so adjust hue < 0 and saturation > 0
2214 | }
2215 | else if (upperSkinToneColor == 1) {
2216 | // We want more orange, so increase saturation
2217 | if (skinToneAdjust > 0.0)
2218 | colorHSV.y += skinToneAdjust * weight * maxSaturationShift;
2219 | // we want more pinks, so decrease hue
2220 | else
2221 | colorHSV.x += skinToneAdjust * weight * maxHueShift;
2222 | }
2223 |
2224 | // final color
2225 | highp vec3 finalColorRGB = hsv2rgb(colorHSV.rgb);
2226 |
2227 | // display
2228 | gl_FragColor = vec4(finalColorRGB, 1.0);
2229 | }
2230 | );
2231 | ------------------------------------------------------------------------
2232 | */
2233 |
2234 | void rgb2hsv(const unsigned char *R, const unsigned char *G, const unsigned char *B, unsigned char *H, unsigned char *S, unsigned char *V)
2235 | {
2236 | int r = *R;
2237 | int g = *G;
2238 | int b = *B;
2239 |
2240 | int h, s;
2241 | int nMax = max3(r, g, b);
2242 | int nMin = min3(r, g, b);
2243 | int diff = nMax - nMin;
2244 |
2245 | if (diff == 0)
2246 | {
2247 | h = 0;
2248 | s = 0;
2249 | }
2250 | else
2251 | {
2252 | if (nMin == b)
2253 | {
2254 | h = 60 * (g - r) / diff + 60;
2255 | }
2256 | else if (nMin == r)
2257 | {
2258 | h = 60 * (b - g) / diff + 180;
2259 | }
2260 | else
2261 | {
2262 | h = 60 * (r - b) / diff + 300;
2263 | }
2264 | // normalize 0`359
2265 | //if (h < 0) h += 360; if (h >= 360) h -= 360;
2266 | if (!((unsigned)(int)(h) < (360)))
2267 | {
2268 | if (h < 0)
2269 | h += 360;
2270 | else
2271 | h -= 360;
2272 | }
2273 | if (nMax == 0)
2274 | {
2275 | s = 0;
2276 | }
2277 | else
2278 | {
2279 | s = 255 * diff / nMax;
2280 | }
2281 | }
2282 |
2283 | *H = (unsigned char)(h >> 1); // 0`179
2284 | *S = (unsigned char)s; // 0`255
2285 | *V = (unsigned char)nMax; // 0`255
2286 | }
2287 |
2288 | void hsv2rgb(const unsigned char *H, const unsigned char *S, const unsigned char *V, unsigned char *R, unsigned char *G, unsigned char *B)
2289 | {
2290 | if (*S > 0)
2291 | {
2292 | int r, g, b;
2293 | r = *V;
2294 | g = *V;
2295 | b = *V;
2296 | float h = *H * (6.0f / 180.0f); // 0`180 -> 0.0`1.0
2297 | int i = (int)h;
2298 | int f = (int)(256 * (h - (float)i));
2299 | int VS = (*V * *S) >> 8;
2300 | int VSF = VS * f;
2301 | switch (i)
2302 | {
2303 | case 0:
2304 | b -= VS;
2305 | g = b + (VSF >> 8);
2306 | break;
2307 | case 1:
2308 | r = *V - (VSF >> 8);
2309 | b -= VS;
2310 | break;
2311 | case 2:
2312 | r -= VS;
2313 | b = r + (VSF >> 8);
2314 | break;
2315 | case 3:
2316 | r -= VS;
2317 | g -= (VSF >> 8);
2318 | break;
2319 | case 4:
2320 | g -= VS;
2321 | r = g + (VSF >> 8);
2322 | break;
2323 | case 5:
2324 | g -= VS;
2325 | b -= (VSF >> 8);
2326 | break;
2327 | default:
2328 | break;
2329 | }
2330 | *R = (unsigned char)(r);
2331 | *G = (unsigned char)(g);
2332 | *B = (unsigned char)(b);
2333 | }
2334 | else
2335 | {
2336 | *R = *V;
2337 | *G = *V;
2338 | *B = *V;
2339 | }
2340 | }
2341 |
2342 | void CPUImageSkinToneFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float skinToneAdjust, float skinHue, float skinHueThreshold, float maxHueShift, float maxSaturationShift, int upperSkinToneColor)
2343 | {
2344 | int Channels = Stride / Width;
2345 | if (Channels == 1)
2346 | {
2347 | return;
2348 | }
2349 | int maxSatShiftAdjust = (int)(maxSaturationShift * 255.0f * skinToneAdjust);
2350 | float maxHueShiftAdjust = maxHueShift * skinToneAdjust;
2351 | unsigned char hueMap[256] = {0};
2352 | int satMap[256] = {0};
2353 | for (unsigned int H = 0; H < 256; H++)
2354 | {
2355 | satMap[H] = 0;
2356 | float hue = H * (1.0f / 255.0f);
2357 | // check how far from skin hue
2358 | float dist = hue - skinHue;
2359 | if (dist > 0.5f)
2360 | dist -= 1.0f;
2361 | if (dist < -0.5f)
2362 | dist += 1.0f;
2363 | dist = (float)(fabsf(dist) * (1.0f / 0.5f)); // normalized to [0,1]
2364 | // Apply Gaussian like filter
2365 | float weightMap = clamp(expf(-dist * dist * skinHueThreshold), 0.0f, 1.0f);
2366 |
2367 | // Using pink/green, so only adjust hue
2368 | if (upperSkinToneColor == 0)
2369 | {
2370 | hue += maxHueShiftAdjust * weightMap;
2371 | // Using pink/orange, so adjust hue < 0 and saturation > 0
2372 | }
2373 | else if (upperSkinToneColor == 1)
2374 | {
2375 | // We want more orange, so increase saturation
2376 | if (skinToneAdjust > 0.0f)
2377 | satMap[H] = (int)(maxSatShiftAdjust * weightMap);
2378 | // we want more pinks, so decrease hue
2379 | else
2380 | hue += maxHueShiftAdjust * weightMap;
2381 | }
2382 | hueMap[H] = ClampToByte(hue * 255.0f);
2383 | }
2384 | unsigned char H, S, V, _S;
2385 | for (int Y = 0; Y < Height; Y++)
2386 | {
2387 | unsigned char *pOutput = Output + (Y * Stride);
2388 | unsigned char *pInput = Input + (Y * Stride);
2389 | for (int X = 0; X < Width; X++)
2390 | {
2391 | unsigned char R = pInput[0];
2392 | unsigned char G = pInput[1];
2393 | unsigned char B = pInput[2];
2394 | // Convert color to HSV, extract hue
2395 | rgb2hsv(&R, &G, &B, &H, &S, &V);
2396 | // final color
2397 | _S = (unsigned char)(S + satMap[H]);
2398 | hsv2rgb(&hueMap[H], &_S, &V, &pOutput[0], &pOutput[1], &pOutput[2]);
2399 |
2400 | pInput += Channels;
2401 | pOutput += Channels;
2402 | }
2403 | }
2404 | }
2405 |
2406 | /*
2407 | CPUImageGaussianBlurFilter: A hardware - optimized, variable - radius Gaussian blur
2408 | texelSpacingMultiplier : A multiplier for the spacing between texels, ranging from 0.0 on up, with a default of 1.0.Adjusting this may slightly increase the blur strength, but will introduce artifacts in the result.Highly recommend using other parameters first, before touching this one.
2409 | blurRadiusInPixels : A radius in pixels to use for the blur, with a default of 2.0.This adjusts the sigma variable in the Gaussian distribution function.
2410 | blurRadiusAsFractionOfImageWidth :
2411 | blurRadiusAsFractionOfImageHeight : Setting these properties will allow the blur radius to scale with the size of the image
2412 | blurPasses : The number of times to sequentially blur the incoming image.The more passes, the slower the filter.
2413 | */
2414 |
2415 | void CalGaussianCoeff(float sigma, float *a0, float *a1, float *a2, float *a3, float *b1, float *b2, float *cprev, float *cnext)
2416 | {
2417 | float alpha, lamma, k;
2418 |
2419 | if (sigma < 0.5f)
2420 | sigma = 0.5f;
2421 | alpha = (float)expf((0.726) * (0.726)) / sigma;
2422 | lamma = (float)expf(-alpha);
2423 | *b2 = (float)expf(-2 * alpha);
2424 | k = (1 - lamma) * (1 - lamma) / (1 + 2 * alpha * lamma - (*b2));
2425 | *a0 = k;
2426 | *a1 = k * (alpha - 1) * lamma;
2427 | *a2 = k * (alpha + 1) * lamma;
2428 | *a3 = -k * (*b2);
2429 | *b1 = -2 * lamma;
2430 | *cprev = (*a0 + *a1) / (1 + *b1 + *b2);
2431 | *cnext = (*a2 + *a3) / (1 + *b1 + *b2);
2432 | }
2433 |
2434 | void gaussianHorizontal(unsigned char *bufferPerLine, const unsigned char *lpRowInitial, unsigned char *lpColumn, int width, int height, int Channels, int Nwidth, float a0a1, float a2a3, float b1b2, float cprev, float cnext)
2435 | {
2436 | int HeightStep = Channels * height;
2437 | int WidthSubOne = width - 1;
2438 | if (Channels == 3)
2439 | {
2440 | float prevOut[3];
2441 | prevOut[0] = (lpRowInitial[0] * cprev);
2442 | prevOut[1] = (lpRowInitial[1] * cprev);
2443 | prevOut[2] = (lpRowInitial[2] * cprev);
2444 | for (int x = 0; x < width; ++x)
2445 | {
2446 | prevOut[0] = ((lpRowInitial[0] * (a0a1)) - (prevOut[0] * (b1b2)));
2447 | prevOut[1] = ((lpRowInitial[1] * (a0a1)) - (prevOut[1] * (b1b2)));
2448 | prevOut[2] = ((lpRowInitial[2] * (a0a1)) - (prevOut[2] * (b1b2)));
2449 | bufferPerLine[0] = (unsigned char)prevOut[0];
2450 | bufferPerLine[1] = (unsigned char)prevOut[1];
2451 | bufferPerLine[2] = (unsigned char)prevOut[2];
2452 | bufferPerLine += Channels;
2453 | lpRowInitial += Channels;
2454 | }
2455 | lpRowInitial -= Channels;
2456 | lpColumn += HeightStep * WidthSubOne;
2457 | bufferPerLine -= Channels;
2458 | prevOut[0] = (lpRowInitial[0] * cnext);
2459 | prevOut[1] = (lpRowInitial[1] * cnext);
2460 | prevOut[2] = (lpRowInitial[2] * cnext);
2461 |
2462 | for (int x = WidthSubOne; x >= 0; --x)
2463 | {
2464 | prevOut[0] = ((lpRowInitial[0] * (a2a3)) - (prevOut[0] * (b1b2)));
2465 | prevOut[1] = ((lpRowInitial[1] * (a2a3)) - (prevOut[1] * (b1b2)));
2466 | prevOut[2] = ((lpRowInitial[2] * (a2a3)) - (prevOut[2] * (b1b2)));
2467 | bufferPerLine[0] += (unsigned char)prevOut[0];
2468 | bufferPerLine[1] += (unsigned char)prevOut[1];
2469 | bufferPerLine[2] += (unsigned char)prevOut[2];
2470 | lpColumn[0] = bufferPerLine[0];
2471 | lpColumn[1] = bufferPerLine[1];
2472 | lpColumn[2] = bufferPerLine[2];
2473 | lpRowInitial -= Channels;
2474 | lpColumn -= HeightStep;
2475 | bufferPerLine -= Channels;
2476 | }
2477 | }
2478 | else if (Channels == 4)
2479 | {
2480 | float prevOut[4];
2481 |
2482 | prevOut[0] = (lpRowInitial[0] * cprev);
2483 | prevOut[1] = (lpRowInitial[1] * cprev);
2484 | prevOut[2] = (lpRowInitial[2] * cprev);
2485 | prevOut[3] = (lpRowInitial[3] * cprev);
2486 | for (int x = 0; x < width; ++x)
2487 | {
2488 | prevOut[0] = ((lpRowInitial[0] * (a0a1)) - (prevOut[0] * (b1b2)));
2489 | prevOut[1] = ((lpRowInitial[1] * (a0a1)) - (prevOut[1] * (b1b2)));
2490 | prevOut[2] = ((lpRowInitial[2] * (a0a1)) - (prevOut[2] * (b1b2)));
2491 | prevOut[3] = ((lpRowInitial[3] * (a0a1)) - (prevOut[3] * (b1b2)));
2492 |
2493 | bufferPerLine[0] = (unsigned char)prevOut[0];
2494 | bufferPerLine[1] = (unsigned char)prevOut[1];
2495 | bufferPerLine[2] = (unsigned char)prevOut[2];
2496 | bufferPerLine[3] = (unsigned char)prevOut[3];
2497 | bufferPerLine += Channels;
2498 | lpRowInitial += Channels;
2499 | }
2500 | lpRowInitial -= Channels;
2501 | lpColumn += HeightStep * WidthSubOne;
2502 | bufferPerLine -= Channels;
2503 |
2504 | prevOut[0] = (lpRowInitial[0] * cnext);
2505 | prevOut[1] = (lpRowInitial[1] * cnext);
2506 | prevOut[2] = (lpRowInitial[2] * cnext);
2507 | prevOut[3] = (lpRowInitial[3] * cnext);
2508 |
2509 | for (int x = WidthSubOne; x >= 0; --x)
2510 | {
2511 | prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
2512 | prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
2513 | prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
2514 | prevOut[3] = ((lpRowInitial[3] * a2a3) - (prevOut[3] * b1b2));
2515 | bufferPerLine[0] += (unsigned char)prevOut[0];
2516 | bufferPerLine[1] += (unsigned char)prevOut[1];
2517 | bufferPerLine[2] += (unsigned char)prevOut[2];
2518 | bufferPerLine[3] += (unsigned char)prevOut[3];
2519 | lpColumn[0] = bufferPerLine[0];
2520 | lpColumn[1] = bufferPerLine[1];
2521 | lpColumn[2] = bufferPerLine[2];
2522 | lpColumn[3] = bufferPerLine[3];
2523 | lpRowInitial -= Channels;
2524 | lpColumn -= HeightStep;
2525 | bufferPerLine -= Channels;
2526 | }
2527 | }
2528 | else if (Channels == 1)
2529 | {
2530 | float prevOut = (lpRowInitial[0] * cprev);
2531 |
2532 | for (int x = 0; x < width; ++x)
2533 | {
2534 | prevOut = ((lpRowInitial[0] * (a0a1)) - (prevOut * (b1b2)));
2535 | bufferPerLine[0] = (unsigned char)prevOut;
2536 | bufferPerLine += Channels;
2537 | lpRowInitial += Channels;
2538 | }
2539 | lpRowInitial -= Channels;
2540 | lpColumn += HeightStep * WidthSubOne;
2541 | bufferPerLine -= Channels;
2542 |
2543 | prevOut = (lpRowInitial[0] * cnext);
2544 |
2545 | for (int x = WidthSubOne; x >= 0; --x)
2546 | {
2547 | prevOut = ((lpRowInitial[0] * a2a3) - (prevOut * b1b2));
2548 | bufferPerLine[0] += (unsigned char)prevOut;
2549 | lpColumn[0] = bufferPerLine[0];
2550 | lpRowInitial -= Channels;
2551 | lpColumn -= HeightStep;
2552 | bufferPerLine -= Channels;
2553 | }
2554 | }
2555 | }
2556 |
2557 | void gaussianVertical(unsigned char *bufferPerLine, const unsigned char *lpRowInitial, unsigned char *lpColInitial, int height, int width, int Channels, float a0a1, float a2a3, float b1b2, float cprev, float cnext)
2558 | {
2559 |
2560 | int WidthStep = Channels * width;
2561 | int HeightSubOne = height - 1;
2562 | if (Channels == 3)
2563 | {
2564 | float prevOut[3];
2565 | prevOut[0] = (lpRowInitial[0] * cprev);
2566 | prevOut[1] = (lpRowInitial[1] * cprev);
2567 | prevOut[2] = (lpRowInitial[2] * cprev);
2568 |
2569 | for (int y = 0; y < height; y++)
2570 | {
2571 | prevOut[0] = ((lpRowInitial[0] * a0a1) - (prevOut[0] * b1b2));
2572 | prevOut[1] = ((lpRowInitial[1] * a0a1) - (prevOut[1] * b1b2));
2573 | prevOut[2] = ((lpRowInitial[2] * a0a1) - (prevOut[2] * b1b2));
2574 | bufferPerLine[0] = (unsigned char)prevOut[0];
2575 | bufferPerLine[1] = (unsigned char)prevOut[1];
2576 | bufferPerLine[2] = (unsigned char)prevOut[2];
2577 | bufferPerLine += Channels;
2578 | lpRowInitial += Channels;
2579 | }
2580 | lpRowInitial -= Channels;
2581 | bufferPerLine -= Channels;
2582 | lpColInitial += WidthStep * HeightSubOne;
2583 | prevOut[0] = (lpRowInitial[0] * cnext);
2584 | prevOut[1] = (lpRowInitial[1] * cnext);
2585 | prevOut[2] = (lpRowInitial[2] * cnext);
2586 | for (int y = HeightSubOne; y >= 0; y--)
2587 | {
2588 | prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
2589 | prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
2590 | prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
2591 | bufferPerLine[0] += (unsigned char)prevOut[0];
2592 | bufferPerLine[1] += (unsigned char)prevOut[1];
2593 | bufferPerLine[2] += (unsigned char)prevOut[2];
2594 | lpColInitial[0] = bufferPerLine[0];
2595 | lpColInitial[1] = bufferPerLine[1];
2596 | lpColInitial[2] = bufferPerLine[2];
2597 | lpRowInitial -= Channels;
2598 | lpColInitial -= WidthStep;
2599 | bufferPerLine -= Channels;
2600 | }
2601 | }
2602 | else if (Channels == 4)
2603 | {
2604 | float prevOut[4];
2605 |
2606 | prevOut[0] = (lpRowInitial[0] * cprev);
2607 | prevOut[1] = (lpRowInitial[1] * cprev);
2608 | prevOut[2] = (lpRowInitial[2] * cprev);
2609 | prevOut[3] = (lpRowInitial[3] * cprev);
2610 |
2611 | for (int y = 0; y < height; y++)
2612 | {
2613 | prevOut[0] = ((lpRowInitial[0] * a0a1) - (prevOut[0] * b1b2));
2614 | prevOut[1] = ((lpRowInitial[1] * a0a1) - (prevOut[1] * b1b2));
2615 | prevOut[2] = ((lpRowInitial[2] * a0a1) - (prevOut[2] * b1b2));
2616 | prevOut[3] = ((lpRowInitial[3] * a0a1) - (prevOut[3] * b1b2));
2617 | bufferPerLine[0] = (unsigned char)prevOut[0];
2618 | bufferPerLine[1] = (unsigned char)prevOut[1];
2619 | bufferPerLine[2] = (unsigned char)prevOut[2];
2620 | bufferPerLine[3] = (unsigned char)prevOut[3];
2621 | bufferPerLine += Channels;
2622 | lpRowInitial += Channels;
2623 | }
2624 | lpRowInitial -= Channels;
2625 | bufferPerLine -= Channels;
2626 | lpColInitial += WidthStep * HeightSubOne;
2627 | prevOut[0] = (lpRowInitial[0] * cnext);
2628 | prevOut[1] = (lpRowInitial[1] * cnext);
2629 | prevOut[2] = (lpRowInitial[2] * cnext);
2630 | prevOut[3] = (lpRowInitial[3] * cnext);
2631 | for (int y = HeightSubOne; y >= 0; y--)
2632 | {
2633 | prevOut[0] = ((lpRowInitial[0] * a2a3) - (prevOut[0] * b1b2));
2634 | prevOut[1] = ((lpRowInitial[1] * a2a3) - (prevOut[1] * b1b2));
2635 | prevOut[2] = ((lpRowInitial[2] * a2a3) - (prevOut[2] * b1b2));
2636 | prevOut[3] = ((lpRowInitial[3] * a2a3) - (prevOut[3] * b1b2));
2637 | bufferPerLine[0] += (unsigned char)prevOut[0];
2638 | bufferPerLine[1] += (unsigned char)prevOut[1];
2639 | bufferPerLine[2] += (unsigned char)prevOut[2];
2640 | bufferPerLine[3] += (unsigned char)prevOut[3];
2641 | lpColInitial[0] = bufferPerLine[0];
2642 | lpColInitial[1] = bufferPerLine[1];
2643 | lpColInitial[2] = bufferPerLine[2];
2644 | lpColInitial[3] = bufferPerLine[3];
2645 | lpRowInitial -= Channels;
2646 | lpColInitial -= WidthStep;
2647 | bufferPerLine -= Channels;
2648 | }
2649 | }
2650 | else if (Channels == 1)
2651 | {
2652 | float prevOut = 0;
2653 | prevOut = (lpRowInitial[0] * cprev);
2654 | for (int y = 0; y < height; y++)
2655 | {
2656 | prevOut = ((lpRowInitial[0] * a0a1) - (prevOut * b1b2));
2657 | bufferPerLine[0] = (unsigned char)prevOut;
2658 | bufferPerLine += Channels;
2659 | lpRowInitial += Channels;
2660 | }
2661 | lpRowInitial -= Channels;
2662 | bufferPerLine -= Channels;
2663 | lpColInitial += WidthStep * HeightSubOne;
2664 | prevOut = (lpRowInitial[0] * cnext);
2665 | for (int y = HeightSubOne; y >= 0; y--)
2666 | {
2667 | prevOut = ((lpRowInitial[0] * a2a3) - (prevOut * b1b2));
2668 | bufferPerLine[0] += (unsigned char)prevOut;
2669 | lpColInitial[0] = bufferPerLine[0];
2670 | lpRowInitial -= Channels;
2671 | lpColInitial -= WidthStep;
2672 | bufferPerLine -= Channels;
2673 | }
2674 | }
2675 | }
2676 |
2677 | void CPUImageGaussianBlurFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float GaussianSigma)
2678 | {
2679 |
2680 | int Channels = Stride / Width;
2681 | float a0, a1, a2, a3, b1, b2, cprev, cnext;
2682 |
2683 | CalGaussianCoeff(GaussianSigma, &a0, &a1, &a2, &a3, &b1, &b2, &cprev, &cnext);
2684 |
2685 | float a0a1 = (a0 + a1);
2686 | float a2a3 = (a2 + a3);
2687 | float b1b2 = (b1 + b2);
2688 |
2689 | int bufferSizePerThread = (Width > Height ? Width : Height) * Channels;
2690 | unsigned char *bufferPerLine = (unsigned char *)malloc((size_t)bufferSizePerThread);
2691 | unsigned char *tempData = (unsigned char *)malloc((size_t)Height * Stride);
2692 | if (bufferPerLine == NULL || tempData == NULL)
2693 | {
2694 | if (tempData)
2695 | {
2696 | free(tempData);
2697 | }
2698 | if (bufferPerLine)
2699 | {
2700 | free(bufferPerLine);
2701 | }
2702 | return;
2703 | }
2704 | for (int y = 0; y < Height; ++y)
2705 | {
2706 | unsigned char *lpRowInitial = Input + Stride * y;
2707 | unsigned char *lpColInitial = tempData + y * Channels;
2708 | gaussianHorizontal(bufferPerLine, lpRowInitial, lpColInitial, Width, Height, Channels, Width, a0a1, a2a3, b1b2, cprev, cnext);
2709 | }
2710 | int HeightStep = Height * Channels;
2711 | for (int x = 0; x < Width; ++x)
2712 | {
2713 | unsigned char *lpColInitial = Output + x * Channels;
2714 | unsigned char *lpRowInitial = tempData + HeightStep * x;
2715 | gaussianVertical(bufferPerLine, lpRowInitial, lpColInitial, Height, Width, Channels, a0a1, a2a3, b1b2, cprev, cnext);
2716 | }
2717 |
2718 | free(bufferPerLine);
2719 | free(tempData);
2720 | }
2721 |
2722 | /*
2723 | CPUImageUnsharpMaskFilter: Applies an unsharp mask
2724 | blurRadiusInPixels : The blur radius of the underlying Gaussian blur.The default is 4.0.
2725 | intensity : The strength of the sharpening, from 0.0 on up, with a default of 1.0
2726 | */
2727 | #define float2fixed(x) (((int)((x)*4096.0f + 0.5f)) << 8)
2728 |
2729 | void rgb2ycbcr(unsigned char R, unsigned char G, unsigned char B, unsigned char *y, unsigned char *cb, unsigned char *cr)
2730 | {
2731 | *y = (unsigned char)((19595 * R + 38470 * G + 7471 * B) >> 16);
2732 | *cb = (unsigned char)(((36962 * (B - *y)) >> 16) + 128);
2733 | *cr = (unsigned char)(((46727 * (R - *y)) >> 16) + 128);
2734 | }
2735 |
2736 | void ycbcr2rgb(unsigned char y, unsigned char Cb, unsigned char Cr, unsigned char *R, unsigned char *G, unsigned char *B)
2737 | {
2738 | int y_fixed = (y << 20) + (1 << 19); // rounding
2739 | int r, g, b;
2740 | int cr = Cr - 128;
2741 | int cb = Cb - 128;
2742 | r = y_fixed + cr * float2fixed(1.40200f);
2743 | g = y_fixed + (cr * -float2fixed(0.71414f)) + ((cb * -float2fixed(0.34414f)) * 0xffff0000);
2744 | b = y_fixed + cb * float2fixed(1.77200f);
2745 | r >>= 20;
2746 | g >>= 20;
2747 | b >>= 20;
2748 | if ((unsigned)r > 255)
2749 | {
2750 | if (r < 0)
2751 | r = 0;
2752 | else
2753 | r = 255;
2754 | }
2755 | if ((unsigned)g > 255)
2756 | {
2757 | if (g < 0)
2758 | g = 0;
2759 | else
2760 | g = 255;
2761 | }
2762 | if ((unsigned)b > 255)
2763 | {
2764 | if (b < 0)
2765 | b = 0;
2766 | else
2767 | b = 255;
2768 | }
2769 | *R =(unsigned char) r;
2770 | *G =(unsigned char)g;
2771 | *B =(unsigned char)b;
2772 | }
2773 |
2774 | void CPUImageUnsharpMaskFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float GaussianSigma, int intensity)
2775 | {
2776 | int Channels = Stride / Width;
2777 | intensity = max(min(intensity, 100), 0);
2778 | int c1 = 256 * (100 - intensity) / 100;
2779 | int c2 = 256 * (100 - (100 - intensity)) / 100;
2780 |
2781 | unsigned char unsharpMaskMap[256 * 256] = {0};
2782 | for (unsigned int PS = 0; PS < 256; PS++)
2783 | {
2784 | unsigned char *pUnsharpMaskMap = unsharpMaskMap + (PS << 8);
2785 | for (unsigned int PD = 0; PD < 256; PD++)
2786 | {
2787 | unsigned char retPD = ClampToByte((PS - PD) + 128);
2788 | retPD = (unsigned char)((PS <= 128) ? (retPD * PS / 128) : (255 - (255 - retPD) * (255 - PS) / 128));
2789 | //增强边缘法
2790 | // unsigned char retPD = ClampToByte((PS - PD) + PS);
2791 | pUnsharpMaskMap[0] = ClampToByte((PS * c1 + retPD * c2) >> 8);
2792 | pUnsharpMaskMap++;
2793 | }
2794 | }
2795 |
2796 | switch (Channels)
2797 | {
2798 | case 4:
2799 | case 3:
2800 | {
2801 | unsigned char *Temp = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
2802 | unsigned char *Blur = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
2803 | if (Blur == NULL || Temp == NULL)
2804 | {
2805 | if (Blur)
2806 | {
2807 | free(Blur);
2808 | }
2809 | if (Temp)
2810 | {
2811 | free(Temp);
2812 | }
2813 | return;
2814 | }
2815 | for (int Y = 0; Y < Height; Y++)
2816 | {
2817 | unsigned char *pInput = Input + (Y * Stride);
2818 | unsigned char *pTemp = Temp + (Y * Width);
2819 | unsigned char *pBlur = Blur + (Y * Width);
2820 | for (int X = 0; X < Width; X++)
2821 | {
2822 | pTemp[0] = (unsigned char)((19595 * pInput[0] + 38470 * pInput[1] + 7471 * pInput[2]) >> 16);
2823 |
2824 | pBlur[0] = pTemp[0];
2825 |
2826 | pInput += Channels;
2827 | pTemp++;
2828 | pBlur++;
2829 | }
2830 | }
2831 | CPUImageGaussianBlurFilter(Temp, Blur, Width, Height, Width, GaussianSigma);
2832 | unsigned char cb, cr;
2833 | for (int Y = 0; Y < Height; Y++)
2834 | {
2835 | unsigned char *pInput = Input + (Y * Stride);
2836 | unsigned char *pOutput = Output + (Y * Stride);
2837 | unsigned char *pTemp = Temp + (Y * Width);
2838 | unsigned char *pBlur = Blur + (Y * Width);
2839 | for (int x = 0; x < Width; x++)
2840 | {
2841 | cb = (unsigned char)((36962 * (pInput[2] - (int)(pTemp[0])) >> 16) + 128);
2842 | cr = (unsigned char)((46727 * (pInput[0] - (int)(pTemp[0])) >> 16) + 128);
2843 | //锐化:高反差叠加
2844 |
2845 | unsigned char *pUnsharpMaskMap = unsharpMaskMap + (pTemp[0] << 8);
2846 |
2847 | ycbcr2rgb(pUnsharpMaskMap[pBlur[0]], cb, cr, &pOutput[0], &pOutput[1], &pOutput[2]);
2848 |
2849 | pTemp++;
2850 | pBlur++;
2851 | pOutput += Channels;
2852 | pInput += Channels;
2853 | }
2854 | }
2855 | free(Temp);
2856 | free(Blur);
2857 | break;
2858 | }
2859 |
2860 | case 1:
2861 | {
2862 | unsigned char *Blur = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
2863 | if (Blur == NULL)
2864 | {
2865 | return;
2866 | }
2867 |
2868 | CPUImageGaussianBlurFilter(Input, Blur, Width, Height, Width, GaussianSigma);
2869 |
2870 | for (int Y = 0; Y < Height; Y++)
2871 | {
2872 | unsigned char *pInput = Input + (Y * Width);
2873 | unsigned char *pBlur = Blur + (Y * Width);
2874 | unsigned char *pOutput = Output + (Y * Width);
2875 | for (int x = 0; x < Width; x++)
2876 | {
2877 | //锐化:高反差叠加
2878 | pOutput[0] =(unsigned char)(pInput[0] - pOutput[0] + 128);
2879 | unsigned char *pUnsharpMaskMap = unsharpMaskMap + (pInput[0] << 8);
2880 | pOutput[0] = pUnsharpMaskMap[pOutput[0]];
2881 |
2882 | pBlur++;
2883 | pOutput++;
2884 | pInput++;
2885 | }
2886 | }
2887 | free(Blur);
2888 | }
2889 |
2890 | break;
2891 |
2892 | default:
2893 | break;
2894 | }
2895 | }
2896 |
2897 | static inline void boxfilterRow(const unsigned char *Input, unsigned char *Output, int Width, int Height, int Channels, int Radius)
2898 | {
2899 | int iRadius = Radius + 1;
2900 | int iScale = (int)((256.0f * 256.0f) / (2 * Radius + 1));
2901 | int iWidthStep = Width * Channels;
2902 | int iWidthStepDec = (Width - 1) * Channels;
2903 | int iRadChannels = Radius * Channels;
2904 | int iRadChannelsPlus = (iRadChannels + Channels);
2905 | switch (Channels)
2906 | {
2907 | case 1:
2908 | {
2909 | for (int y = 0; y < Height; y++)
2910 | {
2911 | // 处理左边缘
2912 | int iY = y * iWidthStep;
2913 | int sum = Input[iY] * Radius;
2914 | for (int x = 0; x < iRadius; x++)
2915 | {
2916 | int p = (y * Width + x) * Channels;
2917 | sum += Input[p];
2918 | }
2919 | Output[iY] = (unsigned char)((sum * iScale) >> 16);
2920 | for (int x = 1; x < iRadius; x++)
2921 | {
2922 | int pLeft = iY + x * Channels;
2923 | int p0 = pLeft + iRadChannels;
2924 |
2925 | sum += Input[p0];
2926 | sum -= Input[iY];
2927 | Output[pLeft] =(unsigned char)((sum * iScale) >> 16);
2928 | }
2929 |
2930 | // 核心区域
2931 | for (int x = iRadius; x < Width - Radius; x++)
2932 | {
2933 | int pKernal = iY + x * Channels;
2934 |
2935 | int i0 = pKernal + iRadChannels;
2936 | int i1 = pKernal - iRadChannelsPlus;
2937 |
2938 | sum += Input[i0];
2939 | sum -= Input[i1];
2940 |
2941 | Output[pKernal] = (unsigned char)((sum * iScale) >> 16);
2942 | }
2943 |
2944 | // 处理右边缘
2945 | for (int x = Width - Radius; x < Width; x++)
2946 | {
2947 | int iRight = iY + x * Channels;
2948 | int i0 = iY + iWidthStepDec;
2949 | int i1 = iRight - iRadChannelsPlus;
2950 |
2951 | sum += Input[i0];
2952 | sum -= Input[i1];
2953 | Output[iRight] =(unsigned char)((sum * iScale) >> 16);
2954 | }
2955 | }
2956 | break;
2957 | }
2958 | case 3:
2959 | {
2960 | for (int y = 0; y < Height; y++)
2961 | {
2962 | // 处理左边缘
2963 |
2964 | int iY = y * iWidthStep;
2965 | int sumR = Input[iY] * Radius;
2966 | int sumG = Input[iY + 1] * Radius;
2967 | int sumB = Input[iY + 2] * Radius;
2968 | for (int x = 0; x < iRadius; x++)
2969 | {
2970 | int i = iY + x * Channels;
2971 | sumR += Input[i];
2972 | sumG += Input[i + 1];
2973 | sumB += Input[i + 2];
2974 | }
2975 | Output[iY] =(unsigned char) ((sumR * iScale) >> 16);
2976 | Output[iY + 1] =(unsigned char) ((sumG * iScale) >> 16);
2977 | Output[iY + 2] =(unsigned char) ((sumB * iScale) >> 16);
2978 | for (int x = 1; x < iRadius; x++)
2979 | {
2980 | int iLeft = iY + x * Channels;
2981 | int i0 = iLeft + iRadChannels;
2982 |
2983 | sumR += Input[i0];
2984 | sumR -= Input[iY];
2985 | sumG += Input[i0 + 1];
2986 | sumG -= Input[iY + 1];
2987 | sumB += Input[i0 + 2];
2988 | sumB -= Input[iY + 2];
2989 | Output[iLeft] =(unsigned char) ((sumR * iScale) >> 16);
2990 | Output[iLeft + 1] =(unsigned char) ((sumG * iScale) >> 16);
2991 | Output[iLeft + 2] =(unsigned char) ((sumB * iScale) >> 16);
2992 | }
2993 |
2994 | // 核心区域
2995 | for (int x = iRadius; x < Width - Radius; x++)
2996 | {
2997 | int iKernal = iY + x * Channels;
2998 |
2999 | int i0 = iKernal + iRadChannels;
3000 | int i1 = iKernal - iRadChannelsPlus;
3001 |
3002 | sumR += Input[i0];
3003 | sumR -= Input[i1];
3004 |
3005 | sumG += Input[i0 + 1];
3006 | sumG -= Input[i1 + 1];
3007 |
3008 | sumB += Input[i0 + 2];
3009 | sumB -= Input[i1 + 2];
3010 | Output[iKernal] = (unsigned char)((sumR * iScale) >> 16);
3011 | Output[iKernal + 1] = (unsigned char)((sumG * iScale) >> 16);
3012 | Output[iKernal + 2] =(unsigned char)((sumB * iScale) >> 16);
3013 | }
3014 |
3015 | // 处理右边缘
3016 | for (int x = Width - Radius; x < Width; x++)
3017 | {
3018 | int iRight = iY + x * Channels;
3019 | int i0 = iY + iWidthStepDec;
3020 | int i1 = iRight - iRadChannelsPlus;
3021 |
3022 | sumR += Input[i0];
3023 | sumR -= Input[i1];
3024 |
3025 | sumG += Input[i0 + 1];
3026 | sumG -= Input[i1 + 1];
3027 |
3028 | sumB += Input[i0 + 2];
3029 | sumB -= Input[i1 + 2];
3030 | Output[iRight] =(unsigned char) ((sumR * iScale) >> 16);
3031 | Output[iRight + 1] = (unsigned char) ((sumG * iScale) >> 16);
3032 | Output[iRight + 2] = (unsigned char) ((sumB * iScale) >> 16);
3033 | }
3034 | }
3035 | break;
3036 | }
3037 | case 4:
3038 | {
3039 | for (int y = 0; y < Height; y++)
3040 | {
3041 | // 处理左边缘
3042 | int iY = y * iWidthStep;
3043 | int sumR = Input[iY] * Radius;
3044 | int sumG = Input[iY + 1] * Radius;
3045 | int sumB = Input[iY + 2] * Radius;
3046 | for (int x = 0; x < iRadius; x++)
3047 | {
3048 | int i = iY + x * Channels;
3049 | sumR += Input[i];
3050 | sumG += Input[i + 1];
3051 | sumB += Input[i + 2];
3052 | }
3053 | Output[iY] = (unsigned char) ((sumR * iScale) >> 16);
3054 | Output[iY + 1] = (unsigned char) ((sumG * iScale) >> 16);
3055 | Output[iY + 2] =(unsigned char) ((sumB * iScale) >> 16);
3056 | Output[iY + 3] = Input[iY + 3];
3057 | for (int x = 1; x < iRadius; x++)
3058 | {
3059 | int iLeft = iY + x * Channels;
3060 | int i0 = iLeft + iRadChannels;
3061 | sumR += Input[i0];
3062 | sumR -= Input[iLeft];
3063 | sumG += Input[i0 + 1];
3064 | sumG -= Input[iLeft + 1];
3065 | sumB += Input[i0 + 2];
3066 | sumB -= Input[iLeft + 2];
3067 | Output[iLeft] = (unsigned char)((sumR * iScale) >> 16);
3068 | Output[iLeft + 1] =(unsigned char) ((sumG * iScale) >> 16);
3069 | Output[iLeft + 2] =(unsigned char) ((sumB * iScale) >> 16);
3070 | Output[iLeft + 3] = Input[iLeft + 3];
3071 | }
3072 |
3073 | // 核心区域
3074 | for (int x = iRadius; x < Width - Radius; x++)
3075 | {
3076 | int iKernal = iY + x * Channels;
3077 |
3078 | int i0 = iKernal + iRadChannels;
3079 | int i1 = iKernal - iRadChannelsPlus;
3080 |
3081 | sumR += Input[i0];
3082 | sumR -= Input[i1];
3083 |
3084 | sumG += Input[i0 + 1];
3085 | sumG -= Input[i1 + 1];
3086 |
3087 | sumB += Input[i0 + 2];
3088 | sumB -= Input[i1 + 2];
3089 | Output[iKernal] =(unsigned char) ( (sumR * iScale) >> 16);
3090 | Output[iKernal + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3091 | Output[iKernal + 2] =(unsigned char) ( (sumB * iScale) >> 16);
3092 | Output[iKernal + 3] = Input[iKernal + 3];
3093 | }
3094 |
3095 | // 处理右边缘
3096 | for (int x = Width - Radius; x < Width; x++)
3097 | {
3098 | int iRight = iY + x * Channels;
3099 | int i0 = iY + iWidthStepDec;
3100 | int i1 = iRight - iRadChannelsPlus;
3101 |
3102 | sumR += Input[i0];
3103 | sumR -= Input[i1];
3104 |
3105 | sumG += Input[i0 + 1];
3106 | sumG -= Input[i1 + 1];
3107 |
3108 | sumB += Input[i0 + 2];
3109 | sumB -= Input[i1 + 2];
3110 | Output[iRight] = (unsigned char) ((sumR * iScale) >> 16);
3111 | Output[iRight + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3112 | Output[iRight + 2] =(unsigned char) ( (sumB * iScale) >> 16);
3113 | Output[iRight + 3] = Input[iRight + 3];
3114 | }
3115 | }
3116 | break;
3117 | }
3118 | default:
3119 | break;
3120 | }
3121 | }
3122 |
3123 | static inline void boxfilterCol(const unsigned char *Input, unsigned char *Output, int Width, int Height, int Channels, int Radius)
3124 | {
3125 | int iScale = (int)((256.0f * 256.0f) / (2 * Radius + 1));
3126 | int iWidthStep = Width * Channels;
3127 | int iWidthStepDec = (Height - 1) * iWidthStep;
3128 | int iRadWidthStep = Radius * iWidthStep;
3129 | int iRadWidthStepDec = (iRadWidthStep + iWidthStep);
3130 | int iHeightRadius = Height - Radius;
3131 | int iRadius = Radius + 1;
3132 | switch (Channels)
3133 | {
3134 | case 1:
3135 | {
3136 | for (int x = 0; x < Width; x++)
3137 | {
3138 | // 处理左边缘
3139 | int iX = x * Channels;
3140 | int sum = Input[iX] * Radius;
3141 | for (int y = 0; y < iRadius; y++)
3142 | {
3143 | int i = (y * Width + x) * Channels;
3144 | sum += Input[i];
3145 | }
3146 | Output[x] = (unsigned char) ((sum * iScale) >> 16);
3147 |
3148 | for (int y = 1; y < iRadius; y++)
3149 | {
3150 | int i = iX + y * iWidthStep;
3151 |
3152 | int i0 = i + iRadWidthStep;
3153 | int i1 = x * Channels;
3154 |
3155 | sum += Input[i0];
3156 | sum -= Input[i1];
3157 | Output[i] =(unsigned char) ( (sum * iScale) >> 16);
3158 | }
3159 |
3160 | // 核心区域
3161 | for (int y = iRadius; y < iHeightRadius; y++)
3162 | {
3163 |
3164 | int iKernal = iX + y * iWidthStep;
3165 | int i0 = iKernal + iRadWidthStep;
3166 | int i1 = iKernal - iRadWidthStepDec;
3167 |
3168 | sum += Input[i0];
3169 | sum -= Input[i1];
3170 | Output[iKernal] = (unsigned char) ((sum * iScale) >> 16);
3171 | }
3172 |
3173 | // 处理右边缘
3174 | for (int y = iHeightRadius; y < Height; y++)
3175 | {
3176 | int iRight = iX + y * iWidthStep;
3177 |
3178 | int i0 = iWidthStepDec + x * Channels;
3179 | int i1 = iRight - iRadWidthStepDec;
3180 |
3181 | sum += Input[i0];
3182 | sum -= Input[i1];
3183 | Output[iRight] =(unsigned char) ( (sum * iScale) >> 16);
3184 | }
3185 | }
3186 |
3187 | break;
3188 | }
3189 | case 3:
3190 | {
3191 | for (int x = 0; x < Width; x++)
3192 | {
3193 | // 处理左边缘
3194 | int iX = x * Channels;
3195 | int sumR = Input[iX] * Radius;
3196 | int sumG = Input[iX + 1] * Radius;
3197 | int sumB = Input[iX + 2] * Radius;
3198 | for (int y = 0; y < iRadius; y++)
3199 | {
3200 | int i = iX + y * iWidthStep;
3201 | sumR += Input[i];
3202 | sumG += Input[i + 1];
3203 | sumB += Input[i + 2];
3204 | }
3205 | Output[iX] = (unsigned char) ((sumR * iScale) >> 16);
3206 | Output[iX + 1] = (unsigned char) ((sumG * iScale) >> 16);
3207 | Output[iX + 2] = (unsigned char) ((sumB * iScale) >> 16);
3208 |
3209 | for (int y = 1; y < iRadius; y++)
3210 | {
3211 | int i = iX + y * iWidthStep;
3212 | int i0 = i + iRadWidthStep;
3213 |
3214 | sumR += Input[i0];
3215 | sumR -= Input[iX];
3216 | sumG += Input[i0 + 1];
3217 | sumG -= Input[iX + 1];
3218 | sumB += Input[i0 + 2];
3219 | sumB -= Input[iX + 2];
3220 | Output[i] = (unsigned char) ((sumR * iScale) >> 16);
3221 | Output[i + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3222 | Output[i + 2] =(unsigned char) ( (sumB * iScale) >> 16);
3223 | }
3224 |
3225 | // 核心区域
3226 | for (int y = iRadius; y < iHeightRadius; y++)
3227 | {
3228 |
3229 | int iKernal = iX + y * iWidthStep;
3230 |
3231 | int i0 = iKernal + iRadWidthStep;
3232 | int i1 = iKernal - iRadWidthStepDec;
3233 |
3234 | sumR += Input[i0];
3235 | sumR -= Input[i1];
3236 | sumG += Input[i0 + 1];
3237 | sumG -= Input[i1 + 1];
3238 | sumB += Input[i0 + 2];
3239 | sumB -= Input[i1 + 2];
3240 | Output[iKernal] = (unsigned char) ((sumR * iScale) >> 16);
3241 | Output[iKernal + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3242 | Output[iKernal + 2] = (unsigned char) ((sumB * iScale) >> 16);
3243 | }
3244 |
3245 | // 处理右边缘
3246 | for (int y = iHeightRadius; y < Height; y++)
3247 | {
3248 | int iRight = iX + y * iWidthStep;
3249 | int i0 = iWidthStepDec + iX;
3250 | int i1 = iRight - iRadWidthStepDec;
3251 |
3252 | sumR += Input[i0];
3253 | sumR -= Input[i1];
3254 | sumG += Input[i0 + 1];
3255 | sumG -= Input[i1 + 1];
3256 | sumB += Input[i0 + 2];
3257 | sumB -= Input[i1 + 2];
3258 | Output[iRight] =(unsigned char) ( (sumR * iScale) >> 16);
3259 | Output[iRight + 1] = (unsigned char) ((sumG * iScale) >> 16);
3260 | Output[iRight + 2] = (unsigned char) ((sumB * iScale) >> 16);
3261 | }
3262 | }
3263 |
3264 | break;
3265 | }
3266 | case 4:
3267 | {
3268 | for (int x = 0; x < Width; x++)
3269 | {
3270 | // 处理左边缘
3271 | int iX = x * Channels;
3272 | int sumR = Input[iX] * Radius;
3273 | int sumG = Input[iX + 1] * Radius;
3274 | int sumB = Input[iX + 2] * Radius;
3275 | for (int y = 0; y < iRadius; y++)
3276 | {
3277 | int i = iX + y * iWidthStep;
3278 | sumR += Input[i];
3279 | sumG += Input[i + 1];
3280 | sumB += Input[i + 2];
3281 | }
3282 | Output[iX] = (unsigned char) ((sumR * iScale) >> 16);
3283 | Output[iX + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3284 | Output[iX + 2] =(unsigned char) ( (sumB * iScale) >> 16);
3285 | Output[iX + 3] = Input[iX + 3];
3286 | for (int y = 1; y < iRadius; y++)
3287 | {
3288 | int i = iX + y * iWidthStep;
3289 | int i0 = i + iRadWidthStep;
3290 | sumR += Input[i0];
3291 | sumR -= Input[iX];
3292 | sumG += Input[i0 + 1];
3293 | sumG -= Input[iX + 1];
3294 | sumB += Input[i0 + 2];
3295 | sumB -= Input[iX + 2];
3296 | Output[i] =(unsigned char) ( (sumR * iScale) >> 16);
3297 | Output[i + 1] = (unsigned char) ((sumG * iScale) >> 16);
3298 | Output[i + 2] = (unsigned char) ((sumB * iScale) >> 16);
3299 | Output[i + 3] = Input[i + 3];
3300 | }
3301 |
3302 | // 核心区域
3303 | for (int y = iRadius; y < iHeightRadius; y++)
3304 | {
3305 |
3306 | int iKernal = iX + y * iWidthStep;
3307 | int i0 = iKernal + iRadWidthStep;
3308 | int i1 = iKernal - iRadWidthStepDec;
3309 | sumR += Input[i0];
3310 | sumR -= Input[i1];
3311 | sumG += Input[i0 + 1];
3312 | sumG -= Input[i1 + 1];
3313 | sumB += Input[i0 + 2];
3314 | sumB -= Input[i1 + 2];
3315 | Output[iKernal] = (unsigned char) ((sumR * iScale) >> 16);
3316 | Output[iKernal + 1] =(unsigned char) ( (sumG * iScale) >> 16);
3317 | Output[iKernal + 2] = (unsigned char) ((sumB * iScale) >> 16);
3318 | Output[iKernal + 3] = Input[iKernal + 3];
3319 | }
3320 |
3321 | // 处理右边缘
3322 | for (int y = iHeightRadius; y < Height; y++)
3323 | {
3324 | int iRight = iX + y * iWidthStep;
3325 |
3326 | int i0 = iWidthStepDec + iX;
3327 | int i1 = iRight - iRadWidthStepDec;
3328 | sumR += Input[i0];
3329 | sumR -= Input[i1];
3330 | sumG += Input[i0 + 1];
3331 | sumG -= Input[i1 + 1];
3332 | sumB += Input[i0 + 2];
3333 | sumB -= Input[i1 + 2];
3334 | Output[iRight] = (unsigned char) ((sumR * iScale) >> 16);
3335 | Output[iRight + 1] = (unsigned char) ((sumG * iScale) >> 16);
3336 | Output[iRight + 2] = (unsigned char) ((sumB * iScale) >> 16);
3337 | Output[iRight + 3] = Input[iRight + 3];
3338 | }
3339 | }
3340 | break;
3341 | }
3342 | default:
3343 | break;
3344 | }
3345 | }
3346 | /*
3347 | CPUImageBoxBlurFilter: A hardware - optimized, variable - radius box blur
3348 | texelSpacingMultiplier : A multiplier for the spacing between texels, ranging from 0.0 on up, with a default of 1.0.Adjusting this may slightly increase the blur strength, but will introduce artifacts in the result.Highly recommend using other parameters first, before touching this one.
3349 | blurRadiusInPixels : A radius in pixels to use for the blur, with a default of 2.0.This adjusts the sigma variable in the Gaussian distribution function.
3350 | blurPasses : The number of times to sequentially blur the incoming image.The more passes, the slower the filter.
3351 |
3352 | */
3353 |
3354 | void CPUImageBoxBlurFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int Radius)
3355 | {
3356 | int Channels = Stride / Width;
3357 | unsigned char *temp = (unsigned char *)malloc((size_t)Width * Height * Channels);
3358 | if (temp == NULL)
3359 | {
3360 | return;
3361 | }
3362 | boxfilterRow(Input, temp, Width, Height, Channels, Radius);
3363 | boxfilterCol(temp, Output, Width, Height, Channels, Radius);
3364 | free(temp);
3365 | }
3366 |
3367 | void CPUImageSharpenFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float Radius, int sharpness, int intensity)
3368 | {
3369 | int Channels = Stride / Width;
3370 | intensity = max(min(intensity, 100), 0);
3371 | int c1 = 256 * (100 - intensity) / 100;
3372 | int c2 = 256 * (100 - (100 - intensity)) / 100;
3373 | //锐化:高反差叠加
3374 | unsigned char sharpnessMap[256 * 256] = {0};
3375 | for (unsigned int PS = 0; PS < 256; PS++)
3376 | {
3377 | unsigned char *pSharpnessMap = sharpnessMap + (PS << 8);
3378 | for (unsigned int PD = 0; PD < 256; PD++)
3379 | {
3380 | unsigned char retPD = ClampToByte((sharpness * (PS - PD)) + 128);
3381 | retPD =(unsigned char) ((PS <= 128) ? (retPD * PS / 128) : (255 - (255 - retPD) * (255 - PS) / 128));
3382 | //增强边缘法
3383 | // unsigned char retPD = ClampToByte(sharpness*(PS - PD) + PS);
3384 |
3385 | pSharpnessMap[0] = ClampToByte((PS * c1 + retPD * c2) >> 8);
3386 | pSharpnessMap++;
3387 | }
3388 | }
3389 | switch (Channels)
3390 | {
3391 | case 4:
3392 | case 3:
3393 | {
3394 | unsigned char *temp = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
3395 | unsigned char *blur = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
3396 | if (blur == NULL || temp == NULL)
3397 | {
3398 | if (temp)
3399 | {
3400 | free(temp);
3401 | }
3402 | if (blur)
3403 | {
3404 | free(blur);
3405 | }
3406 | return;
3407 | }
3408 | for (int Y = 0; Y < Height; Y++)
3409 | {
3410 | unsigned char *pInput = Input + (Y * Stride);
3411 | unsigned char *pTemp = temp + (Y * Width);
3412 | unsigned char *pBlur = blur + (Y * Width);
3413 | for (int X = 0; X < Width; X++)
3414 | {
3415 | pTemp[0] = (unsigned char)((19595 * pInput[0] + 38470 * pInput[1] + 7471 * pInput[2]) >> 16);
3416 | pBlur[0] = pTemp[0];
3417 | pInput += Channels;
3418 | pTemp++;
3419 | pBlur++;
3420 | }
3421 | }
3422 | CPUImageBoxBlurFilter(temp, blur, Width, Height, Width, (int)Radius);
3423 | unsigned char cb, cr;
3424 | for (int Y = 0; Y < Height; Y++)
3425 | {
3426 | unsigned char *pInput = Input + (Y * Stride);
3427 | unsigned char *pOutput = Output + (Y * Stride);
3428 | unsigned char *pTemp = temp + (Y * Width);
3429 | unsigned char *pBlur = blur + (Y * Width);
3430 | for (int x = 0; x < Width; x++)
3431 | {
3432 | cb = (unsigned char)((36962 * (pInput[2] - (int)(pTemp[0])) >> 16) + 128);
3433 | cr = (unsigned char)((46727 * (pInput[0] - (int)(pTemp[0])) >> 16) + 128);
3434 | //锐化:高反差叠加
3435 | unsigned char *pSharpnessMap = sharpnessMap + (pTemp[0] << 8);
3436 |
3437 | ycbcr2rgb(pSharpnessMap[pBlur[0]], cb, cr, &pOutput[0], &pOutput[1], &pOutput[2]);
3438 |
3439 | pTemp++;
3440 | pBlur++;
3441 | pOutput += Channels;
3442 | pInput += Channels;
3443 | }
3444 | }
3445 | free(temp);
3446 | free(blur);
3447 | break;
3448 | }
3449 |
3450 | case 1:
3451 | {
3452 |
3453 | unsigned char *Blur = (unsigned char *)malloc(Width * Height * (sizeof(unsigned char)));
3454 | if (Blur == NULL)
3455 | {
3456 | return;
3457 | }
3458 |
3459 | CPUImageBoxBlurFilter(Input, Blur, Width, Height, Width, (int)Radius);
3460 |
3461 | for (int Y = 0; Y < Height; Y++)
3462 | {
3463 | unsigned char *pInput = Input + (Y * Width);
3464 | unsigned char *pBlur = Blur + (Y * Width);
3465 | unsigned char *pOutput = Output + (Y * Width);
3466 | for (int x = 0; x < Width; x++)
3467 | {
3468 | unsigned char *pSharpnessMap = sharpnessMap + (pInput[0] << 8);
3469 | pOutput[0] = pSharpnessMap[pOutput[0]];
3470 |
3471 | pBlur++;
3472 | pOutput++;
3473 | pInput++;
3474 | }
3475 | }
3476 | free(Blur);
3477 | }
3478 |
3479 | break;
3480 |
3481 | default:
3482 | break;
3483 | }
3484 | }
3485 |
3486 | void CPUImageResamplingFilter(unsigned char *Input, unsigned int Width, unsigned int Height, unsigned int Stride, unsigned char *Output, int newWidth, int newHeight, int dstStride)
3487 | {
3488 | int Channels = Stride / Width;
3489 | int dstOffset = dstStride - Channels * newWidth;
3490 | float xFactor = (float)Width / newWidth;
3491 | float yFactor = (float)Height / newHeight;
3492 |
3493 | int ymax = Height - 1;
3494 | int xmax = Width - 1;
3495 |
3496 | for (int y = 0; y < newHeight; y++)
3497 | {
3498 | float oy = (float)y * yFactor;
3499 | int oy1 = (int)oy;
3500 | int oy2 = (oy1 == ymax) ? oy1 : oy1 + 1;
3501 | float dy1 = oy - (float)oy1;
3502 | float dy2 = 1.0f - dy1;
3503 |
3504 | unsigned char *tp1 = Input + oy1 * Stride;
3505 | unsigned char *tp2 = Input + oy2 * Stride;
3506 |
3507 | for (int x = 0; x < newWidth; x++)
3508 | {
3509 | float ox = (float)x * xFactor;
3510 | int ox1 = (int)ox;
3511 | int ox2 = (ox1 == xmax) ? ox1 : ox1 + 1;
3512 | float dx1 = ox - (float)ox1;
3513 | float dx2 = 1.0f - dx1;
3514 | unsigned char *p1 = tp1 + ox1 * Channels;
3515 | unsigned char *p2 = tp1 + ox2 * Channels;
3516 | unsigned char *p3 = tp2 + ox1 * Channels;
3517 | unsigned char *p4 = tp2 + ox2 * Channels;
3518 |
3519 | for (int i = 0; i < Channels; i++, Output++, p1++, p2++, p3++, p4++)
3520 | {
3521 | *Output = (unsigned char)(dy2 * (dx2 * (*p1) + dx1 * (*p2)) + dy1 * (dx2 * (*p3) + dx1 * (*p4)));
3522 | }
3523 | }
3524 | Output += dstOffset;
3525 | }
3526 | }
3527 |
3528 | void CPUImageCropFilter(const unsigned char *Input, int Width, int Height, int srcStride, unsigned char *Output, int cropX, int cropY, int dstWidth, int dstHeight, int dstStride)
3529 | {
3530 | int Channels = srcStride / Width;
3531 |
3532 | const unsigned char *src = Input + cropY * srcStride + cropX * Channels;
3533 | unsigned char *dst = Output;
3534 |
3535 | for (int y = 0; y < dstHeight; y++)
3536 | {
3537 | memcpy(dst, src, dstStride);
3538 | src += srcStride;
3539 | dst += dstStride;
3540 | }
3541 | }
3542 |
3543 | void CPUImageAutoLevel(const unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float fraction)
3544 | {
3545 | int Channels = Stride / Width;
3546 | switch (Channels)
3547 | {
3548 | case 4:
3549 | case 3:
3550 | {
3551 | unsigned int histoRGB[256 * 3] = {0};
3552 | unsigned int *histoR = &histoRGB[0];
3553 | unsigned int *histoG = &histoRGB[256];
3554 | unsigned int *histoB = &histoRGB[512];
3555 | for (int Y = 0; Y < Height; Y++)
3556 | {
3557 | const unsigned char *pInput = Input + (Y * Stride);
3558 | for (int X = 0; X < Width; X++)
3559 | {
3560 | histoR[pInput[0]]++;
3561 | histoG[pInput[1]]++;
3562 | histoB[pInput[2]]++;
3563 | pInput += Channels;
3564 | }
3565 | }
3566 | int thresholdRMin = 0;
3567 | int thresholdRMax = 0;
3568 | int thresholdGMin = 0;
3569 | int thresholdGMax = 0;
3570 | int thresholdBMin = 0;
3571 | int thresholdBMax = 0;
3572 | int gap = (int)(fraction * Width * Height);
3573 | int sumR = 0;
3574 | int sumG = 0;
3575 | int sumB = 0;
3576 |
3577 | for (int i = 0; sumR < gap; ++i)
3578 | {
3579 | sumR += histoR[i];
3580 | thresholdRMin = i;
3581 | }
3582 | sumR = 0;
3583 | for (int i = 255; sumR < gap; --i)
3584 | {
3585 | sumR += histoR[i];
3586 | thresholdRMax = i;
3587 | }
3588 | for (int i = 0; sumG < gap; ++i)
3589 | {
3590 | sumG += histoG[i];
3591 | thresholdGMin = i;
3592 | }
3593 | sumG = 0;
3594 | for (int i = 255; sumG < gap; --i)
3595 | {
3596 | sumG += histoG[i];
3597 | thresholdGMax = i;
3598 | }
3599 | for (int i = 0; sumB < gap; ++i)
3600 | {
3601 | sumB += histoB[i];
3602 | thresholdBMin = i;
3603 | }
3604 | sumB = 0;
3605 | for (int i = 255; sumB < gap; --i)
3606 | {
3607 | sumB += histoB[i];
3608 | thresholdBMax = i;
3609 | }
3610 | unsigned char MapRGB[256 * 3] = {0};
3611 | unsigned char *MapB = &MapRGB[0];
3612 | unsigned char *MapG = &MapRGB[256];
3613 | unsigned char *MapR = &MapRGB[512];
3614 | for (int i = 0; i < 256; i++)
3615 | {
3616 | if (i < thresholdRMin)
3617 | MapR[i] =(unsigned char) ((i + 0) >> 1);
3618 | else if (i > thresholdRMax)
3619 | MapR[i] = (255);
3620 | else
3621 | MapR[i] = ClampToByte((int)((i - thresholdRMin) * 255.0) / (thresholdRMax - thresholdRMin));
3622 | if (i < thresholdGMin)
3623 | MapG[i] =(unsigned char) ((i + 0) >> 1);
3624 | else if (i > thresholdGMax)
3625 | MapG[i] = (255);
3626 | else
3627 | MapG[i] = ClampToByte((int)((i - thresholdGMin) * 255.0) / (thresholdGMax - thresholdGMin));
3628 | if (i < thresholdBMin)
3629 | MapB[i] = (unsigned char) ((i + 0) >> 1);
3630 | else if (i > thresholdBMax)
3631 | MapB[i] = (255);
3632 | else
3633 | MapB[i] = ClampToByte((int)((i - thresholdBMin) * 255.0) / (thresholdBMax - thresholdBMin));
3634 | }
3635 |
3636 | for (int Y = 0; Y < Height; Y++)
3637 | {
3638 | const unsigned char *pInput = Input + (Y * Stride);
3639 | unsigned char *pOutput = Output + (Y * Stride);
3640 | for (int X = 0; X < Width; X++)
3641 | {
3642 | pOutput[0] = MapR[pInput[0]];
3643 | pOutput[1] = MapG[pInput[1]];
3644 | pOutput[2] = MapB[pInput[2]];
3645 | pInput += Channels;
3646 | pOutput += Channels;
3647 | }
3648 | }
3649 | break;
3650 | }
3651 | case 1:
3652 | {
3653 | unsigned int histoGray[256] = {0};
3654 | for (int Y = 0; Y < Height; Y++)
3655 | {
3656 | const unsigned char *pInput = Input + (Y * Stride);
3657 | for (int X = 0; X < Width; X++)
3658 | {
3659 | histoGray[pInput[0]]++;
3660 | pInput++;
3661 | }
3662 | }
3663 | int thresholdMin = 0;
3664 | int thresholdMax = 0;
3665 | int gap = (int)(fraction * Width * Height);
3666 | int sumGray = 0;
3667 | for (int i = 0; sumGray < gap; ++i)
3668 | {
3669 | sumGray += histoGray[i];
3670 | thresholdMin = i;
3671 | }
3672 | sumGray = 0;
3673 | for (int i = 255; sumGray < gap; --i)
3674 | {
3675 | sumGray += histoGray[i];
3676 | thresholdMax = i;
3677 | }
3678 | unsigned char MapGray[256] = {0};
3679 | if ((thresholdMax - thresholdMin) <= 0)
3680 | return;
3681 | for (int i = 0; i < 256; i++)
3682 | {
3683 | if (i < thresholdMin)
3684 | MapGray[i] = (0);
3685 | else if (i > thresholdMax)
3686 | MapGray[i] = (255);
3687 | else
3688 | MapGray[i] = ClampToByte((int)((i - thresholdMin) * 255.0f) / (thresholdMax - thresholdMin));
3689 | }
3690 |
3691 | for (int Y = 0; Y < Height; Y++)
3692 | {
3693 | const unsigned char *pInput = Input + (Y * Stride);
3694 |
3695 | unsigned char *pOutput = Output + (Y * Stride);
3696 | for (int X = 0; X < Width; X++)
3697 | {
3698 | pOutput[0] = MapGray[pInput[0]];
3699 | pInput++;
3700 | pOutput++;
3701 | }
3702 | }
3703 | }
3704 | break;
3705 |
3706 | default:
3707 | break;
3708 | }
3709 | }
3710 |
3711 | void CPUImageSobelEdge(unsigned char *Input, unsigned char *Output, int Width, int Height)
3712 | {
3713 | if ((Input == NULL) || (Output == NULL))
3714 | return;
3715 | if ((Width <= 0) || (Height <= 0))
3716 | return;
3717 |
3718 | unsigned char *SqrLut = (unsigned char *)malloc(65026 * sizeof(unsigned char));
3719 | unsigned char *RowCopy = (unsigned char *)malloc((Width + 2) * 3 * sizeof(unsigned char));
3720 | if ((SqrLut == NULL) || (RowCopy == NULL))
3721 | {
3722 | if (SqrLut != NULL)
3723 | free(SqrLut);
3724 | if (RowCopy != NULL)
3725 | free(RowCopy);
3726 | }
3727 |
3728 | unsigned char *First = RowCopy, *Second = RowCopy + (Width + 2), *Third = RowCopy + (Width + 2) * 2;
3729 |
3730 | for (int Y = 0; Y < 65026; Y++)
3731 | SqrLut[Y] = (unsigned char)ClampToByte((sqrtf(Y + 0.0f) + 0.5f));
3732 |
3733 | memcpy(Second, Input, 1);
3734 | memcpy(Second + 1, Input, Width);
3735 | memcpy(Second + Width + 1, Input + Width - 1, 1);
3736 |
3737 | memcpy(First, Second, Width + 2);
3738 |
3739 | memcpy(Third, Input + Width, 1);
3740 | memcpy(Third + 1, Input + Width, Width);
3741 | memcpy(Third + Width + 1, Input + Width + Width - 1, 1);
3742 |
3743 | for (int Y = 0; Y < Height; Y++)
3744 | {
3745 | unsigned char *LinePS = Input + Y * Width;
3746 | unsigned char *LinePD = Output + Y * Width;
3747 | if (Y != 0)
3748 | {
3749 | unsigned char *Temp = First;
3750 | First = Second;
3751 | Second = Third;
3752 | Third = Temp;
3753 | }
3754 | if (Y == Height - 1)
3755 | {
3756 | memcpy(Third, Second, Width + 2);
3757 | }
3758 | else
3759 | {
3760 | memcpy(Third, Input + (Y + 1) * Width, 1);
3761 | memcpy(Third + 1, Input + (Y + 1) * Width, Width);
3762 | memcpy(Third + Width + 1, Input + (Y + 1) * Width + Width - 1, 1);
3763 | }
3764 | for (int X = 0; X < Width; X++)
3765 | {
3766 | int Hori = First[X] + 2 * First[X + 1] + First[X + 3] - (Third[X] + 2 * Third[X + 1] + Third[X + 2]);
3767 | int Vert = First[X] + 2 * Second[X] + Third[X] - (First[X + 2] + 2 * Second[X + 2] + Third[X + 2]);
3768 | int Value = Hori * Hori + Vert * Vert;
3769 | LinePD[X] = SqrLut[min(Value, 65025)];
3770 | }
3771 | }
3772 | if (RowCopy)
3773 | {
3774 | free(RowCopy);
3775 | }
3776 | if (SqrLut)
3777 | {
3778 | free(SqrLut);
3779 | }
3780 | }
3781 |
3782 | int CPUImageHoughLines(unsigned char *Input, int Width, int Height, int lineIntensity, int Threshold, float resTheta, int numLine, float *Radius, float *Theta)
3783 | {
3784 | int halfHoughWidth = (int)(sqrtf((float)(Width * Width + Height * Height)));
3785 | int houghWidth = halfHoughWidth * 2;
3786 | int maxTheta = (int)(180.0f / resTheta + 0.5f);
3787 | int houghMapSize = houghWidth * maxTheta;
3788 | unsigned short *houghMap = (unsigned short *)calloc((size_t)houghMapSize, sizeof(unsigned short));
3789 | float *sinLUT = (float *)calloc((size_t)maxTheta, sizeof(float));
3790 | float *cosLUT = (float *)calloc((size_t)maxTheta, sizeof(float));
3791 | if (sinLUT == NULL || cosLUT == NULL || houghMap == NULL)
3792 | {
3793 | if (houghMap)
3794 | {
3795 | free(houghMap);
3796 | }
3797 | if (cosLUT)
3798 | {
3799 | free(cosLUT);
3800 | }
3801 | if (sinLUT)
3802 | {
3803 | free(sinLUT);
3804 | }
3805 | return 0;
3806 | }
3807 | float thetaStep = M_PI / maxTheta;
3808 | for (int theta = 0; theta < maxTheta; theta++)
3809 | {
3810 | sinLUT[theta] = (float)fastSin(theta * thetaStep);
3811 | cosLUT[theta] = (float)fastCos(theta * thetaStep);
3812 | }
3813 |
3814 | for (int y = 0; y < Height; y++)
3815 | {
3816 | unsigned char *pIn = Input + (y * Width);
3817 | for (int x = 0; x < Width; x++)
3818 | {
3819 | if (pIn[x] > Threshold)
3820 | {
3821 | for (int theta = 0; theta < maxTheta; theta++)
3822 | {
3823 | int r = (int)(x * sinLUT[theta] + y * cosLUT[theta] + halfHoughWidth + 0.5f);
3824 | houghMap[r * maxTheta + theta]++;
3825 | }
3826 | }
3827 | }
3828 | }
3829 |
3830 | int nLine = 0;
3831 | for (int i = 0; i < houghMapSize && nLine < numLine; i++)
3832 | {
3833 | if (houghMap[i] > lineIntensity)
3834 | {
3835 | Radius[nLine] = (float)(i / maxTheta);
3836 | Theta[nLine] = (i - Radius[nLine] * maxTheta) * resTheta;
3837 | Radius[nLine] -= halfHoughWidth;
3838 | nLine++;
3839 | }
3840 | }
3841 |
3842 | if (houghMap)
3843 | {
3844 | free(houghMap);
3845 | }
3846 | if (cosLUT)
3847 | {
3848 | free(cosLUT);
3849 | }
3850 | if (sinLUT)
3851 | {
3852 | free(sinLUT);
3853 | }
3854 |
3855 | return nLine;
3856 | }
3857 |
3858 | void CPUImageDrawLine(unsigned char *canvas, int width, int height, int stride, int x1, int y1, int x2, int y2, unsigned char R, unsigned char G, unsigned char B)
3859 | {
3860 | int channels = stride / width;
3861 |
3862 | int xs, ys, xe, ye;
3863 | if (x1 == x2)
3864 | {
3865 | if (y1 < y2)
3866 | {
3867 | ys = y1;
3868 | ye = y2;
3869 | }
3870 | else
3871 | {
3872 | ys = y2;
3873 | ye = y1;
3874 | }
3875 | unsigned char *Line = canvas + x1 * channels;
3876 | for (int r = ys; r <= ye; r++)
3877 | {
3878 | unsigned char *curLine = Line + r * stride;
3879 |
3880 | curLine[0] = R;
3881 | curLine[1] = G;
3882 | curLine[2] = B;
3883 | }
3884 | return;
3885 | }
3886 |
3887 | float a = (float)(y2 - y1) / (x2 - x1);
3888 | int nHeight = height;
3889 |
3890 | if ((a > -1) && (a < 1))
3891 | {
3892 | if (x1 < x2)
3893 | {
3894 | xs = x1;
3895 | xe = x2;
3896 | ys = y1;
3897 | ye = y2;
3898 | }
3899 | else
3900 | {
3901 | xs = x2;
3902 | xe = x1;
3903 | ys = y2;
3904 | ye = y1;
3905 | }
3906 | for (int c = xs; c <= xe; c++)
3907 | {
3908 | unsigned char *Line = canvas + c * channels;
3909 | int r = (int)(a * (c - xs) + ys + 0.5f);
3910 | if (r < 0 || r >= nHeight)
3911 | continue;
3912 | unsigned char *curLine = Line + r * stride;
3913 | curLine[0] = R;
3914 | curLine[1] = G;
3915 | curLine[2] = B;
3916 | }
3917 | }
3918 | else
3919 | {
3920 | float invA = 1.0f / a;
3921 | if (y1 < y2)
3922 | {
3923 | ys = y1;
3924 | ye = y2;
3925 | xs = x1;
3926 | xe = x2;
3927 | }
3928 | else
3929 | {
3930 | ys = y2;
3931 | ye = y1;
3932 | xs = x2;
3933 | xe = x1;
3934 | }
3935 | for (int r = ys; r <= ye; r++)
3936 | {
3937 | int c = (int)(invA * (r - ys) + xs + 0.5f);
3938 | unsigned char *Line = canvas + c * channels;
3939 | if (r < 0 || r >= nHeight)
3940 | continue;
3941 | unsigned char *curLine = Line + r * stride;
3942 | curLine[0] = R;
3943 | curLine[1] = G;
3944 | curLine[2] = B;
3945 | }
3946 | }
3947 | }
3948 |
3949 | bool CPUImageGetImageSize(const char *file_path, int *width, int *height, int *file_size)
3950 | {
3951 | bool has_image_size = false;
3952 | *height = -1;
3953 | *width = -1;
3954 | *file_size = -1;
3955 | FILE *fp = fopen(file_path, "rb");
3956 | if (fp == NULL)
3957 | return has_image_size;
3958 | struct stat st;
3959 | char sigBuf[26];
3960 | if (fstat(fileno(fp), &st) < 0)
3961 | {
3962 | fclose(fp);
3963 | return has_image_size;
3964 | }
3965 | else
3966 | {
3967 | *file_size = (int)st.st_size;
3968 | }
3969 | if (fread(&sigBuf, 26, 1, fp) < 1)
3970 | {
3971 | fclose(fp);
3972 | return has_image_size;
3973 | }
3974 | const char *png_signature = "\211PNG\r\n\032\n";
3975 | const char *ihdr_signature = "IHDR";
3976 | const char *gif87_signature = "GIF87a";
3977 | const char *gif89_signature = "GIF89a";
3978 | const char *jpeg_signature = "\377\330";
3979 | const char *bmp_signature = "BM";
3980 | if ((*file_size >= 10) && (memcmp(sigBuf, gif87_signature, strlen(gif87_signature)) == 0 || memcmp(sigBuf, gif89_signature, strlen(gif89_signature)) == 0))
3981 | {
3982 | // image type: gif
3983 | unsigned short *size_info = (unsigned short *)(sigBuf + 6);
3984 | *width = size_info[0];
3985 | *height = size_info[1];
3986 | has_image_size = true;
3987 | }
3988 | else if ((*file_size >= 24) && (memcmp(sigBuf, png_signature, strlen(png_signature)) == 0 && memcmp(sigBuf + 12, ihdr_signature, strlen(ihdr_signature)) == 0))
3989 | {
3990 | // image type: png
3991 | unsigned long *size_info = (unsigned long *)(sigBuf + 16);
3992 | *width = (int)byteswap_ulong(size_info[0]);
3993 | *height = (int)byteswap_ulong(size_info[1]);
3994 | has_image_size = true;
3995 | }
3996 | else if ((*file_size >= 16) && (memcmp(sigBuf, png_signature, strlen(png_signature)) == 0))
3997 | {
3998 | // image type: old png
3999 | unsigned long *size_info = (unsigned long *)(sigBuf + 8);
4000 | *width = (int)byteswap_ulong(size_info[0]);
4001 | *height = (int)byteswap_ulong(size_info[1]);
4002 | has_image_size = true;
4003 | }
4004 | else if ((*file_size >= 2) && (memcmp(sigBuf, jpeg_signature, strlen(jpeg_signature)) == 0))
4005 | {
4006 | // image type: jpeg
4007 | printf("Jpeg");
4008 | fseek(fp, 0, SEEK_SET);
4009 | char b = 0;
4010 | fread(&sigBuf, 2, 1, fp);
4011 | fread(&b, 1, 1, fp);
4012 | int w = -1;
4013 | int h = -1;
4014 | while (b && ((unsigned char)b & 0xff) != 0xDA)
4015 | {
4016 | while (((unsigned char)b & 0xff) != 0xFF)
4017 | {
4018 | fread(&b, 1, 1, fp);
4019 | }
4020 | while (((unsigned char)b & 0xff) == 0xFF)
4021 | {
4022 | fread(&b, 1, 1, fp);
4023 | }
4024 | if (((unsigned char)b & 0xff) >= 0xC0 && ((unsigned char)b & 0xff) <= 0xC3)
4025 | {
4026 | fread(&sigBuf, 3, 1, fp);
4027 | fread(&sigBuf, 4, 1, fp);
4028 | unsigned short *size_info = (unsigned short *)(sigBuf);
4029 | h = byteswap_ushort(size_info[0]);
4030 | w = byteswap_ushort(size_info[1]);
4031 | break;
4032 | }
4033 | else
4034 | {
4035 | unsigned short chunk_size = 0;
4036 | fread(&chunk_size, 2, 1, fp);
4037 | if (fseek(fp, byteswap_ushort(chunk_size) - 2, SEEK_CUR) != 0)
4038 | break;
4039 | }
4040 | fread(&b, 1, 1, fp);
4041 | }
4042 | if (w != -1 && h != -1)
4043 | {
4044 | *width = w;
4045 | *height = h;
4046 | }
4047 | has_image_size = true;
4048 | }
4049 | else if ((*file_size >= 26) && (memcmp(sigBuf, bmp_signature, strlen(bmp_signature)) == 0))
4050 | {
4051 | // image type: bmp
4052 | unsigned int header_size = (unsigned int)(*(sigBuf + 14));
4053 | if (header_size == 12)
4054 | {
4055 | unsigned short *size_info = (unsigned short *)(sigBuf + 18);
4056 | *width = size_info[0];
4057 | *height = size_info[1];
4058 | }
4059 | else if (header_size >= 40)
4060 | {
4061 | unsigned int *size_info = (unsigned int *)(sigBuf + 18);
4062 | *width = size_info[0];
4063 | *height = Abs((size_info[1]));
4064 | }
4065 | has_image_size = true;
4066 | }
4067 | else if (*file_size >= 2)
4068 | {
4069 | // image type: ico
4070 | fseek(fp, 0, SEEK_SET);
4071 | unsigned short format = 0;
4072 | unsigned short reserved = 0;
4073 | fread(&reserved, 2, 1, fp);
4074 | fread(&format, 2, 1, fp);
4075 | if (reserved == 0 && format == 1)
4076 | {
4077 | unsigned short num = 0;
4078 | fread(&num, 2, 1, fp);
4079 | if (num > 1)
4080 | {
4081 | printf("this is a muti-ico file.");
4082 | }
4083 | else
4084 | {
4085 | char w = 0, h = 0;
4086 | fread(&w, 1, 1, fp);
4087 | fread(&h, 1, 1, fp);
4088 | *width = (int)((unsigned char)w & 0xff);
4089 | *height = (int)((unsigned char)h & 0xff);
4090 | }
4091 | }
4092 | has_image_size = true;
4093 | }
4094 | if (fp != NULL)
4095 | fclose(fp);
4096 | return has_image_size;
4097 | }
4098 | #ifdef __cplusplus
4099 | }
4100 | #endif
--------------------------------------------------------------------------------
/cpuimage.h:
--------------------------------------------------------------------------------
1 | #ifndef _CPUIMAGE_HEADER_
2 | #define _CPUIMAGE_HEADER_
3 |
4 | #ifdef __cplusplus
5 | extern "C" {
6 | #endif
7 |
8 | #if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/
9 | #pragma warning(disable : 4996) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
10 | #endif /*_MSC_VER */
11 |
12 | #ifndef clamp
13 | #define clamp(value, min, max) ((value) > (max) ? (max) : (value) < (min) ? (min) : (value))
14 | #endif
15 | #ifndef ClampToByte
16 | #define ClampToByte(v) (unsigned char)(((unsigned)(int)(v)) < (255) ? (v) : (v < 0) ? (0) : (255))
17 | #endif
18 | #ifndef min
19 | #define min(a, b) (((a) < (b)) ? (a) : (b))
20 | #endif
21 | #ifndef min3
22 | #define min3(a, b, c) min(min((a), (b)), (c))
23 | #endif
24 | #ifndef max
25 | #define max(a, b) (((a) > (b)) ? (a) : (b))
26 | #endif
27 | #ifndef max3
28 | #define max3(a, b, c) max(max((a), (b)), (c))
29 | #endif
30 |
31 | #ifndef M_PI
32 | #define M_PI 3.14159265358979323846f
33 | #endif
34 |
35 | #include
36 | #include
37 | #include
38 | #include
39 | #include "fastmath.h"
40 |
41 | typedef struct
42 | {
43 | int levelMinimum;
44 | int levelMiddle;
45 | int levelMaximum;
46 | int minOutput;
47 | int maxOutput;
48 | bool Enable;
49 | } cpuLevelParams;
50 |
51 | void rgb2yiq(unsigned char *R, unsigned char *G, unsigned char *B, short *Y, short *I, short *Q);
52 |
53 | void yiq2rgb(short *Y, short *I, short *Q, unsigned char *R, unsigned char *G, unsigned char *B);
54 |
55 | void rgb2hsv(const unsigned char *R, const unsigned char *G, const unsigned char *B, unsigned char *H, unsigned char *S,
56 | unsigned char *V);
57 |
58 | void hsv2rgb(const unsigned char *H, const unsigned char *S, const unsigned char *V, unsigned char *R, unsigned char *G,
59 | unsigned char *B);
60 |
61 | void rgb2ycbcr(unsigned char R, unsigned char G, unsigned char B, unsigned char *y, unsigned char *cb, unsigned char *cr);
62 |
63 | void ycbcr2rgb(unsigned char y, unsigned char Cb, unsigned char Cr, unsigned char *R, unsigned char *G, unsigned char *B);
64 |
65 | //--------------------------Color adjustments--------------------------
66 | void CPUImageGrayscaleFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride);
67 |
68 | // float redAdjustment = 1.0f, float greenAdjustment = 1.0f, float blueAdjustment = 1.0f
69 | void CPUImageRGBFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float redAdjustment,
70 | float greenAdjustment, float blueAdjustment);
71 |
72 | // float thresholdMultiplier = 1.0f
73 | void CPUImageAverageLuminanceThresholdFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
74 | float thresholdMultiplier);
75 |
76 | void CPUImageAverageColor(unsigned char *Input, int Width, int Height, int Stride, unsigned char *AverageR,
77 | unsigned char *AverageG, unsigned char *AverageB, unsigned char *AverageA);
78 |
79 | void CPUImageLuminosity(unsigned char *Input, int Width, int Height, int Stride, unsigned char *Luminance);
80 |
81 | // float intensity = 1.0f
82 | void CPUImageColorMatrixFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
83 | float *colorMatrix, float intensity);
84 |
85 | //int intensity = 100
86 | void CPUImageSepiaFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int intensity);
87 |
88 | // unsigned char colorToReplaceR = 0, unsigned char colorToReplaceG = 160, unsigned char colorToReplaceB = 0, float thresholdSensitivity = 0.2f, float smoothing = 0.1f
89 | void CPUImageChromaKeyFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
90 | unsigned char colorToReplaceR, unsigned char colorToReplaceG,
91 | unsigned char colorToReplaceB, float thresholdSensitivity, float smoothing);
92 |
93 | // int intensity = 100
94 | void CPUImageLookupFilter(unsigned char *Input, unsigned char *Output, unsigned char *lookupTable, int Width, int Height,
95 | int Stride, int intensity);
96 |
97 | // float saturation = 1.0
98 | void CPUImageSaturationFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
99 | float saturation);
100 |
101 | // float gamma = 1.0f
102 | void CPUImageGammaFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float gamma);
103 |
104 | // float contrast = 1.0f
105 | void CPUImageContrastFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float contrast);
106 |
107 | //float exposure = 0.0f
108 | void CPUImageExposureFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float exposure);
109 |
110 | //int brightness = 0.0f
111 | void CPUImageBrightnessFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
112 | int brightness);
113 |
114 | //unsigned char firstColorR = 0, unsigned char firstColorG = 0, unsigned char firstColorB = 0.5 * 255, unsigned char secondColorR = 1.0f * 255, unsigned char secondColorG = 0, unsigned char secondColorB = 0, int intensity = 100
115 | void CPUImageFalseColorFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
116 | unsigned char firstColorR, unsigned char firstColorG, unsigned char firstColorB,
117 | unsigned char secondColorR, unsigned char secondColorG, unsigned char secondColorB,
118 | int intensity);
119 |
120 | // float distance = 0.3, float slope = 0, int intensity = 100
121 | void CPUImageHazeFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float distance,
122 | float slope, int intensity);
123 |
124 | // float opacity = 1.0f
125 | void CPUImageOpacityFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float opacity);
126 |
127 | void CPUImageLevelsFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
128 | cpuLevelParams *redLevelParams, cpuLevelParams *greenLevelParams,
129 | cpuLevelParams *blueLevelParams);
130 |
131 | // float hueAdjust = 90.0f
132 | void CPUImageHueFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float hueAdjust);
133 |
134 | // float shadowTintR = 1.0f, float shadowTintG = 0.0f, float shadowTintB = 0.0f, float highlightTintR = 0.0f, float highlightTintG = 0.0f, float highlightTintB = 1.0f, float shadowTintIntensity = 0.0f, float highlightTintIntensity = 0.0f
135 | void CPUImageHighlightShadowTintFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
136 | float shadowTintR, float shadowTintG, float shadowTintB, float highlightTintR,
137 | float highlightTintG, float highlightTintB, float shadowTintIntensity,
138 | float highlightTintIntensity);
139 |
140 | // float shadows = 0.0f, float highlights = 1.0f
141 | void CPUImageHighlightShadowFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
142 | float shadows, float highlights);
143 |
144 | // unsigned char filterColorR = 0.6 * 255, unsigned char filterColorG = 0.45 * 255, unsigned char filterColorB = 0.3 * 255, int intensity = 100
145 | void CPUImageMonochromeFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
146 | unsigned char filterColorR, unsigned char filterColorG, unsigned char filterColorB,
147 | int intensity);
148 |
149 | void CPUImageColorInvertFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride);
150 |
151 | // unsigned char colorAlpha = 255
152 | void CPUImageSolidColorGenerator(unsigned char *Output, int Width, int Height, int Stride, unsigned char colorR,
153 | unsigned char colorG, unsigned char colorB, unsigned char colorAlpha);
154 |
155 | // unsigned char threshold = 127
156 | void CPUImageLuminanceThresholdFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
157 | unsigned char threshold);
158 |
159 | // float temperature = 5000, float tint = 0
160 | void CPUImageWhiteBalanceFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
161 | float temperature, float tint);
162 |
163 | //float vibrance = 1.2
164 | void CPUImageVibranceFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float vibrance);
165 |
166 | // float skinToneAdjust = 0.3f, float skinHue = 0.05f, float skinHueThreshold = 80.0f, float maxHueShift = 0.25f, float maxSaturationShift = 0.4f, int upperSkinToneColor = 0
167 | void CPUImageSkinToneFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
168 | float skinToneAdjust, float skinHue, float skinHueThreshold, float maxHueShift,
169 | float maxSaturationShift, int upperSkinToneColor);
170 |
171 | //float fraction = 0.05f
172 | void CPUImageAutoLevel(const unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float fraction);
173 |
174 | //--------------------------Color adjustments--------------------------
175 |
176 | //--------------------------Image processing--------------------------
177 | void CPUImageGaussianBlurFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
178 | float GaussianSigma);
179 |
180 | // float GaussianSigma = 4, int intensity = 100
181 | void CPUImageUnsharpMaskFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride,
182 | float GaussianSigma, int intensity);
183 |
184 | //int Radius = 3
185 | void CPUImageBoxBlurFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, int Radius);
186 |
187 | // float Radius = 4, int sharpness = 1, int intensity = 100
188 | void CPUImageSharpenFilter(unsigned char *Input, unsigned char *Output, int Width, int Height, int Stride, float Radius,
189 | int sharpness, int intensity);
190 |
191 | void CPUImageResamplingFilter(unsigned char *Input, unsigned int Width, unsigned int Height, unsigned int Stride,
192 | unsigned char *Output, int newWidth, int newHeight, int dstStride);
193 |
194 | void CPUImageCropFilter(const unsigned char *Input, int Width, int Height, int srcStride, unsigned char *Output, int cropX,
195 | int cropY, int dstWidth, int dstHeight, int dstStride);
196 |
197 | void CPUImageSobelEdge(unsigned char *Input, unsigned char *Output, int Width, int Height);
198 |
199 | int CPUImageHoughLines(unsigned char *Input, int Width, int Height, int lineIntensity, int Threshold, float resTheta,
200 | int numLine, float *Radius, float *Theta);
201 |
202 | void CPUImageDrawLine(unsigned char *canvas, int width, int height, int stride, int x1, int y1, int x2, int y2,
203 | unsigned char R, unsigned char G, unsigned char B);
204 | //--------------------------Image processing--------------------------
205 |
206 | //--------------------------preImage processing--------------------------
207 | //ref:https://github.com/scardine/image_size
208 | bool CPUImageGetImageSize(const char *file_path, int *width, int *height, int *file_size);
209 | //--------------------------preImage processing--------------------------
210 |
211 | #ifdef __cplusplus
212 | }
213 | #endif
214 | #endif
--------------------------------------------------------------------------------
/demo:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cpuimage/cpuimagelib/e8da89e1d9a28b4110d0fdbed14159947c6ad9dd/demo
--------------------------------------------------------------------------------
/demo.c:
--------------------------------------------------------------------------------
1 | //如果是Windows的话,调用系统API ShellExecuteA打开图片
2 | #if defined(_MSC_VER)
3 | #define _CRT_SECURE_NO_WARNINGS
4 | #include
5 | #define access _access
6 | #else
7 | #include
8 | #endif
9 | #include "browse.h"
10 | #define USE_SHELL_OPEN
11 | #include "cpuimage.h"
12 | #define STB_IMAGE_STATIC
13 | #define STB_IMAGE_IMPLEMENTATION
14 | #include "stb_image.h"
15 | //ref:https://github.com/nothings/stb/blob/master/stb_image.h
16 | #define TJE_IMPLEMENTATION
17 | #include "tiny_jpeg.h"
18 | //ref:https://github.com/serge-rgb/TinyJPEG/blob/master/tiny_jpeg.h
19 | #include
20 | #include
21 | #include
22 | #include
23 | //计时
24 | #include
25 | #if defined(__APPLE__)
26 | #include
27 | #elif defined(_WIN32)
28 | #define WIN32_LEAN_AND_MEAN
29 | #include
30 | #else // __linux
31 | #include
32 | #ifndef CLOCK_MONOTONIC //_RAW
33 | #define CLOCK_MONOTONIC CLOCK_REALTIME
34 | #endif
35 | #endif
36 | static uint64_t nanotimer()
37 | {
38 | static int ever = 0;
39 | #if defined(__APPLE__)
40 | static mach_timebase_info_data_t frequency;
41 | if (!ever)
42 | {
43 | if (mach_timebase_info(&frequency) != KERN_SUCCESS)
44 | {
45 | return 0;
46 | }
47 | ever = 1;
48 | }
49 | return mach_absolute_time() * frequency.numer / frequency.denom;
50 | #elif defined(_WIN32)
51 | static LARGE_INTEGER frequency;
52 | if (!ever)
53 | {
54 | QueryPerformanceFrequency(&frequency);
55 | ever = 1;
56 | }
57 | LARGE_INTEGER t;
58 | QueryPerformanceCounter(&t);
59 | return (t.QuadPart * (uint64_t)1e9) / frequency.QuadPart;
60 | #else // __linux
61 | struct timespec t;
62 | if (!ever)
63 | {
64 | if (clock_gettime(CLOCK_MONOTONIC, &spec) != 0)
65 | {
66 | return 0;
67 | }
68 | ever = 1;
69 | }
70 | clock_gettime(CLOCK_MONOTONIC, &spec);
71 | return (t.tv_sec * (uint64_t)1e9) + t.tv_nsec;
72 | #endif
73 | }
74 |
75 | static double now()
76 | {
77 | static uint64_t epoch = 0;
78 | if (!epoch)
79 | {
80 | epoch = nanotimer();
81 | }
82 | return (nanotimer() - epoch) / 1e9;
83 | };
84 |
85 | double calcElapsed(double start, double end)
86 | {
87 | double took = -start;
88 | return took + end;
89 | }
90 | #ifndef _MAX_DRIVE
91 | #define _MAX_DRIVE 3
92 | #endif
93 | #ifndef _MAX_FNAME
94 | #define _MAX_FNAME 256
95 | #endif
96 | #ifndef _MAX_EXT
97 | #define _MAX_EXT 256
98 | #endif
99 | #ifndef _MAX_DIR
100 | #define _MAX_DIR 256
101 | #endif
102 | //存储当前传入文件位置的变量
103 | char saveFile[1024];
104 | //加载图片
105 | unsigned char *loadImage(const char *filename, int *Width, int *Height, int *Channels)
106 | {
107 |
108 | return stbi_load(filename, Width, Height, Channels, 0);
109 | }
110 | //保存图片
111 | void saveImage(const char *filename, int Width, int Height, int Channels, unsigned char *Output)
112 | {
113 |
114 | memcpy(saveFile + strlen(saveFile), filename, strlen(filename));
115 | *(saveFile + strlen(saveFile) + 1) = 0;
116 | //保存为jpg
117 | if (!tje_encode_to_file(saveFile, Width, Height, Channels, true, Output))
118 | {
119 | fprintf(stderr, "写入 JPEG 文件失败.\n");
120 | return;
121 | }
122 |
123 | #ifdef USE_SHELL_OPEN
124 | browse(saveFile);
125 | #endif
126 | }
127 |
128 | //分割路径函数
129 | void splitpath(const char *path, char *drv, char *dir, char *name, char *ext)
130 | {
131 | const char *end;
132 | const char *p;
133 | const char *s;
134 | if (path[0] && path[1] == ':')
135 | {
136 | if (drv)
137 | {
138 | *drv++ = *path++;
139 | *drv++ = *path++;
140 | *drv = '\0';
141 | }
142 | }
143 | else if (drv)
144 | *drv = '\0';
145 | for (end = path; *end && *end != ':';)
146 | end++;
147 | for (p = end; p > path && *--p != '\\' && *p != '/';)
148 | if (*p == '.')
149 | {
150 | end = p;
151 | break;
152 | }
153 | if (ext)
154 | for (s = end; (*ext = *s++);)
155 | ext++;
156 | for (p = end; p > path;)
157 | if (*--p == '\\' || *p == '/')
158 | {
159 | p++;
160 | break;
161 | }
162 | if (name)
163 | {
164 | for (s = p; s < end;)
165 | *name++ = *s++;
166 | *name = '\0';
167 | }
168 | if (dir)
169 | {
170 | for (s = path; s < p;)
171 | *dir++ = *s++;
172 | *dir = '\0';
173 | }
174 | }
175 |
176 | //取当前传入的文件位置
177 | void getCurrentFilePath(const char *filePath, char *saveFile)
178 | {
179 | char drive[_MAX_DRIVE];
180 | char dir[_MAX_DIR];
181 | char fname[_MAX_FNAME];
182 | char ext[_MAX_EXT];
183 | splitpath(filePath, drive, dir, fname, ext);
184 | size_t n = strlen(filePath);
185 | memcpy(saveFile, filePath, n);
186 | char *cur_saveFile = saveFile + (n - strlen(ext));
187 | cur_saveFile[0] = '_';
188 | cur_saveFile[1] = 0;
189 | }
190 |
191 | int main(int argc, char **argv)
192 | {
193 | printf("Image Processing \n ");
194 | printf("博客:http://cpuimage.cnblogs.com/ \n ");
195 | printf("支持解析如下图片格式: \n ");
196 | printf("JPG, PNG, TGA, BMP, PSD, GIF, HDR, PIC \n ");
197 |
198 | //检查参数是否正确
199 | if (argc < 2)
200 | {
201 | printf("参数错误。 \n ");
202 | printf("请拖放文件到可执行文件上,或使用命令行:demo.exe 图片 \n ");
203 | printf("请拖放文件例如: demo.exe d:\\image.jpg \n ");
204 |
205 | return 0;
206 | }
207 |
208 | char *szfile = argv[1];
209 | //检查输入的文件是否存在
210 | if (access(szfile, 0) == -1)
211 | {
212 | printf("输入的文件不存在,参数错误! \n ");
213 | }
214 |
215 | getCurrentFilePath(szfile, saveFile);
216 |
217 | int Width = 0; //图片宽度
218 | int Height = 0; //图片高度
219 | int Channels = 0; //图片通道数
220 | unsigned char *inputImage = NULL; //输入图片指针
221 |
222 | int filesize = 0;
223 | CPUImageGetImageSize(szfile, &Width, &Height, &filesize);
224 | printf("file:%s\nfilesize:%d\nwidth:%d\nheight:%d\n", szfile, filesize, Width, Height);
225 |
226 | double startTime = now();
227 | //加载图片
228 | inputImage = loadImage(szfile, &Width, &Height, &Channels);
229 |
230 | double nLoadTime = calcElapsed(startTime, now());
231 | printf("加载耗时: %d 毫秒!\n ", (int)(nLoadTime * 1000));
232 | if ((Channels != 0) && (Width != 0) && (Height != 0))
233 | {
234 | //分配与载入同等内存用于处理后输出结果
235 | unsigned char *outputImg = (unsigned char *)stbi__malloc(Width * Channels * Height * sizeof(unsigned char));
236 | if (inputImage)
237 | {
238 | //如果图片加载成功,则将内容复制给输出内存,方便处理
239 | memcpy(outputImg, inputImage, Width * Channels * Height);
240 | }
241 | else
242 | {
243 | printf("加载文件: %s 失败!\n ", szfile);
244 | }
245 | startTime = now();
246 | float arrRho[100];
247 | float arrTheta[100];
248 | int nTNum = 200;
249 | int nTVal = 100;
250 | float Theta = 1.0f;
251 | CPUImageGrayscaleFilter(inputImage, outputImg, Width, Height, Width * Channels);
252 | CPUImageSobelEdge(outputImg, outputImg, Width, Height);
253 | int nLine = CPUImageHoughLines(outputImg, Width, Height, nTNum, nTVal, Theta, 100, arrRho, arrTheta);
254 | memcpy(outputImg, inputImage, Width * Channels * Height);
255 | for (int i = 0; i < nLine; i++)
256 | {
257 | if (arrTheta[i] == 90)
258 | {
259 | CPUImageDrawLine(outputImg, Width, Height, Width * Channels, (int)arrRho[i], 0, (int)arrRho[i], Height - 1, 255, 0, 0);
260 | }
261 | else
262 | {
263 | int x1 = 0;
264 | int y1 = (int)(arrRho[i] / fastCos(arrTheta[i] * M_PI / 180.0f) + 0.5f);
265 | int x2 = Width - 1;
266 | int y2 = (int)((arrRho[i] - x2 * fastSin(arrTheta[i] * M_PI / 180.0f)) / fastCos(arrTheta[i] * M_PI / 180.0f) + 0.5f);
267 | CPUImageDrawLine(outputImg, Width, Height, Width * Channels, x1, y1, x2, y2, 255, 0, 0);
268 | }
269 | }
270 | //处理算法
271 | double nProcessTime = now();
272 | printf("处理耗时: %d 毫秒!\n ", (int)(nProcessTime * 1000));
273 | //保存处理后的图片
274 | startTime = now();
275 |
276 | saveImage("_done.jpg", Width, Height, Channels, outputImg);
277 | double nSaveTime = calcElapsed(startTime, now());
278 |
279 | printf("保存耗时: %d 毫秒!\n ", (int)(nSaveTime * 1000));
280 | //释放占用的内存
281 | if (outputImg)
282 | {
283 | stbi_image_free(outputImg);
284 | outputImg = NULL;
285 | }
286 |
287 | if (inputImage)
288 | {
289 | stbi_image_free(inputImage);
290 | inputImage = NULL;
291 | }
292 | }
293 | else
294 | {
295 | printf("加载文件: %s 失败!\n", szfile);
296 | }
297 |
298 | getchar();
299 | printf("按任意键退出程序 \n");
300 |
301 | return EXIT_SUCCESS;
302 | }
--------------------------------------------------------------------------------
/fastmath.h:
--------------------------------------------------------------------------------
1 | #ifndef _CPUIMAGE_FASTMATH_HEADER_
2 | #define _CPUIMAGE_FASTMATH_HEADER_
3 | #include
4 | #include
5 |
6 | static inline float fastSin(
7 | float x) {
8 | float y;
9 | x *= -0.318309886f;
10 | y = x + 25165824.0f;
11 | x -= y - 25165824.0f;
12 | x *= (x < 0 ? -x : x) - 1;
13 | return x * (3.5841304553896f * (x < 0 ? -x : x) + 3.1039673861526f);
14 | }
15 |
16 | static inline float fastCos(
17 | float x)
18 | {
19 | return fastSin(x + 1.570796327f);
20 | }
21 | #endif
22 |
--------------------------------------------------------------------------------
/tiny_jpeg.h:
--------------------------------------------------------------------------------
1 | /**
2 | * tiny_jpeg.h
3 | *
4 | * Tiny JPEG Encoder
5 | * - Sergio Gonzalez
6 | *
7 | * This is a readable and simple single-header JPEG encoder.
8 | *
9 | * Features
10 | * - Implements Baseline DCT JPEG compression.
11 | * - No dynamic allocations.
12 | *
13 | * This library is coded in the spirit of the stb libraries and mostly follows
14 | * the stb guidelines.
15 | *
16 | * It is written in C99. And depends on the C standard library.
17 | * Works with C++11
18 | *
19 | *
20 | * ==== Thanks ====
21 | *
22 | * AssociationSirius (Bug reports)
23 | * Bernard van Gastel (Thread-safe defaults, BSD compilation)
24 | *
25 | *
26 | * ==== License ====
27 | *
28 | * This software is in the public domain. Where that dedication is not
29 | * recognized, you are granted a perpetual, irrevocable license to copy and
30 | * modify this file as you see fit.
31 | *
32 | */
33 |
34 | // ============================================================
35 | // Usage
36 | // ============================================================
37 | // Include "tiny_jpeg.h" to and use the public interface defined below.
38 | //
39 | // You *must* do:
40 | //
41 | // #define TJE_IMPLEMENTATION
42 | // #include "tiny_jpeg.h"
43 | //
44 | // in exactly one of your C files to actually compile the implementation.
45 |
46 |
47 | // Here is an example program that loads a bmp with stb_image and writes it
48 | // with Tiny JPEG
49 |
50 | /*
51 |
52 | #define STB_IMAGE_IMPLEMENTATION
53 | #include "stb_image.h"
54 |
55 |
56 | #define TJE_IMPLEMENTATION
57 | #include "tiny_jpeg.h"
58 |
59 |
60 | int main()
61 | {
62 | int width, height, num_components;
63 | unsigned char* data = stbi_load("in.bmp", &width, &height, &num_components, 0);
64 | if ( !data ) {
65 | puts("Could not find file");
66 | return EXIT_FAILURE;
67 | }
68 | bool rgb=true;
69 | if ( !tje_encode_to_file("out.jpg", width, height, num_components,rgb,data) ) {
70 | fprintf(stderr, "Could not write JPEG\n");
71 | return EXIT_FAILURE;
72 | }
73 |
74 | return EXIT_SUCCESS;
75 | }
76 |
77 | */
78 | #if defined(_MSC_VER) && (_MSC_VER >= 1310) /*Visual Studio: A few warning types are not desired here.*/
79 | #pragma warning( disable : 4996 ) /*VS does not like fopen, but fopen_s is not standard C so unusable here*/
80 | #endif /*_MSC_VER */
81 |
82 | #ifdef __cplusplus
83 | extern "C"
84 | {
85 | #endif
86 |
87 | #if defined(__GNUC__) || defined(__clang__)
88 | #pragma GCC diagnostic push
89 | #pragma GCC diagnostic ignored "-Wmissing-field-initializers" // We use {0}, which will zero-out the struct.
90 | #pragma GCC diagnostic ignored "-Wmissing-braces"
91 | #pragma GCC diagnostic ignored "-Wpadded"
92 | #endif
93 |
94 | // ============================================================
95 | // Public interface:
96 | // ============================================================
97 |
98 | #ifndef TJE_HEADER_GUARD
99 | #define TJE_HEADER_GUARD
100 | #include
101 | #ifndef ENABLE_FOPEN
102 | #define ENABLE_FOPEN 1
103 | #endif
104 |
105 | // - tje_encode_to_file -
106 | //
107 | // Usage:
108 | // Takes bitmap data and writes a JPEG-encoded image to disk.
109 | //
110 | // PARAMETERS
111 | // dest_path: filename to which we will write. e.g. "out.jpg"
112 | // width, height: image size in pixels
113 | // num_components: 3 is RGB. 4 is RGBA. Those are the only supported values
114 | // src_data: pointer to the pixel data.
115 | //
116 | // RETURN:
117 | // 0 on error. 1 on success.
118 | #if ENABLE_FOPEN==1
119 | int tje_encode_to_file(const char* dest_path,
120 | const int width,
121 | const int height,
122 | const int num_components,
123 | bool Rgb,
124 | const unsigned char* src_data);
125 | #endif
126 |
127 | // - tje_encode_to_file_at_quality -
128 | //
129 | // Usage:
130 | // Takes bitmap data and writes a JPEG-encoded image to disk.
131 | //
132 | // PARAMETERS
133 | // dest_path: filename to which we will write. e.g. "out.jpg"
134 | // quality: 3: Highest. Compression varies wildly (between 1/3 and 1/20).
135 | // 2: Very good quality. About 1/2 the size of 3.
136 | // 1: Noticeable. About 1/6 the size of 3, or 1/3 the size of 2.
137 | // width, height: image size in pixels
138 | // num_components: 3 is RGB. 4 is RGBA. Those are the only supported values
139 | // src_data: pointer to the pixel data.
140 | //
141 | // RETURN:
142 | // 0 on error. 1 on success.
143 | #if ENABLE_FOPEN==1
144 | int tje_encode_to_file_at_quality(const char* dest_path,
145 | const int quality,
146 | const int width,
147 | const int height,
148 | const int num_components,
149 | bool Rgb,
150 | const unsigned char* src_data);
151 | #endif
152 | // - tje_encode_with_func -
153 | //
154 | // Usage
155 | // Same as tje_encode_to_file_at_quality, but it takes a callback that knows
156 | // how to handle (or ignore) `context`. The callback receives an array `data`
157 | // of `size` bytes, which can be written directly to a file. There is no need
158 | // to free the data.
159 |
160 | typedef void tje_write_func(void* context, void* data, int size);
161 |
162 | int tje_encode_with_func(tje_write_func* func,
163 | void* context,
164 | const int quality,
165 | const int width,
166 | const int height,
167 | const int num_components,
168 | bool Rgb,
169 | const unsigned char* src_data);
170 |
171 | #endif // TJE_HEADER_GUARD
172 |
173 |
174 |
175 | // Implementation: In exactly one of the source files of your application,
176 | // define TJE_IMPLEMENTATION and include tiny_jpeg.h
177 |
178 | // ============================================================
179 | // Internal
180 | // ============================================================
181 | #ifdef TJE_IMPLEMENTATION
182 |
183 |
184 | #define tjei_min(a, b) ((a) < b) ? (a) : (b)
185 | #define tjei_max(a, b) ((a) < b) ? (b) : (a)
186 |
187 |
188 | #if defined(_MSC_VER)
189 | #define TJEI_FORCE_INLINE __forceinline
190 | // #define TJEI_FORCE_INLINE __declspec(noinline) // For profiling
191 | #else
192 | #define TJEI_FORCE_INLINE static // TODO: equivalent for gcc & clang
193 | #endif
194 |
195 | // Only use zero for debugging and/or inspection.
196 | #define TJE_USE_FAST_DCT 1
197 |
198 | // C std lib
199 | #include
200 | #include
201 | #include // floorf, ceilf
202 | #include // FILE, puts
203 | #include // memcpy
204 |
205 |
206 | #define TJEI_BUFFER_SIZE 1024
207 |
208 | #ifdef _WIN32
209 |
210 | #include
211 | #ifndef snprintf
212 | #define snprintf sprintf_s
213 | #endif
214 | // Not quite the same but it works for us. If I am not mistaken, it differs
215 | // only in the return value.
216 |
217 | #endif
218 |
219 | #ifndef NDEBUG
220 |
221 | #ifdef _WIN32
222 | #define tje_log(msg) OutputDebugStringA(msg)
223 | #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
224 | #define tje_log(msg) puts(msg)
225 | #else
226 | #warning "need a tje_log definition for your platform for debugging purposes (not needed if compiling with NDEBUG)"
227 | #endif
228 |
229 | #else // NDEBUG
230 | #define tje_log(msg)
231 | #endif // NDEBUG
232 |
233 |
234 | typedef struct
235 | {
236 | void* context;
237 | tje_write_func* func;
238 | } TJEWriteContext;
239 |
240 | typedef struct
241 | {
242 | // Huffman data.
243 | uint8_t ehuffsize[4][257];
244 | uint16_t ehuffcode[4][256];
245 | uint8_t const * ht_bits[4];
246 | uint8_t const * ht_vals[4];
247 |
248 | // Cuantization tables.
249 | uint8_t qt_luma[64];
250 | uint8_t qt_chroma[64];
251 |
252 | // fwrite by default. User-defined when using tje_encode_with_func.
253 | TJEWriteContext write_context;
254 |
255 | // Buffered output. Big performance win when using the usual stdlib implementations.
256 | size_t output_buffer_count;
257 | uint8_t output_buffer[TJEI_BUFFER_SIZE];
258 | } TJEState;
259 |
260 | // ============================================================
261 | // Table definitions.
262 | //
263 | // The spec defines tjei_default reasonably good quantization matrices and huffman
264 | // specification tables.
265 | //
266 | //
267 | // Instead of hard-coding the final huffman table, we only hard-code the table
268 | // spec suggested by the specification, and then derive the full table from
269 | // there. This is only for didactic purposes but it might be useful if there
270 | // ever is the case that we need to swap huffman tables from various sources.
271 | // ============================================================
272 |
273 |
274 | // K.1 - suggested luminance QT
275 | static const uint8_t tjei_default_qt_luma_from_spec[] =
276 | {
277 | 16,11,10,16, 24, 40, 51, 61,
278 | 12,12,14,19, 26, 58, 60, 55,
279 | 14,13,16,24, 40, 57, 69, 56,
280 | 14,17,22,29, 51, 87, 80, 62,
281 | 18,22,37,56, 68,109,103, 77,
282 | 24,35,55,64, 81,104,113, 92,
283 | 49,64,78,87,103,121,120,101,
284 | 72,92,95,98,112,100,103, 99,
285 | };
286 |
287 | // Unused
288 | #if 0
289 | static const uint8_t tjei_default_qt_chroma_from_spec[] =
290 | {
291 | // K.1 - suggested chrominance QT
292 | 17,18,24,47,99,99,99,99,
293 | 18,21,26,66,99,99,99,99,
294 | 24,26,56,99,99,99,99,99,
295 | 47,66,99,99,99,99,99,99,
296 | 99,99,99,99,99,99,99,99,
297 | 99,99,99,99,99,99,99,99,
298 | 99,99,99,99,99,99,99,99,
299 | 99,99,99,99,99,99,99,99,
300 | };
301 | #endif
302 |
303 | static const uint8_t tjei_default_qt_chroma_from_paper[] =
304 | {
305 | // Example QT from JPEG paper
306 | 16, 12, 14, 14, 18, 24, 49, 72,
307 | 11, 10, 16, 24, 40, 51, 61, 12,
308 | 13, 17, 22, 35, 64, 92, 14, 16,
309 | 22, 37, 55, 78, 95, 19, 24, 29,
310 | 56, 64, 87, 98, 26, 40, 51, 68,
311 | 81, 103, 112, 58, 57, 87, 109, 104,
312 | 121,100, 60, 69, 80, 103, 113, 120,
313 | 103, 55, 56, 62, 77, 92, 101, 99,
314 | };
315 |
316 | // == Procedure to 'deflate' the huffman tree: JPEG spec, C.2
317 |
318 | // Number of 16 bit values for every code length. (K.3.3.1)
319 | static const uint8_t tjei_default_ht_luma_dc_len[16] =
320 | {
321 | 0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0
322 | };
323 | // values
324 | static const uint8_t tjei_default_ht_luma_dc[12] =
325 | {
326 | 0,1,2,3,4,5,6,7,8,9,10,11
327 | };
328 |
329 | // Number of 16 bit values for every code length. (K.3.3.1)
330 | static const uint8_t tjei_default_ht_chroma_dc_len[16] =
331 | {
332 | 0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0
333 | };
334 | // values
335 | static const uint8_t tjei_default_ht_chroma_dc[12] =
336 | {
337 | 0,1,2,3,4,5,6,7,8,9,10,11
338 | };
339 |
340 | // Same as above, but AC coefficients.
341 | static const uint8_t tjei_default_ht_luma_ac_len[16] =
342 | {
343 | 0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d
344 | };
345 | static const uint8_t tjei_default_ht_luma_ac[] =
346 | {
347 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
348 | 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xA1, 0x08, 0x23, 0x42, 0xB1, 0xC1, 0x15, 0x52, 0xD1, 0xF0,
349 | 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0A, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x25, 0x26, 0x27, 0x28,
350 | 0x29, 0x2A, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
351 | 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
352 | 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
353 | 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7,
354 | 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3, 0xC4, 0xC5,
355 | 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xE1, 0xE2,
356 | 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
357 | 0xF9, 0xFA
358 | };
359 |
360 | static const uint8_t tjei_default_ht_chroma_ac_len[16] =
361 | {
362 | 0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77
363 | };
364 | static const uint8_t tjei_default_ht_chroma_ac[] =
365 | {
366 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
367 | 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xA1, 0xB1, 0xC1, 0x09, 0x23, 0x33, 0x52, 0xF0,
368 | 0x15, 0x62, 0x72, 0xD1, 0x0A, 0x16, 0x24, 0x34, 0xE1, 0x25, 0xF1, 0x17, 0x18, 0x19, 0x1A, 0x26,
369 | 0x27, 0x28, 0x29, 0x2A, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
370 | 0x49, 0x4A, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
371 | 0x69, 0x6A, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
372 | 0x88, 0x89, 0x8A, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0xA2, 0xA3, 0xA4, 0xA5,
373 | 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xC2, 0xC3,
374 | 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA,
375 | 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8,
376 | 0xF9, 0xFA
377 | };
378 |
379 |
380 | // ============================================================
381 | // Code
382 | // ============================================================
383 |
384 | // Zig-zag order:
385 | static const uint8_t tjei_zig_zag[64] =
386 | {
387 | 0, 1, 5, 6, 14, 15, 27, 28,
388 | 2, 4, 7, 13, 16, 26, 29, 42,
389 | 3, 8, 12, 17, 25, 30, 41, 43,
390 | 9, 11, 18, 24, 31, 40, 44, 53,
391 | 10, 19, 23, 32, 39, 45, 52, 54,
392 | 20, 22, 33, 38, 46, 51, 55, 60,
393 | 21, 34, 37, 47, 50, 56, 59, 61,
394 | 35, 36, 48, 49, 57, 58, 62, 63,
395 | };
396 |
397 | // Memory order as big endian. 0xhilo -> 0xlohi which looks as 0xhilo in memory.
398 | static uint16_t tjei_be_word(const uint16_t le_word)
399 | {
400 | uint16_t lo = (le_word & 0x00ff);
401 | uint16_t hi = ((le_word & 0xff00) >> 8);
402 | return (uint16_t)((lo << 8) | hi);
403 | }
404 |
405 | // ============================================================
406 | // The following structs exist only for code clarity, debugability, and
407 | // readability. They are used when writing to disk, but it is useful to have
408 | // 1-packed-structs to document how the format works, and to inspect memory
409 | // while developing.
410 | // ============================================================
411 |
412 | static const uint8_t tjeik_jfif_id[] = "JFIF";
413 | static const uint8_t tjeik_com_str[] = "Created by Tiny JPEG Encoder";
414 |
415 | // TODO: Get rid of packed structs!
416 | #pragma pack(push)
417 | #pragma pack(1)
418 | typedef struct
419 | {
420 | uint16_t SOI;
421 | // JFIF header.
422 | uint16_t APP0;
423 | uint16_t jfif_len;
424 | uint8_t jfif_id[5];
425 | uint16_t version;
426 | uint8_t units;
427 | uint16_t x_density;
428 | uint16_t y_density;
429 | uint8_t x_thumb;
430 | uint8_t y_thumb;
431 | } TJEJPEGHeader;
432 |
433 | typedef struct
434 | {
435 | uint16_t com;
436 | uint16_t com_len;
437 | char com_str[sizeof(tjeik_com_str) - 1];
438 | } TJEJPEGComment;
439 |
440 | // Helper struct for TJEFrameHeader (below).
441 | typedef struct
442 | {
443 | uint8_t component_id;
444 | uint8_t sampling_factors; // most significant 4 bits: horizontal. 4 LSB: vertical (A.1.1)
445 | uint8_t qt; // Quantization table selector.
446 | } TJEComponentSpec;
447 |
448 | typedef struct
449 | {
450 | uint16_t SOF;
451 | uint16_t len; // 8 + 3 * frame.num_components
452 | uint8_t precision; // Sample precision (bits per sample).
453 | uint16_t height;
454 | uint16_t width;
455 | uint8_t num_components; // For this implementation, will be equal to 3.
456 | TJEComponentSpec component_spec[3];
457 | } TJEFrameHeader;
458 |
459 | typedef struct
460 | {
461 | uint8_t component_id; // Just as with TJEComponentSpec
462 | uint8_t dc_ac; // (dc|ac)
463 | } TJEFrameComponentSpec;
464 |
465 | typedef struct
466 | {
467 | uint16_t SOS;
468 | uint16_t len;
469 | uint8_t num_components; // 3.
470 | TJEFrameComponentSpec component_spec[3];
471 | uint8_t first; // 0
472 | uint8_t last; // 63
473 | uint8_t ah_al; // o
474 | } TJEScanHeader;
475 | #pragma pack(pop)
476 |
477 |
478 | static void tjei_write(TJEState* state, const void* data, size_t num_bytes, size_t num_elements)
479 | {
480 | size_t to_write = num_bytes * num_elements;
481 |
482 | // Cap to the buffer available size and copy memory.
483 | size_t capped_count = tjei_min(to_write, TJEI_BUFFER_SIZE - 1 - state->output_buffer_count);
484 |
485 | memcpy(state->output_buffer + state->output_buffer_count, data, capped_count);
486 | state->output_buffer_count += capped_count;
487 |
488 | assert (state->output_buffer_count <= TJEI_BUFFER_SIZE - 1);
489 |
490 | // Flush the buffer.
491 | if ( state->output_buffer_count == TJEI_BUFFER_SIZE - 1 ) {
492 | state->write_context.func(state->write_context.context, state->output_buffer, (int)state->output_buffer_count);
493 | state->output_buffer_count = 0;
494 | }
495 |
496 | // Recursively calling ourselves with the rest of the buffer.
497 | if (capped_count < to_write) {
498 | tjei_write(state, (uint8_t*)data+capped_count, to_write - capped_count, 1);
499 | }
500 | }
501 |
502 | static void tjei_write_DQT(TJEState* state, const uint8_t* matrix, uint8_t id)
503 | {
504 | uint16_t DQT = tjei_be_word(0xffdb);
505 | tjei_write(state, &DQT, sizeof(uint16_t), 1);
506 | uint16_t len = tjei_be_word(0x0043); // 2(len) + 1(id) + 64(matrix) = 67 = 0x43
507 | tjei_write(state, &len, sizeof(uint16_t), 1);
508 | assert(id < 4);
509 | uint8_t precision_and_id = id; // 0x0000 8 bits | 0x00id
510 | tjei_write(state, &precision_and_id, sizeof(uint8_t), 1);
511 | // Write matrix
512 | tjei_write(state, matrix, 64*sizeof(uint8_t), 1);
513 | }
514 |
515 | typedef enum
516 | {
517 | TJEI_DC = 0,
518 | TJEI_AC = 1
519 | } TJEHuffmanTableClass;
520 |
521 | static void tjei_write_DHT(TJEState* state,
522 | uint8_t const * matrix_len,
523 | uint8_t const * matrix_val,
524 | TJEHuffmanTableClass ht_class,
525 | uint8_t id)
526 | {
527 | int num_values = 0;
528 | for ( int i = 0; i < 16; ++i ) {
529 | num_values += matrix_len[i];
530 | }
531 | assert(num_values <= 0xffff);
532 |
533 | uint16_t DHT = tjei_be_word(0xffc4);
534 | // 2(len) + 1(Tc|th) + 16 (num lengths) + ?? (num values)
535 | uint16_t len = tjei_be_word(2 + 1 + 16 + (uint16_t)num_values);
536 | assert(id < 4);
537 | uint8_t tc_th = (uint8_t)((((uint8_t)ht_class) << 4) | id);
538 |
539 | tjei_write(state, &DHT, sizeof(uint16_t), 1);
540 | tjei_write(state, &len, sizeof(uint16_t), 1);
541 | tjei_write(state, &tc_th, sizeof(uint8_t), 1);
542 | tjei_write(state, matrix_len, sizeof(uint8_t), 16);
543 | tjei_write(state, matrix_val, sizeof(uint8_t), (size_t)num_values);
544 | }
545 | // ============================================================
546 | // Huffman deflation code.
547 | // ============================================================
548 |
549 | // Returns all code sizes from the BITS specification (JPEG C.3)
550 | static uint8_t* tjei_huff_get_code_lengths(uint8_t huffsize[/*256*/], uint8_t const * bits)
551 | {
552 | int k = 0;
553 | for ( int i = 0; i < 16; ++i ) {
554 | for ( int j = 0; j < bits[i]; ++j ) {
555 | huffsize[k++] = (uint8_t)(i + 1);
556 | }
557 | huffsize[k] = 0;
558 | }
559 | return huffsize;
560 | }
561 |
562 | // Fills out the prefixes for each code.
563 | static uint16_t* tjei_huff_get_codes(uint16_t codes[], uint8_t* huffsize, int64_t count)
564 | {
565 | uint16_t code = 0;
566 | int k = 0;
567 | uint8_t sz = huffsize[0];
568 | for(;;) {
569 | do {
570 | assert(k < count);
571 | codes[k++] = code++;
572 | } while (huffsize[k] == sz);
573 | if (huffsize[k] == 0) {
574 | return codes;
575 | }
576 | do {
577 | code = (uint16_t)(code << 1);
578 | ++sz;
579 | } while( huffsize[k] != sz );
580 | }
581 | }
582 |
583 | static void tjei_huff_get_extended(uint8_t* out_ehuffsize,
584 | uint16_t* out_ehuffcode,
585 | uint8_t const * huffval,
586 | uint8_t* huffsize,
587 | uint16_t* huffcode, int64_t count)
588 | {
589 | int k = 0;
590 | do {
591 | uint8_t val = huffval[k];
592 | out_ehuffcode[val] = huffcode[k];
593 | out_ehuffsize[val] = huffsize[k];
594 | k++;
595 | } while ( k < count );
596 | }
597 | // ============================================================
598 |
599 | // Returns:
600 | // out[1] : number of bits
601 | // out[0] : bits
602 | TJEI_FORCE_INLINE void tjei_calculate_variable_length_int(int value, uint16_t out[2])
603 | {
604 | int abs_val = value;
605 | if ( value < 0 ) {
606 | abs_val = -abs_val;
607 | --value;
608 | }
609 | out[1] = 1;
610 | while( abs_val >>= 1 ) {
611 | ++out[1];
612 | }
613 | out[0] = (uint16_t)(value & ((1 << out[1]) - 1));
614 | }
615 |
616 | // Write bits to file.
617 | TJEI_FORCE_INLINE void tjei_write_bits(TJEState* state,
618 | uint32_t* bitbuffer, uint32_t* location,
619 | uint16_t num_bits, uint16_t bits)
620 | {
621 | // v-- location
622 | // [ ] <-- bit buffer
623 | // 32 0
624 | //
625 | // This call pushes to the bitbuffer and saves the location. Data is pushed
626 | // from most significant to less significant.
627 | // When we can write a full byte, we write a byte and shift.
628 |
629 | // Push the stack.
630 | uint32_t nloc = *location + num_bits;
631 | *bitbuffer |= (uint32_t)(bits << (32 - nloc));
632 | *location = nloc;
633 | while ( *location >= 8 ) {
634 | // Grab the most significant byte.
635 | uint8_t c = (uint8_t)((*bitbuffer) >> 24);
636 | // Write it to file.
637 | tjei_write(state, &c, 1, 1);
638 | if ( c == 0xff ) {
639 | // Special case: tell JPEG this is not a marker.
640 | char z = 0;
641 | tjei_write(state, &z, 1, 1);
642 | }
643 | // Pop the stack.
644 | *bitbuffer <<= 8;
645 | *location -= 8;
646 | }
647 | }
648 |
649 |
650 | // DCT implementation by Thomas G. Lane.
651 | // Obtained through NVIDIA
652 | // http://developer.download.nvidia.com/SDK/9.5/Samples/vidimaging_samples.html#gpgpu_dct
653 | //
654 | // QUOTE:
655 | // This implementation is based on Arai, Agui, and Nakajima's algorithm for
656 | // scaled DCT. Their original paper (Trans. IEICE E-71(11):1095) is in
657 | // Japanese, but the algorithm is described in the Pennebaker & Mitchell
658 | // JPEG textbook (see REFERENCES section in file README). The following code
659 | // is based directly on figure 4-8 in P&M.
660 | //
661 | static void tjei_fdct(float * data)
662 | {
663 | float X07P, X16P, X25P, X34P, X34M, X25M, X16M, X07M;
664 | float X07P34PP, X16P25PP, X16P25PM, X07P34PM;
665 | float z1, z2, z3, z4, z5, z11, z13;
666 | float *In;
667 | int ctr;
668 |
669 | /* Pass 1: process rows. */
670 |
671 | In = data;
672 | for (ctr = 7; ctr >= 0; ctr--) {
673 | X07P = In[0] + In[7];
674 | X07M = In[0] - In[7];
675 | X16P = In[1] + In[6];
676 | X16M = In[1] - In[6];
677 | X25P = In[2] + In[5];
678 | X25M = In[2] - In[5];
679 | X34P = In[3] + In[4];
680 | X34M = In[3] - In[4];
681 |
682 | /* Even part */
683 |
684 | X07P34PP = X07P + X34P; /* phase 2 */
685 | X07P34PM = X07P - X34P;
686 | X16P25PP = X16P + X25P;
687 | X16P25PM = X16P - X25P;
688 |
689 | In[0] = X07P34PP + X16P25PP; /* phase 3 */
690 | In[4] = X07P34PP - X16P25PP;
691 |
692 | z1 = (X16P25PM + X07P34PM) * ((float) 0.707106781f); /* c4 */
693 | In[2] = X07P34PM + z1; /* phase 5 */
694 | In[6] = X07P34PM - z1;
695 |
696 | /* Odd part */
697 |
698 | X07P34PP = X34M + X25M; /* phase 2 */
699 | X16P25PP = X25M + X16M;
700 | X16P25PM = X16M + X07M;
701 |
702 | /* The rotator is modified from fig 4-8 to avoid extra negations. */
703 | z5 = (X07P34PP - X16P25PM) * ((float) 0.382683433f); /* c6 */
704 | z2 = ((float) 0.541196100f) * X07P34PP + z5; /* c2-c6 */
705 | z4 = ((float) 1.306562965f) * X16P25PM + z5; /* c2+c6 */
706 | z3 = X16P25PP * ((float) 0.707106781f); /* c4 */
707 |
708 | z11 = X07M + z3; /* phase 5 */
709 | z13 = X07M - z3;
710 |
711 | In[5] = z13 + z2; /* phase 6 */
712 | In[3] = z13 - z2;
713 | In[1] = z11 + z4;
714 | In[7] = z11 - z4;
715 |
716 | In += 8; /* advance pointer to next row */
717 | }
718 |
719 | /* Pass 2: process columns. */
720 |
721 | In = data;
722 | for (ctr = 8 - 1; ctr >= 0; ctr--) {
723 | X07P = In[8 * 0] + In[8 * 7];
724 | X07M = In[8 * 0] - In[8 * 7];
725 | X16P = In[8 * 1] + In[8 * 6];
726 | X16M = In[8 * 1] - In[8 * 6];
727 | X25P = In[8 * 2] + In[8 * 5];
728 | X25M = In[8 * 2] - In[8 * 5];
729 | X34P = In[8 * 3] + In[8 * 4];
730 | X34M = In[8 * 3] - In[8 * 4];
731 |
732 | /* Even part */
733 |
734 | X07P34PP = X07P + X34P; /* phase 2 */
735 | X07P34PM = X07P - X34P;
736 | X16P25PP = X16P + X25P;
737 | X16P25PM = X16P - X25P;
738 |
739 | In[8 * 0] = X07P34PP + X16P25PP; /* phase 3 */
740 | In[8 * 4] = X07P34PP - X16P25PP;
741 |
742 | z1 = (X16P25PM + X07P34PM) * ((float) 0.707106781f); /* c4 */
743 | In[8 * 2] = X07P34PM + z1; /* phase 5 */
744 | In[8 * 6] = X07P34PM - z1;
745 |
746 | /* Odd part */
747 |
748 | X07P34PP = X34M + X25M; /* phase 2 */
749 | X16P25PP = X25M + X16M;
750 | X16P25PM = X16M + X07M;
751 |
752 | /* The rotator is modified from fig 4-8 to avoid extra negations. */
753 | z5 = (X07P34PP - X16P25PM) * ((float) 0.382683433f); /* c6 */
754 | z2 = ((float) 0.541196100f) * X07P34PP + z5; /* c2-c6 */
755 | z4 = ((float) 1.306562965f) * X16P25PM + z5; /* c2+c6 */
756 | z3 = X16P25PP * ((float) 0.707106781f); /* c4 */
757 |
758 | z11 = X07M + z3; /* phase 5 */
759 | z13 = X07M - z3;
760 |
761 | In[8 * 5] = z13 + z2; /* phase 6 */
762 | In[8 * 3] = z13 - z2;
763 | In[8 * 1] = z11 + z4;
764 | In[8 * 7] = z11 - z4;
765 |
766 | In++; /* advance pointer to next column */
767 | }
768 | }
769 |
770 |
771 | #if !TJE_USE_FAST_DCT
772 | static float slow_fdct(int u, int v, float* data)
773 | {
774 | #define kPI 3.14159265f
775 | float res = 0.0f;
776 | float cu = (u == 0) ? 0.70710678118654f : 1;
777 | float cv = (v == 0) ? 0.70710678118654f : 1;
778 | for ( int y = 0; y < 8; ++y ) {
779 | for ( int x = 0; x < 8; ++x ) {
780 | res += (data[y * 8 + x]) *
781 | cosf(((2.0f * x + 1.0f) * u * kPI) / 16.0f) *
782 | cosf(((2.0f * y + 1.0f) * v * kPI) / 16.0f);
783 | }
784 | }
785 | res *= 0.25f * cu * cv;
786 | return res;
787 | #undef kPI
788 | }
789 | #endif
790 |
791 | #define ABS(x) ((x) < 0 ? -(x) : (x))
792 |
793 | static void tjei_encode_and_write_MCU(TJEState* state,
794 | float* mcu,
795 | #if TJE_USE_FAST_DCT
796 | float* qt, // Pre-processed quantization matrix.
797 | #else
798 | uint8_t* qt,
799 | #endif
800 | uint8_t* huff_dc_len, uint16_t* huff_dc_code, // Huffman tables
801 | uint8_t* huff_ac_len, uint16_t* huff_ac_code,
802 | int* pred, // Previous DC coefficient
803 | uint32_t* bitbuffer, // Bitstack.
804 | uint32_t* location)
805 | {
806 | int du[64]; // Data unit in zig-zag order
807 |
808 | float dct_mcu[64];
809 | memcpy(dct_mcu, mcu, 64 * sizeof(float));
810 |
811 | #if TJE_USE_FAST_DCT
812 | tjei_fdct(dct_mcu);
813 | for ( int i = 0; i < 64; ++i ) {
814 | float fval = dct_mcu[i];
815 | fval *= qt[i];
816 | #if 0
817 | fval = (fval > 0) ? floorf(fval + 0.5f) : ceilf(fval - 0.5f);
818 | #else
819 | fval = floorf(fval + 1024 + 0.5f);
820 | fval -= 1024;
821 | #endif
822 | int val = (int)fval;
823 | du[tjei_zig_zag[i]] = val;
824 | }
825 | #else
826 | for ( int v = 0; v < 8; ++v ) {
827 | for ( int u = 0; u < 8; ++u ) {
828 | dct_mcu[v * 8 + u] = slow_fdct(u, v, mcu);
829 | }
830 | }
831 | for ( int i = 0; i < 64; ++i ) {
832 | float fval = dct_mcu[i] / (qt[i]);
833 | int val = (int)((fval > 0) ? floorf(fval + 0.5f) : ceilf(fval - 0.5f));
834 | du[tjei_zig_zag[i]] = val;
835 | }
836 | #endif
837 |
838 | uint16_t vli[2];
839 |
840 | // Encode DC coefficient.
841 | int diff = du[0] - *pred;
842 | *pred = du[0];
843 | if ( diff != 0 ) {
844 | tjei_calculate_variable_length_int(diff, vli);
845 | // Write number of bits with Huffman coding
846 | tjei_write_bits(state, bitbuffer, location, huff_dc_len[vli[1]], huff_dc_code[vli[1]]);
847 | // Write the bits.
848 | tjei_write_bits(state, bitbuffer, location, vli[1], vli[0]);
849 | } else {
850 | tjei_write_bits(state, bitbuffer, location, huff_dc_len[0], huff_dc_code[0]);
851 | }
852 |
853 | // ==== Encode AC coefficients ====
854 |
855 | int last_non_zero_i = 0;
856 | // Find the last non-zero element.
857 | for ( int i = 63; i > 0; --i ) {
858 | if (du[i] != 0) {
859 | last_non_zero_i = i;
860 | break;
861 | }
862 | }
863 |
864 | for ( int i = 1; i <= last_non_zero_i; ++i ) {
865 | // If zero, increase count. If >=15, encode (FF,00)
866 | int zero_count = 0;
867 | while ( du[i] == 0 ) {
868 | ++zero_count;
869 | ++i;
870 | if (zero_count == 16) {
871 | // encode (ff,00) == 0xf0
872 | tjei_write_bits(state, bitbuffer, location, huff_ac_len[0xf0], huff_ac_code[0xf0]);
873 | zero_count = 0;
874 | }
875 | }
876 | tjei_calculate_variable_length_int(du[i], vli);
877 |
878 | assert(zero_count < 0x10);
879 | assert(vli[1] <= 10);
880 |
881 | uint16_t sym1 = (uint16_t)((uint16_t)zero_count << 4) | vli[1];
882 |
883 | assert(huff_ac_len[sym1] != 0);
884 |
885 | // Write symbol 1 --- (RUNLENGTH, SIZE)
886 | tjei_write_bits(state, bitbuffer, location, huff_ac_len[sym1], huff_ac_code[sym1]);
887 | // Write symbol 2 --- (AMPLITUDE)
888 | tjei_write_bits(state, bitbuffer, location, vli[1], vli[0]);
889 | }
890 |
891 | if (last_non_zero_i != 63) {
892 | // write EOB HUFF(00,00)
893 | tjei_write_bits(state, bitbuffer, location, huff_ac_len[0], huff_ac_code[0]);
894 | }
895 | return;
896 | }
897 |
898 | enum {
899 | TJEI_LUMA_DC,
900 | TJEI_LUMA_AC,
901 | TJEI_CHROMA_DC,
902 | TJEI_CHROMA_AC,
903 | };
904 |
905 | #if TJE_USE_FAST_DCT
906 | struct TJEProcessedQT
907 | {
908 | float chroma[64];
909 | float luma[64];
910 | };
911 | #endif
912 |
913 | // Set up huffman tables in state.
914 | static void tjei_huff_expand(TJEState* state)
915 | {
916 | assert(state);
917 |
918 | state->ht_bits[TJEI_LUMA_DC] = tjei_default_ht_luma_dc_len;
919 | state->ht_bits[TJEI_LUMA_AC] = tjei_default_ht_luma_ac_len;
920 | state->ht_bits[TJEI_CHROMA_DC] = tjei_default_ht_chroma_dc_len;
921 | state->ht_bits[TJEI_CHROMA_AC] = tjei_default_ht_chroma_ac_len;
922 |
923 | state->ht_vals[TJEI_LUMA_DC] = tjei_default_ht_luma_dc;
924 | state->ht_vals[TJEI_LUMA_AC] = tjei_default_ht_luma_ac;
925 | state->ht_vals[TJEI_CHROMA_DC] = tjei_default_ht_chroma_dc;
926 | state->ht_vals[TJEI_CHROMA_AC] = tjei_default_ht_chroma_ac;
927 |
928 | // How many codes in total for each of LUMA_(DC|AC) and CHROMA_(DC|AC)
929 | int32_t spec_tables_len[4] = { 0 };
930 |
931 | for ( int i = 0; i < 4; ++i ) {
932 | for ( int k = 0; k < 16; ++k ) {
933 | spec_tables_len[i] += state->ht_bits[i][k];
934 | }
935 | }
936 |
937 | // Fill out the extended tables..
938 | uint8_t huffsize[4][257];
939 | uint16_t huffcode[4][256];
940 | for ( int i = 0; i < 4; ++i ) {
941 | assert (256 >= spec_tables_len[i]);
942 | tjei_huff_get_code_lengths(huffsize[i], state->ht_bits[i]);
943 | tjei_huff_get_codes(huffcode[i], huffsize[i], spec_tables_len[i]);
944 | }
945 | for ( int i = 0; i < 4; ++i ) {
946 | int64_t count = spec_tables_len[i];
947 | tjei_huff_get_extended(state->ehuffsize[i],
948 | state->ehuffcode[i],
949 | state->ht_vals[i],
950 | &huffsize[i][0],
951 | &huffcode[i][0], count);
952 | }
953 | }
954 |
955 | static int tjei_encode_main(TJEState* state,
956 | const unsigned char* src_data,
957 | const int width,
958 | const int height,
959 | const int src_num_components,
960 | bool Rgb) // else bgr
961 | {
962 | if (src_num_components != 3 && src_num_components != 4) {
963 | return 0;
964 | }
965 |
966 | if (width > 0xffff || height > 0xffff) {
967 | return 0;
968 | }
969 |
970 | #if TJE_USE_FAST_DCT
971 | struct TJEProcessedQT pqt;
972 | // Again, taken from classic japanese implementation.
973 | //
974 | /* For float AA&N IDCT method, divisors are equal to quantization
975 | * coefficients scaled by scalefactor[row]*scalefactor[col], where
976 | * scalefactor[0] = 1
977 | * scalefactor[k] = cos(k*PI/16) * sqrt(2) for k=1..7
978 | * We apply a further scale factor of 8.
979 | * What's actually stored is 1/divisor so that the inner loop can
980 | * use a multiplication rather than a division.
981 | */
982 | static const float aan_scales[] = {
983 | 1.0f, 1.387039845f, 1.306562965f, 1.175875602f,
984 | 1.0f, 0.785694958f, 0.541196100f, 0.275899379f
985 | };
986 |
987 | // build (de)quantization tables
988 | for(int y=0; y<8; y++) {
989 | int line = y << 3;
990 | float aan_scales_y = 8 * aan_scales[y];
991 | for (int x = 0; x < 8; x++) {
992 | const int i = (line)+x;
993 | const uint8_t zag = tjei_zig_zag[i];
994 | pqt.luma[i] = 1.0f / (aan_scales[x] * aan_scales_y* state->qt_luma[zag]);
995 | pqt.chroma[i] = 1.0f / (aan_scales[x] * aan_scales_y * state->qt_chroma[zag]);
996 | }
997 | }
998 | #endif
999 |
1000 | { // Write header
1001 | TJEJPEGHeader header;
1002 | // JFIF header.
1003 | header.SOI = tjei_be_word(0xffd8); // Sequential DCT
1004 | header.APP0 = tjei_be_word(0xffe0);
1005 |
1006 | uint16_t jfif_len = sizeof(TJEJPEGHeader) - 4 /*SOI & APP0 markers*/;
1007 | header.jfif_len = tjei_be_word(jfif_len);
1008 | memcpy(header.jfif_id, (void*)tjeik_jfif_id, 5);
1009 | header.version = tjei_be_word(0x0102);
1010 | header.units = 0x01; // Dots-per-inch
1011 | header.x_density = tjei_be_word(0x0060); // 96 DPI
1012 | header.y_density = tjei_be_word(0x0060); // 96 DPI
1013 | header.x_thumb = 0;
1014 | header.y_thumb = 0;
1015 | tjei_write(state, &header, sizeof(TJEJPEGHeader), 1);
1016 | }
1017 | { // Write comment
1018 | TJEJPEGComment com;
1019 | uint16_t com_len = 2 + sizeof(tjeik_com_str) - 1;
1020 | // Comment
1021 | com.com = tjei_be_word(0xfffe);
1022 | com.com_len = tjei_be_word(com_len);
1023 | memcpy(com.com_str, (void*)tjeik_com_str, sizeof(tjeik_com_str)-1);
1024 | tjei_write(state, &com, sizeof(TJEJPEGComment), 1);
1025 | }
1026 |
1027 | // Write quantization tables.
1028 | tjei_write_DQT(state, state->qt_luma, 0x00);
1029 | tjei_write_DQT(state, state->qt_chroma, 0x01);
1030 |
1031 | { // Write the frame marker.
1032 | TJEFrameHeader header;
1033 | header.SOF = tjei_be_word(0xffc0);
1034 | header.len = tjei_be_word(8 + 3 * 3);
1035 | header.precision = 8;
1036 | assert(width <= 0xffff);
1037 | assert(height <= 0xffff);
1038 | header.width = tjei_be_word((uint16_t)width);
1039 | header.height = tjei_be_word((uint16_t)height);
1040 | header.num_components = 3;
1041 | uint8_t tables[3] = {
1042 | 0, // Luma component gets luma table (see tjei_write_DQT call above.)
1043 | 1, // Chroma component gets chroma table
1044 | 1, // Chroma component gets chroma table
1045 | };
1046 | for (int i = 0; i < 3; ++i) {
1047 | TJEComponentSpec spec;
1048 | spec.component_id = (uint8_t)(i + 1); // No particular reason. Just 1, 2, 3.
1049 | spec.sampling_factors = (uint8_t)0x11;
1050 | spec.qt = tables[i];
1051 |
1052 | header.component_spec[i] = spec;
1053 | }
1054 | // Write to file.
1055 | tjei_write(state, &header, sizeof(TJEFrameHeader), 1);
1056 | }
1057 |
1058 | tjei_write_DHT(state, state->ht_bits[TJEI_LUMA_DC], state->ht_vals[TJEI_LUMA_DC], TJEI_DC, 0);
1059 | tjei_write_DHT(state, state->ht_bits[TJEI_LUMA_AC], state->ht_vals[TJEI_LUMA_AC], TJEI_AC, 0);
1060 | tjei_write_DHT(state, state->ht_bits[TJEI_CHROMA_DC], state->ht_vals[TJEI_CHROMA_DC], TJEI_DC, 1);
1061 | tjei_write_DHT(state, state->ht_bits[TJEI_CHROMA_AC], state->ht_vals[TJEI_CHROMA_AC], TJEI_AC, 1);
1062 |
1063 | // Write start of scan
1064 | {
1065 | TJEScanHeader header;
1066 | header.SOS = tjei_be_word(0xffda);
1067 | header.len = tjei_be_word((uint16_t)(6 + (sizeof(TJEFrameComponentSpec) * 3)));
1068 | header.num_components = 3;
1069 |
1070 | uint8_t tables[3] = {
1071 | 0x00,
1072 | 0x11,
1073 | 0x11,
1074 | };
1075 | for (int i = 0; i < 3; ++i) {
1076 | TJEFrameComponentSpec cs;
1077 | // Must be equal to component_id from frame header above.
1078 | cs.component_id = (uint8_t)(i + 1);
1079 | cs.dc_ac = (uint8_t)tables[i];
1080 |
1081 | header.component_spec[i] = cs;
1082 | }
1083 | header.first = 0;
1084 | header.last = 63;
1085 | header.ah_al = 0;
1086 | tjei_write(state, &header, sizeof(TJEScanHeader), 1);
1087 |
1088 | }
1089 | // Write compressed data.
1090 |
1091 | float du_y[64];
1092 | float du_b[64];
1093 | float du_r[64];
1094 |
1095 | // Set diff to 0.
1096 | int pred_y = 0;
1097 | int pred_b = 0;
1098 | int pred_r = 0;
1099 |
1100 | // Bit stack
1101 | uint32_t bitbuffer = 0;
1102 | uint32_t location = 0;
1103 |
1104 | int rgb_idx[3] = { 0,1,2 };
1105 | if (!Rgb)
1106 | {
1107 | rgb_idx[2] = 0;
1108 | rgb_idx[0] = 2;
1109 | }
1110 | for (int y = 0; y < height; y += 8)
1111 | {
1112 | for (int x = 0; x < width; x += 8)
1113 | {
1114 | // Block loop: ====
1115 | for (int off_y = 0; off_y < 8; ++off_y)
1116 | {
1117 | for (int off_x = 0; off_x < 8; ++off_x)
1118 | {
1119 | int block_index = (off_y << 3) + off_x;
1120 | int src_index = (((y + off_y) * width) + (x + off_x)) * src_num_components;
1121 | int col = x + off_x;
1122 | int row = y + off_y;
1123 |
1124 | if (row >= height) {
1125 | src_index -= (width * (row - height + 1)) * src_num_components;
1126 | }
1127 | if (col >= width) {
1128 | src_index -= (col - width + 1) * src_num_components;
1129 | }
1130 | const unsigned char r = src_data[src_index + rgb_idx[0]];
1131 | const unsigned char g = src_data[src_index + rgb_idx[1]];
1132 | const unsigned char b = src_data[src_index + rgb_idx[2]];
1133 | du_y[block_index] = (float)(((19595 * r + 38470 * g + 7470 * b) >> 16) - 128);
1134 | du_b[block_index] = (float)((-11056 * r - 21712 * g + 32767 * b) >> 16);
1135 | du_r[block_index] = (float)((32767 * r - 27440 * g - 5328 * b) >> 16);
1136 | }
1137 | }
1138 | tjei_encode_and_write_MCU(state, du_y,
1139 | #if TJE_USE_FAST_DCT
1140 | pqt.luma,
1141 | #else
1142 | state->qt_luma,
1143 | #endif
1144 | state->ehuffsize[TJEI_LUMA_DC], state->ehuffcode[TJEI_LUMA_DC],
1145 | state->ehuffsize[TJEI_LUMA_AC], state->ehuffcode[TJEI_LUMA_AC],
1146 | &pred_y, &bitbuffer, &location);
1147 | tjei_encode_and_write_MCU(state, du_b,
1148 | #if TJE_USE_FAST_DCT
1149 | pqt.chroma,
1150 | #else
1151 | state->qt_chroma,
1152 | #endif
1153 | state->ehuffsize[TJEI_CHROMA_DC], state->ehuffcode[TJEI_CHROMA_DC],
1154 | state->ehuffsize[TJEI_CHROMA_AC], state->ehuffcode[TJEI_CHROMA_AC],
1155 | &pred_b, &bitbuffer, &location);
1156 | tjei_encode_and_write_MCU(state, du_r,
1157 | #if TJE_USE_FAST_DCT
1158 | pqt.chroma,
1159 | #else
1160 | state->qt_chroma,
1161 | #endif
1162 | state->ehuffsize[TJEI_CHROMA_DC], state->ehuffcode[TJEI_CHROMA_DC],
1163 | state->ehuffsize[TJEI_CHROMA_AC], state->ehuffcode[TJEI_CHROMA_AC],
1164 | &pred_r, &bitbuffer, &location);
1165 |
1166 |
1167 | }
1168 | }
1169 |
1170 | // Finish the image.
1171 | { // Flush
1172 | if (location > 0 && location < 8) {
1173 | tjei_write_bits(state, &bitbuffer, &location, (uint16_t)(8 - location), 0);
1174 | }
1175 | }
1176 | uint16_t EOI = tjei_be_word(0xffd9);
1177 | tjei_write(state, &EOI, sizeof(uint16_t), 1);
1178 |
1179 | if (state->output_buffer_count) {
1180 | state->write_context.func(state->write_context.context, state->output_buffer, (int)state->output_buffer_count);
1181 | state->output_buffer_count = 0;
1182 | }
1183 |
1184 | return 1;
1185 | }
1186 | #if ENABLE_FOPEN==1
1187 | int tje_encode_to_file(const char* dest_path,
1188 | const int width,
1189 | const int height,
1190 | const int num_components,
1191 | bool Rgb,
1192 | const unsigned char* src_data)
1193 | {
1194 | if (num_components == 1)
1195 | {
1196 | unsigned char* dst_data = (unsigned char*)malloc(width*height * 3);
1197 | if (dst_data == NULL) return 0;
1198 | for (int y = 0; y < height; y++)
1199 | {
1200 | const unsigned char* inLine = src_data + (y * width);
1201 | unsigned char* outLine = dst_data + (y * width * 3);
1202 |
1203 | for (int x = 0; x < width; x++)
1204 | {
1205 | outLine[0] = outLine[1] = outLine[2] = inLine[x];
1206 |
1207 | outLine += 3;
1208 | }
1209 | }
1210 | int res = tje_encode_to_file_at_quality(dest_path, 3, width, height, 3, Rgb, dst_data);
1211 | free(dst_data);
1212 | return res;
1213 | }
1214 | else
1215 | {
1216 | int res = tje_encode_to_file_at_quality(dest_path, 3, width, height, num_components, Rgb, src_data);
1217 | return res;
1218 | }
1219 | }
1220 | #endif
1221 |
1222 | #if ENABLE_FOPEN==1
1223 | static void tjei_stdlib_func(void* context, void* data, int size)
1224 | {
1225 | FILE* fd = (FILE*)context;
1226 | fwrite(data, size, 1, fd);
1227 | }
1228 | #endif
1229 |
1230 | // Define public interface.
1231 | #if ENABLE_FOPEN==1
1232 | int tje_encode_to_file_at_quality(const char* dest_path,
1233 | const int quality,
1234 | const int width,
1235 | const int height,
1236 | const int num_components,
1237 | bool Rgb,
1238 | const unsigned char* src_data)
1239 | {
1240 | FILE* fd = fopen(dest_path, "wb");
1241 | if (!fd) {
1242 | tje_log("Could not open file for writing.");
1243 | return 0;
1244 | }
1245 |
1246 | int result = tje_encode_with_func(tjei_stdlib_func, fd,
1247 | quality, width, height, num_components, Rgb, src_data);
1248 |
1249 | result |= 0 == fclose(fd);
1250 |
1251 | return result;
1252 | }
1253 | #endif
1254 |
1255 | int tje_encode_with_func(tje_write_func* func,
1256 | void* context,
1257 | const int quality,
1258 | const int width,
1259 | const int height,
1260 | const int num_components,
1261 | bool Rgb,
1262 | const unsigned char* src_data)
1263 | {
1264 | if (quality < 1 || quality > 3) {
1265 | tje_log("[ERROR] -- Valid 'quality' values are 1 (lowest), 2, or 3 (highest)\n");
1266 | return 0;
1267 | }
1268 |
1269 | TJEState state = { 0 };
1270 |
1271 | uint8_t qt_factor = 1;
1272 | switch(quality) {
1273 | case 3:
1274 | for ( int i = 0; i < 64; ++i ) {
1275 | state.qt_luma[i] = 1;
1276 | state.qt_chroma[i] = 1;
1277 | }
1278 | break;
1279 | case 2:
1280 | qt_factor = 10;
1281 | // don't break. fall through.
1282 | case 1:
1283 | for ( int i = 0; i < 64; ++i ) {
1284 | state.qt_luma[i] = tjei_default_qt_luma_from_spec[i] / qt_factor;
1285 | if (state.qt_luma[i] == 0) {
1286 | state.qt_luma[i] = 1;
1287 | }
1288 | state.qt_chroma[i] = tjei_default_qt_chroma_from_paper[i] / qt_factor;
1289 | if (state.qt_chroma[i] == 0) {
1290 | state.qt_chroma[i] = 1;
1291 | }
1292 | }
1293 | break;
1294 | default:
1295 | assert(!"invalid code path");
1296 | break;
1297 | }
1298 |
1299 | TJEWriteContext wc = { 0 };
1300 |
1301 | wc.context = context;
1302 | wc.func = func;
1303 |
1304 | state.write_context = wc;
1305 |
1306 |
1307 | tjei_huff_expand(&state);
1308 |
1309 | int result = tjei_encode_main(&state, src_data, width, height, num_components, Rgb);
1310 |
1311 | return result;
1312 | }
1313 | // ============================================================
1314 | #endif // TJE_IMPLEMENTATION
1315 | // ============================================================
1316 | //
1317 | #if defined(__GNUC__) || defined(__clang__)
1318 | #pragma GCC diagnostic pop
1319 | #endif
1320 |
1321 |
1322 | #ifdef __cplusplus
1323 | } // extern C
1324 | #endif
1325 |
1326 |
--------------------------------------------------------------------------------