Implement external post processing
This commit is contained in:
parent
9cbebee116
commit
8863133fe2
21
README.md
21
README.md
|
@ -48,3 +48,24 @@ These are the sections describing the sensors.
|
||||||
* `rate=15` the refresh rate in fps to use for the sensor
|
* `rate=15` the refresh rate in fps to use for the sensor
|
||||||
* `fmt=BGGR8` sets the pixel and bus formats used when capturing from the sensor, only BGGR8 is fully supported
|
* `fmt=BGGR8` sets the pixel and bus formats used when capturing from the sensor, only BGGR8 is fully supported
|
||||||
* `rotate=90` the rotation angle to make the sensor match the screen
|
* `rotate=90` the rotation angle to make the sensor match the screen
|
||||||
|
|
||||||
|
# Post processing
|
||||||
|
|
||||||
|
Megapixels only captures raw frames and stores .dng files. It captures a 5 frame burst and saves it to a temporary
|
||||||
|
location. Then the postprocessing script is run which will generate the final .jpg file and writes it into the
|
||||||
|
pictures directory. Megapixels looks for the post processing script in the following locations:
|
||||||
|
|
||||||
|
* ./postprocess.sh
|
||||||
|
* $XDG_CONFIG_DIR/megapixels/postprocess.sh
|
||||||
|
* ~/.config/megapixels/postprocess.sh
|
||||||
|
* /etc/megapixels/postprocess.sh
|
||||||
|
* /usr/share/megapixels/postprocess.sh
|
||||||
|
|
||||||
|
The bundled postprocess.sh script will copy the first frame of the burst into the picture directory as an DNG
|
||||||
|
file and if dcraw and imagemagick are installed it will generate a JPG and also write that to the picture
|
||||||
|
directory. It supports either the full dcraw or dcraw_emu from libraw.
|
||||||
|
|
||||||
|
It is possible to write your own post processing pipeline my providing your own `postprocess.sh` script at
|
||||||
|
one of the above locations. The first argument to the script is the directory containing the temporary
|
||||||
|
burst files and the second argument is the final path for the image without an extension. For more details
|
||||||
|
see postprocess.sh in this repository.
|
||||||
|
|
91
main.c
91
main.c
|
@ -86,6 +86,9 @@ static int preview_height = -1;
|
||||||
static char *last_path = NULL;
|
static char *last_path = NULL;
|
||||||
static int auto_exposure = 1;
|
static int auto_exposure = 1;
|
||||||
static int auto_gain = 1;
|
static int auto_gain = 1;
|
||||||
|
static int burst_length = 5;
|
||||||
|
static char burst_dir[20];
|
||||||
|
static char processing_script[512];
|
||||||
|
|
||||||
// Widgets
|
// Widgets
|
||||||
GtkWidget *preview;
|
GtkWidget *preview;
|
||||||
|
@ -404,6 +407,8 @@ process_image(const int *p, int size)
|
||||||
struct tm tim;
|
struct tm tim;
|
||||||
uint8_t *pixels;
|
uint8_t *pixels;
|
||||||
char fname[255];
|
char fname[255];
|
||||||
|
char fname_target[255];
|
||||||
|
char command[1024];
|
||||||
char timestamp[30];
|
char timestamp[30];
|
||||||
char uniquecameramodel[255];
|
char uniquecameramodel[255];
|
||||||
GdkPixbuf *pixbuf;
|
GdkPixbuf *pixbuf;
|
||||||
|
@ -453,7 +458,9 @@ process_image(const int *p, int size)
|
||||||
tim = *(localtime(&rawtime));
|
tim = *(localtime(&rawtime));
|
||||||
strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim);
|
strftime(timestamp, 30, "%Y%m%d%H%M%S", &tim);
|
||||||
strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &tim);
|
strftime(datetime, 20, "%Y:%m:%d %H:%M:%S", &tim);
|
||||||
sprintf(fname, "%s/Pictures/IMG%s-%d.dng", getenv("HOME"), timestamp, capture);
|
|
||||||
|
sprintf(fname_target, "%s/Pictures/IMG%s", getenv("HOME"), timestamp);
|
||||||
|
sprintf(fname, "%s/%d.dng", burst_dir, burst_length - capture);
|
||||||
|
|
||||||
if(!(tif = TIFFOpen(fname, "w"))) {
|
if(!(tif = TIFFOpen(fname, "w"))) {
|
||||||
printf("Could not open tiff\n");
|
printf("Could not open tiff\n");
|
||||||
|
@ -516,7 +523,7 @@ process_image(const int *p, int size)
|
||||||
TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 1, ¤t.blacklevel);
|
TIFFSetField(tif, TIFFTAG_BLACKLEVEL, 1, ¤t.blacklevel);
|
||||||
}
|
}
|
||||||
TIFFCheckpointDirectory(tif);
|
TIFFCheckpointDirectory(tif);
|
||||||
printf("Writing frame\n");
|
printf("Writing frame to %s\n", fname);
|
||||||
|
|
||||||
unsigned char *pLine = (unsigned char*)malloc(current.width);
|
unsigned char *pLine = (unsigned char*)malloc(current.width);
|
||||||
for(int row = 0; row < current.height; row++){
|
for(int row = 0; row < current.height; row++){
|
||||||
|
@ -551,8 +558,8 @@ process_image(const int *p, int size)
|
||||||
TIFFClose(tif);
|
TIFFClose(tif);
|
||||||
|
|
||||||
|
|
||||||
// Update the thumbnail if this is the last frame
|
|
||||||
if (capture == 0) {
|
if (capture == 0) {
|
||||||
|
// Update the thumbnail if this is the last frame
|
||||||
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, current.width / (skip*2), current.height / (skip*2));
|
pixbuf = gdk_pixbuf_new(GDK_COLORSPACE_RGB, FALSE, 8, current.width / (skip*2), current.height / (skip*2));
|
||||||
pixels = gdk_pixbuf_get_pixels(pixbuf);
|
pixels = gdk_pixbuf_get_pixels(pixbuf);
|
||||||
quick_debayer_bggr8((const uint8_t *)p, pixels, current.width, current.height, skip);
|
quick_debayer_bggr8((const uint8_t *)p, pixels, current.width, current.height, skip);
|
||||||
|
@ -573,6 +580,12 @@ process_image(const int *p, int size)
|
||||||
g_printerr("%s\n", error->message);
|
g_printerr("%s\n", error->message);
|
||||||
g_clear_error(&error);
|
g_clear_error(&error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Start post-processing the captured burst
|
||||||
|
g_printerr("Post process %s to %s.ext\n", burst_dir, fname_target);
|
||||||
|
sprintf(command, "%s %s %s &", processing_script, burst_dir, fname_target);
|
||||||
|
system(command);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -959,7 +972,18 @@ on_open_directory_clicked(GtkWidget *widget, gpointer user_data)
|
||||||
void
|
void
|
||||||
on_shutter_clicked(GtkWidget *widget, gpointer user_data)
|
on_shutter_clicked(GtkWidget *widget, gpointer user_data)
|
||||||
{
|
{
|
||||||
capture = 5;
|
char template[] = "/tmp/megapixels.XXXXXX";
|
||||||
|
char *tempdir;
|
||||||
|
tempdir = mkdtemp(template);
|
||||||
|
|
||||||
|
if (tempdir == NULL) {
|
||||||
|
g_printerr("Could not make capture directory %s\n", template);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
strcpy(burst_dir, tempdir);
|
||||||
|
|
||||||
|
capture = burst_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -1065,12 +1089,69 @@ find_config(char *conffile)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
find_processor(char *script)
|
||||||
|
{
|
||||||
|
char *xdg_config_home;
|
||||||
|
char filename[] = "postprocess.sh";
|
||||||
|
wordexp_t exp_result;
|
||||||
|
|
||||||
|
// Resolve XDG stuff
|
||||||
|
if ((xdg_config_home = getenv("XDG_CONFIG_HOME")) == NULL) {
|
||||||
|
xdg_config_home = "~/.config";
|
||||||
|
}
|
||||||
|
wordexp(xdg_config_home, &exp_result, 0);
|
||||||
|
xdg_config_home = strdup(exp_result.we_wordv[0]);
|
||||||
|
wordfree(&exp_result);
|
||||||
|
|
||||||
|
// Check postprocess.h in the current working directory
|
||||||
|
sprintf(script, "%s", filename);
|
||||||
|
if(access(script, F_OK) != -1) {
|
||||||
|
sprintf(script, "./%s", filename);
|
||||||
|
printf("Found postprocessor script at %s\n", script);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for a script in XDG_CONFIG_HOME
|
||||||
|
sprintf(script, "%s/megapixels/%s", xdg_config_home, filename);
|
||||||
|
if(access(script, F_OK) != -1) {
|
||||||
|
printf("Found postprocessor script at %s\n", script);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check user overridden /etc/megapixels/postprocessor.sh
|
||||||
|
sprintf(script, "%s/megapixels/%s", SYSCONFDIR, filename);
|
||||||
|
if(access(script, F_OK) != -1) {
|
||||||
|
printf("Found postprocessor script at %s\n", script);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check packaged /usr/share/megapixels/postprocessor.sh
|
||||||
|
sprintf(script, "%s/megapixels/%s", DATADIR, filename);
|
||||||
|
if(access(script, F_OK) != -1) {
|
||||||
|
printf("Found postprocessor script at %s\n", script);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
main(int argc, char *argv[])
|
main(int argc, char *argv[])
|
||||||
{
|
{
|
||||||
|
int ret;
|
||||||
char conffile[512];
|
char conffile[512];
|
||||||
|
|
||||||
find_config(conffile);
|
ret = find_config(conffile);
|
||||||
|
if (ret) {
|
||||||
|
g_printerr("Could not find any config file\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
ret = find_processor(processing_script);
|
||||||
|
if (ret) {
|
||||||
|
g_printerr("Could not find any post-process script\n");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
TIFFSetTagExtender(register_custom_tiff_tags);
|
TIFFSetTagExtender(register_custom_tiff_tags);
|
||||||
|
|
||||||
|
|
|
@ -31,3 +31,7 @@ install_data([
|
||||||
'config/pine64,pinetab.ini',
|
'config/pine64,pinetab.ini',
|
||||||
],
|
],
|
||||||
install_dir : get_option('datadir') / 'megapixels/config/')
|
install_dir : get_option('datadir') / 'megapixels/config/')
|
||||||
|
|
||||||
|
install_data(['postprocess.sh'],
|
||||||
|
install_dir : get_option('datadir') / 'megapixels/',
|
||||||
|
install_mode: 'rwxr-xr-x')
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# The post-processing script gets called after taking a burst of
|
||||||
|
# pictures into a temporary directory. The first argument is the
|
||||||
|
# directory containing the raw files in the burst. The contents
|
||||||
|
# are 1.dng, 2.dng.... up to the number of photos in the burst.
|
||||||
|
#
|
||||||
|
# The second argument is the filename for the final photo without
|
||||||
|
# the extension, like "/home/user/Pictures/IMG202104031234"
|
||||||
|
#
|
||||||
|
# The post-processing script is responsible for cleaning up
|
||||||
|
# temporary directory for the burst.
|
||||||
|
|
||||||
|
if [ "$#" -ne 2 ]; then
|
||||||
|
echo "Usage: $0 [burst-dir] [target-name]"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
BURST_DIR="$1"
|
||||||
|
TARGET_NAME="$2"
|
||||||
|
|
||||||
|
# Copy the first frame of the burst as the raw photo
|
||||||
|
cp "$BURST_DIR"/1.dng "$TARGET_NAME.dng"
|
||||||
|
|
||||||
|
# Create a .jpg if raw processing tools are installed
|
||||||
|
DCRAW=""
|
||||||
|
if command -v "dcraw_emu" &> /dev/null
|
||||||
|
then
|
||||||
|
DCRAW=dcraw_emu
|
||||||
|
fi
|
||||||
|
if command -v "dcraw" &> /dev/null
|
||||||
|
then
|
||||||
|
DCRAW=dcraw
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$DCRAW" ]; then
|
||||||
|
# +M use embedded color matrix
|
||||||
|
# -H 4 Recover highlights by rebuilding them
|
||||||
|
# -o 1 Output in sRGB colorspace
|
||||||
|
# -q 3 Debayer with AHD algorithm
|
||||||
|
# -T Output TIFF
|
||||||
|
# -fbdd 1 Raw denoising with FBDD
|
||||||
|
$DCRAW +M -H 4 -o 1 -q 3 -T -fbdd 1 $BURST_DIR/1.dng
|
||||||
|
|
||||||
|
if command -v convert &> /dev/null
|
||||||
|
then
|
||||||
|
convert "$BURST_DIR"/1.dng.tiff "$TARGET_NAME.jpg"
|
||||||
|
else
|
||||||
|
cp "$BURST_DIR"/1.dng.tiff "$TARGET_NAME.tiff"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Clean up the temp dir containing the burst
|
||||||
|
rm -rf "$BURST_DIR"
|
Loading…
Reference in New Issue