blob: eef6c046eab87ed46465df74ef39c70a2912517f [file] [log] [blame]
Austin Schuh3333ec72022-12-29 16:21:06 -08001/* Copyright (C) 2013-2016, The Regents of The University of Michigan.
2All rights reserved.
3This software was developed in the APRIL Robotics Lab under the
4direction of Edwin Olson, ebolson@umich.edu. This software may be
5available under alternative licensing terms; contact the address above.
6Redistribution and use in source and binary forms, with or without
7modification, are permitted provided that the following conditions are met:
81. Redistributions of source code must retain the above copyright notice, this
9 list of conditions and the following disclaimer.
102. Redistributions in binary form must reproduce the above copyright notice,
11 this list of conditions and the following disclaimer in the documentation
12 and/or other materials provided with the distribution.
13THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
14ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
15WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
16DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
17ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
18(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
19LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
20ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
21(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
22SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23The views and conclusions contained in the software and documentation are those
24of the authors and should not be interpreted as representing official policies,
25either expressed or implied, of the Regents of The University of Michigan.
26*/
27
28#include <assert.h>
29#include <stdio.h>
30#include <stdlib.h>
31#include <string.h>
32#include <math.h>
33
34#include "common/image_u8.h"
35#include "common/pnm.h"
36#include "common/math_util.h"
37
38// least common multiple of 64 (sandy bridge cache line) and 24 (stride
39// needed for RGB in 8-wide vector processing)
40#define DEFAULT_ALIGNMENT_U8 96
41
42image_u8_t *image_u8_create_stride(unsigned int width, unsigned int height, unsigned int stride)
43{
44 uint8_t *buf = calloc(height*stride, sizeof(uint8_t));
45
46 // const initializer
47 image_u8_t tmp = { .width = width, .height = height, .stride = stride, .buf = buf };
48
49 image_u8_t *im = calloc(1, sizeof(image_u8_t));
50 memcpy(im, &tmp, sizeof(image_u8_t));
51 return im;
52}
53
54image_u8_t *image_u8_create(unsigned int width, unsigned int height)
55{
56 return image_u8_create_alignment(width, height, DEFAULT_ALIGNMENT_U8);
57}
58
59image_u8_t *image_u8_create_alignment(unsigned int width, unsigned int height, unsigned int alignment)
60{
61 int stride = width;
62
63 if ((stride % alignment) != 0)
64 stride += alignment - (stride % alignment);
65
66 return image_u8_create_stride(width, height, stride);
67}
68
69image_u8_t *image_u8_copy(const image_u8_t *in)
70{
71 uint8_t *buf = malloc(in->height*in->stride*sizeof(uint8_t));
72 memcpy(buf, in->buf, in->height*in->stride*sizeof(uint8_t));
73
74 // const initializer
75 image_u8_t tmp = { .width = in->width, .height = in->height, .stride = in->stride, .buf = buf };
76
77 image_u8_t *copy = calloc(1, sizeof(image_u8_t));
78 memcpy(copy, &tmp, sizeof(image_u8_t));
79 return copy;
80}
81
82void image_u8_destroy(image_u8_t *im)
83{
84 if (!im)
85 return;
86
87 free(im->buf);
88 free(im);
89}
90
91////////////////////////////////////////////////////////////
92// PNM file i/o
93image_u8_t *image_u8_create_from_pnm(const char *path)
94{
95 return image_u8_create_from_pnm_alignment(path, DEFAULT_ALIGNMENT_U8);
96}
97
98image_u8_t *image_u8_create_from_pnm_alignment(const char *path, int alignment)
99{
100 pnm_t *pnm = pnm_create_from_file(path);
101 if (pnm == NULL)
102 return NULL;
103
104 image_u8_t *im = NULL;
105
106 switch (pnm->format) {
107 case PNM_FORMAT_GRAY: {
108 im = image_u8_create_alignment(pnm->width, pnm->height, alignment);
109
110 if (pnm->max == 255) {
111 for (int y = 0; y < im->height; y++)
112 memcpy(&im->buf[y*im->stride], &pnm->buf[y*im->width], im->width);
113 } else if (pnm->max == 65535) {
114 for (int y = 0; y < im->height; y++)
115 for (int x = 0; x < im->width; x++)
116 im->buf[y*im->stride + x] = pnm->buf[2*(y*im->width + x)];
117 } else {
118 assert(0);
119 }
120
121 break;
122 }
123
124 case PNM_FORMAT_RGB: {
125 im = image_u8_create_alignment(pnm->width, pnm->height, alignment);
126
127 if (pnm->max == 255) {
128 // Gray conversion for RGB is gray = (r + g + g + b)/4
129 for (int y = 0; y < im->height; y++) {
130 for (int x = 0; x < im->width; x++) {
131 uint8_t gray = (pnm->buf[y*im->width*3 + 3*x+0] + // r
132 pnm->buf[y*im->width*3 + 3*x+1] + // g
133 pnm->buf[y*im->width*3 + 3*x+1] + // g
134 pnm->buf[y*im->width*3 + 3*x+2]) // b
135 / 4;
136
137 im->buf[y*im->stride + x] = gray;
138 }
139 }
140 } else if (pnm->max == 65535) {
141 for (int y = 0; y < im->height; y++) {
142 for (int x = 0; x < im->width; x++) {
143 int r = pnm->buf[6*(y*im->width + x) + 0];
144 int g = pnm->buf[6*(y*im->width + x) + 2];
145 int b = pnm->buf[6*(y*im->width + x) + 4];
146
147 im->buf[y*im->stride + x] = (r + g + g + b) / 4;
148 }
149 }
150 } else {
151 assert(0);
152 }
153
154 break;
155 }
156
157 case PNM_FORMAT_BINARY: {
158 im = image_u8_create_alignment(pnm->width, pnm->height, alignment);
159
160 // image is padded to be whole bytes on each row.
161
162 // how many bytes per row on the input?
163 int pbmstride = (im->width + 7) / 8;
164
165 for (int y = 0; y < im->height; y++) {
166 for (int x = 0; x < im->width; x++) {
167 int byteidx = y * pbmstride + x / 8;
168 int bitidx = 7 - (x & 7);
169
170 // ack, black is one according to pbm docs!
171 if ((pnm->buf[byteidx] >> bitidx) & 1)
172 im->buf[y*im->stride + x] = 0;
173 else
174 im->buf[y*im->stride + x] = 255;
175 }
176 }
177 break;
178 }
179 }
180
181 pnm_destroy(pnm);
182 return im;
183}
184
185image_u8_t *image_u8_create_from_f32(image_f32_t *fim)
186{
187 image_u8_t *im = image_u8_create(fim->width, fim->height);
188
189 for (int y = 0; y < fim->height; y++) {
190 for (int x = 0; x < fim->width; x++) {
191 float v = fim->buf[y*fim->stride + x];
192 im->buf[y*im->stride + x] = (int) (255 * v);
193 }
194 }
195
196 return im;
197}
198
199
200int image_u8_write_pnm(const image_u8_t *im, const char *path)
201{
202 FILE *f = fopen(path, "wb");
203 int res = 0;
204
205 if (f == NULL) {
206 res = -1;
207 goto finish;
208 }
209
210 // Only outputs to grayscale
211 fprintf(f, "P5\n%d %d\n255\n", im->width, im->height);
212
213 for (int y = 0; y < im->height; y++) {
214 if (im->width != fwrite(&im->buf[y*im->stride], 1, im->width, f)) {
215 res = -2;
216 goto finish;
217 }
218 }
219
220 finish:
221 if (f != NULL)
222 fclose(f);
223
224 return res;
225}
226
227void image_u8_draw_circle(image_u8_t *im, float x0, float y0, float r, int v)
228{
229 r = r*r;
230
231 for (int y = y0-r; y <= y0+r; y++) {
232 for (int x = x0-r; x <= x0+r; x++) {
233 float d = (x-x0)*(x-x0) + (y-y0)*(y-y0);
234 if (d > r)
235 continue;
236
237 if (x >= 0 && x < im->width && y >= 0 && y < im->height) {
238 int idx = y*im->stride + x;
239 im->buf[idx] = v;
240 }
241 }
242 }
243}
244
245void image_u8_draw_annulus(image_u8_t *im, float x0, float y0, float r0, float r1, int v)
246{
247 r0 = r0*r0;
248 r1 = r1*r1;
249
250 assert(r0 < r1);
251
252 for (int y = y0-r1; y <= y0+r1; y++) {
253 for (int x = x0-r1; x <= x0+r1; x++) {
254 float d = (x-x0)*(x-x0) + (y-y0)*(y-y0);
255 if (d < r0 || d > r1)
256 continue;
257
258 int idx = y*im->stride + x;
259 im->buf[idx] = v;
260 }
261 }
262}
263
264// only widths 1 and 3 supported (and 3 only badly)
265void image_u8_draw_line(image_u8_t *im, float x0, float y0, float x1, float y1, int v, int width)
266{
267 double dist = sqrtf((y1-y0)*(y1-y0) + (x1-x0)*(x1-x0));
268 double delta = 0.5 / dist;
269
270 // terrible line drawing code
271 for (float f = 0; f <= 1; f += delta) {
272 int x = ((int) (x1 + (x0 - x1) * f));
273 int y = ((int) (y1 + (y0 - y1) * f));
274
275 if (x < 0 || y < 0 || x >= im->width || y >= im->height)
276 continue;
277
278 int idx = y*im->stride + x;
279 im->buf[idx] = v;
280 if (width > 1) {
281 im->buf[idx+1] = v;
282 im->buf[idx+im->stride] = v;
283 im->buf[idx+1+im->stride] = v;
284 }
285 }
286}
287
288void image_u8_darken(image_u8_t *im)
289{
290 for (int y = 0; y < im->height; y++) {
291 for (int x = 0; x < im->width; x++) {
292 im->buf[im->stride*y+x] /= 2;
293 }
294 }
295}
296
297static void convolve(const uint8_t *x, uint8_t *y, int sz, const uint8_t *k, int ksz)
298{
299 assert((ksz&1)==1);
300
301 for (int i = 0; i < ksz/2 && i < sz; i++)
302 y[i] = x[i];
303
304 for (int i = 0; i < sz - ksz; i++) {
305 uint32_t acc = 0;
306
307 for (int j = 0; j < ksz; j++)
308 acc += k[j]*x[i+j];
309
310 y[ksz/2 + i] = acc >> 8;
311 }
312
313 for (int i = sz - ksz + ksz/2; i < sz; i++)
314 y[i] = x[i];
315}
316
317void image_u8_convolve_2D(image_u8_t *im, const uint8_t *k, int ksz)
318{
319 assert((ksz & 1) == 1); // ksz must be odd.
320
321 for (int y = 0; y < im->height; y++) {
322
323 uint8_t *x = malloc(sizeof(uint8_t)*im->stride);
324 memcpy(x, &im->buf[y*im->stride], im->stride);
325
326 convolve(x, &im->buf[y*im->stride], im->width, k, ksz);
327 free(x);
328 }
329
330 for (int x = 0; x < im->width; x++) {
331 uint8_t *xb = malloc(sizeof(uint8_t)*im->height);
332 uint8_t *yb = malloc(sizeof(uint8_t)*im->height);
333
334 for (int y = 0; y < im->height; y++)
335 xb[y] = im->buf[y*im->stride + x];
336
337 convolve(xb, yb, im->height, k, ksz);
338 free(xb);
339
340 for (int y = 0; y < im->height; y++)
341 im->buf[y*im->stride + x] = yb[y];
342 free(yb);
343 }
344}
345
346void image_u8_gaussian_blur(image_u8_t *im, double sigma, int ksz)
347{
348 if (sigma == 0)
349 return;
350
351 assert((ksz & 1) == 1); // ksz must be odd.
352
353 // build the kernel.
354 double *dk = malloc(sizeof(double)*ksz);
355
356 // for kernel of length 5:
357 // dk[0] = f(-2), dk[1] = f(-1), dk[2] = f(0), dk[3] = f(1), dk[4] = f(2)
358 for (int i = 0; i < ksz; i++) {
359 int x = -ksz/2 + i;
360 double v = exp(-.5*sq(x / sigma));
361 dk[i] = v;
362 }
363
364 // normalize
365 double acc = 0;
366 for (int i = 0; i < ksz; i++)
367 acc += dk[i];
368
369 for (int i = 0; i < ksz; i++)
370 dk[i] /= acc;
371
372 uint8_t *k = malloc(sizeof(uint8_t)*ksz);
373 for (int i = 0; i < ksz; i++)
374 k[i] = dk[i]*255;
375
376 if (0) {
377 for (int i = 0; i < ksz; i++)
378 printf("%d %15f %5d\n", i, dk[i], k[i]);
379 }
380 free(dk);
381
382 image_u8_convolve_2D(im, k, ksz);
383 free(k);
384}
385
386image_u8_t *image_u8_rotate(const image_u8_t *in, double rad, uint8_t pad)
387{
388 int iwidth = in->width, iheight = in->height;
389 rad = -rad; // interpret y as being "down"
390
391 float c = cos(rad), s = sin(rad);
392
393 float p[][2] = { { 0, 0}, { iwidth, 0 }, { iwidth, iheight }, { 0, iheight} };
394
395 float xmin = HUGE_VALF, xmax = -HUGE_VALF, ymin = HUGE_VALF, ymax = -HUGE_VALF;
396 float icx = iwidth / 2.0, icy = iheight / 2.0;
397
398 for (int i = 0; i < 4; i++) {
399 float px = p[i][0] - icx;
400 float py = p[i][1] - icy;
401
402 float nx = px*c - py*s;
403 float ny = px*s + py*c;
404
405 xmin = fmin(xmin, nx);
406 xmax = fmax(xmax, nx);
407 ymin = fmin(ymin, ny);
408 ymax = fmax(ymax, ny);
409 }
410
411 int owidth = ceil(xmax-xmin), oheight = ceil(ymax - ymin);
412 image_u8_t *out = image_u8_create(owidth, oheight);
413
414 // iterate over output pixels.
415 for (int oy = 0; oy < oheight; oy++) {
416 for (int ox = 0; ox < owidth; ox++) {
417 // work backwards from destination coordinates...
418 // sample pixel centers.
419 float sx = ox - owidth / 2.0 + .5;
420 float sy = oy - oheight / 2.0 + .5;
421
422 // project into input-image space
423 int ix = floor(sx*c + sy*s + icx);
424 int iy = floor(-sx*s + sy*c + icy);
425
426 if (ix >= 0 && iy >= 0 && ix < iwidth && iy < iheight)
427 out->buf[oy*out->stride+ox] = in->buf[iy*in->stride + ix];
428 else
429 out->buf[oy*out->stride+ox] = pad;
430 }
431 }
432
433 return out;
434}
435
436image_u8_t *image_u8_decimate(image_u8_t *im, float ffactor)
437{
438 int width = im->width, height = im->height;
439
440 if (ffactor == 1.5) {
441 int swidth = width / 3 * 2, sheight = height / 3 * 2;
442
443 image_u8_t *decim = image_u8_create(swidth, sheight);
444
445 int y = 0, sy = 0;
446 while (sy < sheight) {
447 int x = 0, sx = 0;
448 while (sx < swidth) {
449
450 // a b c
451 // d e f
452 // g h i
453 uint8_t a = im->buf[(y+0)*im->stride + (x+0)];
454 uint8_t b = im->buf[(y+0)*im->stride + (x+1)];
455 uint8_t c = im->buf[(y+0)*im->stride + (x+2)];
456
457 uint8_t d = im->buf[(y+1)*im->stride + (x+0)];
458 uint8_t e = im->buf[(y+1)*im->stride + (x+1)];
459 uint8_t f = im->buf[(y+1)*im->stride + (x+2)];
460
461 uint8_t g = im->buf[(y+2)*im->stride + (x+0)];
462 uint8_t h = im->buf[(y+2)*im->stride + (x+1)];
463 uint8_t i = im->buf[(y+2)*im->stride + (x+2)];
464
465 decim->buf[(sy+0)*decim->stride + (sx + 0)] =
466 (4*a+2*b+2*d+e)/9;
467 decim->buf[(sy+0)*decim->stride + (sx + 1)] =
468 (4*c+2*b+2*f+e)/9;
469
470 decim->buf[(sy+1)*decim->stride + (sx + 0)] =
471 (4*g+2*d+2*h+e)/9;
472 decim->buf[(sy+1)*decim->stride + (sx + 1)] =
473 (4*i+2*f+2*h+e)/9;
474
475 x += 3;
476 sx += 2;
477 }
478
479 y += 3;
480 sy += 2;
481 }
482
483 return decim;
484 }
485
486 int factor = (int) ffactor;
487
488 int swidth = 1 + (width - 1)/factor;
489 int sheight = 1 + (height - 1)/factor;
490 image_u8_t *decim = image_u8_create(swidth, sheight);
491 int sy = 0;
492 for (int y = 0; y < height; y += factor) {
493 int sx = 0;
494 for (int x = 0; x < width; x += factor) {
495 decim->buf[sy*decim->stride + sx] = im->buf[y*im->stride + x];
496 sx++;
497 }
498 sy++;
499 }
500 return decim;
501}
502
503void image_u8_fill_line_max(image_u8_t *im, const image_u8_lut_t *lut, const float *xy0, const float *xy1)
504{
505 // what is the maximum distance that will result in drawing into our LUT?
506 float max_dist2 = (lut->nvalues-1)/lut->scale;
507 float max_dist = sqrt(max_dist2);
508
509 // the orientation of the line
510 double theta = atan2(xy1[1]-xy0[1], xy1[0]-xy0[0]);
511 double v = sin(theta), u = cos(theta);
512
513 int ix0 = iclamp(fmin(xy0[0], xy1[0]) - max_dist, 0, im->width-1);
514 int ix1 = iclamp(fmax(xy0[0], xy1[0]) + max_dist, 0, im->width-1);
515
516 int iy0 = iclamp(fmin(xy0[1], xy1[1]) - max_dist, 0, im->height-1);
517 int iy1 = iclamp(fmax(xy0[1], xy1[1]) + max_dist, 0, im->height-1);
518
519 // the line segment xy0---xy1 can be parameterized in terms of line coordinates.
520 // We fix xy0 to be at line coordinate 0.
521 float xy1_line_coord = (xy1[0]-xy0[0])*u + (xy1[1]-xy0[1])*v;
522
523 float min_line_coord = fmin(0, xy1_line_coord);
524 float max_line_coord = fmax(0, xy1_line_coord);
525
526 for (int iy = iy0; iy <= iy1; iy++) {
527 float y = iy+.5;
528
529 for (int ix = ix0; ix <= ix1; ix++) {
530 float x = ix+.5;
531
532 // compute line coordinate of this pixel.
533 float line_coord = (x - xy0[0])*u + (y - xy0[1])*v;
534
535 // find point on line segment closest to our current pixel.
536 if (line_coord < min_line_coord)
537 line_coord = min_line_coord;
538 else if (line_coord > max_line_coord)
539 line_coord = max_line_coord;
540
541 float px = xy0[0] + line_coord*u;
542 float py = xy0[1] + line_coord*v;
543
544 double dist2 = (x-px)*(x-px) + (y-py)*(y-py);
545
546 // not in our LUT?
547 int idx = dist2 * lut->scale;
548 if (idx >= lut->nvalues)
549 continue;
550
551 uint8_t lut_value = lut->values[idx];
552 uint8_t old_value = im->buf[iy*im->stride + ix];
553 if (lut_value > old_value)
554 im->buf[iy*im->stride + ix] = lut_value;
555 }
556 }
557}