├── .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 | 55 | 56 | -------------------------------------------------------------------------------- /prj/codeblocks/edge_detection.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 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 | --------------------------------------------------------------------------------