summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rwxr-xr-xscripts/vsp-lib.sh18
-rw-r--r--src/.gitignore2
-rw-r--r--src/Makefile22
-rw-r--r--src/gen-image.c1246
5 files changed, 1281 insertions, 9 deletions
diff --git a/Makefile b/Makefile
index 4e4a02d..a104078 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-SUBDIRS=data scripts tests
+SUBDIRS=data scripts src tests
recursive=all clean install
diff --git a/scripts/vsp-lib.sh b/scripts/vsp-lib.sh
index 4d3d166..6a59505 100755
--- a/scripts/vsp-lib.sh
+++ b/scripts/vsp-lib.sh
@@ -72,17 +72,17 @@ compare_frame_fuzzy() {
img_a=$3
img_b=$4
- png_a=${img_a/bin/png}
- png_b=${img_b/bin/png}
+ pnm_a=${img_a/bin/pnm}
+ pnm_b=${img_b/bin/pnm}
- raw2rgbpnm -f $fmt -s $size $img_a $png_a > /dev/null
- raw2rgbpnm -f $fmt -s $size $img_b $png_b > /dev/null
+ raw2rgbpnm -f $fmt -s $size $img_a $pnm_a > /dev/null
+ raw2rgbpnm -f $fmt -s $size $img_b $pnm_b > /dev/null
- ae=$(compare -metric ae $png_a $png_b /dev/null 2>&1)
- mae=$(compare -metric mae $png_a $png_b /dev/null 2>&1 | sed 's/.*(\(.*\))/\1/')
+ ae=$(compare -metric ae $pnm_a $pnm_b /dev/null 2>&1)
+ mae=$(compare -metric mae $pnm_a $pnm_b /dev/null 2>&1 | sed 's/.*(\(.*\))/\1/')
- rm $png_a
- rm $png_b
+ rm $pnm_a
+ rm $pnm_b
width=$(echo $size | cut -d 'x' -f 1)
height=$(echo $size | cut -d 'x' -f 2)
@@ -136,6 +136,8 @@ compare_frames() {
}
done
+ rm -f frames/$reference
+
echo $result
}
diff --git a/src/.gitignore b/src/.gitignore
new file mode 100644
index 0000000..0c9be86
--- /dev/null
+++ b/src/.gitignore
@@ -0,0 +1,2 @@
+*.o
+gen-image
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..564ee24
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,22 @@
+CROSS_COMPILE ?=
+
+CC := $(CROSS_COMPILE)gcc
+CFLAGS ?= -O0 -g -W -Wall -Wno-unused-parameter -Iinclude
+LDFLAGS ?=
+LIBS := -lm
+GEN-IMAGE := gen-image
+
+%.o : %.c
+ $(CC) $(CFLAGS) -c -o $@ $<
+
+all: $(GEN-IMAGE)
+
+$(GEN-IMAGE): gen-image.o
+ $(CC) $(LDFLAGS) -o $@ $^ $(LIBS)
+
+clean:
+ -rm -f *.o
+ -rm -f $(GEN-IMAGE)
+
+install:
+ cp $(GEN-IMAGE) $(INSTALL_DIR)/
diff --git a/src/gen-image.c b/src/gen-image.c
new file mode 100644
index 0000000..87dd72a
--- /dev/null
+++ b/src/gen-image.c
@@ -0,0 +1,1246 @@
+/*
+ * Copyright (C) 2016 Laurent Pinchart <laurent.pinchart@ideasonboard.com>
+ *
+ * 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.
+ */
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <math.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <linux/videodev2.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+#define min(a, b) ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a < _b ? _a : _b; \
+})
+
+#define max(a, b) ({ \
+ typeof(a) _a = (a); \
+ typeof(b) _b = (b); \
+ _a > _b ? _a : _b; \
+})
+
+#define clamp(val, low, high) max(low, min(high, val));
+
+struct format_color_component {
+ unsigned char length;
+ unsigned char offset;
+};
+
+struct format_rgb_info {
+ unsigned int bpp;
+ struct format_color_component red;
+ struct format_color_component green;
+ struct format_color_component blue;
+ struct format_color_component alpha;
+};
+
+enum format_yuv_order {
+ YUV_YCbCr = 1,
+ YUV_YCrCb = 2,
+ YUV_YC = 4,
+ YUV_CY = 8,
+};
+
+struct format_yuv_info {
+ unsigned int num_planes;
+ enum format_yuv_order order;
+ unsigned int xsub;
+ unsigned int ysub;
+};
+
+struct format_info {
+ const char *name;
+ bool is_yuv;
+ struct format_rgb_info rgb;
+ struct format_yuv_info yuv;
+};
+
+struct image {
+ const struct format_info *format;
+ unsigned int width;
+ unsigned int height;
+ unsigned int size;
+ void *data;
+};
+
+struct params {
+ unsigned int alpha;
+ enum v4l2_ycbcr_encoding encoding;
+ enum v4l2_quantization quantization;
+};
+
+struct options {
+ const char *input_filename;
+ const char *output_filename;
+ const char *histo_filename;
+
+ const struct format_info *output_format;
+ unsigned int output_height;
+ unsigned int output_width;
+
+ bool process_yuv;
+ unsigned int compose;
+ struct params params;
+};
+
+/* -----------------------------------------------------------------------------
+ * Format information
+ */
+
+#define MAKE_RGB_INFO(rl, ro, gl, go, bl, bo, al, ao) \
+ .red = { (rl), (ro) }, .green = { (gl), (go) }, \
+ .blue = { (bl), (bo) }, .alpha = { (al), (ao) }
+
+static const struct format_info format_info[] = {
+ /*
+ * The alpha channel maps to the X (don't care) bits for the XRGB
+ * formats.
+ */
+ { "RGB332", false, .rgb = { 8, MAKE_RGB_INFO(3, 5, 3, 2, 2, 0, 0, 0) } },
+ { "ARGB444", false, .rgb = { 16, MAKE_RGB_INFO(4, 8, 4, 4, 4, 0, 4, 12) } },
+ { "XRGB444", false, .rgb = { 16, MAKE_RGB_INFO(4, 8, 4, 4, 4, 0, 4, 12) } },
+ { "ARGB555", false, .rgb = { 16, MAKE_RGB_INFO(5, 10, 5, 5, 5, 0, 1, 15) } },
+ { "XRGB555", false, .rgb = { 16, MAKE_RGB_INFO(5, 10, 5, 5, 5, 0, 1, 15) } },
+ { "RGB565", false, .rgb = { 16, MAKE_RGB_INFO(5, 11, 6, 5, 5, 0, 0, 0) } },
+ { "BGR24", false, .rgb = { 24, MAKE_RGB_INFO(8, 16, 8, 8, 8, 0, 0, 0) } },
+ { "RGB24", false, .rgb = { 24, MAKE_RGB_INFO(8, 0, 8, 8, 8, 16, 0, 0) } },
+ { "ABGR32", false, .rgb = { 32, MAKE_RGB_INFO(8, 16, 8, 8, 8, 0, 8, 24) } },
+ { "XBGR32", false, .rgb = { 32, MAKE_RGB_INFO(8, 16, 8, 8, 8, 0, 8, 24) } },
+ { "ARGB32", false, .rgb = { 32, MAKE_RGB_INFO(8, 8, 8, 16, 8, 24, 8, 0) } },
+ { "XRGB32", false, .rgb = { 32, MAKE_RGB_INFO(8, 8, 8, 16, 8, 24, 8, 0) } },
+ { "UYVY", true, .yuv = { 1, YUV_YCbCr | YUV_CY, 2, 1 } },
+ { "VYUY", true, .yuv = { 1, YUV_YCrCb | YUV_CY, 2, 1 } },
+ { "YUYV", true, .yuv = { 1, YUV_YCbCr | YUV_YC, 2, 1 } },
+ { "YVYU", true, .yuv = { 1, YUV_YCrCb | YUV_YC, 2, 1 } },
+ { "NV12M", true, .yuv = { 2, YUV_YCbCr, 2, 2 } },
+ { "NV21M", true, .yuv = { 2, YUV_YCrCb, 2, 2 } },
+ { "NV16M", true, .yuv = { 2, YUV_YCbCr, 2, 1 } },
+ { "NV61M", true, .yuv = { 2, YUV_YCrCb, 2, 1 } },
+ { "YUV420M", true, .yuv = { 3, YUV_YCbCr, 2, 2 } },
+ { "YVU420M", true, .yuv = { 3, YUV_YCrCb, 2, 2 } },
+ { "YUV422M", true, .yuv = { 3, YUV_YCbCr, 2, 1 } },
+ { "YVU422M", true, .yuv = { 3, YUV_YCrCb, 2, 1 } },
+ { "YUV444M", true, .yuv = { 3, YUV_YCbCr, 1, 1 } },
+ { "YVU444M", true, .yuv = { 3, YUV_YCrCb, 1, 1 } },
+ { "YUV24", true, .yuv = { 1, YUV_YCbCr | YUV_YC, 1, 1 } },
+};
+
+static const struct format_info *format_by_name(const char *name)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(format_info); i++) {
+ if (!strcmp(name, format_info[i].name))
+ return &format_info[i];
+ }
+
+ return NULL;
+}
+
+/* -----------------------------------------------------------------------------
+ * File I/O
+ */
+
+static int file_read(int fd, void *buffer, size_t size)
+{
+ unsigned int offset = 0;
+
+ while (offset < size) {
+ ssize_t nbytes;
+
+ nbytes = read(fd, buffer + offset, size - offset);
+ if (nbytes < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ if (nbytes == 0)
+ return offset;
+
+ offset += nbytes;
+ }
+
+ return size;
+}
+
+static int file_write(int fd, const void *buffer, size_t size)
+{
+ unsigned int offset = 0;
+
+ while (offset < size) {
+ ssize_t nbytes;
+
+ nbytes = write(fd, buffer + offset, size - offset);
+ if (nbytes < 0) {
+ if (errno == EINTR)
+ continue;
+
+ return -errno;
+ }
+
+ offset += nbytes;
+ }
+
+ return 0;
+}
+
+/* -----------------------------------------------------------------------------
+ * Image initialization
+ */
+
+static struct image *image_new(const struct format_info *format,
+ unsigned int width, unsigned int height)
+{
+ struct image *image;
+
+ image = malloc(sizeof(*image));
+ if (!image)
+ return NULL;
+
+ memset(image, 0, sizeof(*image));
+ image->format = format;
+ image->width = width;
+ image->height = height;
+
+ if (format->is_yuv)
+ image->size = image->width * image->height
+ * (8 + 2 * 8 / format->yuv.xsub / format->yuv.ysub)
+ / 8;
+ else
+ image->size = image->width * image->height
+ * format->rgb.bpp / 8;
+
+ image->data = malloc(image->size);
+ if (!image->data) {
+ printf("Not enough memory for image data\n");
+ free(image);
+ return NULL;
+ }
+
+ return image;
+}
+
+static void image_delete(struct image *image)
+{
+ if (!image)
+ return;
+
+ free(image->data);
+ free(image);
+}
+
+/* -----------------------------------------------------------------------------
+ * Image read and write
+ */
+
+static int pnm_read_bytes(int fd, char *buffer, size_t size)
+{
+ int ret;
+
+ ret = file_read(fd, buffer, size);
+ if (ret < 0) {
+ printf("Unable to read PNM file: %s (%d)\n", strerror(-ret),
+ ret);
+ return ret;
+ }
+ if ((size_t)ret != size) {
+ printf("Invalid PNM file: file too short\n");
+ return -ENODATA;
+ }
+
+ return 0;
+}
+
+static int pnm_read_integer(int fd)
+{
+ unsigned int value = 0;
+ int ret;
+ char c;
+
+ do {
+ ret = pnm_read_bytes(fd, &c, 1);
+ } while (!ret && isspace(c));
+
+ if (ret)
+ return ret;
+
+ while (!ret && isdigit(c)) {
+ value = value * 10 + c - '0';
+ ret = pnm_read_bytes(fd, &c, 1);
+ }
+
+ if (ret)
+ return ret;
+
+ if (!isspace(c))
+ return -EINVAL;
+
+ return value;
+}
+
+static struct image *pnm_read(const char *filename)
+{
+ struct image *image;
+ unsigned int width;
+ unsigned int height;
+ char buffer[2];
+ int ret;
+ int fd;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ printf("Unable to open PNM file %s: %s (%d)\n", filename,
+ strerror(errno), errno);
+ return NULL;
+ }
+
+ /* Read and validate the header. */
+ ret = pnm_read_bytes(fd, buffer, 2);
+ if (ret < 0)
+ goto done;
+
+ if (buffer[0] != 'P' || buffer[1] != '6') {
+ printf("Invalid PNM file: invalid signature\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Read the width, height and depth. */
+ ret = pnm_read_integer(fd);
+ if (ret < 0) {
+ printf("Invalid PNM file: invalid width\n");
+ goto done;
+ }
+
+ width = ret;
+
+ ret = pnm_read_integer(fd);
+ if (ret < 0) {
+ printf("Invalid PNM file: invalid height\n");
+ goto done;
+ }
+
+ height = ret;
+
+ ret = pnm_read_integer(fd);
+ if (ret < 0) {
+ printf("Invalid PNM file: invalid depth\n");
+ goto done;
+ }
+
+ if (ret != 255) {
+ printf("Invalid PNM file: unsupported depth %u\n", ret);
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Allocate the image and read the data. */
+ image = image_new(format_by_name("RGB24"), width, height);
+ if (!image)
+ goto done;
+
+ ret = pnm_read_bytes(fd, image->data, image->size);
+ if (ret < 0) {
+ image_delete(image);
+ image = NULL;
+ }
+
+done:
+ close(fd);
+
+ return ret ? NULL : image;
+}
+
+static struct image *image_read(const char *filename)
+{
+ return pnm_read(filename);
+}
+
+static int image_write(const struct image *image, const char *filename)
+{
+ int ret;
+ int fd;
+
+ fd = open(filename, O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0) {
+ printf("Unable to open output file %s: %s (%d)\n", filename,
+ strerror(errno), errno);
+ return -errno;
+ }
+
+ ret = file_write(fd, image->data, image->size);
+ if (ret < 0)
+ printf("Unable to write output image: %s (%d)\n",
+ strerror(-ret), ret);
+ else
+ ftruncate(fd, image->size);
+
+ close(fd);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Image formatting
+ */
+
+static void image_format_rgb8(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ const uint8_t *idata = input->data;
+ uint8_t *odata = output->data;
+ uint8_t r, g, b;
+ unsigned int x, y;
+
+ idata = input->data;
+ odata = output->data;
+
+ for (y = 0; y < input->height; ++y) {
+ for (x = 0; x < input->width; ++x) {
+ /* There's only one RGB8 variant supported, hardcode it. */
+ r = *idata++ >> 5;
+ g = *idata++ >> 5;
+ b = *idata++ >> 6;
+
+ *odata++ = (r << 5) | (g << 2) | b;
+ }
+ }
+}
+
+static void image_format_rgb16(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ const struct format_info *format = output->format;
+ const uint8_t *idata = input->data;
+ uint16_t *odata = output->data;
+ uint8_t r, g, b, a;
+ unsigned int x, y;
+
+ for (y = 0; y < input->height; ++y) {
+ for (x = 0; x < input->width; ++x) {
+ r = *idata++ >> (8 - format->rgb.red.length);
+ g = *idata++ >> (8 - format->rgb.green.length);
+ b = *idata++ >> (8 - format->rgb.blue.length);
+ a = params->alpha >> (8 - format->rgb.alpha.length);
+
+ *odata++ = (r << format->rgb.red.offset)
+ | (g << format->rgb.green.offset)
+ | (b << format->rgb.blue.offset)
+ | (a << format->rgb.alpha.offset);
+ }
+ }
+}
+
+static void image_format_rgb24(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ struct color_rgb24 {
+ unsigned int value:24;
+ } __attribute__((__packed__));
+
+ const struct format_info *format = output->format;
+ const uint8_t *idata = input->data;
+ struct color_rgb24 *odata = output->data;
+ uint8_t r, g, b, a;
+ unsigned int x, y;
+
+ idata = input->data;
+ odata = output->data;
+
+ for (y = 0; y < input->height; ++y) {
+ for (x = 0; x < input->width; ++x) {
+ r = *idata++ >> (8 - format->rgb.red.length);
+ g = *idata++ >> (8 - format->rgb.green.length);
+ b = *idata++ >> (8 - format->rgb.blue.length);
+ a = params->alpha >> (8 - format->rgb.alpha.length);
+
+ *odata++ = (struct color_rgb24){ .value =
+ (r << format->rgb.red.offset) |
+ (g << format->rgb.green.offset) |
+ (b << format->rgb.blue.offset) |
+ (a << format->rgb.alpha.offset) };
+ }
+ }
+}
+
+static void image_format_rgb32(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ const struct format_info *format = output->format;
+ const uint8_t *idata = input->data;
+ uint32_t *odata = output->data;
+ uint8_t r, g, b, a;
+ unsigned int x, y;
+
+ for (y = 0; y < input->height; ++y) {
+ for (x = 0; x < input->width; ++x) {
+ r = *idata++ >> (8 - format->rgb.red.length);
+ g = *idata++ >> (8 - format->rgb.green.length);
+ b = *idata++ >> (8 - format->rgb.blue.length);
+ a = params->alpha >> (8 - format->rgb.alpha.length);
+
+ *odata++ = (r << format->rgb.red.offset)
+ | (g << format->rgb.green.offset)
+ | (b << format->rgb.blue.offset)
+ | (a << format->rgb.alpha.offset);
+ }
+ }
+}
+
+/*
+ * In YUV packed and planar formats, when subsampling horizontally average the
+ * chroma components of the two pixels to match the hardware behaviour.
+ */
+static void image_format_yuv_packed(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ const struct format_info *format = output->format;
+ const uint8_t *idata = input->data;
+ uint8_t *o_y = output->data + ((format->yuv.order & YUV_YC) ? 0 : 1);
+ uint8_t *o_c = output->data + ((format->yuv.order & YUV_CY) ? 0 : 1);
+ unsigned int u_offset = (format->yuv.order & YUV_YCrCb) ? 2 : 0;
+ unsigned int v_offset = (format->yuv.order & YUV_YCbCr) ? 2 : 0;
+ unsigned int x;
+ unsigned int y;
+
+ for (y = 0; y < output->height; ++y) {
+ for (x = 0; x < output->width; x += 2) {
+ o_y[2*x] = idata[3*x];
+ o_y[2*x + 2] = idata[3*x + 3];
+ o_c[2*x + u_offset] = (idata[3*x + 1] + idata[3*x + 4]) / 2;
+ o_c[2*x + v_offset] = (idata[3*x + 2] + idata[3*x + 5]) / 2;
+ }
+
+ o_y += input->width * 2;
+ o_c += input->width * 2;
+ idata += input->width * 3;
+ }
+}
+
+static void image_format_yuv_planar(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ const struct format_info *format = output->format;
+ const uint8_t *idata;
+ uint8_t *o_y = output->data;
+ uint8_t *o_c = o_y + output->width * output->height;
+ uint8_t *o_u;
+ uint8_t *o_v;
+ unsigned int xsub = format->yuv.xsub;
+ unsigned int ysub = format->yuv.ysub;
+ unsigned int c_stride;
+ unsigned int x;
+ unsigned int y;
+
+ if (format->yuv.num_planes == 2) {
+ o_u = (format->yuv.order & YUV_YCbCr) ? o_c : o_c + 1;
+ o_v = (format->yuv.order & YUV_YCrCb) ? o_c : o_c + 1;
+ c_stride = 2;
+ } else {
+ unsigned int c_size = output->width * output->height
+ / xsub / ysub;
+
+ o_u = (format->yuv.order & YUV_YCbCr) ? o_c : o_c + c_size;
+ o_v = (format->yuv.order & YUV_YCrCb) ? o_c : o_c + c_size;
+ c_stride = 1;
+ }
+
+ idata = input->data;
+ for (y = 0; y < output->height; ++y) {
+ for (x = 0; x < output->width; ++x)
+ *o_y++ = idata[3*x];
+
+ idata += input->width * 3;
+ }
+
+ idata = input->data;
+ for (y = 0; y < output->height / ysub; ++y) {
+ if (xsub == 1) {
+ for (x = 0; x < output->width; x += xsub) {
+ o_u[x*c_stride/xsub] = idata[3*x + 1];
+ o_v[x*c_stride/xsub] = idata[3*x + 2];
+ }
+ } else {
+ for (x = 0; x < output->width; x += xsub) {
+ o_u[x*c_stride/xsub] = (idata[3*x + 1] + idata[3*x + 4]) / 2;
+ o_v[x*c_stride/xsub] = (idata[3*x + 2] + idata[3*x + 5]) / 2;
+ }
+ }
+
+ o_u += output->width * c_stride / xsub;
+ o_v += output->width * c_stride / xsub;
+ idata += input->width * 3 * ysub;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Colorspace handling
+ *
+ * The code is inspired by the v4l2-tpg Linux kernel driver.
+ */
+
+static void colorspace_matrix(enum v4l2_ycbcr_encoding encoding,
+ enum v4l2_quantization quantization,
+ int (*matrix)[3][3])
+{
+#define COEFF(v, r) ((int)(0.5 + (v) * (r) * 256.0))
+
+ static const int bt601[3][3] = {
+ { COEFF(0.299, 219), COEFF(0.587, 219), COEFF(0.114, 219) },
+ { COEFF(-0.169, 224), COEFF(-0.331, 224), COEFF(0.5, 224) },
+ { COEFF(0.5, 224), COEFF(-0.419, 224), COEFF(-0.081, 224) },
+ };
+ static const int bt601_full[3][3] = {
+ { COEFF(0.299, 255), COEFF(0.587, 255), COEFF(0.114, 255) },
+ { COEFF(-0.169, 255), COEFF(-0.331, 255), COEFF(0.5, 255) },
+ { COEFF(0.5, 255), COEFF(-0.419, 255), COEFF(-0.081, 255) },
+ };
+ static const int rec709[3][3] = {
+ { COEFF(0.2126, 219), COEFF(0.7152, 219), COEFF(0.0722, 219) },
+ { COEFF(-0.1146, 224), COEFF(-0.3854, 224), COEFF(0.5, 224) },
+ { COEFF(0.5, 224), COEFF(-0.4542, 224), COEFF(-0.0458, 224) },
+ };
+ static const int rec709_full[3][3] = {
+ { COEFF(0.2126, 255), COEFF(0.7152, 255), COEFF(0.0722, 255) },
+ { COEFF(-0.1146, 255), COEFF(-0.3854, 255), COEFF(0.5, 255) },
+ { COEFF(0.5, 255), COEFF(-0.4542, 255), COEFF(-0.0458, 255) },
+ };
+ static const int smpte240m[3][3] = {
+ { COEFF(0.212, 219), COEFF(0.701, 219), COEFF(0.087, 219) },
+ { COEFF(-0.116, 224), COEFF(-0.384, 224), COEFF(0.5, 224) },
+ { COEFF(0.5, 224), COEFF(-0.445, 224), COEFF(-0.055, 224) },
+ };
+ static const int smpte240m_full[3][3] = {
+ { COEFF(0.212, 255), COEFF(0.701, 255), COEFF(0.087, 255) },
+ { COEFF(-0.116, 255), COEFF(-0.384, 255), COEFF(0.5, 255) },
+ { COEFF(0.5, 255), COEFF(-0.445, 255), COEFF(-0.055, 255) },
+ };
+ static const int bt2020[3][3] = {
+ { COEFF(0.2627, 219), COEFF(0.6780, 219), COEFF(0.0593, 219) },
+ { COEFF(-0.1396, 224), COEFF(-0.3604, 224), COEFF(0.5, 224) },
+ { COEFF(0.5, 224), COEFF(-0.4598, 224), COEFF(-0.0402, 224) },
+ };
+ static const int bt2020_full[3][3] = {
+ { COEFF(0.2627, 255), COEFF(0.6780, 255), COEFF(0.0593, 255) },
+ { COEFF(-0.1396, 255), COEFF(-0.3604, 255), COEFF(0.5, 255) },
+ { COEFF(0.5, 255), COEFF(-0.4698, 255), COEFF(-0.0402, 255) },
+ };
+
+ bool full = quantization == V4L2_QUANTIZATION_FULL_RANGE;
+ unsigned int i;
+ const int (*m)[3][3];
+
+ switch (encoding) {
+ case V4L2_YCBCR_ENC_601:
+ default:
+ m = full ? &bt601_full : &bt601;
+ break;
+ case V4L2_YCBCR_ENC_709:
+ m = full ? &rec709_full : &rec709;
+ break;
+ case V4L2_YCBCR_ENC_BT2020:
+ m = full ? &bt2020_full : &bt2020;
+ break;
+ case V4L2_YCBCR_ENC_SMPTE240M:
+ m = full ? &smpte240m_full : &smpte240m;
+ break;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(*m); ++i)
+ memcpy((*matrix)[i], (*m)[i], sizeof((*m)[i]));
+}
+
+static void colorspace_rgb2ycbcr(int m[3][3],
+ enum v4l2_quantization quantization,
+ const uint8_t rgb[3], uint8_t ycbcr[3])
+{
+ bool full = quantization == V4L2_QUANTIZATION_FULL_RANGE;
+ unsigned int y_offset = full ? 0 : 16;
+ int r, g, b;
+ int y, cb, cr;
+ int div;
+
+ r = rgb[0] << 4;
+ g = rgb[1] << 4;
+ b = rgb[2] << 4;
+
+ div = (1 << (8 + 4)) * 255;
+ y = (m[0][0] * r + m[0][1] * g + m[0][2] * b + y_offset * div) / div;
+ cb = (m[1][0] * r + m[1][1] * g + m[1][2] * b + 128 * div) / div;
+ cr = (m[2][0] * r + m[2][1] * g + m[2][2] * b + 128 * div) / div;
+
+ ycbcr[0] = y;
+ ycbcr[1] = cb;
+ ycbcr[2] = cr;
+}
+
+static void image_colorspace_rgb_to_yuv(const struct image *input,
+ struct image *output,
+ const struct params *params)
+{
+ int matrix[3][3];
+ const uint8_t *idata = input->data;
+ uint8_t *odata = output->data;
+ unsigned int x;
+ unsigned int y;
+
+ colorspace_matrix(params->encoding, params->quantization, &matrix);
+
+ for (y = 0; y < output->height; ++y) {
+ for (x = 0; x < output->width; ++x) {
+ colorspace_rgb2ycbcr(matrix, params->quantization, idata, odata);
+ idata += 3;
+ odata += 3;
+ }
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Image scaling
+ */
+
+static void image_scale_bilinear(const struct image *input, struct image *output)
+{
+#define _C0(x, y) (idata[0][((y)*input->width+(x)) * 3])
+#define _C1(x, y) (idata[1][((y)*input->width+(x)) * 3])
+#define _C2(x, y) (idata[2][((y)*input->width+(x)) * 3])
+ const uint8_t *idata[3] = { input->data, input->data + 1, input->data + 2 };
+ uint8_t *odata = output->data;
+ uint8_t c0, c1, c2;
+ unsigned int u, v;
+
+ for (v = 0; v < output->height; ++v) {
+ double v_input = (double)v / (output->height - 1) * (input->height - 1);
+ unsigned int y = floor(v_input);
+ double v_ratio = v_input - y;
+
+ for (u = 0; u < output->width; ++u) {
+ double u_input = (double)u / (output->width - 1) * (input->width - 1);
+ unsigned int x = floor(u_input);
+ double u_ratio = u_input - x;
+
+ c0 = (_C0(x, y) * (1 - u_ratio) + _C0(x+1, y) * u_ratio) * (1 - v_ratio)
+ + (_C0(x, y+1) * (1 - u_ratio) + _C0(x+1, y+1) * u_ratio) * v_ratio;
+ c1 = (_C1(x, y) * (1 - u_ratio) + _C1(x+1, y) * u_ratio) * (1 - v_ratio)
+ + (_C1(x, y+1) * (1 - u_ratio) + _C1(x+1, y+1) * u_ratio) * v_ratio;
+ c2 = (_C2(x, y) * (1 - u_ratio) + _C2(x+1, y) * u_ratio) * (1 - v_ratio)
+ + (_C2(x, y+1) * (1 - u_ratio) + _C2(x+1, y+1) * u_ratio) * v_ratio;
+
+ *odata++ = c0;
+ *odata++ = c1;
+ *odata++ = c2;
+ }
+ }
+#undef _C0
+#undef _C1
+#undef _C2
+}
+
+static void image_scale(const struct image *input, struct image *output,
+ const struct params *params)
+{
+ image_scale_bilinear(input, output);
+}
+
+/* -----------------------------------------------------------------------------
+ * Image composing
+ */
+
+static void image_compose(const struct image *input, struct image *output,
+ unsigned int num_inputs)
+{
+ const uint8_t *idata = input->data;
+ uint8_t *odata = output->data;
+ unsigned int offset = 50;
+ unsigned int y;
+ unsigned int i;
+
+ memset(odata, 0, output->size);
+
+ for (i = 0; i < num_inputs; ++i) {
+ unsigned int dst_offset = (offset * output->width + offset) * 3;
+
+ if (offset >= output->width || offset >= output->height)
+ break;
+
+ for (y = 0; y < output->height - offset; ++y)
+ memcpy(odata + y * output->width * 3 + dst_offset,
+ idata + y * output->width * 3,
+ (output->width - offset) * 3);
+
+ offset += 50;
+ }
+}
+
+/* -----------------------------------------------------------------------------
+ * Histogram
+ */
+
+static void histogram_compute(const struct image *image, void *histo)
+{
+ const uint8_t *data = image->data;
+ uint8_t comp_min[3] = { 255, 255, 255 };
+ uint8_t comp_max[3] = { 0, 0, 0 };
+ uint32_t comp_sums[3] = { 0, 0, 0 };
+ uint32_t comp_bins[3][64];
+ unsigned int comp_map[3];
+ unsigned int x, y;
+ unsigned int i, j;
+
+ if (image->format->is_yuv)
+ memcpy(comp_map, (unsigned int[3]){ 2, 0, 1 }, sizeof(comp_map));
+ else
+ memcpy(comp_map, (unsigned int[3]){ 0, 1, 2 }, sizeof(comp_map));
+
+ memset(comp_bins, 0, sizeof(comp_bins));
+
+ for (y = 0; y < image->height; ++y) {
+ for (x = 0; x < image->width; ++x) {
+ for (i = 0; i < 3; ++i) {
+ comp_min[i] = min(*data, comp_min[i]);
+ comp_max[i] = max(*data, comp_max[i]);
+ comp_sums[i] += *data;
+ comp_bins[i][*data >> 2]++;
+ data++;
+ }
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(comp_min); ++i) {
+ *(uint8_t *)histo++ = comp_min[comp_map[i]];
+ *(uint8_t *)histo++ = 0;
+ *(uint8_t *)histo++ = comp_max[comp_map[i]];
+ *(uint8_t *)histo++ = 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(comp_sums); ++i) {
+ *(uint32_t *)histo = comp_sums[comp_map[i]];
+ histo += 4;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(comp_bins); ++i) {
+ for (j = 0; j < ARRAY_SIZE(comp_bins[i]); ++j) {
+ *(uint32_t *)histo = comp_bins[comp_map[i]][j];
+ histo += 4;
+ }
+ }
+}
+
+static int histogram(const struct image *image, const char *filename)
+{
+ uint8_t data[3*4 + 3*4 + 3*64*4];
+ int ret;
+ int fd;
+
+ histogram_compute(image, data);
+
+ fd = open(filename, O_WRONLY | O_CREAT,
+ S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+ if (fd < 0) {
+ printf("Unable to open histogram file %s: %s (%d)\n", filename,
+ strerror(errno), errno);
+ return -errno;
+ }
+
+ ret = file_write(fd, data, sizeof(data));
+ if (ret < 0)
+ printf("Unable to write histogram: %s (%d)\n",
+ strerror(-ret), ret);
+ else
+ ftruncate(fd, sizeof(data));
+
+ close(fd);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Processing pipeline
+ */
+
+static int process(const struct options *options)
+{
+ struct image *input = NULL;
+ struct image *output = NULL;
+ unsigned int output_width;
+ unsigned int output_height;
+ int ret = 0;
+
+ /* Read the input image */
+ input = image_read(options->input_filename);
+ if (!input) {
+ ret = -EINVAL;
+ goto done;
+ }
+
+ /* Convert colorspace */
+ if (options->process_yuv) {
+ struct image *yuv;
+
+ yuv = image_new(format_by_name("YUV24"), input->width,
+ input->height);
+ if (!yuv) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ image_colorspace_rgb_to_yuv(input, yuv, &options->params);
+ image_delete(input);
+ input = yuv;
+ }
+
+ /* Scale */
+ if (options->output_width && options->output_height) {
+ output_width = options->output_width;
+ output_height = options->output_height;
+ } else {
+ output_width = input->width;
+ output_height = input->height;
+ }
+
+ if (input->width != output_width ||
+ input->height != output_height) {
+ struct image *scaled;
+
+ scaled = image_new(input->format, output_width, output_height);
+ if (!scaled) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ image_scale(input, scaled, &options->params);
+ image_delete(input);
+ input = scaled;
+ }
+
+ /* Compose */
+ if (options->compose) {
+ struct image *composed;
+
+ composed = image_new(input->format, input->width, input->height);
+ if (!composed) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ image_compose(input, composed, options->compose);
+ image_delete(input);
+ input = composed;
+ }
+
+ /* Compute the histogram */
+ if (options->histo_filename) {
+ ret = histogram(input, options->histo_filename);
+ if (ret)
+ goto done;
+ }
+
+ /* Format the output */
+ if (input->format->is_yuv && !options->output_format->is_yuv) {
+ printf("RGB output with YUV processing not supported\n");
+ ret = -EINVAL;
+ goto done;
+ }
+
+ if (!input->format->is_yuv && options->output_format->is_yuv) {
+ const struct format_info *format = format_by_name("YUV24");
+ struct image *converted;
+
+ converted = image_new(format, input->width, input->height);
+ if (!converted) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ image_colorspace_rgb_to_yuv(input, converted, &options->params);
+ image_delete(input);
+ input = converted;
+ }
+
+ output = image_new(options->output_format, input->width, input->height);
+ if (!output) {
+ ret = -ENOMEM;
+ goto done;
+ }
+
+ if (output->format->is_yuv) {
+ switch (output->format->yuv.num_planes) {
+ case 1:
+ image_format_yuv_packed(input, output, &options->params);
+ break;
+ case 2:
+ case 3:
+ image_format_yuv_planar(input, output, &options->params);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ } else {
+ switch (output->format->rgb.bpp) {
+ case 8:
+ image_format_rgb8(input, output, &options->params);
+ break;
+ case 16:
+ image_format_rgb16(input, output, &options->params);
+ break;
+ case 24:
+ image_format_rgb24(input, output, &options->params);
+ break;
+ case 32:
+ image_format_rgb32(input, output, &options->params);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ }
+
+ if (ret < 0) {
+ printf("Output formatting failed\n");
+ goto done;
+ }
+
+ /* Write the output image */
+ if (options->output_filename) {
+ ret = image_write(output, options->output_filename);
+ if (ret)
+ goto done;
+ }
+
+ ret = 0;
+
+done:
+ image_delete(input);
+ image_delete(output);
+ return ret;
+}
+
+/* -----------------------------------------------------------------------------
+ * Usage, argument parsing and main
+ */
+
+static void usage(const char *argv0)
+{
+ printf("Usage: %s [options] <infile.pnm>\n\n", argv0);
+ printf("Convert the input image stored in <infile> in PNM format to\n");
+ printf("the target format and resolution and store the resulting\n");
+ printf("image in raw binary form\n\n");
+ printf("Supported options:\n");
+ printf("-a, --alpha value Set the alpha value. Valid syntaxes are floating\n");
+ printf(" point values ([0.0 - 1.0]), fixed point values ([0-255])\n");
+ printf(" or percentages ([0%% - 100%%]). Defaults to 1.0\n");
+ printf("-c, --compose n Compose n copies of the image offset by (50,50) over a black background\n");
+ printf("-e, --encoding enc Set the YCbCr encoding method. Valid values are\n");
+ printf(" BT.601, REC.709, BT.2020 and SMPTE240M\n");
+ printf("-f, --format format Set the output image format\n");
+ printf(" Defaults to RGB24 if not specified\n");
+ printf(" Use -f help to list the supported formats\n");
+ printf("-h, --help Show this help screen\n");
+ printf("-H, --histogram file Compute histogram on the output image and store it to file\n");
+ printf("-o, --output file Store the output image to file\n");
+ printf("-q, --quantization q Set the quantization method. Valid values are\n");
+ printf(" limited or full\n");
+ printf("-s, --size WxH Set the output image size\n");
+ printf(" Defaults to the input size if not specified\n");
+ printf("-y, --yuv Perform all processing in YUV space\n");
+}
+
+static void list_formats(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(format_info); i++)
+ printf("%s\n", format_info[i].name);
+}
+
+static struct option opts[] = {
+ {"alpha", 1, 0, 'a'},
+ {"compose", 1, 0, 'c'},
+ {"encoding", 1, 0, 'e'},
+ {"format", 1, 0, 'f'},
+ {"help", 0, 0, 'h'},
+ {"histogram", 1, 0, 'H'},
+ {"output", 1, 0, 'o'},
+ {"quantization", 1, 0, 'q'},
+ {"size", 1, 0, 's'},
+ {"yuv", 0, 0, 'y'},
+ {0, 0, 0, 0}
+};
+
+static int parse_args(struct options *options, int argc, char *argv[])
+{
+ char *endptr;
+ int c;
+
+ if (argc < 3) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ memset(options, 0, sizeof(*options));
+ options->output_format = format_by_name("RGB24");
+ options->params.alpha = 255;
+ options->params.encoding = V4L2_YCBCR_ENC_601;
+ options->params.quantization = V4L2_QUANTIZATION_LIM_RANGE;
+
+ opterr = 0;
+ while ((c = getopt_long(argc, argv, "a:c:e:f:hH:o:q:s:y", opts, NULL)) != -1) {
+
+ switch (c) {
+ case 'a': {
+ int alpha;
+
+ if (strchr(optarg, '.')) {
+ alpha = strtod(optarg, &endptr) * 255;
+ if (*endptr != 0)
+ alpha = -1;
+ } else {
+ alpha = strtoul(optarg, &endptr, 10);
+ if (*endptr == '%')
+ alpha = alpha * 255 / 100;
+ else if (*endptr != 0)
+ alpha = -1;
+ }
+
+ if (alpha < 0 || alpha > 255) {
+ printf("Invalid alpha value '%s'\n", optarg);
+ return 1;
+ }
+
+ options->params.alpha = alpha;
+ break;
+ }
+
+ case 'c':
+ options->compose = strtoul(optarg, &endptr, 10);
+ if (*endptr != 0) {
+ printf("Invalid compose value '%s'\n", optarg);
+ return 1;
+ }
+ break;
+
+ case 'e':
+ if (!strcmp(optarg, "BT.601")) {
+ options->params.encoding = V4L2_YCBCR_ENC_601;
+ } else if (!strcmp(optarg, "REC.709")) {
+ options->params.encoding = V4L2_YCBCR_ENC_709;
+ } else if (!strcmp(optarg, "BT.2020")) {
+ options->params.encoding = V4L2_YCBCR_ENC_BT2020;
+ } else if (!strcmp(optarg, "SMPTE240M")) {
+ options->params.encoding = V4L2_YCBCR_ENC_SMPTE240M;
+ } else {
+ printf("Invalid encoding value '%s'\n", optarg);
+ return 1;
+ }
+ break;
+
+ case 'f':
+ if (!strcmp("help", optarg)) {
+ list_formats();
+ return 1;
+ }
+
+ options->output_format = format_by_name(optarg);
+ if (!options->output_format) {
+ printf("Unsupported output format '%s'\n", optarg);
+ return 1;
+ }
+
+ break;
+
+ case 'h':
+ usage(argv[0]);
+ exit(0);
+ break;
+
+ case 'H':
+ options->histo_filename = optarg;
+ break;
+
+ case 'o':
+ options->output_filename = optarg;
+ break;
+
+ case 'q':
+ if (!strcmp(optarg, "limited")) {
+ options->params.quantization = V4L2_QUANTIZATION_LIM_RANGE;
+ } else if (!strcmp(optarg, "full")) {
+ options->params.quantization = V4L2_QUANTIZATION_FULL_RANGE;
+ } else {
+ printf("Invalid quantization value '%s'\n", optarg);
+ return 1;
+ }
+ break;
+
+ break;
+
+ case 's':
+ options->output_width = strtol(optarg, &endptr, 10);
+ if (*endptr != 'x' || endptr == optarg) {
+ printf("Invalid size '%s'\n", optarg);
+ return 1;
+ }
+
+ options->output_height = strtol(endptr + 1, &endptr, 10);
+ if (*endptr != 0) {
+ printf("Invalid size '%s'\n", optarg);
+ return 1;
+ }
+ break;
+
+ case 'y':
+ options->process_yuv = true;
+ break;
+
+ default:
+ printf("Invalid option -%c\n", c);
+ printf("Run %s -h for help.\n", argv[0]);
+ return 1;
+ }
+ }
+
+ if (optind != argc - 1) {
+ usage(argv[0]);
+ return 1;
+ }
+
+ options->input_filename = argv[optind];
+
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
+ struct options options;
+ int ret;
+
+ ret = parse_args(&options, argc, argv);
+ if (ret)
+ return ret;
+
+ ret = process(&options);
+ if (ret)
+ return 1;
+
+ return 0;
+}