summaryrefslogtreecommitdiff
path: root/src/image.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/image.c')
-rw-r--r--src/image.c417
1 files changed, 417 insertions, 0 deletions
diff --git a/src/image.c b/src/image.c
new file mode 100644
index 0000000..ec29751
--- /dev/null
+++ b/src/image.c
@@ -0,0 +1,417 @@
+/* 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: <nico at ni.fr.eu.org>
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <png.h>
+
+#include "image.h"
+#include "utils.h"
+
+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;
+ uint8_t wg = reference->g;
+ uint8_t wb = reference->b;
+ 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;
+}