├── .gitignore
├── LICENSE
├── README.md
├── prj
├── build.sh
└── codeblocks
│ ├── crop.cbp
│ ├── edge_detection.cbp
│ └── edge_detection.workspace
└── src
├── bmp.c
├── bmp.h
├── canny.c
├── canny.h
├── common.c
├── convolution.c
├── convolution.h
├── crop.c
├── edges.c
├── hough.c
├── hough.h
├── image.c
├── image.h
├── perspective.c
├── perspective.h
├── timer.c
├── timer.h
└── types.h
/.gitignore:
--------------------------------------------------------------------------------
1 | *.bmp
2 | *.o
3 | main
4 | /bin
5 | /prj
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Carlos Asensio Martinez
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 | * Fast Edge Detection Library *
2 |
3 | This library is a basic edge detection using Canny and Hough (well known) algoritms.
4 | I decide to do my own library because the ones that I found were complex or not well optimized.
5 |
6 | This library has no dependencies, is portable small and fast.
7 |
8 | ## Still is a work in progress library.
9 | A lot of testing must be done. Until now I just coded the basics to implement the hough, but as a library is far from be usable.
10 |
--------------------------------------------------------------------------------
/prj/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # im too lazy to build a makefile right now
4 | mkdir obj
5 | cd obj
6 |
7 | gcc -c ../../src/convolution.c -O3 -o convolution.o
8 | gcc -c ../../src/image.c -O3 -o image.o
9 | gcc -c ../../src/bmp.c -O3 -o bmp.o
10 | gcc -c ../../src/canny.c -O3 -o canny.o
11 | gcc -c ../../src/hough.c -O3 -o hough.o
12 | gcc -c ../../src/timer.c -O3 -o timer.o
13 | gcc ../../src/edges.c -O3 -o ../../bin/edges timer.o hough.o canny.o image.o bmp.o convolution.o
14 |
15 |
16 | gcc -c ../../src/perspective.c -O3 -o perspective.o
17 | gcc ../../src/crop.c -O3 -o ../../bin/crop perspective.o image.o bmp.o
18 |
--------------------------------------------------------------------------------
/prj/codeblocks/crop.cbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/prj/codeblocks/edge_detection.cbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
--------------------------------------------------------------------------------
/prj/codeblocks/edge_detection.workspace:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/src/bmp.c:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | * Loading part taken from
4 | * http://www.vbforums.com/showthread.php?t=261522
5 | * BMP info:
6 | * http://en.wikipedia.org/wiki/BMP_file_format
7 | *
8 | * Note: the magic number has been removed from the bmpfile_header_t
9 | * structure since it causes alignment problems
10 | * bmpfile_magic_t should be written/read first
11 | * followed by the
12 | * bmpfile_header_t
13 | * [this avoids compiler-specific alignment pragmas etc.]
14 | */
15 |
16 | #include "bmp.h"
17 | #include
18 |
19 | typedef struct {
20 | uint8 magic[2];
21 | } bmpfile_magic_t;
22 |
23 | typedef struct {
24 | uint32 filesz;
25 | uint16 creator1;
26 | uint16 creator2;
27 | uint32 bmp_offset;
28 | } bmpfile_header_t;
29 |
30 | typedef struct {
31 | uint32 header_sz;
32 | int32 width;
33 | int32 height;
34 | uint16 nplanes;
35 | uint16 bitspp;
36 | uint32 compress_type;
37 | uint32 bmp_bytesz;
38 | int32 hres;
39 | int32 vres;
40 | uint32 ncolors;
41 | uint32 nimpcolors;
42 | } bitmap_info_header_t;
43 |
44 | typedef struct {
45 | uint8 r;
46 | uint8 g;
47 | uint8 b;
48 | uint8 nothing;
49 | } rgb_t;
50 |
51 |
52 | uint32 load_image(const char *filename, SImage *pImage)
53 | {
54 | release_image(pImage);
55 |
56 | bitmap_info_header_t myInfoHeader;
57 | bitmap_info_header_t *bitmapInfoHeader = &myInfoHeader;
58 |
59 |
60 | FILE *filePtr = fopen(filename, "rb");
61 | if (filePtr == 0x0) {
62 | return LE_CANT_OPEN;
63 | }
64 |
65 | bmpfile_magic_t mag;
66 | if (fread(&mag, sizeof(bmpfile_magic_t), 1, filePtr) != 1) {
67 | fclose(filePtr);
68 | return LE_CANT_READ;
69 | }
70 |
71 | // verify that this is a bmp file by check bitmap id
72 | // warning: dereferencing type-punned pointer will break
73 | // strict-aliasing rules [-Wstrict-aliasing]
74 | if (*((uint16*)mag.magic) != 0x4D42) {
75 | fclose(filePtr);
76 | return LE_INVALID_MAGIC;
77 | }
78 |
79 | bmpfile_header_t bitmapFileHeader; // our bitmap file header
80 | // read the bitmap file header
81 | if (fread(&bitmapFileHeader, sizeof(bmpfile_header_t), 1, filePtr) != 1) {
82 | fclose(filePtr);
83 | return LE_CANT_READ;
84 | }
85 |
86 | // read the bitmap info header
87 | if (fread(bitmapInfoHeader, sizeof(bitmap_info_header_t), 1, filePtr) != 1) {
88 | fclose(filePtr);
89 | return LE_CANT_READ;
90 | }
91 |
92 | if (bitmapInfoHeader->compress_type != 0)
93 | fprintf(stderr, "Warning, compression is not supported.\n");
94 |
95 | // move file point to the beginning of bitmap data
96 | if (fseek(filePtr, bitmapFileHeader.bmp_offset, SEEK_SET)) {
97 | fclose(filePtr);
98 | return LE_CANT_READ;
99 | }
100 |
101 | //printf("Reading img: %dbpp, %d x %d\n", bitmapInfoHeader->bitspp, bitmapInfoHeader->width, bitmapInfoHeader->height);
102 | EPixelFormat pf = ((bitmapInfoHeader->bitspp == 32)?PF_ARGB:(bitmapInfoHeader->bitspp == 24)?PF_RGB:PF_GRAY);
103 | *pImage = create_image(bitmapInfoHeader->width, bitmapInfoHeader->height, pf);
104 | // allocate enough memory for the bitmap image data
105 | //pixel_t *bitmapImage = pImage->mpData;
106 |
107 | /*
108 | // read in the bitmap image data
109 | size_t pad, count=0;
110 | unsigned char c;
111 | pad = 4*ceil(bitmapInfoHeader->bitspp*bitmapInfoHeader->width/32.) - bitmapInfoHeader->width;
112 | for(size_t i=0; iheight; i++){
113 | for(size_t j=0; jwidth; j++){
114 | if (fread(&c, sizeof(unsigned char), 1, filePtr) != 1) {
115 | fclose(filePtr);
116 | return LE_CANT_READ;
117 | }
118 | pImage->mpData[count++] = (uint8) c;
119 | }
120 | fseek(filePtr, pad, SEEK_CUR);
121 | }
122 | */
123 |
124 | // If we were using unsigned char as pixel_t, then:
125 | fread(pImage->mpData, 1, bitmapInfoHeader->bmp_bytesz, filePtr);
126 |
127 | // close file and return bitmap image data
128 | fclose(filePtr);
129 | return LE_NO_ERROR;
130 | }
131 |
132 |
133 | uint32 save_image(const char *filename, const SImage *_pImg)
134 | {
135 | return save_image_ex(filename, _pImg->mpData, _pImg->mWidth, _pImg->mHeight, _pImg->mBpp);
136 | }
137 |
138 | uint32 save_image_ex(const char *filename, const uint8 *_pData, uint32 _w, uint32 _h, uint32 _bpp)
139 | {
140 | unsigned int headers[13];
141 | FILE * outfile;
142 | int extrabytes;
143 | int paddedsize;
144 | int x;
145 | int y;
146 | int n;
147 | int red, green, blue;
148 | int WIDTH = _w;
149 | int HEIGHT = _h;
150 |
151 | extrabytes = 4 - ((WIDTH * 3) % 4); // How many bytes of padding to add to each
152 | // horizontal line - the size of which must
153 | // be a multiple of 4 bytes.
154 | if (extrabytes == 4)
155 | extrabytes = 0;
156 |
157 | paddedsize = ((WIDTH * 3) + extrabytes) * HEIGHT;
158 |
159 | // Headers...
160 | // Note that the "BM" identifier in bytes 0 and 1 is NOT included in these "headers".
161 |
162 | headers[0] = paddedsize + 54; // bfSize (whole file size)
163 | headers[1] = 0; // bfReserved (both)
164 | headers[2] = 54; // bfOffbits
165 | headers[3] = 40; // biSize
166 | headers[4] = WIDTH; // biWidth
167 | headers[5] = HEIGHT; // biHeight
168 |
169 | // Would have biPlanes and biBitCount in position 6, but they're shorts.
170 | // It's easier to write them out separately (see below) than pretend
171 | // they're a single int, especially with endian issues...
172 |
173 | headers[7] = 0; // biCompression
174 | headers[8] = paddedsize; // biSizeImage
175 | headers[9] = 0; // biXPelsPerMeter
176 | headers[10] = 0; // biYPelsPerMeter
177 | headers[11] = 0; // biClrUsed
178 | headers[12] = 0; // biClrImportant
179 |
180 | outfile = fopen(filename, "wb");
181 |
182 | //
183 | // Headers begin...
184 | // When printing ints and shorts, we write out 1 character at a time to avoid endian issues.
185 | //
186 |
187 | fprintf(outfile, "BM");
188 |
189 | for (n = 0; n <= 5; n++)
190 | {
191 | fprintf(outfile, "%c", headers[n] & 0x000000FF);
192 | fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
193 | fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
194 | fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
195 | }
196 |
197 | // These next 4 characters are for the biPlanes and biBitCount fields.
198 |
199 | fprintf(outfile, "%c", 1);
200 | fprintf(outfile, "%c", 0);
201 | fprintf(outfile, "%c", 24);
202 | fprintf(outfile, "%c", 0);
203 |
204 | for (n = 7; n <= 12; n++)
205 | {
206 | fprintf(outfile, "%c", headers[n] & 0x000000FF);
207 | fprintf(outfile, "%c", (headers[n] & 0x0000FF00) >> 8);
208 | fprintf(outfile, "%c", (headers[n] & 0x00FF0000) >> 16);
209 | fprintf(outfile, "%c", (headers[n] & (unsigned int) 0xFF000000) >> 24);
210 | }
211 |
212 | //
213 | // Headers done, now write the data...
214 | //
215 |
216 | //for (y = HEIGHT - 1; y >= 0; y--) // BMP image format is written from bottom to top...
217 | for (y = 0; y < HEIGHT; ++y)
218 | {
219 | for (x = 0; x < WIDTH; x++)
220 | {
221 | if(_bpp == 1)
222 | {
223 | red = _pData[x + y * WIDTH];
224 | green = _pData[x + y * WIDTH];
225 | blue = _pData[x + y * WIDTH];
226 | }
227 | else
228 | {
229 | int bpp = 3;
230 | int extra = 0;
231 | if(_bpp == 4)
232 | {
233 | bpp = 4;
234 | extra = 1;
235 | }
236 | red = _pData[x*bpp + y * WIDTH*bpp + extra + 0];
237 | green = _pData[x*bpp + y * WIDTH*bpp + extra + 1];
238 | blue = _pData[x*bpp + y * WIDTH*bpp + extra + 2];
239 | }
240 |
241 | // Also, it's written in (b,g,r) format...
242 |
243 | fprintf(outfile, "%c", blue);
244 | fprintf(outfile, "%c", green);
245 | fprintf(outfile, "%c", red);
246 | }
247 | if (extrabytes) // See above - BMP lines must be of lengths divisible by 4.
248 | {
249 | for (n = 1; n <= extrabytes; n++)
250 | {
251 | fprintf(outfile, "%c", 0);
252 | }
253 | }
254 | }
255 |
256 | fclose(outfile);
257 | return LE_NO_ERROR;
258 |
259 | }
--------------------------------------------------------------------------------
/src/bmp.h:
--------------------------------------------------------------------------------
1 | #ifndef _BMP_H_
2 | #define _BMP_H_
3 | #include "types.h"
4 | #include "image.h"
5 |
6 | typedef enum ELoadError
7 | {
8 | LE_NO_ERROR,
9 | LE_CANT_OPEN,
10 | LE_CANT_READ,
11 | LE_CANT_WRITE,
12 | LE_INVALID_MAGIC,
13 | } ELoadError;
14 |
15 | uint32 load_image(const char *filename, SImage *pImage);
16 | uint32 save_image(const char *filename, const SImage *_pImg);
17 | uint32 save_image_ex(const char *filename, const uint8 *_pData, uint32 _w, uint32 _h, uint32 _bpp);
18 |
19 | #endif
--------------------------------------------------------------------------------
/src/canny.c:
--------------------------------------------------------------------------------
1 | #include "canny.h"
2 | #include "convolution.h"
3 | #include
4 | #include
5 |
6 | // https://github.com/brunokeymolen/canny/blob/master/canny.cpp
7 | // https://rosettacode.org/wiki/Canny_edge_detector#C
8 |
9 | #define MAX_BRIGHTNESS 255
10 | #define TMP_BRIGHTNESS 100
11 |
12 | //-------------------------------------
13 | typedef struct SCannyWorkspace
14 | {
15 | uint32 width;
16 | uint32 height;
17 |
18 | uint8 *pMap;
19 | double *pGradient;
20 | double *pMaxima;
21 | uint8 *pDirection;
22 | } SCannyWorkspace;
23 | //-------------------------------------
24 |
25 | //-------------------------------------
26 | static SCannyWorkspace *spCannyWorkspace = 0x0;
27 | //-------------------------------------
28 |
29 | // private methods
30 | //-------------------------------------
31 | void canny_priv(uint8 *_pData, const uint32 _width, const uint32 _height, const uint32 _tmin, const uint32 _tmax, SCannyWorkspace *_pWorkspace);
32 | void traceEdgesWithHysteresis(uint8 *_pData, const uint32 _width, const uint32 _height, const uint32 _tmin, const uint32 _tmax, SCannyWorkspace *_pWorkspace);
33 | SCannyWorkspace *create_workspace_canny(const uint32 _width, const uint32 _height);
34 | void release_workspace_canny(SCannyWorkspace *_pWorkspace);
35 | //-------------------------------------
36 |
37 | //-------------------------------------
38 | void
39 | init_canny(const uint32 _width, const uint32 _height)
40 | {
41 | if(spCannyWorkspace == 0x0)
42 | {
43 | spCannyWorkspace = create_workspace_canny(_width, _height);
44 | }
45 | else if(spCannyWorkspace->width != _width || spCannyWorkspace->height != _height)
46 | {
47 | // TODO: Alert the user that using canny in different image sizes can decrease the efficiency
48 | release_workspace_canny(spCannyWorkspace);
49 | spCannyWorkspace = create_workspace_canny(_width, _height);
50 | }
51 | }
52 |
53 | //-------------------------------------
54 | void
55 | release_canny()
56 | {
57 | release_workspace_canny(spCannyWorkspace);
58 | spCannyWorkspace = 0x0;
59 | }
60 |
61 | //-------------------------------------
62 | void
63 | canny(uint8 *_pData, const uint32 _width, const uint32 _height, const uint32 _tmin, const uint32 _tmax)
64 | {
65 | init_canny(_width, _height);
66 | canny_priv(_pData, _width, _height, _tmin, _tmax, spCannyWorkspace);
67 | }
68 |
69 | //-------------------------------------
70 | void
71 | canny_priv(uint8 *_pData, const uint32 _width, const uint32 _height, const uint32 _tmin, const uint32 _tmax, SCannyWorkspace *_pWorkspace)
72 | {
73 | if(_pWorkspace == 0x0)
74 | {
75 | return;
76 | }
77 |
78 | // 1. Apply gaussian blur _img -> pMap
79 | applyGaussianBlur(_pData, _pWorkspace->pMap, _width, _height, 5);
80 |
81 | // 2. Compute gradients and directions
82 | getGradientsAndDirections(_pData, _width, _height, _pWorkspace->pGradient, _pWorkspace->pDirection);
83 |
84 | // 3. Compute local maxima
85 | computeLocalMaxima(_width, _height, _pWorkspace->pGradient, _pWorkspace->pDirection, _pWorkspace->pMaxima);
86 |
87 | // 4. Trace edges
88 | traceEdgesWithHysteresis(_pData, _width, _height, _tmin, _tmax, _pWorkspace);
89 | }
90 |
91 | //-------------------------------------
92 | void
93 | traceEdgesWithHysteresis(uint8 *_pData, const uint32 _width, const uint32 _height, const uint32 _tmin, const uint32 _tmax, SCannyWorkspace *_pWorkspace)
94 | {
95 | #if 0
96 | // Reuse array as a stack. width*height/2 elements should be enough.
97 | int32 *edges = (int32 *)_pWorkspace->pGradient;
98 | memset(edges, 0, sizeof(int32) * _width * _height);
99 | // Tracing edges with hysteresis . Non-recursive implementation.
100 | uint8 *pData = _pData;
101 | double *pMax = _pWorkspace->pMaxima;
102 | uint32 c = 1;
103 | for (int j = 1; j < _height - 1; ++j)
104 | {
105 | for (int i = 1; i < _width - 1; ++i)
106 | {
107 | if (pMax[c] >= _tmax && pData[c] == 0)
108 | { // trace edges
109 | pData[c] = MAX_BRIGHTNESS;
110 | int nedges = 1;
111 | edges[0] = c;
112 |
113 | do
114 | {
115 | nedges--;
116 | const int t = edges[nedges];
117 |
118 | int nbs[8]; // neighbours
119 | nbs[0] = t - _width; // nn
120 | nbs[1] = t + _width; // ss
121 | nbs[2] = t + 1; // ww
122 | nbs[3] = t - 1; // ee
123 | nbs[4] = nbs[0] + 1; // nw
124 | nbs[5] = nbs[0] - 1; // ne
125 | nbs[6] = nbs[1] + 1; // sw
126 | nbs[7] = nbs[1] - 1; // se
127 |
128 | for (int k = 0; k < 8; k++)
129 | {
130 | if (pMax[nbs[k]] >= _tmin && pData[nbs[k]] == 0)
131 | {
132 | pData[nbs[k]] = MAX_BRIGHTNESS;
133 | edges[nedges] = nbs[k];
134 | nedges++;
135 | }
136 | }
137 | } while (nedges > 0);
138 | }
139 | c++;
140 | }
141 | c += 2;
142 | }
143 | #else
144 | //uint32 x, y;
145 |
146 | // Reuse array as a stack. width*height/2 elements should be enough.
147 | uint32 numPoints = 0;
148 | uint32 *pPoints = (uint32 *)_pWorkspace->pGradient;
149 |
150 | // Mark double threshold _tmin and _tmax
151 | uint8 *pData = _pData;
152 | double *pMax = _pWorkspace->pMaxima;
153 | uint32 i, size = _width * _height;
154 |
155 | for (i = 0; i < size; ++i) {
156 | if (*pMax > _tmax)
157 | {
158 | *pData = MAX_BRIGHTNESS;
159 | }
160 | else if (*pMax > _tmin)
161 | {
162 | if(i > (_width + 1) && i < (size - _width - 1))
163 | {
164 | *pData = TMP_BRIGHTNESS;
165 | pPoints[numPoints] = i;
166 | ++numPoints;
167 | }
168 | else
169 | {
170 | *pData = 0;
171 | }
172 | }
173 | else
174 | {
175 | *pData = 0;
176 | }
177 | ++pMax;
178 | ++pData;
179 | }
180 |
181 | // 5. Refine edges with hysteresis
182 |
183 | for (i = 0; i < numPoints; ++i)
184 | {
185 | pData = _pData + pPoints[i];
186 | if (*(pData - _width - 1) == MAX_BRIGHTNESS ||
187 | *(pData - _width) == MAX_BRIGHTNESS ||
188 | *(pData - _width + 1) == MAX_BRIGHTNESS ||
189 | *(pData - 1) == MAX_BRIGHTNESS ||
190 | *(pData + 1) == MAX_BRIGHTNESS ||
191 | *(pData + _width - 1) == MAX_BRIGHTNESS ||
192 | *(pData + _width) == MAX_BRIGHTNESS ||
193 | *(pData + _width + 1) == MAX_BRIGHTNESS)
194 | {
195 | *pData = MAX_BRIGHTNESS;
196 | } else {
197 | *pData = 0;
198 | }
199 | }
200 | #endif
201 | }
202 |
203 | //-------------------------------------
204 | SCannyWorkspace *
205 | create_workspace_canny(const uint32 _width, const uint32 _height)
206 | {
207 | SCannyWorkspace * work = (SCannyWorkspace *)malloc(sizeof(SCannyWorkspace));
208 | work->width = _width;
209 | work->height = _height;
210 |
211 | uint32 size = _width * _height;
212 | work->pMap = (uint8 *)malloc(size * sizeof(uint8));
213 | work->pGradient = (double *)malloc(size * sizeof(double));
214 | work->pMaxima = (double *)malloc(size * sizeof(double));
215 | work->pDirection = (uint8 *)malloc(size * sizeof(uint8));
216 | return work;
217 | }
218 |
219 | //-------------------------------------
220 | void
221 | release_workspace_canny(SCannyWorkspace *_pWorkspace)
222 | {
223 | if(_pWorkspace != 0x0)
224 | {
225 | _pWorkspace->width = 0;
226 | _pWorkspace->height = 0;
227 | if(_pWorkspace->pMap != 0x0)
228 | {
229 | free(_pWorkspace->pMap);
230 | _pWorkspace->pMap = 0x0;
231 | }
232 |
233 | if(_pWorkspace->pGradient != 0x0)
234 | {
235 | free(_pWorkspace->pGradient);
236 | _pWorkspace->pGradient = 0x0;
237 | }
238 |
239 | if(_pWorkspace->pMaxima != 0x0)
240 | {
241 | free(_pWorkspace->pMaxima);
242 | _pWorkspace->pMaxima = 0x0;
243 | }
244 |
245 | if(_pWorkspace->pDirection != 0x0)
246 | {
247 | free(_pWorkspace->pDirection);
248 | _pWorkspace->pDirection = 0x0;
249 | }
250 |
251 | free(_pWorkspace);
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/src/canny.h:
--------------------------------------------------------------------------------
1 | #ifndef _CANNY_H_
2 | #define _CANNY_H_
3 |
4 |
5 | // https://github.com/brunokeymolen/canny/blob/master/canny.cpp
6 | // https://rosettacode.org/wiki/Canny_edge_detector#C
7 |
8 | #include "types.h"
9 |
10 | void init_canny(const uint32 _width, const uint32 _height);
11 | void release_canny();
12 | void canny(uint8 *_pData, uint32 _width, uint32 _height, const uint32 _tmin, const uint32 _tmax);
13 |
14 | #endif
15 |
--------------------------------------------------------------------------------
/src/common.c:
--------------------------------------------------------------------------------
1 | /*
2 | * Here are the methods that are so small or too common to have a dedicated file
3 | */
4 |
5 | #include
6 | #include
7 |
8 | void
9 | CalculateHistogram(const char *_pBuffer, uint32 _bufferSize, char _pHistogram[256])
10 | {
11 | int i;
12 | int idx;
13 | memset(_pHistogram, 0, 256);
14 | if(_pBuffer == 0x0 || _bufferSize == 0)
15 | return;
16 | for(i = 0; i < _size; ++i)
17 | {
18 | idx = _pBuffer[ptr];
19 | _pHistogram[idx]++;
20 | }
21 | }
22 |
23 | // http://www.labbookpages.co.uk/software/imgProc/otsuThreshold.html
24 | uint32
25 | CalculateOtsu(uint32 _bufferSize, char _pHistogram[256])
26 | {
27 | int i;
28 | int threshold = 0;
29 | int sumB = 0;
30 | int wB = 0;
31 | int wF = 0;
32 | float varMax = 0;
33 |
34 | // Total number of pixels
35 | int total = _bufferSize;
36 | int sum = 0;
37 | for (i=0 ; i<256 ; ++i)
38 | {
39 | sum += i * _pHistogram[i];
40 | }
41 |
42 | for (i=0 ; i<256 ; ++i)
43 | {
44 | wB += _pHistogram[t]; // Weight Background
45 | if (wB == 0) continue;
46 |
47 | wF = total - wB; // Weight Foreground
48 | if (wF == 0) break;
49 |
50 | sumB += (float) (i * _pHistogram[i]);
51 |
52 | float mB = (float)(sumB) / (float)wB; // Mean Background
53 | float mF = (float)(sum - sumB) / (float)wF; // Mean Foreground
54 |
55 | // Calculate Between Class Variance
56 | float varBetween = (float)wB * (float)wF * (mB - mF) * (mB - mF);
57 |
58 | // Check if new maximum found
59 | if (varBetween > varMax) {
60 | varMax = varBetween;
61 | threshold = t;
62 | }
63 | }
64 | return threshold;
65 | }
66 |
--------------------------------------------------------------------------------
/src/convolution.c:
--------------------------------------------------------------------------------
1 | #include "convolution.h"
2 | #include
3 | #include
4 | #include
5 |
6 | // Take care, enable this define can create artifacts in the vertical borders on small images
7 | #define IMPROVE_LOOPS
8 |
9 | uint8 applyConvolution(uint8 *_pData, uint32 _width, uint32 _height, uint32 _x, uint32 _y, uint8 *_pKernel, uint8 _kernel_size, uint32 _kernel_sum);
10 | uint8 decodeSegment(uint32 x, uint32 y);
11 |
12 | //-------------------------------------
13 | void getGradientsAndDirections(uint8 *_pData, uint32 _width, uint32 _height, double *_pGradient, uint8 *_pDirection)
14 | {
15 |
16 | const int32 Gx[] = {-1, 0, 1,
17 | -2, 0, 2,
18 | -1, 0, 1};
19 |
20 | const int32 Gy[] = { 1, 2, 1,
21 | 0, 0, 0,
22 | -1,-2,-1};
23 |
24 | // apply sobel kernels and store gradients and directions
25 | int kernel_size = 3;
26 | int kernel_half = kernel_size >> 1;
27 | //int leap = kernel_size - 1;
28 | int offset_xy = kernel_half; // 3x3
29 | int src_pos = offset_xy + (offset_xy * _width);
30 |
31 |
32 | #ifdef IMPROVE_LOOPS
33 | uint32 i, size = _height * _width - (_width * offset_xy * 2);
34 | for(i = 0; i < size; ++i)
35 | {
36 | #else
37 | for (int y = offset_xy; y < _height - offset_xy; ++y)
38 | {
39 | for (int x = offset_xy; x < _width - offset_xy; ++x)
40 | {
41 | #endif
42 | int32 convolve_X = 0.0;
43 | int32 convolve_Y = 0.0;
44 |
45 | int conv_pos = src_pos - offset_xy + (-offset_xy * _width);
46 | int k = 0;
47 | for (int ky = 0; ky < kernel_size; ky++) {
48 | for (int kx = 0; kx < kernel_size; kx++) {
49 | convolve_X += _pData[conv_pos] * Gx[k];
50 | convolve_Y += _pData[conv_pos] * Gy[k];
51 | ++conv_pos;
52 | ++k;
53 | }
54 | conv_pos += _width - kernel_size;
55 | }
56 |
57 | _pGradient[src_pos] = ((sqrt((convolve_X * convolve_X) + (convolve_Y * convolve_Y))));
58 | _pDirection[src_pos] = decodeSegment(convolve_X, convolve_Y);
59 |
60 | ++src_pos;
61 | }
62 | #ifndef IMPROVE_LOOPS
63 | src_pos += leap;
64 | }
65 | #endif
66 | }
67 |
68 | //-------------------------------------
69 | void computeLocalMaxima(uint32 _width, uint32 _height, double *_pGradient, uint8 *_pDirection, double *_pMaxima)
70 | {
71 | memcpy(_pMaxima, _pGradient, _width *_height * sizeof(double));
72 |
73 | // delete the points where de direcction and gradients does not fit
74 | int kernel_size = 3;
75 | int kernel_half = kernel_size >> 1;
76 | //int leap = kernel_size - 1;
77 | int offset_xy = kernel_half; // 3x3
78 | int pos = offset_xy + (offset_xy * _width);
79 |
80 | int32 lt = - (_width + 1);
81 | int32 tt = - _width;
82 | int32 rt = - (_width - 1);
83 | int32 ll = -1;
84 | int32 rr = 1;
85 | int32 lb = (_width - 1);
86 | int32 bb = _width;
87 | int32 rb = (_width + 1);
88 | double *pGrad = _pGradient + pos;
89 | double *pMax = _pMaxima + pos;
90 | uint8 *pDir = _pDirection + pos;
91 |
92 | #ifdef IMPROVE_LOOPS
93 | uint32 i, size = _height * _width - (_width * offset_xy * 2);
94 | for (i = 0; i < size; ++i)
95 | {
96 | #else
97 |
98 | for (int y = offset_xy; y < _height - offset_xy; ++y)
99 | {
100 | for (int x = offset_xy; x < _width - offset_xy; ++x)
101 | {
102 | #endif
103 | if ((*pDir == 0 ) ||
104 | (*pDir == 1 && (*(pGrad + ll) >= *pGrad || *(pGrad + rr) >= *pGrad)) ||
105 | (*pDir == 2 && (*(pGrad + rt) >= *pGrad || *(pGrad + lb) >= *pGrad)) ||
106 | (*pDir == 3 && (*(pGrad + tt) >= *pGrad || *(pGrad + bb) >= *pGrad)) ||
107 | (*pDir == 4 && (*(pGrad + lt) >= *pGrad || *(pGrad + rb) >= *pGrad)))
108 | {
109 | *pMax = 0;
110 | }
111 | ++pGrad;
112 | ++pMax;
113 | ++pDir;
114 | }
115 |
116 | #ifndef IMPROVE_LOOPS
117 | pos += leap;
118 | pGrad += leap;
119 | pMax += leap;
120 | pDir += leap;
121 | }
122 | #endif
123 | }
124 |
125 | //-------------------------------------
126 | uint8 applyGaussianBlur(uint8 *_pIn, uint8 *_pOut, uint32 _width, uint32 _height, uint32 _kernel_size)
127 | {
128 |
129 | const uint32 Gaus3x3Sum = 16;
130 | const uint8 Gaus3x3[] = { 1, 2, 1,
131 | 2, 4, 2, // * 1/16
132 | 1, 2, 1};
133 |
134 |
135 | const uint32 Gaus5x5Sum = 159;
136 | const uint8 Gaus5x5[] = { 2, 4, 5, 4, 2,
137 | 4, 9, 12, 9, 4,
138 | 5, 12, 15, 12, 5, // * 1/159
139 | 4, 9, 12, 9, 4,
140 | 2, 4, 5, 4, 2 };
141 |
142 | //uint8 *pSrc = _pIn;
143 | uint8 *pDst = _pOut;
144 | uint32 px;
145 | uint32 py;
146 | uint8 *pKernel;
147 | uint32 kernel_sum;
148 | if(_kernel_size == 5)
149 | {
150 | pKernel = (uint8 *)Gaus5x5;
151 | kernel_sum = Gaus5x5Sum;
152 | } else {
153 | _kernel_size = 3;
154 | pKernel = (uint8 *)Gaus3x3;
155 | kernel_sum = Gaus3x3Sum;
156 | }
157 | px = _kernel_size;
158 | py = _kernel_size;
159 |
160 | if(_kernel_size > _width || _kernel_size > _height)
161 | {
162 | return 0;
163 | }
164 |
165 |
166 | // Maybe is better to copy the full image in the destiantion, so we dont need to copy segements of memory
167 | // after apply the blur ¿?¿?¿?
168 | // memcpy(_pOut, _pIn, _width*_height*sizeof(uint8));
169 |
170 | uint32 maxWidth = _width - _kernel_size;
171 | uint32 maxHeight = _height - _kernel_size;
172 | pDst = _pOut + (_kernel_size + _kernel_size * _width);
173 | for(py = _kernel_size; py < maxHeight; ++py)
174 | {
175 | for(px = _kernel_size; px < maxWidth; ++px)
176 | {
177 | uint8 pixel = applyConvolution(_pIn, _width, _height, px, py, pKernel, _kernel_size, kernel_sum);
178 | *pDst = pixel;
179 | ++pDst;
180 | }
181 | pDst += _kernel_size-1;
182 | }
183 | /*
184 | // Now we need to copy the unblurred pixels, all the kernel_size_half frame
185 | //
186 | // ************
187 | // ************
188 | // ** **
189 | // ** **
190 | // ************
191 | // ************
192 | //
193 |
194 | uint32 kernel_half = _kernel_size >> 1;
195 | pSrc = _pIn;
196 | pDst = _pOut;
197 | // full horizontal lines
198 | for(py = 0; py < kernel_half; ++py)
199 | {
200 | memcpy(pSrc, pDst, _width);
201 | pSrc += _width;
202 | pDst += _width;
203 | }
204 |
205 | //vertical parts
206 | memcpy(pSrc, pDst, kernel_half);
207 | pSrc += _width - kernel_half;
208 | pDst += _width - kernel_half;
209 | for(py = kernel_half; py < _height - kernel_half; ++py)
210 | {
211 | memcpy(pSrc, pDst, _kernel_size - 1);
212 | pSrc += _width;
213 | pDst += _width;
214 | }
215 |
216 | // full horizontal lines
217 | pSrc += kernel_half;
218 | pDst += kernel_half;
219 | for(py = _height - kernel_half; py < _height; ++py)
220 | {
221 | memcpy(pSrc, pDst, _width);
222 | pSrc += _width;
223 | pDst += _width;
224 | }
225 | */
226 | return 1;
227 | }
228 |
229 |
230 |
231 | //-------------------------------------
232 | uint8 applyConvolution(uint8 *_pData, uint32 _width, uint32 _height, uint32 _x, uint32 _y, uint8 *_pKernel, uint8 _kernel_size, uint32 _kernel_sum)
233 | {
234 |
235 | // these security checks can be avoid if we only call this method from our GaussianBlur method
236 | // if(kernel_half > _width || kernel_half > _height)
237 | // {
238 | // return 0;
239 | // }
240 | // if(_x < kernel_half || _y < kernel_half || _x > (_width - kernel_half) || _y > (_height - kernel_half))
241 | // {
242 | // if(_x < _width || _y < _height)
243 | // return _pData[_x + _y * _width];
244 | // return 0;
245 | // }
246 |
247 | uint32 convolution = 0;
248 | const uint32 kernel_half = _kernel_size >> 1;
249 | const uint32 leap = _width - _kernel_size;
250 | _pData += (_x - kernel_half) + (_y - kernel_half) * _width; // initial offset
251 | for(int j = 0; j < _kernel_size; ++j)
252 | {
253 | for(int i = 0; i < _kernel_size; ++i)
254 | {
255 | convolution += *(_pData) * *(_pKernel);
256 | ++_pData;
257 | ++_pKernel;
258 | }
259 | _pData += leap;
260 | }
261 | return (uint8)((double)convolution / (double)_kernel_sum);
262 | }
263 |
264 | //-------------------------------------
265 | uint8 decodeSegment(uint32 x, uint32 y)
266 | {
267 |
268 | uint8 segment = 0;
269 |
270 | if (x != 0.0 && y != 0.0)
271 | {
272 | double theta = atan2(x, y); // radians. atan2 range: -PI,+PI,
273 | // theta : 0 - 2PI
274 | //#define RANGE_NORMALIZE
275 | #ifdef RANGE_NORMALIZE
276 | theta = (float)(fmod(theta + M_PI, M_PI) / M_PI) * 8;
277 | if (theta <= 1 || theta > 7) // 0 deg
278 | segment = 1; // "-"
279 | else if (theta > 1 && theta <= 3) // 45 deg
280 | segment = 2; // "/"
281 | else if (theta > 3 && theta <= 5) // 90 deg
282 | segment = 3; // "|"
283 | else if (theta > 5 && theta <= 7) // 135 deg
284 | segment = 4; // "\"
285 | #else
286 | theta = theta * (180.0 / M_PI); // degrees
287 |
288 | if ((theta <= 22.5 && theta >= -22.5) || (theta <= -157.5) || (theta >= 157.5))
289 | segment = 1; // "-"
290 | else if ((theta > 22.5 && theta <= 67.5) || (theta > -157.5 && theta <= -112.5))
291 | segment = 2; // "/"
292 | else if ((theta > 67.5 && theta <= 112.5) || (theta >= -112.5 && theta < -67.5))
293 | segment = 3; // "|"
294 | else if ((theta >= -67.5 && theta < -22.5) || (theta > 112.5 && theta < 157.5))
295 | segment = 4; // "\"
296 | #endif
297 | }
298 | return segment;
299 | }
300 |
301 |
--------------------------------------------------------------------------------
/src/convolution.h:
--------------------------------------------------------------------------------
1 | #ifndef _CONVOLUTION_H_
2 | #define _CONVOLUTION_H_
3 | #include "types.h"
4 |
5 | void getGradientsAndDirections(uint8 *_pData, uint32 _width, uint32 _height, double *_pGradient, uint8 *_pDirection);
6 | void computeLocalMaxima(uint32 _width, uint32 _height, double *_pGradient, uint8 *_pDirection, double *_pMaxima);
7 | uint8 applyGaussianBlur(uint8 *_pIn, uint8 *_pOut, uint32 _width, uint32 _height, uint32 _kernel_size);
8 |
9 | #endif
10 |
--------------------------------------------------------------------------------
/src/crop.c:
--------------------------------------------------------------------------------
1 | #include "perspective.h"
2 | #include "image.h"
3 | #include "bmp.h"
4 |
5 | int main(int argc, char *argv[])
6 | {
7 |
8 | uint32 side = 255;
9 | uint32 w = 255;
10 | uint32 h = 255;
11 | //STextureTriangle tri1 = set_triangle(0, 0, 0.05, 0.8, 0, h, 0.9, 0.1, w, 0, 0.2, 0.05);
12 | //STextureTriangle tri2 = set_triangle(w, 0, 0.2, 0.05, 0, h, 0.9, 0.1, w, h, 0.5, 0.0);
13 | STextureTriangle tri1 = set_triangle(0, 0, 0.5, 0.0, 0, h, 0.0, 0.5, w, 0, 1.0, 0.5);
14 | STextureTriangle tri2 = set_triangle(w, 0, 1.0, 0.5, 0, h, 0.0, 0.5, w, h, 0.5, 1.0);
15 |
16 | SImage imgOut, imgRGB;
17 |
18 | reset_image(&imgOut);
19 | reset_image(&imgRGB);
20 |
21 | // create XOR image
22 | imgRGB = create_image(side, side, PF_ARGB);
23 | for(uint32 y = 0; y < side; ++y)
24 | {
25 | for(uint32 x = 0; x < side; ++x)
26 | {
27 | //imgRGB.mpData[(x+y*side)] = (x % 255) ^ (y % 255);
28 | imgRGB.mpData[(x+y*side) * 4 + 0] = (x % 255) ^ (y % 255);
29 | imgRGB.mpData[(x+y*side) * 4 + 1] = (x % 255) ^ (y % 255);
30 | imgRGB.mpData[(x+y*side) * 4 + 2] = (x % 255) ^ (y % 255);
31 | imgRGB.mpData[(x+y*side) * 4 + 3] = (x % 255) ^ (y % 255);
32 | }
33 | }
34 | save_image("perspective_in.bmp", &imgRGB);
35 |
36 | // crop image with perspective
37 | imgOut = create_image(w, h, PF_ARGB);
38 | render_textured_triangle(&tri1, &imgRGB, &imgOut);
39 | render_textured_triangle(&tri2, &imgRGB, &imgOut);
40 | save_image("perspective.bmp", &imgOut);
41 |
42 |
43 | release_image(&imgRGB);
44 | release_image(&imgOut);
45 | }
46 |
--------------------------------------------------------------------------------
/src/edges.c:
--------------------------------------------------------------------------------
1 | #include "canny.h"
2 | #include "hough.h"
3 | #include "image.h"
4 | #include "bmp.h"
5 | #include "timer.h"
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 |
12 | void draw2(SImage *_pImg, float _theta, float _rho);
13 | void draw(SImage *_pImg, float _theta, float _rho);
14 |
15 | #define IS_ZERO(x) (CHECK_EPSILON(x, 0.0001))
16 | #define CHECK_EPSILON(x, e) (fabs(x) <= e)
17 |
18 |
19 | char *input_filename = "in.bmp";
20 | int hough_threshold = 150;
21 | int canny_min_threshold = 50;
22 | int canny_max_threshold = 125;
23 | int apply_canny = 1;
24 | int max_lines = 16;
25 | int verbose = 0;
26 |
27 | #define STR_CANNY_DISABLE "--disable_canny"
28 | #define STR_VERBOSE "--verbose"
29 |
30 | #define STR_HOUGH_THRESHOLD "-hough"
31 | #define STR_CANNY_MIN_THRESHOLD "-canny_min"
32 | #define STR_CANNY_MAX_THRESHOLD "-canny_max"
33 | #define STR_MAX_LINES "-lines"
34 | #define STR_INPUT_FILENAME "-input"
35 |
36 | int
37 | get_args(int argc, char *argv[])
38 | {
39 | int ret = 0;
40 | for(int i = 1; i < argc; ++i)
41 | {
42 | if(strcmp(argv[i], STR_CANNY_DISABLE) == 0)
43 | {
44 | apply_canny = 0;
45 | }
46 | else if(strcmp(argv[i], STR_HOUGH_THRESHOLD) == 0)
47 | {
48 | ++i;
49 | hough_threshold = atoi(argv[i]);
50 | }
51 | else if(strcmp(argv[i], STR_CANNY_MIN_THRESHOLD) == 0)
52 | {
53 | ++i;
54 | canny_min_threshold = atoi(argv[i]);
55 | }
56 | else if(strcmp(argv[i], STR_CANNY_MAX_THRESHOLD) == 0)
57 | {
58 | ++i;
59 | canny_max_threshold = atoi(argv[i]);
60 | }
61 | else if(strcmp(argv[i], STR_MAX_LINES) == 0)
62 | {
63 | ++i;
64 | max_lines = atoi(argv[i]);
65 | }
66 | else if(strcmp(argv[i], STR_INPUT_FILENAME) == 0)
67 | {
68 | ++i;
69 | input_filename = argv[i];
70 | }
71 | else if(strcmp(argv[i], STR_VERBOSE) == 0)
72 | {
73 | verbose = 1;
74 | }
75 | else if(strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "-?") == 0 || strcmp(argv[i], "/h") == 0)
76 | {
77 | ret = 1;
78 | }
79 | }
80 | return ret;
81 | }
82 |
83 | void
84 | print_help()
85 | {
86 | printf("\n");
87 | printf("You can set any of these properties, set the property name followed by the parameter value:\n");
88 | printf(" '%s'\t\tnumber (default is 150) [indicates the value for hough threshold, thats the number of points to define a line]\n", STR_HOUGH_THRESHOLD);
89 | printf(" '%s'\t\tnumber (default is 50) [indicates the min threshold for canny filter]\n", STR_CANNY_MIN_THRESHOLD);
90 | printf(" '%s'\t\tnumber (default is 125) [indicates the max threshold for canny filter]\n", STR_CANNY_MAX_THRESHOLD);
91 | printf(" '%s'\t\tnumber (default is 16) [indicates the maximun number of lines to store]\n", STR_MAX_LINES);
92 | printf(" '%s'\t\tfilename (default is 'in.bmp') [indicates the filename of the input image]\n", STR_INPUT_FILENAME);
93 | printf("\n");
94 | printf("You can set any of these flags:\n");
95 | printf(" '%s'\t[indicates it MUST NOT apply canny to the input image]\n", STR_CANNY_DISABLE);
96 | printf(" '%s'\t\t[indicates it MUST dump info messages]\n", STR_VERBOSE);
97 | printf("\n");
98 | }
99 |
100 | int
101 | main(int argc, char *argv[])
102 | {
103 | if(get_args(argc, argv))
104 | {
105 | print_help();
106 | return 0;
107 | }
108 |
109 | //if(verbose == 1)
110 | {
111 | printf("Current properties:\n");
112 | printf(" %s\t %s\n", STR_INPUT_FILENAME, input_filename);
113 | printf(" %s\t %d\n", STR_HOUGH_THRESHOLD, hough_threshold);
114 | printf(" %s\t %d\n", STR_CANNY_MIN_THRESHOLD, canny_min_threshold);
115 | printf(" %s\t %d\n", STR_CANNY_MAX_THRESHOLD, canny_max_threshold);
116 | printf(" %s\t %d\n", STR_MAX_LINES, max_lines);
117 | if(apply_canny == 0)
118 | printf(" %s\n", STR_CANNY_DISABLE);
119 | if(verbose == 1)
120 | printf(" %s\n", STR_VERBOSE);
121 | }
122 |
123 | //const char *fileIn = input_filename;
124 |
125 | const uint32 maxLines = max_lines;
126 | SPolar lineBuffer[maxLines];
127 |
128 | SImage imgRGB, imgGray, img;
129 |
130 | reset_image(&imgRGB);
131 | reset_image(&imgGray);
132 | reset_image(&img);
133 |
134 | ELoadError error = load_image(input_filename, &imgRGB);
135 | if(error != LE_NO_ERROR)
136 | {
137 | uint32 side = 350;
138 | imgRGB = create_image(side, side, PF_GRAY);
139 | for(uint32 y = 0; y < side; ++y)
140 | {
141 | for(uint32 x = 0; x < side; ++x)
142 | {
143 | imgRGB.mpData[x+y*side] = (x % 255) ^ (y % 255);
144 | }
145 | }
146 | save_image(input_filename, &imgRGB);
147 | error = LE_NO_ERROR;
148 | }
149 |
150 | if(error == LE_NO_ERROR)
151 | {
152 | imgGray = convertToGray(imgRGB);
153 | #if 1
154 | copy_image(&imgGray, &img);
155 | if(apply_canny == 1)
156 | canny(img.mpData, img.mWidth, img.mHeight, canny_min_threshold, canny_max_threshold);
157 | save_image("out.bmp", &img);
158 | uint32 foundLines = hough(img.mpData, img.mWidth, img.mHeight, hough_threshold, lineBuffer, maxLines);
159 |
160 | save_hough_workspace("out_hough_workspace.bmp");
161 |
162 |
163 | //if(verbose == 1)
164 | {
165 | printf("%d lines found.\n", foundLines);
166 | printf("print lines.\n");
167 | }
168 | release_image(&imgRGB);
169 | imgRGB = convertToARGB(img);
170 | uint32 i;
171 | for(i =0; i < foundLines; ++i)
172 | {
173 | draw(&imgRGB, lineBuffer[i].theta, lineBuffer[i].rho);
174 | //draw2(&imgRGB, lineBuffer[i].theta, lineBuffer[i].rho);
175 | }
176 | //draw_line(&imgRGB, 0xFF0000, 0, 0, imgRGB.mWidth, imgRGB.mHeight);
177 | //draw_line(&imgRGB, 0xFF00FF00, imgRGB.mWidth/2, 0, imgRGB.mWidth/2, imgRGB.mHeight);
178 | save_image("out_hough.bmp", &imgRGB);
179 | #else
180 | uint64 currentTime;
181 | uint64 lastTime;
182 | uint64 startTime = current_time_ms();
183 | uint32 frames = 0;
184 | uint64 cannyTime = 0;
185 | uint64 houghTime = 0;
186 |
187 | lastTime = (current_time_ms() - startTime)/1000;
188 | while(1)
189 | {
190 | copy_image(&imgGray, &img);
191 |
192 | uint64 preTime = current_time_ms();
193 | canny(img.mpData, img.mWidth, img.mHeight, 50, 125);
194 | uint64 postTime = current_time_ms();
195 | uint32 foundLines = hough(img.mpData, img.mWidth, img.mHeight, 8, lineBuffer, maxLines);
196 | uint64 postTime2 = current_time_ms();
197 | ++frames;
198 | cannyTime += postTime - preTime;
199 | houghTime += postTime2 - postTime;
200 |
201 | //printf("canny: %llums, hough: %llums, total: %llums\n", postTime - preTime, postTime2 - postTime, postTime2 - preTime);
202 | currentTime = (current_time_ms() - startTime)/1000;
203 | if(currentTime != lastTime)
204 | {
205 | printf("fps: %d, canny: %fms, hough: %fms\n", frames, (double)cannyTime / (double)frames, (double)houghTime / (double)frames);
206 | lastTime = currentTime;
207 | cannyTime = 0;
208 | houghTime = 0;
209 | frames = 0;
210 | }
211 | }
212 | #endif
213 | // printf("found %d lines. (max %d)\n", foundLines, maxLines);
214 | // for(uint32 i = 0; i < foundLines; ++i)
215 | // {
216 | // printf("line %d) r: %f, t: %f\n", i, lineBuffer[i].rho, lineBuffer[i].theta);
217 | // }
218 |
219 | //save_image("out.bmp", &img);
220 |
221 | }
222 | else
223 | {
224 | char *pError = "Unknonwn error";
225 | switch(error)
226 | {
227 | case LE_NO_ERROR:
228 | pError = "NO_ERROR";
229 | break;
230 | case LE_CANT_OPEN:
231 | pError = "CANT_OPEN";
232 | break;
233 | case LE_CANT_READ:
234 | pError = "CANT_READ";
235 | break;
236 | case LE_CANT_WRITE:
237 | pError = "CANT_WRITE";
238 | break;
239 | case LE_INVALID_MAGIC:
240 | pError = "INVALID_MAGIC";
241 | break;
242 | }
243 | printf("Error '%s' while opening '%s'\n", pError, input_filename);
244 | }
245 |
246 | release_image(&imgRGB);
247 | release_image(&imgGray);
248 | release_image(&img);
249 | release_canny();
250 | release_hough();
251 |
252 | return 0;
253 | }
254 |
255 |
256 | void draw2(SImage *_pImg, float _theta, float _rho)
257 | {
258 | int x1, y1, x2, y2;
259 | x1 = y1 = x2 = y2 = 0;
260 | uint32 w = _pImg->mWidth;
261 | uint32 h = _pImg->mHeight;
262 |
263 | if(_theta >= 45*DEG2RAD && _theta <= 135*DEG2RAD)
264 | {
265 | //y = (r - x cos(t)) / sin(t)
266 | x1 = 0;
267 | y1 = ((double)(_rho) - ((x1 - (w/2) ) * cos(_theta))) / sin(_theta) + (h / 2);
268 | x2 = w;
269 | y2 = ((double)(_rho) - ((x2 - (w/2) ) * cos(_theta))) / sin(_theta) + (h / 2);
270 | }
271 | else
272 | {
273 | //x = (r - y sin(t)) / cos(t);
274 | y1 = 0;
275 | x1 = ((double)(_rho) - ((y1 - (h/2) ) * sin(_theta))) / cos(_theta) + (w / 2);
276 | y2 = h;
277 | x2 = ((double)(_rho) - ((y2 - (h/2) ) * sin(_theta))) / cos(_theta) + (w / 2);
278 | }
279 | printf("draw line2: t:%f, r:%f => (%d, %d) - (%d, %d)\n", _theta, _rho, x1, y1, x2, y2);
280 | }
281 |
282 |
283 | void draw(SImage *_pImg, float _theta, float _rho)
284 | {
285 | int x1, y1, x2, y2;
286 | uint32 w = _pImg->mWidth;
287 | uint32 h = _pImg->mHeight;
288 |
289 |
290 | double a = cos(_theta);
291 | double b = sin(_theta);
292 | double x0 = a * _rho;
293 | double y0 = b * _rho;
294 | double x = x0 - b; // (cos(_theta) * _rho) - sin(_theta)
295 | double y = y0 + a; // (sin(_theta) * _rho) + cos(_theta)
296 |
297 |
298 | double m = (b != 0) ? (-a / b) : 0;
299 | double q = (b != 0) ? (_rho / b) : 0;
300 |
301 | // double r = sqrt( x*x + y*y );
302 | // double t = inv_tan( y / x );
303 |
304 |
305 |
306 | // x1 = (int)(x0 + 1000*(-b));
307 | // y1 = (int)(y0 + 1000*(a));
308 | // x2 = (int)(x0 - 1000*(-b));
309 | // y2 = (int)(y0 - 1000*(a));
310 |
311 |
312 | // y - y0 = (y0 + a) - y0 => a
313 | // x - x0 = (x0 - b) - x0 => -b
314 | // coefA = (y - y0) / (x - x0) => (a / -b) => m
315 | // _r / b
316 | // coefB = y0 - (coefA * x0) => (b * _rho) - ((a / -b) * a * _rho)
317 | //
318 | double coefA = ((x != x0) ? ((y - y0) / (x - x0)) : 0.0);
319 | double coefB = y0 - (coefA * x0);
320 |
321 | if (fabs(b) < (M_PI / 4))
322 | {//vertical line : from y = 0 to y = frameSize.height
323 | y1 = 0;
324 | x1 = (coefA != 0.0 ? (int)(-coefB / coefA) : (int)x0) ;
325 | y2 = h;
326 | x2 = (coefA != 0.0 ? (int)((y2 - coefB) / coefA) : (int)x0) ;
327 | }
328 | else
329 | {//horizontal line : from x = 0 to x = frameSize.width
330 | x1 = 0;
331 | y1 = (int)coefB;
332 | x2 = w;
333 | y2 = (int)(coefA * x2 + coefB) ;
334 | }
335 |
336 |
337 | //--
338 | uint32 i = 0;
339 | SCartesian p[4];
340 | p[0].x = 0;
341 | p[0].y = 0;
342 | p[1].x = 0;
343 | p[1].y = 0;
344 | if(IS_ZERO(m))
345 | {
346 | if (fabs(b) < (M_PI / 4))
347 | {
348 | p[i].x = x0 + w/2;
349 | p[i].y = 0;
350 | ++i;
351 | p[i].x = x0 + w/2;
352 | p[i].y = h;
353 | ++i;
354 | }
355 | else
356 | {
357 | p[i].x = 0;
358 | p[i].y = y0 + h/2;
359 | ++i;
360 | p[i].x = w;
361 | p[i].y = y0 + h/2;
362 | ++i;
363 | }
364 | }
365 | else
366 | {
367 | // http://www.keymolen.com/2013/05/hough-transformation-c-implementation.html
368 | i = 0;
369 | if(_theta >= (M_PI/4) && _theta <= (3*M_PI/4))
370 | {
371 | //y = (r - x cos(t)) / sin(t)
372 | p[i].x = 0;
373 | p[i].y = (_rho + ((w/2) * a)) / b + (h/2);
374 | ++i;
375 | p[i].x = w - 0;
376 | p[i].y = (_rho - ((w/2) * a)) / b + (h/2);
377 | ++i;
378 | }
379 | else
380 | {
381 | //x = (r - y sin(t)) / cos(t);
382 | p[i].y = 0;
383 | p[i].x = (_rho + ((h/2) * b)) / a + (w/2);
384 | ++i;
385 | p[i].y = h - 0;
386 | p[i].x = (_rho - ((h/2) * b)) / a + (w/2);
387 | ++i;
388 | }
389 | }
390 |
391 | if(verbose == 1)
392 | {
393 | printf("draw line: t:%f (%.02fº), r:%.02f --> coef( %f, %f) (m: %f, q: %f) px: (%f, %f)\n", _theta, _theta * RAD2DEG, _rho, coefA, coefB, m, q, x0, y0);
394 | printf(" line: (%03d, %03d) - (%03d, %03d)\n", x1, y1, x2, y2);
395 | printf(" line: (%03d, %03d) - (%03d, %03d) \n", p[0].x, p[0].y, p[1].x, p[1].y);
396 | }
397 | //draw_line(_pImg, 0xFF0000, x1, y1, x2, y2);
398 | draw_line(_pImg, 0xFF0000, p[0].x, p[0].y, p[1].x, p[1].y);
399 |
400 | //draw_line(_pImg, 0xFF00FF, x1, y1, x2, y2);
401 | }
402 |
403 |
--------------------------------------------------------------------------------
/src/hough.c:
--------------------------------------------------------------------------------
1 | #include "hough.h"
2 | #include "image.h"
3 | #include "bmp.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | //-------------------------------------
10 | typedef struct SCollisionPoint
11 | {
12 | uint32 index;
13 | uint32 acc;
14 | }SCollisionPoint;
15 | //-------------------------------------
16 |
17 | //-------------------------------------
18 | typedef struct SHoughWorkspace
19 | {
20 | //--
21 | uint32 width;
22 | uint32 height;
23 | double accMaxR;
24 | uint32 accHeight;
25 | uint32 accWidth;
26 | uint32 *pAccum;
27 | uint32 accCenterX;
28 | uint32 accCenterY;
29 |
30 | //--
31 | SCollisionPoint *pCandidates;
32 | uint32 numCandidates;
33 |
34 | //--
35 | float *pSin;
36 | float *pCos;
37 |
38 | } SHoughWorkspace;
39 | //-------------------------------------
40 |
41 |
42 | //-------------------------------------
43 | static SHoughWorkspace *spHoughWorkspace = 0x0;
44 | //-------------------------------------
45 |
46 | uint32 HoughLinesStandard(uint8 *_pData, uint32 _width, uint32 _height, uint32 _threshold, SPolar *_lineBuffer, uint32 _size, SHoughWorkspace *_pWorkspace);
47 | uint8 CheckMaximaLocal(uint32 *_pAccum, uint32 _index, uint32 _width, uint32 _height);
48 | uint8 insert_sort(uint32 _idx, uint32 _acc, SHoughWorkspace *_pWorkspace);
49 | SHoughWorkspace *create_workspace_hough(const uint32 _width, const uint32 _height);
50 | void reset_workspace_hough(SHoughWorkspace *_pWorkspace);
51 | void release_workspace_hough(SHoughWorkspace *_pWorkspace);
52 | //-------------------------------------
53 |
54 | //-------------------------------------
55 | void
56 | init_hough(const uint32 _width, const uint32 _height)
57 | {
58 | if(spHoughWorkspace == 0x0)
59 | {
60 | spHoughWorkspace = create_workspace_hough(_width, _height);
61 | }
62 | else if(spHoughWorkspace->width != _width || spHoughWorkspace->height != _height)
63 | {
64 | // TODO: Alert the user that using hough in different image sizes can decrease the efficiency
65 | release_workspace_hough(spHoughWorkspace);
66 | spHoughWorkspace = create_workspace_hough(_width, _height);
67 | }
68 | }
69 |
70 | //-------------------------------------
71 | void
72 | release_hough()
73 | {
74 | release_workspace_hough(spHoughWorkspace);
75 | spHoughWorkspace = 0x0;
76 | }
77 |
78 | //-------------------------------------
79 | uint32
80 | hough(uint8 *_pData, uint32 _width, uint32 _height, uint32 _threshold, SPolar *_lineBuffer, uint32 _size)
81 | {
82 | init_hough(_width, _height);
83 | return HoughLinesStandard(_pData, _width, _height, _threshold, _lineBuffer, _size, spHoughWorkspace);
84 | }
85 |
86 | //
87 | // source from openCV library, adapted as needed
88 | //
89 | //-------------------------------------
90 | uint32
91 | HoughLinesStandard(uint8 *_pData, uint32 _width, uint32 _height, uint32 _threshold,
92 | SPolar *_lineBuffer, uint32 _size, SHoughWorkspace *_pWorkspace)
93 | {
94 | int32 x, y;
95 | uint32 r, t;
96 |
97 | uint32 *pAccum = _pWorkspace->pAccum;
98 | float *pSin = _pWorkspace->pSin;
99 | float *pCos = _pWorkspace->pCos;
100 | int32 centerX = _width / 2;
101 | int32 centerY = _height / 2;
102 | double accMaxR = _pWorkspace->accMaxR;
103 | double accHeight = _pWorkspace->accHeight;
104 | //uint8* pData = _pData;
105 |
106 | reset_workspace_hough(_pWorkspace);
107 |
108 | // stage 1. fill accumulator
109 | for( y = 0; y < _height; ++y )
110 | {
111 | for( x = 0; x < _width; ++x )
112 | {
113 | if( *_pData > 0 )
114 | {
115 | double posX = (double)(x - centerX);
116 | double posY = (double)(y - centerY);
117 | //printf(" - pixel white\n");
118 | for(t = 0; t < _pWorkspace->accWidth; ++t )
119 | {
120 | r = (uint32)((posX * pCos[t]) + (posY * pSin[t]) + accMaxR + 0.5f);
121 | pAccum[(r * _pWorkspace->accWidth) + t]++;
122 | }
123 | }
124 | ++_pData;
125 | }
126 | }
127 |
128 |
129 | // stage 2. find local maximums and store the candidates in a sorted array
130 | for(r = 0; r < _pWorkspace->accHeight; ++r )
131 | {
132 | for(t = 0; t < _pWorkspace->accWidth; ++t )
133 | {
134 | uint32 base = (r * _pWorkspace->accWidth) + t;
135 | if( pAccum[base] > _threshold && CheckMaximaLocal(pAccum, base, _pWorkspace->accWidth, _pWorkspace->accHeight) == 0)
136 | {
137 | //printf(" - Sort line %d (r: %d, t: %d)\n", base, r, t);
138 | insert_sort(base, pAccum[base], _pWorkspace);
139 | }
140 | }
141 | }
142 |
143 | // stage 3. store the first lines to the output buffer
144 | uint32 i, linesMax = ((_size <= _pWorkspace->numCandidates) ? _size : _pWorkspace->numCandidates);
145 | for( i = 0; i < linesMax; i++ )
146 | {
147 | SPolar line;
148 | int32 idx = _pWorkspace->pCandidates[i].index;
149 | line.rho = (float)(((float)idx / (float)_pWorkspace->accWidth) - (accHeight/2));
150 | //line.theta = (idx - ((int)line.rho * _pWorkspace->accWidth)) * DEG2RAD;
151 | line.theta = (float)((idx % _pWorkspace->accWidth) * DEG2RAD);
152 | _lineBuffer[i] = line;
153 | printf(" * Save line %d (r: %f, t: %f) = %d\n", idx, line.rho, line.theta, _pWorkspace->pCandidates[i].acc);
154 | }
155 |
156 | return linesMax;
157 | }
158 |
159 |
160 | void
161 | save_hough_workspace(const char *filename)
162 | {
163 |
164 | int32 x, y;
165 | int32 max;
166 |
167 | uint32 *pAccum = spHoughWorkspace->pAccum;
168 | uint32 w = spHoughWorkspace->accWidth;
169 | uint32 h = spHoughWorkspace->accHeight;
170 |
171 | max = 0;
172 | for(y = 0; y < h; ++y )
173 | {
174 | for(x = 0; x < w; ++x )
175 | {
176 | uint32 base = (y * w) + x;
177 | if(pAccum[base] > max)
178 | max = pAccum[base];
179 | }
180 | }
181 |
182 | SImage img = create_image(w, h, 1);
183 | for(y = 0; y < h; ++y )
184 | {
185 | for(x = 0; x < w; ++x )
186 | {
187 | uint32 base = (y * w) + x;
188 | img.mpData[base] = (uint8)(((float)pAccum[base] / (float)max ) * 255);
189 | }
190 | }
191 | save_image(filename, &img);
192 | release_image(&img);
193 | }
194 |
195 | //-------------------------------------
196 | uint8
197 | CheckMaximaLocal(uint32 *_pAccum, uint32 _index, uint32 _width, uint32 _height)
198 | {
199 | uint8 ret = 0;
200 | //#define CHECK_MAXIMA_SIDES
201 | #ifdef CHECK_MAXIMA_SIDES
202 | ret = (_index > _width && _index < (_height -1)*_width &&
203 | _pAccum[_index] > _pAccum[_index - 1] &&
204 | _pAccum[_index] >= _pAccum[_index + 1] &&
205 | _pAccum[_index] > _pAccum[_index - _width] &&
206 | _pAccum[_index] >= _pAccum[_index + _width] ) ? 0 : 1;
207 | #else
208 | uint32 local_max = _pAccum[_index];
209 | uint32 max = local_max;
210 | int32 size = 4;
211 | int32 x = _index % _width;
212 | int32 y = _index / _width;
213 | uint32 minY = ((y - size) >= 0 ) ? (y - size) : 0;
214 | uint32 maxY = ((y + size) < _height) ? (y + size) : _height;
215 | uint32 minX = ((x - size) >= 0 ) ? (x - size) : 0;
216 | uint32 maxX = ((x + size) < _width ) ? (x + size) : _width;
217 | uint32 pos = minX + minY * _width;
218 | uint32 w = maxX - minX;
219 | ret = 0;
220 | for(int32 ly = minY; ly < maxY; ++ly)
221 | {
222 | for(int32 lx = minX; lx < maxX; ++lx)
223 | {
224 | if( _pAccum[pos] > max )
225 | {
226 | ret = 1;
227 | ly = maxY;
228 | lx = maxX;
229 | }
230 | ++pos;
231 | }
232 | pos += _width - w;
233 | }
234 | #endif
235 | return ret;
236 | }
237 |
238 | //-------------------------------------
239 | uint8
240 | insert_sort(uint32 _idx, uint32 _acc, SHoughWorkspace *_pWorkspace)
241 | {
242 | uint8 inserted = 0;
243 | SCollisionPoint *pSorted = _pWorkspace->pCandidates;
244 | uint32 numCandidates = _pWorkspace->numCandidates;
245 |
246 | for(uint32 i = 0; i < numCandidates; ++i)
247 | {
248 | if(_acc > pSorted[i].acc || (_acc == pSorted[i].acc && _idx < pSorted[i].index))
249 | {
250 | inserted = 1;
251 | for(uint32 j = numCandidates; j > i; --j)
252 | {
253 | pSorted[j] = pSorted[j-1];
254 | }
255 |
256 | _pWorkspace->numCandidates++;
257 | pSorted[i].acc = _acc;
258 | pSorted[i].index = _idx;
259 | break;
260 | }
261 | }
262 |
263 | if(inserted == 0)
264 | {
265 | pSorted[numCandidates].acc = _acc;
266 | pSorted[numCandidates].index = _idx;
267 | _pWorkspace->numCandidates++;
268 | }
269 | return inserted;
270 | }
271 |
272 | //-------------------------------------
273 | SHoughWorkspace *
274 | create_workspace_hough(const uint32 _width, const uint32 _height)
275 | {
276 | SHoughWorkspace * work = (SHoughWorkspace *)malloc(sizeof(SHoughWorkspace));
277 |
278 |
279 | work->width = _width;
280 | work->height = _height;
281 | work->accMaxR = ((sqrt(2.0) * (double)((_height>_width)?_height:_width)) / 2.0);
282 | work->accHeight = (uint32)(work->accMaxR * 2.0 + 0.5); // -r -> +r
283 | work->accWidth = 180;
284 | work->pAccum = (uint32 *)malloc(work->accWidth * work->accHeight * sizeof(uint32));
285 | work->accCenterX = _width >> 1;
286 | work->accCenterY = _height >> 1;
287 |
288 | work->pCandidates = (SCollisionPoint *)malloc(work->accWidth * work->accHeight * sizeof(SCollisionPoint));
289 | work->numCandidates = 0;
290 |
291 | printf("Creating hough workpace with: width: %d, height: %d, accMaxR: %f, maxR: %d, maxT: %d\n", _width, _height, work->accMaxR, work->accHeight, work->accWidth);
292 |
293 | work->pSin = (float *)malloc(work->accWidth * sizeof(float));
294 | work->pCos = (float *)malloc(work->accWidth * sizeof(float));
295 | for(int n = 0; n < work->accWidth; ++n)
296 | {
297 | work->pSin[n] = (float)(sin((double)n * DEG2RAD));
298 | work->pCos[n] = (float)(cos((double)n * DEG2RAD));
299 | }
300 |
301 | return work;
302 | }
303 |
304 | //-------------------------------------
305 | void
306 | reset_workspace_hough(SHoughWorkspace *_pWorkspace)
307 | {
308 | memset(_pWorkspace->pAccum, 0, sizeof(_pWorkspace->pAccum[0]) * _pWorkspace->accWidth * _pWorkspace->accHeight);
309 | memset(_pWorkspace->pCandidates, 0, sizeof(_pWorkspace->pCandidates[0]) * _pWorkspace->accWidth * _pWorkspace->accHeight);
310 | _pWorkspace->numCandidates = 0;
311 | }
312 | //-------------------------------------
313 | void
314 | release_workspace_hough(SHoughWorkspace *_pWorkspace)
315 | {
316 | if(_pWorkspace != 0x0)
317 | {
318 | _pWorkspace->numCandidates = 0;
319 | if(_pWorkspace->pAccum != 0x0)
320 | {
321 | free(_pWorkspace->pAccum);
322 | _pWorkspace->pAccum = 0x0;
323 | }
324 |
325 | if(_pWorkspace->pCandidates != 0x0)
326 | {
327 | free(_pWorkspace->pCandidates);
328 | _pWorkspace->pCandidates = 0x0;
329 | }
330 |
331 | if(_pWorkspace->pSin != 0x0)
332 | {
333 | free(_pWorkspace->pSin);
334 | _pWorkspace->pSin = 0x0;
335 | }
336 |
337 | if(_pWorkspace->pCos != 0x0)
338 | {
339 | free(_pWorkspace->pCos);
340 | _pWorkspace->pCos = 0x0;
341 | }
342 |
343 | free(_pWorkspace);
344 | }
345 | }
346 |
--------------------------------------------------------------------------------
/src/hough.h:
--------------------------------------------------------------------------------
1 | #ifndef _HOUGH_H_
2 | #define _HOUGH_H_
3 |
4 | #include "types.h"
5 |
6 | // https://github.com/opencv/opencv/blob/master/modules/imgproc/src/hough.cpp
7 | // http://www.keymolen.com/2013/05/hough-transformation-c-implementation.html
8 |
9 | typedef struct SPolar
10 | {
11 | float rho;
12 | float theta;
13 | } SPolar;
14 |
15 | typedef struct SCartesian
16 | {
17 | int32 x;
18 | int32 y;
19 | } SCartesian;
20 |
21 | void init_hough(const uint32 _width, const uint32 _height);
22 | void release_hough();
23 | uint32 hough(uint8 *_pData, uint32 _width, uint32 _height, uint32 _threshold, SPolar *_lineBuffer, uint32 _size);
24 |
25 | void save_hough_workspace(const char *filename);
26 |
27 | #endif
28 |
--------------------------------------------------------------------------------
/src/image.c:
--------------------------------------------------------------------------------
1 | #include "image.h"
2 | #include
3 | #include
4 | #include
5 |
6 | //-------------------------------------
7 | SImage create_image(uint32 _width, uint32 _height, EPixelFormat _format)
8 | {
9 | SImage img;
10 | reset_image(&img);
11 | uint32 size = (_width * _height * _format);
12 | if(size > 0 && _format != PF_UNKNOWN)
13 | {
14 | img.mpData = (uint8 *)malloc(size);
15 | if(img.mpData != 0x0)
16 | {
17 | img.mWidth = _width;
18 | img.mHeight = _height;
19 | img.mLeap = 0;
20 | img.mBpp = _format;
21 | memset(img.mpData, 0, size);
22 | }
23 | }
24 | return img;
25 | }
26 |
27 | //-------------------------------------
28 | void reset_image(SImage *_pImg)
29 | {
30 | _pImg->mWidth = 0;
31 | _pImg->mHeight = 0;
32 | _pImg->mLeap = 0;
33 | _pImg->mBpp = PF_UNKNOWN;
34 | _pImg->mpData = 0x0;
35 | }
36 |
37 | //-------------------------------------
38 | void release_image(SImage *_pImg)
39 | {
40 | if(_pImg->mpData != 0x0)
41 | {
42 | free(_pImg->mpData);
43 | }
44 | reset_image(_pImg);
45 | }
46 | //-------------------------------------
47 | void copy_image(SImage *_pImg, SImage *_pCopy)
48 | {
49 | if(_pCopy == 0x0)
50 | return;
51 |
52 | if(_pCopy->mWidth != _pImg->mWidth || _pCopy->mHeight != _pImg->mHeight || _pCopy->mBpp != _pImg->mBpp )
53 | {
54 | release_image(_pCopy);
55 | *_pCopy = create_image(_pImg->mWidth, _pImg->mHeight, _pImg->mBpp);
56 | }
57 |
58 | uint32 size = _pImg->mWidth * _pImg->mHeight * _pImg->mBpp;
59 | memcpy(_pCopy->mpData, _pImg->mpData, size);
60 | }
61 |
62 | //-------------------------------------
63 | SImage convertToGray(const SImage _source)
64 | {
65 | SImage pDst;
66 | uint8 *pData = (uint8 *)_source.mpData;
67 |
68 | uint32 i, size = _source.mWidth * _source.mHeight;
69 | //uint8 r, g, b;
70 | reset_image(&pDst);
71 | pDst = create_image(_source.mWidth, _source.mHeight, PF_GRAY);
72 | if(_source.mBpp == PF_GRAY)
73 | {
74 | memcpy(pDst.mpData, pData, size);
75 | }
76 | else
77 | {
78 | if(_source.mBpp == PF_ARGB)
79 | {
80 | for(i = 0; i < size; ++i)
81 | {
82 | uint8 b = pData[0];
83 | uint8 g = pData[1];
84 | uint8 r = pData[2];
85 | //uint8 a = pData[3];
86 | pDst.mpData[i] = (0.298f * (float)r + 0.586f * (float)g + 0.114f * (float)b);
87 | pData += 4;
88 | }
89 | }
90 | else if(_source.mBpp == PF_RGB)
91 | {
92 | for(i = 0; i < size; ++i)
93 | {
94 | uint8 r = pData[0];
95 | uint8 g = pData[1];
96 | uint8 b = pData[2];
97 | pDst.mpData[i] = (0.298f * (float)r + 0.586f * (float)g + 0.114f * (float)b);
98 | pData += 3;
99 | }
100 | }
101 | else
102 | {
103 | // ERROR !!!!
104 | }
105 | }
106 | return pDst;
107 | }
108 |
109 | //-------------------------------------
110 | SImage convertToARGB(const SImage _source)
111 | {
112 | SImage pDst;
113 | uint8 *pData = (uint8 *)_source.mpData;
114 |
115 | uint32 i, size = _source.mWidth * _source.mHeight;
116 | //uint8 r, g, b;
117 | reset_image(&pDst);
118 | pDst = create_image(_source.mWidth, _source.mHeight, PF_ARGB);
119 | if(_source.mBpp == PF_GRAY)
120 | {
121 | uint32 *pDataOut = (uint32 *)pDst.mpData;
122 | for(i = 0; i < size; ++i)
123 | {
124 | uint32 g = *pData;
125 | uint32 color = (g << 24) | (g << 16) | (g<<8);
126 | pDataOut[i] = color;
127 | ++pData;
128 | }
129 | }
130 | else
131 | {
132 | if(_source.mBpp == PF_ARGB)
133 | {
134 | memcpy(pDst.mpData, pData, size*4);
135 | }
136 | else if(_source.mBpp == PF_RGB)
137 | {
138 | for(i = 0; i < size; ++i)
139 | {
140 | pDst.mpData[i*3 + 0] = *pData;
141 | ++pData;
142 | pDst.mpData[i*3 + 1] = *pData;
143 | ++pData;
144 | pDst.mpData[i*3 + 2] = *pData;
145 | ++pData;
146 | }
147 | }
148 | else
149 | {
150 | // ERROR !!!!
151 | }
152 | }
153 | return pDst;
154 | }
155 |
156 |
157 | void dump(const SImage _source)
158 | {
159 | dump_data(_source.mpData, _source.mWidth, _source.mHeight, _source.mBpp);
160 | }
161 |
162 | void dump_data(const uint8*_pData, const uint32 _width, const uint32 _height, const uint32 _bpp)
163 | {
164 | printf("\n\nImage %d x %d (%d bpp)\n", _width, _height, _bpp);
165 | for (int y = 0; y < _height; ++y)
166 | {
167 | for (int x = 0; x < _width; ++x)
168 | {
169 | if(_bpp == 4)
170 | {
171 | printf(" %08X", ((uint32 *)_pData)[x + y * _width]);
172 | } else {
173 | printf(" %02X", _pData[x + y * _width]);
174 | }
175 | }
176 | printf("\n");
177 | }
178 | }
179 |
180 |
181 |
182 | void draw_line_4bpp(SImage *_pImage, uint32 color, int x0, int y0, int x1, int y1)
183 | {
184 | uint32 pos;
185 | int dx = abs(x1-x0);
186 | int dy = abs(y1-y0);
187 | int sx = x0dy) ? dx : -dy)/2;
190 | int p;
191 |
192 | while(x0!=x1 || y0!=y1)
193 | {
194 | if( x0 < 0 || x0 > _pImage->mWidth || y0 < 0 || y0 > _pImage->mHeight)
195 | {
196 | break;
197 | }
198 | pos = x0 + y0 * _pImage->mWidth;
199 | ((uint32 *)_pImage->mpData)[pos] = color;
200 | p = d;
201 | if (p >-dx) { d -= dy; x0 += sx; }
202 | if (p < dy) { d += dx; y0 += sy; }
203 | }
204 | }
205 |
206 |
207 | void draw_line_3bpp(SImage *_pImage, uint32 color, int x0, int y0, int x1, int y1)
208 | {
209 | uint8 r = ((color>>16) & 0xFF);
210 | uint8 g = ((color>>8) & 0xFF);
211 | uint8 b = ((color) & 0xFF);
212 | uint32 pos;
213 | int dx = abs(x1-x0);
214 | int dy = abs(y1-y0);
215 | int sx = x0dy) ? dx : -dy)/2;
218 | int p;
219 |
220 | while(x0!=x1 || y0!=y1)
221 | {
222 | if( x0 < 0 || x0 > _pImage->mWidth || y0 < 0 || y0 > _pImage->mHeight)
223 | {
224 | break;
225 | }
226 | pos = x0*3 + y0 * _pImage->mWidth*3;
227 | _pImage->mpData[pos + 0] = r;
228 | _pImage->mpData[pos + 1] = g;
229 | _pImage->mpData[pos + 2] = b;
230 | p = d;
231 | if (p >-dx) { d -= dy; x0 += sx; }
232 | if (p < dy) { d += dx; y0 += sy; }
233 | }
234 | }
235 |
236 |
237 | void draw_line_1bpp(SImage *_pImage, uint32 color, int x0, int y0, int x1, int y1)
238 | {
239 | uint8 col = (0.298f * (float)((color>>16) & 0xFF) + 0.586f * (float)((color>>8) & 0xFF) + 0.114f * (float)((color) & 0xFF));
240 | uint32 pos;
241 | int dx = abs(x1-x0);
242 | int dy = abs(y1-y0);
243 | int sx = x0dy) ? dx : -dy)/2;
246 | int p;
247 |
248 | while(x0!=x1 || y0!=y1)
249 | {
250 | if( x0 < 0 || x0 > _pImage->mWidth || y0 < 0 || y0 > _pImage->mHeight)
251 | {
252 | break;
253 | }
254 | pos = x0 + y0 * _pImage->mWidth;
255 | _pImage->mpData[pos] = col;
256 | p = d;
257 | if (p >-dx) { d -= dy; x0 += sx; }
258 | if (p < dy) { d += dx; y0 += sy; }
259 | }
260 | }
261 |
262 | void draw_line(SImage *_pImage, uint32 color, int x0, int y0, int x1, int y1)
263 | {
264 | if(_pImage != 0x0 && _pImage->mpData != 0x0)
265 | {
266 | if(_pImage->mBpp == 4)
267 | draw_line_4bpp(_pImage, color, x0, y0, x1, y1);
268 | else if(_pImage->mBpp == 3)
269 | draw_line_3bpp(_pImage, color, x0, y0, x1, y1);
270 | else if(_pImage->mBpp == 1)
271 | draw_line_1bpp(_pImage, color, x0, y0, x1, y1);
272 | }
273 | }
274 |
--------------------------------------------------------------------------------
/src/image.h:
--------------------------------------------------------------------------------
1 | #ifndef _IMAGE_H_
2 | #define _IMAGE_H_
3 | #include "types.h"
4 |
5 | typedef enum EPixelFormat {
6 | PF_UNKNOWN = 0,
7 | PF_ARGB = 4,
8 | PF_RGB = 3,
9 | PF_GRAY = 1
10 | }EPixelFormat;
11 |
12 | typedef struct SImage {
13 | uint32 mWidth;
14 | uint32 mHeight;
15 | uint32 mLeap;
16 | EPixelFormat mBpp;
17 | uint8 *mpData;
18 | }SImage;
19 |
20 |
21 | SImage create_image(uint32 _width, uint32 _height, EPixelFormat _format);
22 | void reset_image(SImage *_pImg);
23 | void release_image(SImage *_pImg);
24 | void copy_image(SImage *_pImg, SImage *_pCopy);
25 | SImage convertToGray(const SImage _source);
26 | SImage convertToARGB(const SImage _source);
27 | void dump(const SImage _source);
28 | void dump_data(const uint8*_pData, const uint32 _width, const uint32 _height, const uint32 _bpp);
29 | void draw_line(SImage *_pImage, uint32 color, int x0, int y0, int x1, int y1);
30 |
31 | #endif
32 |
--------------------------------------------------------------------------------
/src/perspective.c:
--------------------------------------------------------------------------------
1 | #include "perspective.h"
2 |
3 |
4 | //--
5 | int check_side(int _X, int _Y, int _dX, int _dY, int _x, int _y);
6 | void get_barycentric(const STextureTriangle *_pTri, int _x, int _y, double *_pL1, double *_pL2, double *_pL3);
7 | void get_cartesian(const STextureTriangle *_pTri, double _l1, double _l2, double _l3, float *_pU, float *_pV);
8 | uint32 get_pixel(const SImage *_pImg, float _x, float _y);
9 | void set_pixel(SImage *_pImg, int _x, int _y, uint32 _color);
10 | void write_pixel(SImage *_pOutput, const SImage *_pTexture, int _x, int _y, float _u, float _v);
11 |
12 |
13 | //--
14 | STextureTriangle set_triangle(int _x1, int _y1, float _u1, float _v1, int _x2, int _y2, float _u2, float _v2, int _x3, int _y3, float _u3, float _v3)
15 | {
16 | STextureTriangle ret;
17 | ret = init_triangle(ret, _x1, _y1, _x2, _y2, _x3, _y3);
18 | ret = init_texture (ret, _u1, _v1, _u2, _v2, _u3, _v3);
19 | return ret;
20 | }
21 |
22 | //--
23 | STextureTriangle init_triangle(STextureTriangle tri, int _x1, int _y1, int _x2, int _y2, int _x3, int _y3)
24 | {
25 | STextureTriangle ret = tri;
26 |
27 | ret.points[0].x = _x1;
28 | ret.points[0].y = _y1;
29 | ret.points[1].x = _x2;
30 | ret.points[1].y = _y2;
31 | ret.points[2].x = _x3;
32 | ret.points[2].y = _y3;
33 |
34 | //compute determinant
35 | ret.det = (ret.points[1].y - ret.points[2].y) * (ret.points[0].x - ret.points[2].x) + (ret.points[2].x - ret.points[1].x) * (ret.points[0].y - ret.points[2].y);
36 |
37 | return ret;
38 | }
39 |
40 | //--
41 | STextureTriangle init_texture(STextureTriangle tri, float _u1, float _v1, float _u2, float _v2, float _u3, float _v3)
42 | {
43 | STextureTriangle ret = tri;
44 |
45 | ret.points[0].u = _u1;
46 | ret.points[0].v = _v1;
47 | ret.points[1].u = _u2;
48 | ret.points[1].v = _v2;
49 | ret.points[2].u = _u3;
50 | ret.points[2].v = _v3;
51 |
52 | return ret;
53 | }
54 |
55 | //-------------------------------------
56 | // Returns a value related to the position of the point (x,y) relative to the edge defined by the points (X,Y) and (X+dX, Y+dY)
57 | // > 0 if (x,y) is to the "right" side
58 | // == 0 if (x,y) is exactly on the line
59 | // < 0 if (x,y) is to the "left " side
60 | int check_side(int _X, int _Y, int _dX, int _dY, int _x, int _y)
61 | {
62 | return (_x - _X) * _dY - (_y - _Y) * _dX;
63 | }
64 |
65 | //-------------------------------------
66 | // gets the barycentric coordinates given a cartesian coordinates.
67 | void get_barycentric(const STextureTriangle *_pTri, int _x, int _y, double *_pL1, double *_pL2, double *_pL3)
68 | {
69 | *_pL1 = ((_pTri->points[1].y - _pTri->points[2].y ) * ((double)_x - _pTri->points[2].x) + (_pTri->points[2].x - _pTri->points[1].x) * ((double)_y - _pTri->points[2].y)) / (double)_pTri->det;
70 | *_pL2 = ((_pTri->points[2].y - _pTri->points[0].y ) * ((double)_x - _pTri->points[2].x) + (_pTri->points[0].x - _pTri->points[2].x) * ((double)_y - _pTri->points[2].y)) / (double)_pTri->det;
71 | *_pL3 = (1.0 - *_pL1 - *_pL2);
72 | }
73 |
74 | //-------------------------------------
75 | // gets the cartesian coordinates given a barycentric coordinates.
76 | void get_cartesian(const STextureTriangle *_pTri, double _l1, double _l2, double _l3, float *_pU, float *_pV)
77 | {
78 | *_pU = _l1 * _pTri->points[0].u + _l2 * _pTri->points[1].u + _l3 * _pTri->points[2].u;
79 | *_pV = _l1 * _pTri->points[0].v + _l2 * _pTri->points[1].v + _l3 * _pTri->points[2].v;
80 | }
81 |
82 | //-------------------------------------
83 | // Gets the color of a (u,v) texture coordinates.
84 | // Advice the params are floats!!
85 | uint32 get_pixel(const SImage *_pImg, float _x, float _y)
86 | {
87 | int tx = _pImg->mWidth * _x;
88 | int ty = _pImg->mHeight * _y;
89 | int bpp = _pImg->mBpp;
90 | int pos = (ty * _pImg->mWidth + tx);
91 | uint32 color = 0;
92 |
93 | if(bpp == 1)
94 | {
95 | color = _pImg->mpData[pos];
96 | }
97 | else if(bpp == 3)
98 | {
99 | pos *= bpp;
100 | uint32 r = _pImg->mpData[pos + 0];
101 | uint32 g = _pImg->mpData[pos + 1];
102 | uint32 b = _pImg->mpData[pos + 2];
103 | color = ((r & 0xFF) << 16) + ((g & 0xFF) << 8) + (b & 0xFF);
104 | }
105 | else if(bpp == 4)
106 | {
107 | color = ((uint32 *)_pImg->mpData)[pos];
108 | }
109 | return color;
110 | }
111 |
112 | //-------------------------------------
113 | // Sets the color of a pixel
114 | void set_pixel(SImage *_pImg, int _x, int _y, uint32 _color)
115 | {
116 | int bpp = _pImg->mBpp;
117 | int pos = (_y * _pImg->mWidth + _x);
118 |
119 | if(bpp == 1)
120 | {
121 | _pImg->mpData[pos] = (uint8)(_color & 0xFF);
122 | }
123 | else if(bpp == 3)
124 | {
125 | uint8 r = (uint8)((_color >> 16) & 0xFF);
126 | uint8 g = (uint8)((_color >> 8) & 0xFF);
127 | uint8 b = (uint8)((_color ) & 0xFF);
128 | pos *= bpp;
129 | _pImg->mpData[pos + 0] = r;
130 | _pImg->mpData[pos + 1] = g;
131 | _pImg->mpData[pos + 2] = b;
132 | }
133 | else if(bpp == 4)
134 | {
135 | ((uint32 *)_pImg->mpData)[pos] = _color;
136 | }
137 | }
138 |
139 | //-------------------------------------
140 | // Writes a texture value into a destination image, on the specified position.
141 | void write_pixel(SImage *_pOutput, const SImage *_pTexture, int _x, int _y, float _u, float _v)
142 | {
143 | uint32 color = get_pixel(_pTexture, _u, _v);
144 | set_pixel(_pOutput, _x, _y, color);
145 | }
146 |
147 | //-------------------------------------
148 | // Rendes a triangle with texture
149 | void render_textured_triangle(const STextureTriangle *_pTri, const SImage *_pTexture, SImage *_pOutput)
150 | {
151 | int x, y;
152 | float u, v;
153 | double l1, l2, l3;
154 | int sx, sy, ex, ey;
155 |
156 | //get triangle bounding box
157 | sx = MIN(_pTri->points[0].x, MIN(_pTri->points[1].x, _pTri->points[2].x));
158 | sy = MIN(_pTri->points[0].y, MIN(_pTri->points[1].y, _pTri->points[2].y));
159 | ex = MAX(_pTri->points[0].x, MAX(_pTri->points[1].x, _pTri->points[2].x));
160 | ey = MAX(_pTri->points[0].y, MAX(_pTri->points[1].y, _pTri->points[2].y));
161 |
162 | #define CHECK_SIDES
163 | #ifdef CHECK_SIDES
164 | //E(x+1,y) = E(x,y) + dY
165 | //E(x,y+1) = E(x,y) - dX
166 | int sd1, sd2, sd3;
167 | // store the direction vectors
168 | int diffX_A2B = _pTri->points[1].x - _pTri->points[0].x;
169 | int diffX_B2C = _pTri->points[2].x - _pTri->points[1].x;
170 | int diffX_C2A = _pTri->points[0].x - _pTri->points[2].x;
171 | int diffY_A2B = _pTri->points[1].y - _pTri->points[0].y;
172 | int diffY_B2C = _pTri->points[2].y - _pTri->points[1].y;
173 | int diffY_C2A = _pTri->points[0].y - _pTri->points[2].y;
174 |
175 | for (y = sy; y < ey; ++y) {
176 | x = sx;
177 | sd1 = check_side(_pTri->points[0].x, _pTri->points[0].y, diffX_A2B, diffY_A2B, x, y);
178 | sd2 = check_side(_pTri->points[1].x, _pTri->points[1].y, diffX_B2C, diffY_B2C, x, y);
179 | sd3 = check_side(_pTri->points[2].x, _pTri->points[2].y, diffX_C2A, diffY_C2A, x, y);
180 |
181 | for (; x < ex; ++x) {
182 | sd1 += diffY_A2B;
183 | sd2 += diffY_B2C;
184 | sd3 += diffY_C2A;
185 |
186 | //set_pixel(_pOutput, x, y, 0x20); // just testing
187 | // check if we are inside the triangle
188 | if (sd1 >= 0 && sd2 >= 0 && sd3 >= 0) {
189 | get_barycentric(_pTri, x, y, &l1, &l2, &l3);
190 | get_cartesian(_pTri, l1, l2, l3, &u, &v);
191 | write_pixel(_pOutput, _pTexture, x, y, u, v);
192 | }
193 | }
194 | }
195 | #else
196 | for (y = sy; y < ey; ++y) {
197 | for (x = sx; x < ex; ++x) {
198 | get_barycentric(_pTri, x, y, &l1, &l2, &l3);
199 | //set_pixel(_pOutput, x, y, 0x20); // just testing
200 | // check if we are inside the triangle
201 | if (l1 >= 0 && l2 >= 0 && l3 >= 0)
202 | {
203 | get_cartesian(_pTri, l1, l2, l3, &u, &v);
204 | write_pixel(_pOutput, _pTexture, x, y, u, v);
205 | }
206 | }
207 | }
208 | #endif
209 | }
210 |
211 |
--------------------------------------------------------------------------------
/src/perspective.h:
--------------------------------------------------------------------------------
1 | #ifndef _PERSPECTIVE_H_
2 | #define _PERSPECTIVE_H_
3 | #include "types.h"
4 | #include "image.h"
5 |
6 | // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.157.4621&rep=rep1&type=pdf
7 | // https://github.com/4DA/simple-rasterizer/
8 | // https://github.com/ssloy/tinyrenderer/
9 |
10 | typedef struct STextureVector
11 | {
12 | int x;
13 | int y;
14 | float u;
15 | float v;
16 | }STextureVector;
17 |
18 | typedef struct STextureTriangle
19 | {
20 | STextureVector points[3];
21 | int det;
22 | } STextureTriangle;
23 |
24 | //--
25 | STextureTriangle set_triangle(int _x1, int _y1, float _u1, float _v1, int _x2, int _y2, float _u2, float _v2, int _x3, int _y3, float _u3, float _v3);
26 | STextureTriangle init_triangle(STextureTriangle tri, int _x1, int _y1, int _x2, int _y2, int _x3, int _y3);
27 | STextureTriangle init_texture(STextureTriangle tri, float _u1, float _v1, float _u2, float _v2, float _u3, float _v3);
28 |
29 | //--
30 | void render_textured_triangle(const STextureTriangle *_pTri, const SImage *_pTexture, SImage *_pOutput);
31 |
32 | #endif
33 |
--------------------------------------------------------------------------------
/src/timer.c:
--------------------------------------------------------------------------------
1 | #include "timer.h"
2 | #define _POSIX_C_SOURCE 199309L
3 | #include
4 | #include
5 | #include
6 | #include
7 |
8 | uint64 current_time_ms()
9 | {
10 | uint64 ms = 0; // Milliseconds
11 | //time_t s; // Seconds
12 |
13 | #if _LINUX_
14 | #define USE_CLOCK_GETTIME
15 | #endif // _LINUX_
16 |
17 | #ifdef USE_CLOCK_GETTIME
18 | struct timespec spec;
19 | clock_gettime(CLOCK_REALTIME, &spec);
20 |
21 | ms = spec.tv_sec*1000;
22 | ms += round(spec.tv_nsec / 1.0e6); // Convert nanoseconds to milliseconds
23 | #else
24 | //time_t t = time(0x0);
25 | ms = (clock() / (CLOCKS_PER_SEC / 1000));
26 | #endif // USE_CLOCK_GETTIME
27 |
28 | return ms;
29 | }
30 |
--------------------------------------------------------------------------------
/src/timer.h:
--------------------------------------------------------------------------------
1 | #ifndef _TIMER_H_
2 | #define _TIMER_H_
3 |
4 | #include "types.h"
5 |
6 | uint64 current_time_ms();
7 |
8 | #endif
9 |
--------------------------------------------------------------------------------
/src/types.h:
--------------------------------------------------------------------------------
1 | #ifndef _TYPES_H_
2 | #define _TYPES_H_
3 |
4 | typedef char int8;
5 | typedef short int int16;
6 | typedef int int32;
7 | typedef long long int64;
8 | typedef unsigned char uint8;
9 | typedef unsigned short int uint16;
10 | typedef unsigned int uint32;
11 | typedef unsigned long long uint64;
12 |
13 |
14 | // C99 doesn't define M_PI (GNU-C99 does)
15 | #ifndef M_PI
16 | #define M_PI 3.14159265358979323846264338327
17 | #endif
18 |
19 | #ifndef DEG2RAD
20 | #define DEG2RAD (M_PI / 180.0)
21 | #endif
22 |
23 | #ifndef RAD2DEG
24 | #define RAD2DEG (180.0 / M_PI)
25 | #endif
26 |
27 |
28 | #ifndef MIN
29 | #define MIN(a, b) ((a < b) ? a : b)
30 | #endif
31 |
32 | #ifndef MAX
33 | #define MAX(a, b) ((a > b) ? a : b)
34 | #endif
35 |
36 |
37 | #endif
38 |
--------------------------------------------------------------------------------