瀏覽代碼

Initial commit

Martijn Braam 3 年之前
當前提交
7d20defa87
共有 12 個文件被更改,包括 809 次插入0 次删除
  1. 5 0
      .gitignore
  2. 18 0
      CMakeLists.txt
  3. 153 0
      main.c
  4. 15 0
      meson.build
  5. 422 0
      postprocess.c
  6. 36 0
      postprocess.h
  7. 38 0
      stacker.cpp
  8. 26 0
      stacker.h
  9. 49 0
      stackercpp.cpp
  10. 28 0
      stackercpp.h
  11. 7 0
      util.c
  12. 12 0
      util.h

+ 5 - 0
.gitignore

@@ -0,0 +1,5 @@
+/.idea
+/build
+/cmake-build-debug
+/out
+/test

+ 18 - 0
CMakeLists.txt

@@ -0,0 +1,18 @@
+cmake_minimum_required(VERSION 3.16)
+project(postprocessd C CXX)
+
+set(CMAKE_C_STANDARD 11)
+
+include(FindPkgConfig)
+
+find_package(PkgConfig REQUIRED)
+pkg_check_modules(RAW REQUIRED libraw)
+pkg_check_modules(TIFF REQUIRED libtiff-4)
+pkg_check_modules(JPEG REQUIRED libjpeg)
+pkg_check_modules(EXIF REQUIRED libexif)
+pkg_check_modules(CV REQUIRED opencv4)
+
+add_executable(postprocessd main.c postprocess.c postprocess.h util.c util.h stacker.cpp stacker.h stackercpp.cpp stackercpp.h)
+target_link_libraries(postprocessd ${RAW_LIBRARIES} ${TIFF_LIBRARIES} ${JPEG_LIBRARIES} ${EXIF_LIBRARIES} ${CV_LIBRARIES})
+target_include_directories(postprocessd PUBLIC ${RAW_INCLUDE_DIRS} ${TIFF_INCLUDE_DIRS} ${JPEG_INCUDE_DIRS} ${EXIF_INCUDE_DIRS} ${CV_INCLUDE_DIRS})
+target_compile_options(postprocessd PUBLIC ${RAW_CFLAGS_OTHER} ${TIFF_CFLAGS_OTHER} ${JPEG_CFLAGS_OTHER} ${EXIF_CFLAGS_OTHER} ${CV_CFLAGS_OTHER})

+ 153 - 0
main.c

@@ -0,0 +1,153 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+
+#include "util.h"
+#include "postprocess.h"
+
+static char socket_path[100];
+
+struct Job {
+        pid_t pid;
+        char burstdir[255];
+        char target[255];
+        int savedng;
+};
+
+
+pid_t
+start_processing(struct Job job)
+{
+    pid_t child_pid = fork();
+    if (child_pid < 0) {
+        err("fork failed");
+    } else if (child_pid > 0) {
+        // parent process
+        return child_pid;
+    } else {
+        // child process
+        postprocess_internal(job.burstdir, job.target);
+        exit(0);
+    }
+    return -1;
+}
+
+int
+listen_on_socket()
+{
+    int sock;
+    int ret;
+    struct sockaddr_un addr;
+
+    // Clean existing socket
+    if (remove(socket_path) == -1 && errno != ENOENT) {
+        err("could not clean up old socket");
+    }
+
+    // Make new unix domain socket to listen on
+    sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (sock < 0) {
+        err("could not make socket fd");
+        return 0;
+    }
+
+    if (bind(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) < 0) {
+        err("failed to bind socket");
+        return 0;
+    }
+
+    if (listen(sock, 20) < 0) {
+        err("failed to listen");
+        return 0;
+    }
+
+    return sock;
+}
+
+int
+is_alive()
+{
+    int sock;
+    struct sockaddr_un addr;
+
+    sock = socket(AF_UNIX, SOCK_SEQPACKET, 0);
+    if (sock < 0) {
+        err("could not make socket fd");
+        return 0;
+    }
+
+    memset(&addr, 0, sizeof(struct sockaddr_un));
+    addr.sun_family = AF_UNIX;
+    strncpy(addr.sun_path, socket_path, sizeof(addr.sun_path) - 1);
+    if (connect(sock, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) < 0) {
+        err("failed to open socket");
+        return 0;
+    }
+    return 0;
+}
+
+void
+queue_job(struct Job job)
+{
+
+}
+
+void
+make_socket_path()
+{
+    char fname[80];
+    char *xdg_runtime_dir = getenv("XDG_RUNTIME_DIR");
+    char *user = getenv("USER");
+
+    snprintf(fname, sizeof(fname), "postprocessd-%s.sock", user);
+
+    if (xdg_runtime_dir) {
+        snprintf(socket_path, sizeof(socket_path), "%s/%s", xdg_runtime_dir, fname);
+    } else {
+        snprintf(socket_path, sizeof(socket_path), "/tmp/%s", fname);
+    }
+}
+
+int
+main(int argc, char *argv[])
+{
+    int sock;
+    struct Job job;
+
+    if (argc != 4) {
+        printf("usage: %s burst-dir target-name save-dng\n", argv[0]);
+        exit(1);
+    }
+
+    postprocess_setup();
+
+    // Parse command line arguments into the job struct
+    job.pid = 0;
+    strncpy(job.burstdir, argv[1], sizeof(job.burstdir));
+    strncpy(job.target, argv[2], sizeof(job.target));
+    if (strcmp(argv[3], "0") == 0) {
+        job.savedng = 0;
+    } else {
+        job.savedng = 1;
+    }
+
+    make_socket_path();
+
+    // Check if an existing instance is running, if not start a listener
+    if (is_alive() == 0) {
+        printf("first instance, listening\n");
+        sock = listen_on_socket();
+    } else {
+        // A processing job is already running, queue this job on the existing process and block
+        printf("second instance, running\n");
+        queue_job(job);
+    }
+
+    start_processing(job);
+    wait(NULL);
+
+    return 0;
+}

+ 15 - 0
meson.build

@@ -0,0 +1,15 @@
+project('postprocessd', 'c', 'cpp', version: '0.1.0',
+    default_options : ['c_std=c11', 'cpp_std=c++11'])
+
+add_project_arguments('-D_GNU_SOURCE', language : 'c')
+add_project_arguments('-D_GNU_SOURCE', language : 'cpp')
+
+raw = dependency('libraw')
+tiff = dependency('libtiff-4')
+jpeg = dependency('libjpeg')
+exif = dependency('libexif')
+cv = dependency('opencv4')
+
+executable('postprocessd', 'main.c','postprocess.c', 'stacker.cpp', 'stackercpp.cpp', 'util.c',
+    dependencies: [raw, tiff, jpeg, exif, cv],
+    install: true)

+ 422 - 0
postprocess.c

@@ -0,0 +1,422 @@
+#include <assert.h>
+#include <string.h>
+#include <dirent.h>
+#include <libraw/libraw.h>
+#include <tiffio.h>
+#include <jpeglib.h>
+#include <libexif/exif-data.h>
+#include "postprocess.h"
+#include "stacker.h"
+
+static void
+register_custom_tiff_tags(TIFF *tif)
+{
+    static const TIFFFieldInfo custom_fields[] = {
+        {TIFFTAG_FORWARDMATRIX1, -1, -1, TIFF_SRATIONAL, FIELD_CUSTOM, 1, 1,
+            "ForwardMatrix1"},
+    };
+
+    // Add missing dng fields
+    TIFFMergeFieldInfo(tif, custom_fields,
+        sizeof(custom_fields) / sizeof(custom_fields[0]));
+}
+
+libraw_processed_image_t *
+debayer_file(char *filename)
+{
+    libraw_data_t *raw;
+    int ret;
+
+    raw = libraw_init(0);
+    if (libraw_open_file(raw, filename) != LIBRAW_SUCCESS) {
+        err("could not open file");
+        libraw_close(raw);
+    }
+
+    if (libraw_unpack(raw) != LIBRAW_SUCCESS) {
+        err("could not unpack file");
+        libraw_close(raw);
+    }
+
+    raw->params.no_auto_bright = 1;
+
+    if (libraw_dcraw_process(raw) != LIBRAW_SUCCESS) {
+        err("could not process file");
+        libraw_free_image(raw);
+        libraw_close(raw);
+    }
+
+    libraw_processed_image_t *proc_img = libraw_dcraw_make_mem_image(raw, &ret);
+    libraw_free_image(raw);
+
+    if (!proc_img) {
+        err("could not export image");
+        libraw_close(raw);
+    }
+    libraw_recycle(raw);
+    libraw_close(raw);
+    return proc_img;
+}
+
+void
+save_jpeg(char *path, libraw_processed_image_t *data, int quality, ExifData *exif)
+{
+    unsigned char *exif_data;
+    unsigned int exif_len;
+    FILE *out = fopen(path, "wb");
+    if (!out) {
+        err("could not open target file");
+        exit(1);
+    }
+
+    struct jpeg_compress_struct jpeg;
+    struct jpeg_error_mgr error_mgr;
+    jpeg.err = jpeg_std_error(&error_mgr);
+    jpeg_create_compress(&jpeg);
+    jpeg_stdio_dest(&jpeg, out);
+    jpeg.image_width = data->width;
+    jpeg.image_height = data->height;
+    jpeg.input_components = 3;
+    jpeg.in_color_space = JCS_RGB;
+    jpeg_set_defaults(&jpeg);
+    jpeg_set_quality(&jpeg, quality, 1);
+    jpeg_start_compress(&jpeg, 1);
+
+    // Write exif
+    exif_data_save_data(exif, &exif_data, &exif_len);
+    jpeg_write_marker(&jpeg, JPEG_APP1, exif_data, exif_len);
+
+    // Write image data
+    JSAMPROW row_pointer;
+    int row_stride = jpeg.image_width * jpeg.input_components;
+
+    while (jpeg.next_scanline < jpeg.image_height) {
+        row_pointer = (JSAMPROW) &data->data[jpeg.next_scanline * row_stride];
+        jpeg_write_scanlines(&jpeg, &row_pointer, 1);
+    }
+
+    jpeg_finish_compress(&jpeg);
+    jpeg_destroy_compress(&jpeg);
+    fclose(out);
+}
+
+static ExifEntry *
+init_tag(ExifData *exif, ExifIfd ifd, ExifTag tag)
+{
+    ExifEntry *entry;
+    /* Return an existing tag if one exists */
+    if (!((entry = exif_content_get_entry(exif->ifd[ifd], tag)))) {
+        /* Allocate a new entry */
+        entry = exif_entry_new();
+        assert(entry != NULL); /* catch an out of memory condition */
+        entry->tag = tag; /* tag must be set before calling
+				 exif_content_add_entry */
+
+        /* Attach the ExifEntry to an IFD */
+        exif_content_add_entry(exif->ifd[ifd], entry);
+
+        /* Allocate memory for the entry and fill with default data */
+        exif_entry_initialize(entry, tag);
+
+        /* Ownership of the ExifEntry has now been passed to the IFD.
+         * One must be very careful in accessing a structure after
+         * unref'ing it; in this case, we know "entry" won't be freed
+         * because the reference count was bumped when it was added to
+         * the IFD.
+         */
+        exif_entry_unref(entry);
+    }
+    return entry;
+}
+
+void
+exif_set_string(ExifEntry *ed, const char *s, size_t size)
+{
+    if (ed->data) {
+        free(ed->data);
+    }
+    ed->components = size + 1;
+    ed->size = sizeof(char) * ed->components;
+    ed->data = (unsigned char *) malloc(ed->size);
+    if (!ed->data) {
+        err("Could not allocate exif string");
+        exit(1);
+    }
+    strncpy((char *) ed->data, (char *) s, size);
+    exif_entry_fix(ed);
+}
+
+ExifData *
+create_exif(struct Imagedata data)
+{
+    ExifEntry *entry;
+    ExifRational rational;
+    long denominator = 100000;
+    ExifData *exif = exif_data_new();
+    if (!exif) {
+        err("Could not initialize libexif");
+    }
+    exif_data_set_option(exif, EXIF_DATA_OPTION_FOLLOW_SPECIFICATION);
+    exif_data_set_data_type(exif, EXIF_DATA_TYPE_COMPRESSED);
+    exif_data_set_byte_order(exif, EXIF_BYTE_ORDER_INTEL);
+    exif_data_fix(exif);
+
+    // Width
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_PIXEL_X_DIMENSION);
+    exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, data.width);
+
+    // Height
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_PIXEL_Y_DIMENSION);
+    exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, data.height);
+
+    // Colorspace, 1=sRGB
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_COLOR_SPACE);
+    exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, 1);
+
+    // Exposure program, enum
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_PROGRAM);
+    exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, data.exposure_program);
+
+    // Camera make
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_MAKE);
+    exif_set_string(entry, data.make, strlen(data.make));
+
+    // Camera model
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_MODEL);
+    exif_set_string(entry, data.model, strlen(data.model));
+
+    // Processing software
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_SOFTWARE);
+    exif_set_string(entry, data.software, strlen(data.software));
+
+    // Orientation
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_ORIENTATION);
+    exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, data.orientation);
+
+    // Various datetime fields
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME);
+    exif_set_string(entry, data.datetime, strlen(data.datetime));
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_DIGITIZED);
+    exif_set_string(entry, data.datetime, strlen(data.datetime));
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_DATE_TIME_ORIGINAL);
+    exif_set_string(entry, data.datetime, strlen(data.datetime));
+
+    // Exposure time
+    rational.numerator = (long) ((double) data.exposure_time * denominator);
+    rational.denominator = denominator;
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_EXPOSURE_TIME);
+    exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, rational);
+
+    // fnumber
+    rational.numerator = (long) ((double) data.fnumber * denominator);
+    rational.denominator = denominator;
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FNUMBER);
+    exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, rational);
+
+    // aperture
+    rational.numerator = (long) ((double) data.aperture * denominator);
+    rational.denominator = denominator;
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_APERTURE_VALUE);
+    exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, rational);
+
+    // focal length
+    rational.numerator = (long) ((double) data.focal_length * denominator);
+    rational.denominator = denominator;
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH);
+    exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, rational);
+
+    // focal length, 35mm equiv
+    rational.numerator = (long) ((double) data.focal_length_35mm * denominator);
+    rational.denominator = denominator;
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FOCAL_LENGTH_IN_35MM_FILM);
+    exif_set_rational(entry->data, EXIF_BYTE_ORDER_INTEL, rational);
+
+    // ISO
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_ISO_SPEED);
+    exif_set_long(entry->data, EXIF_BYTE_ORDER_INTEL, data.isospeed);
+
+    // Flash
+    entry = init_tag(exif, EXIF_IFD_EXIF, EXIF_TAG_FLASH);
+    exif_set_short(entry->data, EXIF_BYTE_ORDER_INTEL, data.flash);
+    return exif;
+}
+
+struct Imagedata
+read_exif(char *filename)
+{
+    struct Imagedata imagedata;
+    uint16_t subifd_count;
+    uint32_t *subifd_offsets;
+    uint32_t exif_offset;
+    char *temp;
+
+    TIFF *im = TIFFOpen(filename, "r");
+
+    if (im == NULL) {
+        fprintf(stderr, "Could not open tiff");
+        exit(1);
+    }
+
+    if (TIFFGetField(im, TIFFTAG_MAKE, &temp) != 1) {
+        err("failed to read TIFFTAG_MAKE");
+    }
+    imagedata.make = strdup(temp);
+
+    if (TIFFGetField(im, TIFFTAG_MODEL, &temp) != 1) {
+        err("failed to read TIFFTAG_MODEL");
+    }
+    imagedata.model = strdup(temp);
+
+    if (TIFFGetField(im, TIFFTAG_SOFTWARE, &temp) != 1) {
+        err("failed to read TIFFTAG_SOFTWARE");
+    }
+    imagedata.software = strdup(temp);
+
+    if (TIFFGetField(im, TIFFTAG_DATETIME, &temp) != 1) {
+        err("failed to read TIFFTAG_DATETIME");
+    }
+    imagedata.datetime = strdup(temp);
+
+    if (TIFFGetField(im, TIFFTAG_ORIENTATION, &imagedata.orientation) != 1) {
+        err("failed to read TIFFTAG_ORIENTATION");
+    }
+
+    // Get the EXIF directory for the rest of the metadata
+    if (TIFFGetField(im, TIFFTAG_EXIFIFD, &exif_offset) != 1) {
+        err("failed to read TIFFTAG_EXIFIFD");
+    }
+
+    // Read the actual picture instead of the embedded thumbnail
+    if (TIFFGetField(im, TIFFTAG_SUBIFD, &subifd_count, &subifd_offsets)) {
+        if (subifd_count > 0) {
+            TIFFSetSubDirectory(im, subifd_offsets[0]);
+        }
+    }
+
+    if (TIFFGetField(im, TIFFTAG_IMAGELENGTH, &imagedata.height) != 1) {
+        err("failed to read TIFFTAG_IMAGELENGTH");
+    }
+    if (TIFFGetField(im, TIFFTAG_IMAGEWIDTH, &imagedata.width) != 1) {
+        err("failed to read TIFFTAG_IMAGEWIDTH");
+    }
+    if (TIFFGetField(im, TIFFTAG_BITSPERSAMPLE, &imagedata.bitspersample) != 1) {
+        err("failed to read TIFFTAG_BITSPERSAMPLE");
+    }
+
+    // Read the exif directory
+    TIFFReadEXIFDirectory(im, exif_offset);
+
+    if (TIFFGetField(im, EXIFTAG_EXPOSUREPROGRAM, &imagedata.exposure_program) != 1) {
+        err("failed to read EXIFTAG_EXPOSUREPROGRAM");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_EXPOSURETIME, &imagedata.exposure_time) != 1) {
+        err("failed to read EXIFTAG_EXPOSURETIME");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_ISOSPEED, &imagedata.isospeed) != 1) {
+        err("failed to read EXIFTAG_ISOSPEED");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_FNUMBER, &imagedata.fnumber) != 1) {
+        err("failed to read EXIFTAG_FNUMBER");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_APERTUREVALUE, &imagedata.aperture) != 1) {
+        err("failed to read EXIFTAG_APERTUREVALUE");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_FOCALLENGTH, &imagedata.focal_length) != 1) {
+        err("failed to read EXIFTAG_FOCALLENGTH");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_FOCALLENGTHIN35MMFILM, &imagedata.focal_length_35mm) != 1) {
+        err("failed to read EXIFTAG_FOCALLENGTHIN35MMFILM");
+    }
+
+    if (TIFFGetField(im, EXIFTAG_FLASH, &imagedata.flash) != 1) {
+        err("failed to read EXIFTAG_FLASH");
+    }
+    TIFFClose(im);
+    return imagedata;
+}
+
+static int
+filter_dng(const struct dirent *dir)
+{
+    if (!dir)
+        return 0;
+
+    if (dir->d_type == DT_REG) {
+        const char *ext = strrchr(dir->d_name, '.');
+        if ((!ext) || (ext == dir->d_name))
+            return 0;
+        else {
+            if (strcmp(ext, ".dng") == 0)
+                return 1;
+        }
+    }
+
+    return 0;
+}
+
+void
+postprocess_setup()
+{
+    TIFFSetTagExtender(register_custom_tiff_tags);
+}
+
+void
+postprocess_internal(char *burst_dir, char *target_dir)
+{
+    struct dirent **namelist;
+    int burst_size;
+    int first = 1;
+    struct Imagedata imagedata;
+    libraw_processed_image_t *decoded;
+    ExifData *exif;
+    char path[512];
+    char outpath[512];
+    char stackpath[512];
+
+    burst_size = scandir(burst_dir, &namelist, filter_dng, alphasort);
+    if (burst_size < 0) {
+        printf("oop\n");
+        exit(1);
+    }
+    if (burst_size == 0) {
+        printf("No DNG files found\n");
+        exit(1);
+    }
+    stacker_t *stacker = stacker_create();
+    while (burst_size--) {
+        printf("DNG: %s\n", namelist[burst_size]->d_name);
+        snprintf(path, sizeof(path), "%s/%s", burst_dir, namelist[burst_size]->d_name);
+        snprintf(outpath, sizeof(outpath), "%s.%d.jpg", target_dir, burst_size);
+        if (first) {
+            first = 0;
+            imagedata = read_exif(path);
+            exif = create_exif(imagedata);
+        }
+        decoded = debayer_file(path);
+        stacker_add_image(stacker, decoded->data, decoded->width, decoded->height);
+        save_jpeg(outpath, decoded, 90, exif);
+        free(namelist[burst_size]);
+    }
+    free(namelist);
+
+    // Convert opencv result to a libraw struct and feed it in the jpeg encoder with the original exif data
+    char *stacked = stacker_get_result(stacker);
+    int result_size = decoded->width * decoded->height * 3;
+    libraw_processed_image_t *stacked_data = (libraw_processed_image_t *) malloc(
+        sizeof(libraw_processed_image_t) + result_size);
+    memset(stacked_data, 0, sizeof(libraw_processed_image_t));
+    stacked_data->colors = 3;
+    stacked_data->width = decoded->width;
+    stacked_data->height = decoded->height;
+    memmove(stacked_data->data, stacked, result_size);
+
+    // Save the final file
+    snprintf(stackpath, sizeof(stackpath), "%s.stacked.jpeg", target_dir);
+    save_jpeg(stackpath, stacked_data, 90, exif);
+}

+ 36 - 0
postprocess.h

@@ -0,0 +1,36 @@
+#ifndef POSTPROCESSD__POSTPROCESS_H
+#define POSTPROCESSD__POSTPROCESS_H
+
+#include <stdint.h>
+#include "util.h"
+
+#define TIFFTAG_FORWARDMATRIX1 50964
+
+#define JPEG_APP1 JPEG_APP0+1
+
+struct Imagedata {
+        uint32_t width;
+        uint32_t height;
+        uint8_t bitspersample;
+        char *make;
+        char *model;
+        char *software;
+        int orientation;
+        char *datetime;
+        uint16_t exposure_program;
+        float exposure_time;
+        float aperture;
+        int isospeed;
+        int flash;
+        float fnumber;
+        float focal_length;
+        float focal_length_35mm;
+};
+
+void
+postprocess_internal(char *burst_dir, char *target_dir);
+
+void
+postprocess_setup();
+
+#endif //POSTPROCESSD__POSTPROCESS_H

+ 38 - 0
stacker.cpp

@@ -0,0 +1,38 @@
+#include <stdlib.h>
+#include "stacker.h"
+#include "stackercpp.h"
+
+stacker_t *
+stacker_create()
+{
+    stacker_t *st;
+    Stacker *obj;
+
+    st = (__typeof__(st)) malloc(sizeof(*st));
+    obj = new Stacker();
+    st->obj = obj;
+    return st;
+}
+
+void
+stacker_add_image(stacker_t *st, char *data, int width, int height)
+{
+    Stacker *obj;
+    if (st == NULL) {
+        return;
+    }
+
+    obj = static_cast<Stacker * >(st->obj);
+    obj->add_frame(data, width, height);
+}
+
+char *
+stacker_get_result(stacker_t *st)
+{
+    Stacker *obj;
+    if (st == NULL) {
+        return NULL;
+    }
+    obj = static_cast<Stacker * >(st->obj);
+    return obj->get_result();
+}

+ 26 - 0
stacker.h

@@ -0,0 +1,26 @@
+#ifndef POSTPROCESSD__STACKER_H
+#define POSTPROCESSD__STACKER_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct stacker {
+        void *obj;
+};
+typedef struct stacker stacker_t;
+
+stacker_t *
+stacker_create();
+
+void
+stacker_add_image(stacker_t *st, char *data, int width, int height);
+
+char *
+stacker_get_result(stacker_t *st);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif //POSTPROCESSD__STACKER_H

+ 49 - 0
stackercpp.cpp

@@ -0,0 +1,49 @@
+#include "stackercpp.h"
+
+Stacker::Stacker()
+{
+    Stacker::layers = 0;
+}
+
+void
+Stacker::add_frame(char *data, int width, int height)
+{
+    Mat warp_matrix = Mat::eye(3, 3, CV_32F);
+    Mat mat = Mat(height, width, CV_8UC3, data);
+    Mat grayscale = Mat(height, width, CV_8UC1);
+    cv::cvtColor(mat, grayscale, cv::COLOR_BGR2GRAY);
+    int number_of_iterations = 5000;
+    double termination_eps = 1e-10;
+    TermCriteria criteria(TermCriteria::COUNT + TermCriteria::EPS, number_of_iterations, termination_eps);
+    if (layers == 0) {
+        // First image in the stack is used as the reference to align the next frames to
+        Stacker::reference = grayscale;
+
+        // Create black image to accumulate the average
+        Stacker::stacked = Mat(height, width, CV_64FC3);
+        Stacker::stacked.setTo(Scalar(0, 0, 0, 0));
+
+        // Add first frame to the stack
+        Stacker::stacked += mat;
+        Stacker::layers += 1;
+    } else {
+        // All frames after the initial one are stacked
+        Mat warped = Mat(Stacker::stacked.rows, Stacker::stacked.cols, CV_32FC1);
+        cv::findTransformECC(grayscale, Stacker::reference, warp_matrix, MOTION_HOMOGRAPHY, criteria);
+        warpPerspective(mat, warped, warp_matrix, warped.size(), INTER_LINEAR);
+
+        // Add the warped image to the stack
+        Stacker::stacked += warped;
+        Stacker::layers += 1;
+    }
+}
+
+char *
+Stacker::get_result()
+{
+    Stacker::stacked.convertTo(Stacker::stacked, CV_8U, 1. / Stacker::layers);
+    size_t size = Stacker::stacked.total() * Stacker::stacked.elemSize();
+    char *data = (char *) malloc(size);
+    std::memcpy(data, Stacker::stacked.data, size * sizeof(char));
+    return data;
+}

+ 28 - 0
stackercpp.h

@@ -0,0 +1,28 @@
+#include <opencv2/opencv.hpp>
+#include <opencv2/core.hpp>
+#include <opencv2/imgcodecs.hpp>
+#include <opencv2/video/tracking.hpp>
+
+using namespace std;
+using namespace cv;
+
+#ifndef POSTPROCESSD__STACKERCPP_H
+#define POSTPROCESSD__STACKERCPP_H
+
+class Stacker {
+public:
+        Stacker();
+
+        void
+        add_frame(char *data, int width, int height);
+
+        char *
+        get_result();
+
+private:
+        int layers;
+        cv::Mat reference;
+        cv::Mat stacked;
+};
+
+#endif //POSTPROCESSD__STACKERCPP_H

+ 7 - 0
util.c

@@ -0,0 +1,7 @@
+#include "util.h"
+
+void
+err(char *message)
+{
+    fprintf(stderr, "%s (%s)\n", message, strerror(errno));
+}

+ 12 - 0
util.h

@@ -0,0 +1,12 @@
+#ifndef POSTPROCESSD__UTIL_H
+#define POSTPROCESSD__UTIL_H
+
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+
+void
+err(char *message);
+
+
+#endif //POSTPROCESSD__UTIL_H