/* Camicro - Microscope camera viewer. * * Copyright (C) 2019 Nicolas Schodet * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * Contact : * Web: http://ni.fr.eu.org/ * Email: */ #include "image.h" #include "utils.h" #include #include #include typedef uint8_t u8x16 __attribute__((vector_size(16))); typedef uint16_t u16x8 __attribute__((vector_size(16))); GQuark image_error_quark(void) { return g_quark_from_static_string("image-error-quark"); } static void image_free(struct image *image, void *user_data) { free(image); } struct image * image_new(int width, int height, int stride, enum image_format format) { size_t struct_size_aligned = (sizeof(struct image) + 15) / 16 * 16; size_t image_size_aligned = ((height * stride) + 15) / 16 * 16; uint8_t *m = aligned_alloc(16, struct_size_aligned + image_size_aligned); if (!m) g_error("aligned_alloc: failed to allocate"); struct image *image = (struct image *) m; image->width = width; image->height = height; image->stride = stride; image->format = format; image->pixels = m + struct_size_aligned; image->refs = 1; image->release = image_free; image->release_user_data = NULL; return image; } void image_ref(struct image *image) { image->refs++; } void image_unref(struct image *image) { image->refs--; if (image->refs == 0 && image->release) image->release(image, image->release_user_data); } static void image_histogram_sgrbg8(const struct image *image, uint32_t histogram[3][256]) { g_assert(image->width % 2 == 0); g_assert(image->height % 2 == 0); g_assert(image->stride == image->width); memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); const uint8_t *p = image->pixels; for (int i = 0; i < image->height; i += 2) { for (int j = 0; j < image->width; j += 2) { uint8_t g = *p++; histogram[1][g] += 2; uint8_t r = *p++; histogram[0][r] += 4; } for (int j = 0; j < image->width; j += 2) { uint8_t b = *p++; histogram[2][b] += 4; uint8_t g = *p++; histogram[1][g] += 2; } } } static void image_histogram_xrgb32(const struct image *image, uint32_t histogram[3][256]) { g_assert(image->stride == image->width * 4); memset(histogram, 0, 3 * 256 * sizeof(uint32_t)); const uint8_t *p = image->pixels; const uint8_t *pend = image->pixels + image->height * image->stride; while (p != pend) { uint8_t b = *p++; uint8_t g = *p++; uint8_t r = *p++; p++; histogram[0][r]++; histogram[1][g]++; histogram[2][b]++; } } void image_histogram(const struct image *image, uint32_t histogram[3][256]) { switch (image->format) { case IMAGE_FORMAT_SGRBG8: image_histogram_sgrbg8(image, histogram); break; case IMAGE_FORMAT_XBGR32: image_histogram_xrgb32(image, histogram); break; default: g_assert_not_reached(); } } void image_white_balance_calibrate(const struct image *image, struct image_white_balance_reference *reference) { uint32_t histogram[3][256]; image_histogram(image, histogram); const uint32_t top_ignore = image->width * image->height / 1000; uint8_t top[3]; for (int i = 0; i < 3; i++) { uint32_t acc = 0; uint8_t j; for (j = 255; acc < top_ignore && j > 0; j--) { acc += histogram[i][j]; } top[i] = j; } reference->r = top[0]; reference->g = top[1]; reference->b = top[2]; } static void image_white_balance_factors( const struct image_white_balance_reference *reference, uint16_t *fr, uint16_t *fg, uint16_t *fb) { uint8_t wr = reference->r ?: 1; uint8_t wg = reference->g ?: 1; uint8_t wb = reference->b ?: 1; if (wr > wg && wr > wb) { *fr = 256; *fg = 256 * wr / wg; *fb = 256 * wr / wb; } else if (wg > wr && wg > wb) { *fr = 256 * wg / wr; *fg = 256; *fb = 256 * wg / wb; } else { *fr = 256 * wb / wr; *fg = 256 * wb / wg; *fb = 256; } } static void image_white_balance_sgrbg8(struct image *image, const struct image_white_balance_reference *reference) { g_assert(image->width % 16 == 0); g_assert(image->height % 2 == 0); g_assert(image->stride == image->width); uint16_t fr, fg, fb; image_white_balance_factors(reference, &fr, &fg, &fb); u8x16 *p = (u8x16 *) image->pixels; const u8x16 zero = { 0 }; const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23 }; const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31 }; const u16x8 mulo = { fg, fr, fg, fr, fg, fr, fg, fr }; const u16x8 mule = { fb, fg, fb, fg, fb, fg, fb, fg }; const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }; for (int i = 0; i < image->height; i += 2) { for (int j = 0; j < image->width; j += 16) { u8x16 pix4 = *p; u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mulo) != (u16x8) zero; pix2_0 = ((pix2_0 * mulo) | pix2_0_ovf) / 256; u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mulo) != (u16x8) zero; pix2_1 = ((pix2_1 * mulo) | pix2_1_ovf) / 256; pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); *p++ = pix4; } for (int j = 0; j < image->width; j += 16) { u8x16 pix4 = *p; u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mule) != (u16x8) zero; pix2_0 = ((pix2_0 * mule) | pix2_0_ovf) / 256; u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mule) != (u16x8) zero; pix2_1 = ((pix2_1 * mule) | pix2_1_ovf) / 256; pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); *p++ = pix4; } } } static void image_white_balance_xbgr32(struct image *image, const struct image_white_balance_reference *reference) { g_assert(image->width % 4 == 0); g_assert(image->stride == image->width * 4); uint16_t fr, fg, fb; image_white_balance_factors(reference, &fr, &fg, &fb); u8x16 *p = (u8x16 *) image->pixels; u8x16 *pend = (u8x16 *) (image->pixels + image->height * image->stride); const u8x16 zero = { 0 }; const u8x16 ex0 = { 0, 16, 1, 17, 2, 18, 3, 19, 4, 20, 5, 21, 6, 22, 7, 23 }; const u8x16 ex1 = { 8, 24, 9, 25, 10, 26, 11, 27, 12, 28, 13, 29, 14, 30, 15, 31 }; const u16x8 mul = { fb, fg, fr, 255, fb, fg, fr, 255 }; const u8x16 imp = { 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30 }; while (p != pend) { u8x16 pix4 = *p; u16x8 pix2_0 = (u16x8) __builtin_shuffle(pix4, zero, ex0); u16x8 pix2_0_ovf = __builtin_ia32_pmulhuw128(pix2_0, mul) != (u16x8) zero; pix2_0 = ((pix2_0 * mul) | pix2_0_ovf) / 256; u16x8 pix2_1 = (u16x8) __builtin_shuffle(pix4, zero, ex1); u16x8 pix2_1_ovf = __builtin_ia32_pmulhuw128(pix2_1, mul) != (u16x8) zero; pix2_1 = ((pix2_1 * mul) | pix2_1_ovf) / 256; pix4 = __builtin_shuffle((u8x16) pix2_0, (u8x16) pix2_1, imp); *p++ = pix4; } } void image_white_balance(struct image *image, const struct image_white_balance_reference *reference) { switch (image->format) { case IMAGE_FORMAT_SGRBG8: image_white_balance_sgrbg8(image, reference); break; case IMAGE_FORMAT_XBGR32: image_white_balance_xbgr32(image, reference); break; default: g_assert_not_reached(); } } static void image_convert_to_xbgr32_from_sgrbg8(struct image *dst, const struct image *src) { /* * Compute missing value using an average of its neighbours. * Input pattern: * G R G R G R * B G B G B G * G R G R G R * B G B G B G */ g_assert(dst->width == src->width && dst->height == src->height); const int in_stride = src->stride; const int out_stride = dst->stride; int width = src->width; int height = src->height; /* Skip first line and column. */ const uint8_t *in = src->pixels + in_stride + 1; uint8_t *out = dst->pixels + out_stride + 4; /* Loop over lines. */ for (int i = 1; i < height - 1; i += 2) { /* Even lines. */ const uint8_t *in_stop = in + (width - 2); while (in != in_stop) { *out++ = (in[-1] + in[+1] + 1) >> 1; /* B */ *out++ = in[0]; /* G */ *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* R */ *out++ = 255; /* A */ in++; *out++ = in[0]; /* B */ *out++ = (in[-in_stride] + in[+in_stride] /* G */ + in[-1] + in[+1] + 2) >> 2; *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* R */ + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; *out++ = 255; /* A */ in++; } /* Fill first and last pixels. */ out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; out[0] = out[-4]; out[1] = out[-3]; out[2] = out[-2]; out[3] = out[-1]; out += out_stride - (width - 2) * 4; in += in_stride - (width - 2); /* Odd lines. */ in_stop = in + (width - 2); while (in != in_stop) { *out++ = (in[-in_stride - 1] + in[-in_stride + 1] /* B */ + in[+in_stride - 1] + in[+in_stride + 1] + 2) >> 2; *out++ = (in[-in_stride] + in[+in_stride] /* G */ + in[-1] + in[+1] + 2) >> 2; *out++ = in[0]; /* R */ *out++ = 255; /* A */ in++; *out++ = (in[-in_stride] + in[+in_stride] + 1) >> 1; /* B */ *out++ = in[0]; /* G */ *out++ = (in[-1] + in[+1] + 1) >> 1; /* R */ *out++ = 255; /* A */ in++; } /* Fill first and last pixels. */ out[-(width - 1) * 4 + 0] = out[-(width - 2) * 4 + 0]; out[-(width - 1) * 4 + 1] = out[-(width - 2) * 4 + 1]; out[-(width - 1) * 4 + 2] = out[-(width - 2) * 4 + 2]; out[-(width - 1) * 4 + 3] = out[-(width - 2) * 4 + 3]; out[0] = out[-4]; out[1] = out[-3]; out[2] = out[-2]; out[3] = out[-1]; out += out_stride - (width - 2) * 4; in += in_stride - (width - 2); } /* Last line. */ out -= 4; memcpy(out, out - out_stride, width * 4); /* First line. */ out -= (height - 1) * out_stride; memcpy(out, out + out_stride, width * 4); } void image_convert(struct image *dst, const struct image *src) { g_assert(dst->width == src->width && dst->height == src->height); if (dst->format == src->format) { g_assert(dst->stride == src->stride); memcpy(dst->pixels, src->pixels, src->stride * src->height); } else { switch (src->format) { case IMAGE_FORMAT_SGRBG8: switch (dst->format) { case IMAGE_FORMAT_XBGR32: image_convert_to_xbgr32_from_sgrbg8(dst, src); break; default: g_assert_not_reached(); } break; default: g_assert_not_reached(); } } } bool image_save(const struct image *image, const char *name, GError **error) { g_return_val_if_fail(error == NULL || *error == NULL, false); png_image pimage; memset(&pimage, 0, sizeof(pimage)); pimage.version = PNG_IMAGE_VERSION; pimage.width = image->width; pimage.height = image->height; switch(image->format) { case IMAGE_FORMAT_SGRBG8: pimage.format = PNG_FORMAT_GRAY; break; case IMAGE_FORMAT_XBGR32: pimage.format = PNG_FORMAT_BGRA; break; default: g_assert_not_reached(); } int r = png_image_write_to_file(&pimage, name, 0, image->pixels, image->stride, NULL); if (r == 0) { g_set_error(error, IMAGE_ERROR, IMAGE_ERROR_SAVE, "can not write image: %s", pimage.message); return false; } return true; }