Raw File
shoot.c
/** \file
 * Shooting experiments: intervalometer, LCD RemoteShot. More to come.
 * 
 * (C) 2010 Alex Dumitrache, broscutamaker@gmail.com
 */
/*
 * Magic Lantern is Copyright (C) 2009 Trammell Hudson <hudson+ml@osresearch.net>
 * 
 * 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.,
 * 51 Franklin Street, Fifth Floor,
 * Boston, MA  02110-1301, USA.
 */

#include "dryos.h"
#include "bmp.h"
#include "version.h"
#include "config.h"
#include "menu.h"
#include "property.h"
#include "lens.h"
#include "gui.h"
#include "math.h"

void move_lv_afframe(int dx, int dy);
void movie_start();
void movie_end();
void display_trap_focus_info();
void display_lcd_remote_icon(int x0, int y0);
void intervalometer_stop();
void get_out_of_play_mode(int extra_wait);
void wait_till_next_second();
void zoom_sharpen_step();
void zoom_auto_exposure_step();
void ensure_play_or_qr_mode_after_shot();

static void bulb_ramping_showinfo();
int bulb_ramp_calibration_running = 0;

int display_idle()
{
    extern thunk ShootOlcApp_handler;
    if (lv) return liveview_display_idle();
    else return gui_state == GUISTATE_IDLE && !gui_menu_shown() &&
        ((!DISPLAY_IS_ON && CURRENT_DIALOG_MAYBE == 0) || (intptr_t)get_current_dialog_handler() == (intptr_t)&ShootOlcApp_handler);
}

static char dcim_dir_suffix[6];
static char dcim_dir[100];
PROP_HANDLER(PROP_DCIM_DIR_SUFFIX)
{
    snprintf(dcim_dir_suffix, sizeof(dcim_dir_suffix), (const char *)buf);
}
const char* get_dcim_dir()
{
    snprintf(dcim_dir, sizeof(dcim_dir), CARD_DRIVE "DCIM/%03d%s", folder_number, dcim_dir_suffix);
    return dcim_dir;
}

static float bulb_shutter_valuef = 1.0;
#define BULB_SHUTTER_VALUE_MS (int)roundf(bulb_shutter_valuef * 1000.0)
#define BULB_SHUTTER_VALUE_S (int)roundf(bulb_shutter_valuef)

/*
static CONFIG_INT("uniwb.mode", uniwb_mode, 0);
static CONFIG_INT("uniwb.old.wb_mode", uniwb_old_wb_mode, 0);
static CONFIG_INT("uniwb.old.gain_R", uniwb_old_gain_R, 0);
static CONFIG_INT("uniwb.old.gain_G", uniwb_old_gain_G, 0);
static CONFIG_INT("uniwb.old.gain_B", uniwb_old_gain_B, 0);

int uniwb_is_active_check_lensinfo_only() 
{
    return 
        lens_info.wb_mode == WB_CUSTOM &&
        lens_info.WBGain_R == 1024 && lens_info.WBGain_G == 1024 && lens_info.WBGain_B == 1024;
}

int uniwb_is_active() 
{
    return 
        uniwb_mode &&
        uniwb_is_active_check_lensinfo_only();
}*/

CONFIG_INT("iso_selection", iso_selection, 0);

CONFIG_INT("hdr.enabled", hdr_enabled, 0);

PROP_INT(PROP_AEB, aeb_setting);
#define HDR_ENABLED (hdr_enabled && !aeb_setting) // when Canon bracketing is active, ML bracketing should not run

CONFIG_INT("hdr.frames", hdr_steps, 1);
CONFIG_INT("hdr.ev_spacing", hdr_stepsize, 16);
static CONFIG_INT("hdr.delay", hdr_delay, 1);
static CONFIG_INT("hdr.seq", hdr_sequence, 1);
static CONFIG_INT("hdr.iso", hdr_iso, 0);
static CONFIG_INT("hdr.scripts", hdr_scripts, 2);

static CONFIG_INT( "interval.timer.index", interval_timer_index, 10 );
static CONFIG_INT( "interval.start.timer.index", interval_start_timer_index, 3 );
static CONFIG_INT( "interval.movie.duration.index", interval_movie_duration_index, 2);
static CONFIG_INT( "interval.stop_after", interval_stop_after, 0 );
//~ static CONFIG_INT( "interval.stop.after", interval_stop_after, 0 );

static int intervalometer_pictures_taken = 0;
static int intervalometer_next_shot_time = 0;


CONFIG_INT( "focus.trap", trap_focus, 0);
static CONFIG_INT( "audio.release-level", audio_release_level, 10);
static CONFIG_INT( "flash_and_no_flash", flash_and_no_flash, 0);
static CONFIG_INT( "lv_3rd_party_flash", lv_3rd_party_flash, 0);
static CONFIG_INT( "silent.pic", silent_pic_enabled, 0 );     
static CONFIG_INT( "silent.pic.mode", silent_pic_mode, 0 );    // 0 = normal, 1 = hi-res, 2 = slit-scan, 3 = long-exp
static CONFIG_INT( "silent.pic.submode", silent_pic_submode, 0);   // simple, burst, fullhd
#define silent_pic_burst (silent_pic_submode == 1)
#define silent_pic_fullhd (silent_pic_submode == 2)
static CONFIG_INT( "silent.pic.highres", silent_pic_highres, 0);   // index of matrix size (2x1 .. 5x5)
static CONFIG_INT( "silent.pic.sweepdelay", silent_pic_sweepdelay, 350);
static CONFIG_INT( "silent.pic.slitscan.skipframes", silent_pic_slitscan_skipframes, 1);
//~ static CONFIG_INT( "silent.pic.longexp.time.index", silent_pic_longexp_time_index, 5);
//~ static CONFIG_INT( "silent.pic.longexp.method", silent_pic_longexp_method, 0);
static CONFIG_INT( "zoom.enable.face", zoom_enable_face, 0);
static CONFIG_INT( "zoom.disable.x5", zoom_disable_x5, 0);
static CONFIG_INT( "zoom.disable.x10", zoom_disable_x10, 0);
static CONFIG_INT( "zoom.sharpen", zoom_sharpen, 0);
static CONFIG_INT( "zoom.halfshutter", zoom_halfshutter, 0);
static CONFIG_INT( "zoom.focus_ring", zoom_focus_ring, 0);
       CONFIG_INT( "zoom.auto.exposure", zoom_auto_exposure, 0);
static CONFIG_INT( "bulb.timer", bulb_timer, 0);
static CONFIG_INT( "bulb.duration.index", bulb_duration_index, 5);
static CONFIG_INT( "mlu.auto", mlu_auto, 1);

extern int lcd_release_running;

//New option for the sensitivty of the motion release
static CONFIG_INT( "motion.release-level", motion_detect_level, 8);
static CONFIG_INT( "motion.trigger", motion_detect_trigger, 0);

int get_silent_pic() { return silent_pic_enabled; } // silent pic will disable trap focus

static CONFIG_INT("bulb.ramping", bulb_ramping_enabled, 0);
static CONFIG_INT("bulb.ramping.auto", bramp_auto_exposure, 1);
static CONFIG_INT("bulb.ramping.auto.speed", bramp_auto_ramp_speed, 100); // max 0.1 EV/shot
//~ static CONFIG_INT("bulb.ramping.smooth", bramp_auto_smooth, 50);
static CONFIG_INT("bulb.ramping.percentile", bramp_percentile, 50);
static CONFIG_INT("bulb.ramping.manual.expo", bramp_manual_speed_evx1000_per_shot, 1000);
static CONFIG_INT("bulb.ramping.manual.focus", bramp_manual_speed_focus_steps_per_shot, 1000);

#define BULB_EXPOSURE_CONTROL_ACTIVE (intervalometer_running && bulb_ramping_enabled && (bramp_auto_exposure || bramp_manual_speed_evx1000_per_shot!=1000))

static int intervalometer_running = 0;
int is_intervalometer_running() { return intervalometer_running; }
static int audio_release_running = 0;
int motion_detect = 0;
//int motion_detect_level = 8;

static int timer_values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 15, 16, 18, 20, 25, 30, 35, 40, 45, 50, 55, 60, 70, 80, 90, 100, 110, 120, 135, 150, 165, 180, 195, 210, 225, 240, 270, 300, 360, 420, 480, 540, 600, 660, 720, 780, 840, 900, 1200, 1800, 2700, 3600, 5400, 7200, 9000, 10800, 14400, 18000, 21600, 25200, 28800};
//~ static int timer_values_longexp[] = {5, 7, 10, 15, 20, 30, 50, 60, 120, 180, 300, 600, 900, 1800};
/*
static const char* format_time_minutes_seconds(int seconds)
{
    static char msg[30];
    if (seconds < 60)
        snprintf(msg, sizeof(msg), "%ds", seconds);
    else if (seconds % 60 == 0)
        snprintf(msg, sizeof(msg), "%dm", seconds / 60);
    else
        snprintf(msg, sizeof(msg), "%dm%ds", seconds / 60, seconds % 60);
    return msg;
}*/

static const char* format_time_hours_minutes_seconds(int seconds)
{
    static char msg[50];
    
    msg[0] = '\0';
    if (seconds >= 3600) 
    { 
        STR_APPEND(msg, "%dh", seconds / 3600); 
        seconds = seconds % 3600;
    }

    if (seconds >= 60) 
    { 
        STR_APPEND(msg, "%dm", seconds / 60); 
        seconds = seconds % 60;
    }

    if (seconds || !msg[0])
    {
        STR_APPEND(msg, "%ds", seconds);
    }
    
    return msg;
}

typedef int (*CritFunc)(int);
// crit returns negative if the tested value is too high, positive if too low, 0 if perfect
static int bin_search(int lo, int hi, CritFunc crit)
{
    ASSERT(crit);
    if (lo >= hi-1) return lo;
    int m = (lo+hi)/2;
    int c = crit(m);
    if (c == 0) return m;
    if (c > 0) return bin_search(m, hi, crit);
    return bin_search(lo, m, crit);
}

static int get_exposure_time_ms()
{
    if (is_bulb_mode()) return BULB_SHUTTER_VALUE_MS;
    else return raw2shutter_ms(lens_info.raw_shutter);
}

int get_exposure_time_raw()
{
    if (is_bulb_mode()) return shutterf_to_raw(bulb_shutter_valuef);
    return lens_info.raw_shutter;
}

static void timelapse_calc_display(void* priv, int x, int y, int selected)
{
    int d = timer_values[*(int*)priv];
    int total_shots = interval_stop_after ? (int)MIN((int)interval_stop_after*100, (int)avail_shot) : (int)avail_shot;
    int total_time_s = d * total_shots;
    int total_time_m = total_time_s / 60;
    bmp_printf(FONT(FONT_LARGE, COLOR_WHITE, COLOR_BLACK), 
        x, y,
        "%dh%02dm, %dshots, %dfps => %02dm%02ds", 
        total_time_m / 60, 
        total_time_m % 60, 
        total_shots, video_mode_fps, 
        (total_shots / video_mode_fps) / 60, 
        (total_shots / video_mode_fps) % 60
    );
}

static void
interval_timer_display( void * priv, int x, int y, int selected )
{
    int d = timer_values[*(int*)priv];
    if (!d)
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Take pics like crazy"
        );
    else
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "%s: %s%s",
            (!is_movie_mode() || silent_pic_enabled) ? 
                "Take a pic every" : 
                "REC a clip every",
            format_time_hours_minutes_seconds(d),
            (!is_movie_mode() || silent_pic_enabled) ? // possible jitter warning
                (raw2shutter_ms(lens_info.raw_shutter) > (d-1) * 1000 + 500 ? " Jitter!" : "" ) 
            : "" // movie mode, no warning
        );
    }
    
    menu_draw_icon(x, y, MNI_PERCENT, (*(int*)priv) * 100 / COUNT(timer_values));
}

static void
interval_start_after_display( void * priv, int x, int y, int selected )
{
    int d = timer_values[*(int*)priv];
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Start after     : %s",
        format_time_hours_minutes_seconds(d)
    );

    menu_draw_icon(x, y, MNI_PERCENT, (*(int*)priv) * 100 / COUNT(timer_values));
}

static void
interval_stop_after_display( void * priv, int x, int y, int selected )
{
    int d = (*(int*)priv) * 100;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        d ? "Stop after      : %d shots" 
          : "Stop after      : %s",
        d ? d : (intptr_t) "Disabled"
    );
    if (!d) menu_draw_icon(x, y, MNI_OFF, 0);
}

static void
interval_movie_stop_display( void * priv, int x, int y, int selected )
{
    interval_movie_duration_index = COERCE(interval_movie_duration_index, 0, interval_timer_index-1);
    int d = timer_values[interval_movie_duration_index];

    if ((is_movie_mode() && !silent_pic_enabled) || selected)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Stop REC after  : %s",
            format_time_hours_minutes_seconds(d)
        );
        if (!is_movie_mode() || silent_pic_enabled)
            menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Movie mode inactive.");
        else
            menu_draw_icon(x, y, MNI_PERCENT, (*(int*)priv) * 100 / COUNT(timer_values));
    }
    else menu_draw_icon(x, y, MNI_NONE, 0);
}

static void
interval_timer_toggle( void * priv, int delta )
{
    int * ptr = priv;
    *ptr = mod(*ptr + delta, COUNT(timer_values));
}

static void 
intervalometer_display( void * priv, int x, int y, int selected )
{
    int p = *(int*)priv;
    if (p)
    {
        int d = timer_values[interval_timer_index];
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Intervalometer  : ON, %s%s",
            format_time_hours_minutes_seconds(d),
            bulb_ramping_enabled ? ", BRamp" : (!is_movie_mode() || silent_pic_enabled) ? "" : ", Movie"
        );
        if (selected) timelapse_calc_display(&interval_timer_index, x - font_large.width*2, y + font_large.height * 10, selected);
    }
    else
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Intervalometer  : OFF"
        );
    }
}

/*static void bramp_auto_smooth_print( void * priv, int x, int y, int selected )
{
    float f = (float)bramp_auto_smooth / 100.0;
    float b = f*f - 2*f + 1;
    float a = f*f;
    float max_ev = b/(1+a); // at 1EV exposure difference
    int max_ev_x100 = (int)roundf(max_ev*100.0);

    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Smooth Factor: 0.%d",
        bramp_auto_smooth/10
    );
    bmp_printf(FONT_MED, x + font_large.width * 19, y + (font_large.height - font_med.height)/2, 
        "MAX %d.%02d EV/shot", 
        max_ev_x100 / 100, max_ev_x100 % 100
    );
}*/

static int get_smooth_factor_from_max_ev_speed(int speed_x1000)
{
    float ev = COERCE((float)speed_x1000 / 1000.0, 0.001, 0.98);
    float f = (sqrtf(2*ev - ev*ev) - 1) / (ev-1);
    int fi = (int)roundf(f * 100);
    return COERCE(fi, 1, 99);
}
static void bramp_auto_ramp_speed_print( void * priv, int x, int y, int selected )
{
    int max_ev_x1000 = bramp_auto_ramp_speed;
    int f = get_smooth_factor_from_max_ev_speed(max_ev_x1000);

    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "MAX RampSpeed: %d.%03d EV/shot",
        ABS(max_ev_x1000) / 1000,
        ABS(max_ev_x1000) % 1000
    );
    if (selected)
    bmp_printf(FONT_MED, x + font_large.width * 24, y - font_med.height, 
        "f=0.%02d", 
        f
    );
    menu_draw_icon(x, y, MNI_PERCENT, log_length(max_ev_x1000) * 100 / log_length(1000));
}
static void manual_expo_ramp_print( void * priv, int x, int y, int selected )
{
    int evx1000 = (int)bramp_manual_speed_evx1000_per_shot - 1000;
    if (!evx1000)
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Man. ExpoRamp: OFF"
    );
    else
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Man. ExpoRamp: %s%d.%03d EV/shot",
        evx1000 > 0 ? "+" : evx1000 < 0 ? "-" : "",
        ABS(evx1000) / 1000,
        ABS(evx1000) % 1000
    );
    menu_draw_icon(x, y, MNI_BOOL(evx1000), 0);
}

static void manual_focus_ramp_print( void * priv, int x, int y, int selected )
{
    int steps = (int)bramp_manual_speed_focus_steps_per_shot - 1000;
    if (!steps)
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Man.FocusRamp: OFF"
    );
    else
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Man.FocusRamp: %s%d steps/shot",
        steps > 0 ? "+" : "",
        steps
    );
    if (steps && is_manual_focus())
        menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "This feature requires autofocus enabled.");
    menu_draw_icon(x, y, MNI_BOOL(steps), 0);
}

static void bulb_ramping_print( void * priv, int x, int y, int selected )
{
    int evx1000 = (int)bramp_manual_speed_evx1000_per_shot - 1000;
    int steps = (int)bramp_manual_speed_focus_steps_per_shot - 1000;

    static char msg[100];
    snprintf(msg, sizeof(msg), "Bulb/Focus Ramp : ");

    // try to write this as compact as possible, there's very little space in the menu
    if (!bulb_ramping_enabled)
    {
        STR_APPEND(msg, "OFF");
    }
    else
    {
        if (bramp_auto_exposure)
        {
            STR_APPEND(msg, bramp_auto_exposure==1 ? "Sunset" : bramp_auto_exposure==2 ? "Sunrise" : "Auto");
        }
        if (evx1000)
        {
            STR_APPEND(msg, "%s.", evx1000 >= 1000 ? "+1" : evx1000 <= -1000 ? "-1" : evx1000 > 0 ? "+" : "-");
            int r = ABS(evx1000) % 1000;
            if (r % 100 == 0)       { STR_APPEND(msg, "%01d", r / 100); }
            else if (r % 10 == 0)   { STR_APPEND(msg, "%02d", r / 10 ); }
            else                    { STR_APPEND(msg, "%03d", r      ); }
            STR_APPEND(msg, "EV");
        }
        if (steps)
        {
            STR_APPEND(msg, "%s%dFS", steps > 0 ? "+" : "", steps);
        }
    }
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "%s",
        msg
    );
    if (bulb_ramping_enabled && !bramp_auto_exposure && !evx1000 && !steps)
        menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Nothing enabled from the submenu.");
    menu_draw_icon(x, y, MNI_BOOL(bulb_ramping_enabled), 0);
}

static int ev_values[] = {-1000, -750, -500, -200, -100, -50, -20, -10, -5, -2, -1, 0, 1, 2, 5, 10, 20, 50, 100, 200, 500, 750, 1000};

static void bramp_manual_evx1000_toggle(void* priv, int delta)
{
    int value = (int)bramp_manual_speed_evx1000_per_shot - 1000;
    int i = 0;
    for (i = 0; i < COUNT(ev_values); i++)
        if (ev_values[i] >= value) break;
    i = mod(i + delta, COUNT(ev_values));
    bramp_manual_speed_evx1000_per_shot = ev_values[i] + 1000;
}

static void bramp_auto_ramp_speed_toggle(void* priv, int delta)
{
    int value = (int)bramp_auto_ramp_speed;
    int i = 0;
    for (i = 0; i < COUNT(ev_values); i++)
        if (ev_values[i] >= value) break;
    
    do {
        i = mod(i + delta, COUNT(ev_values));
    } while (ev_values[i] <= 0);
        
    bramp_auto_ramp_speed = ev_values[i];
}

// 10-90
/*
static void bramp_smooth_toggle(void* priv, int delta)
{
    int value = *(int*)priv / 10 - 10;
    value = mod(value + delta, 9);
    *(int*)priv = value * 10 + 10;
}
*/

// in lcdsensor.c
void lcd_release_display( void * priv, int x, int y, int selected );

static void
audio_release_display( void * priv, int x, int y, int selected )
{
    //~ if (audio_release_running)
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Audio RemoteShot: %s, level=%d",
        audio_release_running ? "ON" : "OFF",
        audio_release_level
    );
    /*else
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Audio RemoteShot: OFF"
        );*/
    //~ menu_draw_icon(x, y, audio_release_running ? MNI_PERCENT : MNI_OFF, audio_release_level * 100 / 30);
}

//GUI Functions for the motion detect sensitivity.  

static void 
motion_detect_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Motion Detect   : %s, level=%d",
        motion_detect == 0 ? "OFF" :
        motion_detect_trigger == 0 ? "EXP" : "DIF",
        motion_detect_level
    );
    menu_draw_icon(x, y, MNI_BOOL_LV(motion_detect));
}


int get_trap_focus() { return trap_focus; }

void set_flash_firing(int mode)
{
    lens_wait_readytotakepic(64);
    mode = COERCE(mode, 0, 2);
    prop_request_change(PROP_STROBO_FIRING, &mode, 4);
}
static void 
flash_and_no_flash_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Flash / No flash: %s",
        strobo_firing == 2 ? "N/A" : 
        flash_and_no_flash ? "ON " : "OFF"
    );
}

/*static void
flash_and_no_flash_toggle( void * priv )
{
    flash_and_no_flash = !flash_and_no_flash;
    if (!flash_and_no_flash)
        set_flash_firing(0); // force on
}*/

                                                 //2  4  6  9 12 16 20 25
static const int16_t silent_pic_sweep_modes_l[] = {2, 2, 2, 3, 3, 4, 4, 5};
static const int16_t silent_pic_sweep_modes_c[] = {1, 2, 3, 3, 4, 4, 5, 5};
#define SILENTPIC_NL COERCE(silent_pic_sweep_modes_l[COERCE(silent_pic_highres,0,COUNT(silent_pic_sweep_modes_l)-1)], 0, 5)
#define SILENTPIC_NC COERCE(silent_pic_sweep_modes_c[COERCE(silent_pic_highres,0,COUNT(silent_pic_sweep_modes_c)-1)], 0, 5)

static void 
silent_pic_display( void * priv, int x, int y, int selected )
{
    if (!silent_pic_enabled)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Silent Picture  : OFF"
        );
    }
    else if (silent_pic_mode == 0)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Silent Picture  : %s",
            silent_pic_burst ? "Burst" : 
            silent_pic_fullhd ? "FullHD" : "Single"
        );
    }
    else if (silent_pic_mode == 1)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Silent Pic HiRes: %dx%d",
            SILENTPIC_NL,
            SILENTPIC_NC
        );
        bmp_printf(FONT_MED, x + 430, y+5, "%dx%d", SILENTPIC_NC*(1024-8), SILENTPIC_NL*(680-8));
    }
    /*else if (silent_pic_mode == 3)
    {
        int t = timer_values_longexp[mod(silent_pic_longexp_time_index, COUNT(timer_values_longexp))];
        unsigned fnt = selected ? MENU_FONT_SEL : MENU_FONT;
        bmp_printf(
            FONT(fnt, COLOR_RED, FONT_BG(fnt)),
            x, y,
            "Silent Pic LongX: %ds",
            t
            //~ silent_pic_longexp_method == 0 ? "AVG" :
            //~ silent_pic_longexp_method == 1 ? "MAX" :
            //~ silent_pic_longexp_method == 2 ? "SUM" : "err"
        );
    }*/
    /*else if (silent_pic_mode == 2)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "Slit-scan Pic   : 1ln/%dclk",
            silent_pic_slitscan_skipframes
        );
    }*/
}

#ifdef AFFRAME_PROP_LEN
static int afframe[AFFRAME_PROP_LEN];
PROP_HANDLER( PROP_LV_AFFRAME ) {
    ASSERT(len == AFFRAME_PROP_LEN);

    clear_lv_afframe(); 

    crop_set_dirty(10);
    afframe_set_dirty();
    
    memcpy(afframe, buf, AFFRAME_PROP_LEN);
}
#else
static int afframe[100]; // dummy
#endif

void get_afframe_pos(int W, int H, int* x, int* y)
{
    *x = (afframe[2] + afframe[4]/2) * W / afframe[0];
    *y = (afframe[3] + afframe[5]/2) * H / afframe[1];
}

static int face_zoom_request = 0;

PROP_HANDLER( PROP_HALF_SHUTTER ) {
    #if !defined(CONFIG_50D) && !defined(CONFIG_5D2)
    int v = *(int*)buf;
    if (zoom_enable_face)
    {
        if (v == 0 && lv && lvaf_mode == 2 && gui_state == 0 && !recording) // face detect
            face_zoom_request = 1;
    }
    #endif

    zoom_sharpen_step();
    zoom_auto_exposure_step();
}

static int zoom_was_triggered_by_halfshutter = 0;

PROP_HANDLER(PROP_LV_DISPSIZE)
{
    ASSERT(buf[0] == 1 || buf[0] == 5 || buf[0] == 10);
    
    zoom_sharpen_step();
    zoom_auto_exposure_step();
    
    if (buf[0] == 1) zoom_was_triggered_by_halfshutter = 0;
}

void set_lv_zoom(int zoom)
{
    if (recording) return;
    zoom = COERCE(zoom, 1, 10);
    if (zoom > 1 && zoom < 10) zoom = 5;
    prop_request_change(PROP_LV_DISPSIZE, &zoom, 4);
}

int handle_shutter_events(struct event * event)
{
    return 1;
#if 0 // not reliable
    if (HDR_ENABLED)
    {
        switch(event->param)
        {
            case BGMT_PRESS_HALFSHUTTER:
            case BGMT_UNPRESS_HALFSHUTTER:
            {
                int h = HALFSHUTTER_PRESSED;
                if (!h) msleep(50); // avoids cancelling self-timer too early
                halfshutter_action(h);
            }
        }
    }
    return 1;
#endif
}

/*int sweep_lv_on = 0;
static void 
sweep_lv_start(void* priv)
{
    sweep_lv_on = 1;
}*/

int center_lv_aff = 0;
void center_lv_afframe()
{
    center_lv_aff = 1;
}
void center_lv_afframe_do()
{
    if (!lv || gui_menu_shown() || gui_state != GUISTATE_IDLE) return;
    int cx = (afframe[0] - afframe[4])/2;
    int cy = (afframe[1] - afframe[5])/2;
    move_lv_afframe(cx-afframe[2], cy-afframe[3]);
}

void move_lv_afframe(int dx, int dy)
{
#ifdef AFFRAME_PROP_LEN
    if (!liveview_display_idle()) return;
    afframe[2] = COERCE(afframe[2] + dx, 500, afframe[0] - afframe[4]);
    afframe[3] = COERCE(afframe[3] + dy, 500, afframe[1] - afframe[5]);
    prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);
#endif
}

/*
static void 
sweep_lv()
{
    if (recording) return;
    if (!lv) return;
    menu_stop();
    msleep(2000);
    set_lv_zoom(5);
    msleep(2000);
    
    int i,j;
    for (i = 0; i < 5; i++)
    {
        for (j = 0; j < 5; j++)
        {
            bmp_printf(FONT_LARGE, 50, 50, "AFF %d, %d ", i, j);
            afframe[2] = 250 + 918 * j;
            afframe[3] = 434 + 490 * i;
            prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);
            msleep(100);
        }
    }

    set_lv_zoom(1);
}*/

static char* silent_pic_get_name()
{
    static char imgname[100];
    static int silent_number = 1; // cache this number for speed (so it won't check all files until 10000 to find the next free number)
    
    static int prev_file_number = -1;
    static int prev_folder_number = -1;
    
    if (prev_file_number != file_number) silent_number = 1;
    if (prev_folder_number != folder_number) silent_number = 1;
    
    prev_file_number = file_number;
    prev_folder_number = folder_number;
    
    if (intervalometer_running)
    {
        for ( ; silent_number < 100000000; silent_number++)
        {
            snprintf(imgname, sizeof(imgname), "%s/%08d.422", get_dcim_dir(), silent_number);
            unsigned size;
            if( FIO_GetFileSize( imgname, &size ) != 0 ) break;
            if (size == 0) break;
        }
    }
    else
    {
        for ( ; silent_number < 10000; silent_number++)
        {
            snprintf(imgname, sizeof(imgname), "%s/%04d%04d.422", get_dcim_dir(), file_number, silent_number);
            unsigned size;
            if( FIO_GetFileSize( imgname, &size ) != 0 ) break;
            if (size == 0) break;
        }
    }
    bmp_printf(FONT_MED, 100, 80, "%s    ", imgname);
    return imgname;
}

static int ms100_clock = 0;
static void
ms100_clock_task( void* unused )
{
    TASK_LOOP
    {
        msleep(100);
        ms100_clock += 100;
    }
}
TASK_CREATE( "ms100_clock_task", ms100_clock_task, 0, 0x19, 0x1000 );

int get_ms_clock_value() { return ms100_clock; }

int expfuse_running = 0;
static int expfuse_num_images = 0;
static struct semaphore * set_maindial_sem = 0;

int compute_signature(int* start, int num)
{
    int c = 0;
    int* p;
    for (p = start; p < start + num; p++)
    {
        c += *p;
    }
    //~ return SIG_60D_110;
    return c;
}

static void add_yuv_acc16bit_src8bit(void* acc, void* src, int numpix)
{
    ASSERT(acc);
    ASSERT(src);
    int16_t* accs = acc;
    uint16_t* accu = acc;
    int8_t* srcs = src;
    uint8_t* srcu = src;
    int i;
    for (i = 0; i < numpix; i++)
    {
        accs[i*2] += srcs[i*2]; // chroma, signed
        accu[i*2+1] += srcu[i*2+1]; // luma, unsigned
    }
}

static void div_yuv_by_const_dst8bit_src16bit(void* dst, void* src, int numpix, int den)
{
    ASSERT(dst);
    ASSERT(src);
    int8_t* dsts = dst;
    uint8_t* dstu = dst;
    int16_t* srcs = src;
    uint16_t* srcu = src;
    int i;
    for (i = 0; i < numpix; i++)
    {
        dsts[i*2] = srcs[i*2] / den; // chroma, signed
        dstu[i*2+1] = srcu[i*2+1] / den; // luma, unsigned
    }
}

// octave:
// x = linspace(0,1,256);
// f = @(x) exp(-(x-0.5).^2 ./ 0.32) # mean=0.5, sigma=0.4
// sprintf("0x%02x, ",f(x) * 100)
static uint8_t gauss_lut[] = {0x2d, 0x2e, 0x2e, 0x2f, 0x30, 0x30, 0x31, 0x31, 0x32, 0x32, 0x33, 0x34, 0x34, 0x35, 0x35, 0x36, 0x37, 0x37, 0x38, 0x38, 0x39, 0x39, 0x3a, 0x3b, 0x3b, 0x3c, 0x3c, 0x3d, 0x3e, 0x3e, 0x3f, 0x3f, 0x40, 0x41, 0x41, 0x42, 0x42, 0x43, 0x44, 0x44, 0x45, 0x45, 0x46, 0x46, 0x47, 0x48, 0x48, 0x49, 0x49, 0x4a, 0x4a, 0x4b, 0x4c, 0x4c, 0x4d, 0x4d, 0x4e, 0x4e, 0x4f, 0x4f, 0x50, 0x50, 0x51, 0x51, 0x52, 0x52, 0x53, 0x53, 0x54, 0x54, 0x55, 0x55, 0x56, 0x56, 0x57, 0x57, 0x58, 0x58, 0x58, 0x59, 0x59, 0x5a, 0x5a, 0x5a, 0x5b, 0x5b, 0x5c, 0x5c, 0x5c, 0x5d, 0x5d, 0x5d, 0x5e, 0x5e, 0x5e, 0x5f, 0x5f, 0x5f, 0x5f, 0x60, 0x60, 0x60, 0x60, 0x61, 0x61, 0x61, 0x61, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x63, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x62, 0x61, 0x61, 0x61, 0x61, 0x60, 0x60, 0x60, 0x60, 0x5f, 0x5f, 0x5f, 0x5f, 0x5e, 0x5e, 0x5e, 0x5d, 0x5d, 0x5d, 0x5c, 0x5c, 0x5c, 0x5b, 0x5b, 0x5a, 0x5a, 0x5a, 0x59, 0x59, 0x58, 0x58, 0x58, 0x57, 0x57, 0x56, 0x56, 0x55, 0x55, 0x54, 0x54, 0x53, 0x53, 0x52, 0x52, 0x51, 0x51, 0x50, 0x50, 0x4f, 0x4f, 0x4e, 0x4e, 0x4d, 0x4d, 0x4c, 0x4c, 0x4b, 0x4a, 0x4a, 0x49, 0x49, 0x48, 0x48, 0x47, 0x46, 0x46, 0x45, 0x45, 0x44, 0x44, 0x43, 0x42, 0x42, 0x41, 0x41, 0x40, 0x3f, 0x3f, 0x3e, 0x3e, 0x3d, 0x3c, 0x3c, 0x3b, 0x3b, 0x3a, 0x39, 0x39, 0x38, 0x38, 0x37, 0x37, 0x36, 0x35, 0x35, 0x34, 0x34, 0x33, 0x32, 0x32, 0x31, 0x31, 0x30, 0x30, 0x2f, 0x2e, 0x2e, 0x2d};

static void weighted_mean_yuv_init_acc32bit_ws16bit(void* acc, void* weightsum, int numpix)
{
    bzero32(acc, numpix*8);
    bzero32(weightsum, numpix*4);
}

static void weighted_mean_yuv_add_acc32bit_src8bit_ws16bit(void* acc, void* src, void* weightsum, int numpix)
{
    int32_t* accs = acc;
    uint32_t* accu = acc;
    int8_t* srcs = src;
    uint8_t* srcu = src;
    uint16_t* ws = weightsum;
    int i;
    for (i = 0; i < numpix; i++)
    {
        int w = gauss_lut[srcu[i*2+1]];
        accs[i*2] += srcs[i*2] * w; // chroma, signed
        accu[i*2+1] += srcu[i*2+1] * w; // luma, unsigned
        ws[i] += w;
    }
}

static void weighted_mean_yuv_div_dst8bit_src32bit_ws16bit(void* dst, void* src, void* weightsum, int numpix)
{
    int8_t* dsts = dst;
    uint8_t* dstu = dst;
    int32_t* srcs = src;
    uint32_t* srcu = src;
    uint16_t* ws = weightsum;
    int i;
    for (i = 0; i < numpix; i++)
    {
        int wt = ws[i];
        dsts[i*2] = srcs[i*2] / wt; // chroma, signed
        dstu[i*2+1] = COERCE(srcu[i*2+1] / wt, 0, 255); // luma, unsigned
    }
}

void next_image_in_play_mode(int dir)
{
    if (!PLAY_MODE) return;
    void* buf_lv = get_yuv422_vram()->vram;
    // ask for next image
    fake_simple_button(dir > 0 ? BGMT_WHEEL_DOWN : BGMT_WHEEL_UP);
    int k = 0;
    // wait for image buffer location to be flipped => next image was loaded
    while (get_yuv422_vram()->vram == buf_lv && k < 50) 
    {
        msleep(100);
        k++;
    }
}

void playback_compare_images_task(int dir)
{
    ASSERT(set_maindial_sem);
    take_semaphore(set_maindial_sem, 0);
    
    if (!PLAY_MODE) { fake_simple_button(BGMT_PLAY); msleep(500); }
    if (!PLAY_MODE) { NotifyBox(1000, "CompareImages: Not in PLAY mode"); return; }
    
    if (dir == 0) // reserved for intervalometer
    {
        next_image_in_play_mode(-1);
        dir = 1;
    }
    
    void* aux_buf = (void*)YUV422_HD_BUFFER_2;
    void* current_buf;
    int w = get_yuv422_vram()->width;
    int h = get_yuv422_vram()->height;
    int buf_size = w * h * 2;
    current_buf = get_yuv422_vram()->vram;
    yuv_halfcopy(aux_buf, current_buf, w, h, 1);
    next_image_in_play_mode(dir);
    current_buf = get_yuv422_vram()->vram;
    yuv_halfcopy(aux_buf, current_buf, w, h, 0);
    current_buf = get_yuv422_vram()->vram;
    memcpy(current_buf, aux_buf, buf_size);
    give_semaphore(set_maindial_sem);
}

void playback_compare_images(int dir)
{
    task_create("playcompare_task", 0x1c, 0, playback_compare_images_task, (void*)dir);
}

void expfuse_preview_update_task(int dir)
{
    ASSERT(set_maindial_sem);
    take_semaphore(set_maindial_sem, 0);
    void* buf_acc = (void*)YUV422_HD_BUFFER_1;
    void* buf_ws = (void*)YUV422_HD_BUFFER_2;
    void* buf_lv = get_yuv422_vram()->vram;
    int numpix = get_yuv422_vram()->width * get_yuv422_vram()->height;
    if (!expfuse_running)
    {
        // first image 
        weighted_mean_yuv_init_acc32bit_ws16bit(buf_acc, buf_ws, numpix);
        weighted_mean_yuv_add_acc32bit_src8bit_ws16bit(buf_acc, buf_lv, buf_ws, numpix);
        expfuse_num_images = 1;
        expfuse_running = 1;
    }
    next_image_in_play_mode(dir);
    buf_lv = get_yuv422_vram()->vram; // refresh
    // add new image

    weighted_mean_yuv_add_acc32bit_src8bit_ws16bit(buf_acc, buf_lv, buf_ws, numpix);
    weighted_mean_yuv_div_dst8bit_src32bit_ws16bit(buf_lv, buf_acc, buf_ws, numpix);
    expfuse_num_images++;
    bmp_printf(FONT_MED, 0, 0, "%d images  ", expfuse_num_images);
    //~ bmp_printf(FONT_LARGE, 0, 480 - font_large.height, "Do not press Delete!");

    give_semaphore(set_maindial_sem);
}

void expfuse_preview_update(int dir)
{
    task_create("expfuse_task", 0x1c, 0, expfuse_preview_update_task, (void*)dir);
}

// that's extremely inefficient
static int find_422(int * index, char* fn)
{
    struct fio_file file;
    struct fio_dirent * dirent = 0;
    int N = 0;
    
    dirent = FIO_FindFirstEx( get_dcim_dir(), &file );
    if( IS_ERROR(dirent) )
    {
        bmp_printf( FONT_LARGE, 40, 40, "dir err" );
        return 0;
    }

    do {
        if (file.mode & 0x10) continue; // is a directory
        int n = strlen(file.name);
        if ((n > 4) && (streq(file.name + n - 4, ".422")))
            N++;
    } while( FIO_FindNextEx( dirent, &file ) == 0);
    FIO_CleanupAfterFindNext_maybe(dirent);

    static int old_N = 0;
    if (N != old_N) // number of pictures was changed, display the last one
    {
        old_N = N;
        *index = N-1;
    }
    
    *index = mod(*index, N);

    dirent = FIO_FindFirstEx( get_dcim_dir(), &file );
    if( IS_ERROR(dirent) )
    {
        bmp_printf( FONT_LARGE, 40, 40, "dir err" );
        return 0;
    }

    int k = 0;
    int found = 0;
    do {
        if (file.mode & 0x10) continue; // is a directory
        int n = strlen(file.name);
        if ((n > 4) && (streq(file.name + n - 4, ".422")))
        {
            if (k == *index)
            {
                snprintf(fn, 100, "%s/%s", get_dcim_dir(), file.name);
                found = 1;
            }
            k++;
        }
    } while( FIO_FindNextEx( dirent, &file ) == 0);
    FIO_CleanupAfterFindNext_maybe(dirent);
    return found;
}

void play_next_422_task(int dir)
{
    ASSERT(set_maindial_sem);
    take_semaphore(set_maindial_sem, 0);
    
    static int index = -1;
    static char ffn[100];
    
    index += dir;
    
    if (find_422(&index, ffn))
    {
        play_422(ffn);
        //~ bmp_printf(FONT_LARGE, 0, 0, ffn);
    }
    else
    {
        bmp_printf(FONT_LARGE, 0, 0, "No 422 files found");
    }

    give_semaphore(set_maindial_sem);
}


void play_next_422(int dir)
{
    task_create("422_task", 0x1c, 0, play_next_422_task, (void*)dir);
}


/*
static void
silent_pic_take_longexp()
{
    bmp_printf(FONT_MED, 100, 100, "Psst!");
    struct vram_info * vram = get_yuv422_hd_vram();
    int bufsize = vram->height * vram->pitch;
    int numpix = vram->height * vram->width;
    void* longexp_buf = 0x44000060 + bufsize + 4096;
    bzero32(longexp_buf, bufsize*2);
    
    // check if the buffer appears to be used
    int i;
    int s1 = compute_signature(longexp_buf, bufsize/2);
    msleep(100);
    int s2 = compute_signature(longexp_buf, bufsize/2);
    if (s1 != s2) { bmp_printf(FONT_MED, 100, 100, "Psst! can't use buffer at %x ", longexp_buf); return; }

    ms100_clock = 0;
    int tmax = timer_values_longexp[silent_pic_longexp_time_index] * 1000;
    int num = 0;
    while (ms100_clock < tmax)
    {
        bmp_printf(FONT_MED, 100, 100, "Psst! Taking a long-exp silent pic (%dimg,%ds/%ds)...   ", num, ms100_clock/1000, tmax/1000);
        add_yuv_acc16bit_src8bit(longexp_buf, vram->vram, numpix);
        num += 1;
    }
    open_canon_menu();
    msleep(500);
    div_yuv_by_const_dst8bit_src16bit(vram->vram, longexp_buf, numpix, num);
    char* imgname = silent_pic_get_name();
    FILE* f = FIO_CreateFileEx(imgname);
    if (f == INVALID_PTR)
    {
        bmp_printf(FONT_SMALL, 120, 40, "FCreate: Err %s", imgname);
        return;
    }
    FIO_WriteFile(f, vram->vram, vram->height * vram->pitch);
    FIO_CloseFile(f);
    clrscr(); play_422(imgname);
    bmp_printf(FONT_MED, 100, 100, "Psst! Just took a long-exp silent pic   ");
}
*/

void ensure_movie_mode()
{
    if (!is_movie_mode())
    {
        #ifdef CONFIG_50D
        if (!lv) force_liveview();
        GUI_SetLvMode(2);
        GUI_SetMovieSize_b(1);
        #else
            #ifdef CONFIG_5D2
                GUI_SetLvMode(2);
            #else
                #ifdef CONFIG_500D
                if (shooting_mode == SHOOTMODE_ADEP) set_shooting_mode(SHOOTMODE_CA);
                #endif
                set_shooting_mode(SHOOTMODE_MOVIE);
            #endif
        #endif
        msleep(500); 
    }
    if (!lv) force_liveview();
}

static int
silent_pic_ensure_movie_mode()
{
    if (silent_pic_fullhd && !is_movie_mode()) 
    { 
        ensure_movie_mode();
    }
    #ifndef CONFIG_600D // on 600D you only have to go in movie mode
    if (silent_pic_fullhd && !recording)
    {
        movie_start();
        return 1;
    }
    #endif
    return 0;
}

static void stop_recording_and_delete_movie()
{
    if (recording)
    {
        movie_end();
        char name[100];
        snprintf(name, sizeof(name), "%s/MVI_%04d.THM", get_dcim_dir(), file_number);
        FIO_RemoveFile(name);
        snprintf(name, sizeof(name), "%s/MVI_%04d.MOV", get_dcim_dir(), file_number);
        FIO_RemoveFile(name);
    }
}

static void
silent_pic_stop_dummy_movie()
{ 
    #ifndef CONFIG_600D
    stop_recording_and_delete_movie();
    #endif
}

void
silent_pic_take_simple(int interactive)
{
    int movie_started = silent_pic_ensure_movie_mode();
    
    char* imgname = silent_pic_get_name();

    struct vram_info * vram = get_yuv422_hd_vram();
    int p = vram->pitch;
    int h = vram->height;
    
    lv_request_pause_updating(300);
    msleep(50);
    
    dump_seg(get_yuv422_hd_vram()->vram, p * h, imgname);

    lv_wait_for_pause_updating_to_finish();

    //~ if (interactive && !silent_pic_burst)
    //~ {
        //~ NotifyBoxHide();
        //~ msleep(500); clrscr();
        //~ play_422(imgname);
        //~ msleep(1000);
    //~ }

    if (interactive && !silent_pic_burst) // single mode
    {
        while (get_halfshutter_pressed()) msleep(100);
    }

    msleep(100);
    
    if (movie_started) silent_pic_stop_dummy_movie();
}

void
silent_pic_take_lv_dbg()
{
    struct vram_info * vram = get_yuv422_vram();
    int silent_number;
    char imgname[100];
    for (silent_number = 0 ; silent_number < 1000; silent_number++) // may be slow after many pics
    {
        snprintf(imgname, sizeof(imgname), CARD_DRIVE "VRAM%d.422", silent_number); // should be in root, because Canon's "dispcheck" saves screenshots there too
        unsigned size;
        if( FIO_GetFileSize( imgname, &size ) != 0 ) break;
        if (size == 0) break;
    }
    dump_seg(vram->vram, vram->pitch * vram->height, imgname);
}

void
silent_pic_take_test()
{
    char* imgname = silent_pic_get_name();

    struct vram_info * vram = get_yuv422_hd_vram();
    int p = vram->pitch;
    int h = vram->height;
    
    dump_seg(get_yuv422_hd_vram()->vram, p * h, imgname);
}

int silent_pic_matrix_running = 0;
 void
silent_pic_take_sweep(int interactive)
{
#ifdef AFFRAME_PROP_LEN
    if (recording) return;
    if (!lv) return;
    if (SILENTPIC_NL > 4 || SILENTPIC_NC > 4)
    {
        if ((af_mode & 0xF) != 3 )
        {
            NotifyBox(2000, "Matrices higher than 4x4\n"
                            "require manual focus.   "); 
            msleep(2000);
            return; 
        }
    }

    bmp_printf(FONT_MED, 100, 100, "Psst! Preparing for high-res pic   ");
    while (get_halfshutter_pressed()) msleep(100);
    menu_stop();

    bmp_draw_rect(COLOR_WHITE, (5-SILENTPIC_NC) * 360/5, (5-SILENTPIC_NL)*240/5, SILENTPIC_NC*720/5-1, SILENTPIC_NL*480/5-1);
    msleep(200);
    if (interactive) msleep(2000);
    redraw(); msleep(100);
    
    int afx0 = afframe[2];
    int afy0 = afframe[3];

    set_lv_zoom(5);
    msleep(1000);

    struct vram_info * vram = get_yuv422_hd_vram();

    char* imgname = silent_pic_get_name();

    FILE* f = FIO_CreateFileEx(imgname);
    if (f == INVALID_PTR)
    {
        bmp_printf(FONT_SMALL, 120, 40, "FCreate: Err %s", imgname);
        return;
    }
    int i,j;
    int NL = SILENTPIC_NL;
    int NC = SILENTPIC_NC;
    int x0 = (SENSOR_RES_X - NC * 1024) / 2;
    int y0 = (SENSOR_RES_Y - NL * 680) / 2;
    for (i = 0; i < NL; i++)
    {
        for (j = 0; j < NC; j++)
        {
            // afframe[2,3]: x,y
            // range obtained by moving the zoom window: 250 ... 3922, 434 ... 2394 => upper left corner
            // full-res: 5202x3465
            // buffer size: 1024x680
            bmp_printf(FONT_MED, 100, 100, "Psst! Taking a high-res pic [%d,%d]      ", i, j);
            afframe[2] = x0 + 1024 * j;
            afframe[3] = y0 + 680 * i;
            prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);
            //~ msleep(500);
            msleep(silent_pic_sweepdelay);
            FIO_WriteFile(f, vram->vram, 1024 * 680 * 2);
            //~ bmp_printf(FONT_MED, 20, 150, "=> %d", ans);
            msleep(50);
        }
    }
    FIO_CloseFile(f);
    
    // restore
    set_lv_zoom(1);
    msleep(1000);
    afframe[2] = afx0;
    afframe[3] = afy0;
    prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);

    bmp_printf(FONT_MED, 100, 100, "Psst! Just took a high-res pic   ");
#endif
}

static void vsync(volatile int* addr)
{
    int i;
    int v0 = *addr;
    for (i = 0; i < 100; i++)
    {
        if (*addr != v0) return;
        msleep(MIN_MSLEEP);
    }
    bmp_printf(FONT_MED, 30, 100, "vsync failed");
}

/*
static void
silent_pic_take_slitscan(int interactive)
{
    #if defined(CONFIG_550D) || defined(CONFIG_500D) || defined(CONFIG_60D)
    //~ if (recording) return; // vsync fails
    if (!lv) return;
    menu_stop();

    int movie_started = silent_pic_ensure_movie_mode();

    while (get_halfshutter_pressed()) msleep(100);
    msleep(500);
    clrscr();

    uint8_t * const lvram = UNCACHEABLE(YUV422_LV_BUFFER_1);
    int lvpitch = YUV422_LV_PITCH;
    uint8_t * const bvram = bmp_vram();
    if (!bvram) return;
    #define BMPPITCH 960

    struct vram_info * vram = get_yuv422_hd_vram();
    NotifyBox(60000, "Psst! Slit-scan pic (%dx%d)", vram->width, vram->height);

    char* imgname = silent_pic_get_name();

    FILE* f = FIO_CreateFileEx(imgname);
    if (f == INVALID_PTR)
    {
        bmp_printf(FONT_SMALL, 120, 40, "FCreate: Err %s", imgname);
        return;
    }
    int i;
    for (i = 0; i < vram->height; i++)
    {
        int k;
        for (k = 0; k < (int)silent_pic_slitscan_skipframes; k++)
            vsync((void*)YUV422_HD_BUFFER_DMA_ADDR);
        
        FIO_WriteFile(f, (void*)(YUV422_HD_BUFFER_DMA_ADDR + i * vram->pitch), vram->pitch);

        int y = i * 480 / vram->height;
        uint16_t * const v_row = (uint16_t*)( lvram + y * lvpitch );        // 1 pixel
        uint8_t * const b_row = (uint8_t*)( bvram + y * BMPPITCH);          // 1 pixel
        uint16_t* lvp; // that's a moving pointer through lv vram
        uint8_t* bp;  // through bmp vram
        for (lvp = v_row, bp = b_row; lvp < v_row + 720 ; lvp++, bp++)
            *bp = ((*lvp) * 41 >> 16) + 38;
        
        if (get_halfshutter_pressed())
        {
            FIO_CloseFile(f);
            FIO_RemoveFile(imgname);
            clrscr();
            NotifyBoxHide();
            NotifyBox(2000, "Slit-scan cancelled.");
            while (get_halfshutter_pressed()) msleep(100);
            if (movie_started) silent_pic_stop_dummy_movie();
            return;
        }
    }
    FIO_CloseFile(f);
    if (movie_started) silent_pic_stop_dummy_movie();

    NotifyBoxHide();
    //~ NotifyBox(2000, "Psst! Just took a slit-scan pic");

    if (!interactive) return;

    PauseLiveView();
    play_422(imgname);
    // wait half-shutter press and clear the screen
    while (!get_halfshutter_pressed()) msleep(100);
    while (get_halfshutter_pressed()) msleep(100);
    clrscr();
    ResumeLiveView();
    
    #endif
}*/

static void
silent_pic_take(int interactive) // for remote release, set interactive=0
{
    if (!silent_pic_enabled) return;

    if (!lv) force_liveview();

    //~ if (beep_enabled) Beep();
    
    //~ idle_globaldraw_dis();
    
    if (silent_pic_mode == 0) // normal
        silent_pic_take_simple(interactive);
    else if (silent_pic_mode == 1) // hi-res
    {
        silent_pic_matrix_running = 1;
        silent_pic_take_sweep(interactive);
    }
    //~ else if (silent_pic_mode == 2) // slit-scan
        //~ silent_pic_take_slitscan(interactive);
    //~ else if (silent_pic_mode == 3) // long exposure
        //~ silent_pic_take_longexp();

    //~ idle_globaldraw_en();

    silent_pic_matrix_running = 0;

}


static void 
iso_display( void * priv, int x, int y, int selected )
{
    int fnt = selected ? MENU_FONT_SEL : MENU_FONT;
    bmp_printf(
        fnt,
        x, y,
        "ISO         : %s        ", 
        lens_info.iso ? "" : "Auto"
    );

    if (lens_info.iso)
    {
        if (lens_info.raw_iso == lens_info.iso_equiv_raw)
        {
            bmp_printf(
                fnt,
                x + 14 * font_large.width, y,
                "%d", raw2iso(lens_info.iso_equiv_raw)
            );
        }
        else
        {
            int dg = lens_info.iso_equiv_raw - lens_info.raw_iso;
            dg = dg * 10/8;
            bmp_printf(
                fnt,
                x + 14 * font_large.width, y,
                "%d (%d,%s%d.%dEV)", 
                raw2iso(lens_info.iso_equiv_raw),
                raw2iso(lens_info.raw_iso),
                dg > 0 ? "+" : "-", 
                ABS(dg)/10, ABS(dg)%10
            );
        }
    }

    menu_draw_icon(x, y, lens_info.iso ? MNI_PERCENT : MNI_AUTO, (lens_info.raw_iso - codes_iso[1]) * 100 / (codes_iso[COUNT(codes_iso)-1] - codes_iso[1]));
}

int is_native_iso(int iso)
{
    switch(iso)
    {
        case 100:
        case 200:
        case 400:
        case 800:
        case 1600:
        case 3200:
        //~ case 6400: // those are digital gains applied to 3200 ISO
        //~ case 12800:
        //~ case 25600:
            return 1;
    }
    return 0;
}

int is_lowgain_iso(int iso)
{
    switch(iso)
    {
        case 160:  // ISO 200 - 1/3EV
        case 320:  // ISO 400 - 1/3EV
        case 640:  // ISO 800 - 1/3EV
        case 1250: // ISO 1600 - 1/3EV
        case 2500: // ISO 3200 - 1/3EV
        //~ case 5000: // this is ISO 3200 analog gain + 2/3EV digital gain
        return 1;
    }
    return 0;
}

int is_round_iso(int iso)
{
    return is_native_iso(iso) || is_lowgain_iso(iso) || iso == 0
        || iso == 6400 || iso == 12800 || iso == 25600;
}

void
analog_iso_toggle( void * priv, int sign )
{
    int r = lens_info.raw_iso;
    int a, d;
    split_iso(r, &a, &d);
    a = COERCE(a + sign * 8, 72, 112);
    lens_set_rawiso(a + d);
}

void
digital_iso_toggle( void * priv, int sign )
{
    int r = lens_info.raw_iso;
    int a, d;
    split_iso(r, &a, &d);
    d = COERCE(d + sign, -3, (a == 112 ? 16 : 4));
    while (d > 8 && d < 16) d += sign;
    lens_set_rawiso(a + d);
}

void
fullstop_iso_toggle( void * priv, int sign )
{
    int min_iso = get_htp() ? 80 : 72; // iso 100
    int max_iso = 120; // iso 6400
    int r = lens_info.raw_iso;
    if (!r) r = sign > 0 ? min_iso-8 : max_iso+8;
    int rounded = ((r+3)/8) * 8;
    rounded = COERCE(rounded + sign * 8, min_iso, max_iso);
    lens_set_rawiso(rounded);
}


void
iso_toggle( void * priv, int sign )
{
    if (is_movie_mode())
    {
        if ((lens_info.raw_iso == 72 && sign < 0) ||
            (lens_info.raw_iso == 80 && sign < 0 && get_htp()) ||
            (lens_info.raw_iso == 120 && sign > 0))
        {
            lens_set_rawiso(0); // ISO auto
            return;
        }

        if (iso_selection == 1) // constant DIGIC gain, full-stop analog
        {
            fullstop_iso_toggle(priv, sign);
            return;
        }
    }

    set_display_gain_equiv(0); // disable DIGIC iso
    
    int i = raw2index_iso(lens_info.raw_iso);
    int k;
    for (k = 0; k < 10; k++)
    {
        i = mod(i + sign, COUNT(codes_iso));
        

        while (!is_round_iso(values_iso[i]))
            i = mod(i + sign, COUNT(codes_iso));
        
        if (lens_set_rawiso(codes_iso[i])) break;
    }
}


static void 
shutter_display( void * priv, int x, int y, int selected )
{
    char msg[100];
    if (is_movie_mode())
    {
        int s = get_current_shutter_reciprocal_x1000();
        int deg = 360 * fps_get_current_x1000() / s;
        //~ ASSERT(deg <= 360);
        snprintf(msg, sizeof(msg),
            "Shutter     : 1/%d.%03d, %d ",
            s/1000, s%1000, 
            deg);
    }
    else
    {
        snprintf(msg, sizeof(msg),
            "Shutter     : 1/%d",
            lens_info.shutter
        );
    }
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        msg
    );
    if (is_movie_mode())
    {
        int xc = x + font_large.width * (strlen(msg) - 1);
        draw_circle(xc + 2, y + 7, 3, COLOR_WHITE);
        draw_circle(xc + 2, y + 7, 4, COLOR_WHITE);
    }
    //~ bmp_printf(FONT_MED, x + 550, y+5, "[Q]=Auto");
    menu_draw_icon(x, y, lens_info.raw_shutter ? MNI_PERCENT : MNI_WARNING, lens_info.raw_shutter ? (lens_info.raw_shutter - codes_shutter[1]) * 100 / (codes_shutter[COUNT(codes_shutter)-1] - codes_shutter[1]) : (intptr_t) "Shutter speed is automatic - cannot adjust manually.");
}

void
shutter_toggle(void* priv, int sign)
{
    if (!lens_info.raw_shutter) return;
    int i = raw2index_shutter(lens_info.raw_shutter);
    int k;
    for (k = 0; k < 15; k++)
    {
        int new_i = i;
        new_i = mod(new_i + sign, COUNT(codes_shutter));

        //~ bmp_printf(FONT_MED, 100, 300, "%d -> %d ", codes_shutter[i0], codes_shutter[new_i]);
        
        if (priv == (void*)-1 && (new_i == 0 || i + sign != new_i)) // wrapped around
            break;
        i = new_i;
        if (lens_set_rawshutter(codes_shutter[i])) break;
    }
}

static void 
aperture_display( void * priv, int x, int y, int selected )
{
    int a = lens_info.aperture;
    int av = ABS(lens_info.raw_aperture - 8);
    if (!lens_info.name[0]) // for unchipped lenses, always display zero
        a = 0;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Aperture    : f/%d.%d (AV %d.%d)",
        a / 10,
        a % 10, 
        av / 8, 
        (av % 8) * 10/8
    );
    menu_draw_icon(x, y, lens_info.aperture ? MNI_PERCENT : MNI_WARNING, lens_info.aperture ? (uintptr_t)((lens_info.raw_aperture - codes_aperture[1]) * 100 / (codes_shutter[COUNT(codes_aperture)-1] - codes_aperture[1])) : (uintptr_t) (lens_info.name[0] ? "Aperture is automatic - cannot adjust manually." : "Manual lens - cannot adjust aperture."));
}

void
aperture_toggle( void* priv, int sign)
{
    if (!lens_info.name[0]) return; // only chipped lenses can change aperture
    if (!lens_info.raw_aperture) return;
    int amin = codes_aperture[1];
    int amax = codes_aperture[COUNT(codes_aperture)-1];
    
    int a = lens_info.raw_aperture;

    for (int k = 0; k < 4; k++)
    {
        do {
            a += sign;
            if (priv == (void*)-1) // don't wrap around
            {
                if (a > amax) { a = amax; break; }
                if (a < amin) { a = amin; break; }
            }
            else // allow wrap around
            {
                if (a > amax) a = amin;
                if (a < amin) a = amax;
            }
            if (lens_info.raw_aperture_min >= lens_info.raw_aperture_max) break;
        }
        while (a < lens_info.raw_aperture_min || a > lens_info.raw_aperture_max);

        if (lens_set_rawaperture(a)) break;
    }
}


void
kelvin_toggle( void* priv, int sign )
{
    //~ if (uniwb_is_active()) return;

    int k;
    switch (lens_info.wb_mode)
    {
        case WB_SUNNY: k = 5200; break;
        case WB_SHADE: k = 7000; break;
        case WB_CLOUDY: k = 6000; break;
        case WB_TUNGSTEN: k = 3200; break;
        case WB_FLUORESCENT: k = 4000; break;
        case WB_FLASH: k = 6500; break; // maybe?
        default: k = lens_info.kelvin;
    }
    k = (k/KELVIN_STEP) * KELVIN_STEP;
    if (priv == (void*)-1) // no wrap around
        k = COERCE(k + sign * KELVIN_STEP, KELVIN_MIN, KELVIN_MAX);
    else // allow wrap around
        k = KELVIN_MIN + mod(k - KELVIN_MIN + sign * KELVIN_STEP, KELVIN_MAX - KELVIN_MIN + KELVIN_STEP);
    
    lens_set_kelvin(k);
}

PROP_INT( PROP_WB_KELVIN_PH, wb_kelvin_ph );

static void 
kelvin_display( void * priv, int x, int y, int selected )
{
    if (lens_info.wb_mode == WB_KELVIN)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "WhiteBalance: %dK%s   ",
            lens_info.kelvin,
            lens_info.kelvin == wb_kelvin_ph ? "" : "*"
        );
        menu_draw_icon(x, y, MNI_PERCENT, (lens_info.kelvin - KELVIN_MIN) * 100 / (KELVIN_MAX - KELVIN_MIN));
    }
/*    else if (lens_info.wb_mode == WB_CUSTOM && !uniwb_is_active())
    {
        int mul_R = 1000 * 1024 / lens_info.WBGain_R;
        int mul_G = 1000 * 1024 / lens_info.WBGain_G;
        int mul_B = 1000 * 1024 / lens_info.WBGain_B;

        mul_R = (mul_R + 5) / 10;
        mul_G = (mul_G + 5) / 10;
        mul_B = (mul_B + 5) / 10;

        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "WhiteBalance: %d.%02d %d.%02d %d.%02d",
            mul_R/100, mul_R%100,
            mul_G/100, mul_G%100,
            mul_B/100, mul_B%100
        );
        menu_draw_icon(x, y, MNI_NAMED_COLOR, (intptr_t) "RGB");
    } */
    else
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "WhiteBalance: %s",
            //~ (uniwb_is_active()      ? "UniWB   " : 
            (lens_info.wb_mode == 0 ? "Auto    " : 
            (lens_info.wb_mode == 1 ? "Sunny   " :
            (lens_info.wb_mode == 2 ? "Cloudy  " : 
            (lens_info.wb_mode == 3 ? "Tungsten" : 
            (lens_info.wb_mode == 4 ? "Fluor.  " : 
            (lens_info.wb_mode == 5 ? "Flash   " : 
            (lens_info.wb_mode == 6 ? "Custom  " : 
            (lens_info.wb_mode == 8 ? "Shade   " :
             "unknown"))))))))
        );
        menu_draw_icon(x, y, MNI_AUTO, 0);
    }
    //~ bmp_printf(FONT_MED, x + 550, y+5, "[Q]=Auto");
}

static void 
kelvin_wbs_display( void * priv, int x, int y, int selected )
{
    kelvin_display(priv, x, y, selected);
    x += font_large.width * (lens_info.wb_mode == WB_CUSTOM ? 30 : 22);
    if (lens_info.wbs_gm)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "%s%d",
            lens_info.wbs_gm > 0 ? "G" : "M", ABS(lens_info.wbs_gm)
        );
        x += font_large.width * 2;
    }
    if (lens_info.wbs_ba)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "%s%d",
            lens_info.wbs_ba > 0 ? "A" : "B", ABS(lens_info.wbs_ba)
        );
    }
}

static int kelvin_auto_flag = 0;
static int wbs_gm_auto_flag = 0;
static void kelvin_auto()
{
    if (lv) kelvin_auto_flag = 1;
}

static void wbs_gm_auto()
{
    if (lv) wbs_gm_auto_flag = 1;
}

void kelvin_n_gm_auto()
{
    if (lv)
    {
        kelvin_auto_flag = 1;
        wbs_gm_auto_flag = 1;
    }
}

static void
wb_custom_gain_display( void * priv, int x, int y, int selected )
{
    int p = (intptr_t) priv;
    int raw_value =
        p==1 ? lens_info.WBGain_R :
        p==2 ? lens_info.WBGain_G :
               lens_info.WBGain_B ;

    int multiplier = 1000 * 1024 / raw_value;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "%s multiplier: %d.%03d",
        p==1 ? "R" : p==2 ? "G" : "B",
        multiplier/1000, multiplier%1000
    );
    if (lens_info.wb_mode != WB_CUSTOM)
        menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Custom white balance is not active => not used.");
    //~ else if (uniwb_is_active()) 
        //~ menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "UniWB is active.");
}

static void
wb_custom_gain_toggle( void * priv, int delta )
{
    //~ if (uniwb_is_active()) return;
    int p = (intptr_t) priv;
    int deltaR = p == 1 ? -delta * 16 * MAX(1, lens_info.WBGain_R/1024) : 0;
    int deltaG = p == 2 ? -delta * 16 * MAX(1, lens_info.WBGain_G/1024) : 0;
    int deltaB = p == 3 ? -delta * 16 * MAX(1, lens_info.WBGain_B/1024) : 0;
    lens_set_custom_wb_gains(lens_info.WBGain_R + deltaR, lens_info.WBGain_G + deltaG, lens_info.WBGain_B + deltaB);
}

/*
static void uniwb_save_normal_wb_params()
{
    if (uniwb_is_active_check_lensinfo_only()) return;
    //~ info_led_blink(1,50,50);
    uniwb_old_wb_mode = lens_info.wb_mode;
    if (lens_info.WBGain_R != 1024 || lens_info.WBGain_G != 1024 || lens_info.WBGain_B != 1024)
    {
        uniwb_old_gain_R = lens_info.WBGain_R;
        uniwb_old_gain_G = lens_info.WBGain_G;
        uniwb_old_gain_B = lens_info.WBGain_B;
    }
}

static void uniwb_enable()
{
    uniwb_save_normal_wb_params();
    lens_set_custom_wb_gains(1024, 1024, 1024);
}

static void uniwb_disable()
{
    //~ info_led_blink(2,200,200);
    if (!uniwb_old_gain_R) return;
    lens_set_custom_wb_gains(uniwb_old_gain_R, uniwb_old_gain_G, uniwb_old_gain_B);
    prop_request_change(PROP_WB_MODE_LV, &uniwb_old_wb_mode, 4);
    prop_request_change(PROP_WB_MODE_PH, &uniwb_old_wb_mode, 4);
    msleep(100);
    if (!uniwb_is_active_check_lensinfo_only()) // successfully disabled
    {
        uniwb_old_gain_R = uniwb_old_gain_G = uniwb_old_gain_B = uniwb_old_wb_mode = 0;
    }
}

void uniwb_step()
{
    //~ if (!lv) return;
    
    int uniwb_desired_state = 0;
    switch (uniwb_mode)
    {
        case 0: // always off
            uniwb_desired_state = 0;
            break;
        case 1: // always on
            uniwb_desired_state = 1;
            break;
        case 2: // halfshutter
            uniwb_desired_state = get_halfshutter_pressed();
            break;
        case 3: // halfshutter not pressed
            uniwb_desired_state = !get_halfshutter_pressed();
            break;
    }

    if (!display_idle() && !gui_menu_shown())
    {
        uniwb_save_normal_wb_params(); // maybe user is changing WB settings from Canon menu - save them as non-uniWB params
    }
    else if (uniwb_desired_state == 0) 
    {
        if (uniwb_old_gain_R) uniwb_disable();
    }
    else
    {
        if (!uniwb_is_active()) uniwb_enable();
    }
}
*/

static int crit_kelvin(int k)
{
    if (!lv) return 0;

    if (k > 0)
    {
        lens_set_kelvin(k * KELVIN_STEP);
        msleep(750);
    }

    int Y, U, V;
    get_spot_yuv(100, &Y, &U, &V);
    //~ BMP_LOCK( draw_ml_bottombar(0,0); )

    int R = Y + 1437 * V / 1024;
    //~ int G = Y -  352 * U / 1024 - 731 * V / 1024;
    int B = Y + 1812 * U / 1024;

    return B - R;
}

static int crit_wbs_gm(int k)
{
    if (!lv) return 0;

    k = COERCE(k, -10, 10);
    lens_set_wbs_gm(k);
    msleep(750);

    int Y, U, V;
    get_spot_yuv(100, &Y, &U, &V);

    int R = Y + 1437 * V / 1024;
    int G = Y -  352 * U / 1024 - 731 * V / 1024;
    int B = Y + 1812 * U / 1024;

    //~ BMP_LOCK( draw_ml_bottombar(0,0); )
    return (R+B)/2 - G;
}

static void kelvin_auto_run()
{
    if (ext_monitor_rca) { NotifyBox(2000, "Not working on SD monitors."); return; }
    
    menu_stop();
    int c0 = crit_kelvin(-1); // test current kelvin
    int i;
    if (c0 > 0) i = bin_search(lens_info.kelvin/KELVIN_STEP, KELVIN_MAX/KELVIN_STEP + 1, crit_kelvin);
    else i = bin_search(KELVIN_MIN/KELVIN_STEP, lens_info.kelvin/KELVIN_STEP + 1, crit_kelvin);
    lens_set_kelvin(i * KELVIN_STEP);
    redraw();
}

static void wbs_gm_auto_run()
{
    if (ext_monitor_rca) { NotifyBox(2000, "Not working on SD monitors."); return; }

    menu_stop();
    int c0 = crit_wbs_gm(100); // test current value
    int i;
    if (c0 > 0) i = bin_search(lens_info.wbs_gm, 10, crit_wbs_gm);
    else i = bin_search(-9, lens_info.wbs_gm + 1, crit_wbs_gm);
    lens_set_wbs_gm(i);
    redraw();
}

static void 
wbs_gm_display( void * priv, int x, int y, int selected )
{
        int gm = lens_info.wbs_gm;
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "WBShift G/M : %s%d", 
            gm > 0 ? "Green " : (gm < 0 ? "Magenta " : ""), 
            ABS(gm)
        );
        menu_draw_icon(x, y, MNI_PERCENT, (-lens_info.wbs_gm + 9) * 100 / 18);
    //~ bmp_printf(FONT_MED, x + 550, y+5, "[Q]=Auto");
}

static void
wbs_gm_toggle( void * priv, int sign )
{
    int gm = lens_info.wbs_gm;
    int newgm = mod((gm + 9 - sign), 19) - 9;
    newgm = newgm & 0xFF;
    prop_request_change(PROP_WBS_GM, &newgm, 4);
}


static void 
wbs_ba_display( void * priv, int x, int y, int selected )
{
        int ba = lens_info.wbs_ba;
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "WBShift B/A : %s%d", 
            ba > 0 ? "Amber " : (ba < 0 ? "Blue " : ""), 
            ABS(ba)
        );
        menu_draw_icon(x, y, MNI_PERCENT, (lens_info.wbs_ba + 9) * 100 / 18);
}

static void
wbs_ba_toggle( void * priv, int sign )
{
    int ba = lens_info.wbs_ba;
    int newba = mod((ba + 9 + sign), 19) - 9;
    newba = newba & 0xFF;
    prop_request_change(PROP_WBS_BA, &newba, 4);
}

static void
contrast_toggle( void * priv, int sign )
{
    int c = lens_get_contrast();
    if (c < -4 || c > 4) return;
    int newc = mod((c + 4 + sign), 9) - 4;
    lens_set_contrast(newc);
}


static void 
contrast_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Contrast    : %d ",
        lens_get_contrast()
    );
    menu_draw_icon(x, y, MNI_PERCENT, (lens_get_contrast() + 4) * 100 / 8);
}

static void
sharpness_toggle( void * priv, int sign )
{
    int c = lens_get_sharpness();
    if (c < 0 || c > 7) return;
    int newc = mod(c + sign, 8);
    lens_set_sharpness(newc);
}

static void 
sharpness_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Sharpness   : %d ",
        lens_get_sharpness()
    );
    menu_draw_icon(x, y, MNI_PERCENT, (lens_get_sharpness()) * 100 / 7);
}

static void
saturation_toggle( void * priv, int sign )
{
    int c = lens_get_saturation();
    if (c < -4 || c > 4) return;
    int newc = mod((c + 4 + sign), 9) - 4;
    lens_set_saturation(newc);
}

static void 
saturation_display( void * priv, int x, int y, int selected )
{
    int s = lens_get_saturation();
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        (s >= -4 && s <= 4) ? 
            "Saturation  : %d " :
            "Saturation  : N/A",
        s
    );
    if (s >= -4 && s <= 4) menu_draw_icon(x, y, MNI_PERCENT, (s + 4) * 100 / 8);
    else menu_draw_icon(x, y, MNI_WARNING, 0);
}

static void
color_tone_toggle( void * priv, int sign )
{
    int c = lens_get_color_tone();
    if (c < -4 || c > 4) return;
    int newc = mod((c + 4 + sign), 9) - 4;
    lens_set_color_tone(newc);
}

static void 
color_tone_display( void * priv, int x, int y, int selected )
{
    int s = lens_get_color_tone();
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        (s >= -4 && s <= 4) ? 
            "Color Tone  : %d " :
            "Color Tone  : N/A",
        s
    );
    if (s >= -4 && s <= 4) menu_draw_icon(x, y, MNI_PERCENT, (s + 4) * 100 / 8);
    else menu_draw_icon(x, y, MNI_WARNING, 0);
}


static CONFIG_INT("picstyle.rec.sub", picstyle_rec_sub, 1);
static CONFIG_INT("picstyle.rec", picstyle_rec, 0);
static int picstyle_before_rec = 0; // if you use a custom picstyle during REC, the old one will be saved here

static char user_picstyle_name_1[50] = "";
static char user_picstyle_name_2[50] = "";
static char user_picstyle_name_3[50] = "";
static char user_picstyle_shortname_1[10] = "";
static char user_picstyle_shortname_2[10] = "";
static char user_picstyle_shortname_3[10] = "";

static void copy_picstyle_name(char* fullname, char* shortname, char* name)
{
    snprintf(fullname, 50, "%s", name);
    // CineStyle => CineS
    // Flaat_10p => Fl10p
    // Flaat_2   => Flaa2
    // Flaat03   => Fla03
    
    int L = strlen(name);
    shortname[0] = name[0];
    shortname[1] = name[1];
    shortname[2] = name[2];
    shortname[3] = name[3];
    shortname[4] = name[4];
    shortname[5] = '\0';
    
    if (isdigit(name[L-3]))
        shortname[2] = name[L-3];
    if (isdigit(name[L-3]) || isdigit(name[L-2]))
        shortname[3] = name[L-2];
    if (isdigit(name[L-3]) || isdigit(name[L-2]) || isdigit(name[L-1]))
        shortname[4] = name[L-1];
}

PROP_HANDLER(PROP_PC_FLAVOR1_PARAM)
{
    copy_picstyle_name(user_picstyle_name_1, user_picstyle_shortname_1, (char*) buf + 4);
}
PROP_HANDLER(PROP_PC_FLAVOR2_PARAM)
{
    copy_picstyle_name(user_picstyle_name_2, user_picstyle_shortname_2, (char*) buf + 4);
}
PROP_HANDLER(PROP_PC_FLAVOR3_PARAM)
{
    copy_picstyle_name(user_picstyle_name_3, user_picstyle_shortname_3, (char*) buf + 4);
}

static PROP_INT(PROP_PICSTYLE_OF_USERDEF1, picstyle_of_user1);
static PROP_INT(PROP_PICSTYLE_OF_USERDEF2, picstyle_of_user2);
static PROP_INT(PROP_PICSTYLE_OF_USERDEF3, picstyle_of_user3);


const char* get_picstyle_name(int raw_picstyle)
{
    return
        raw_picstyle == 0x81 ? "Standard" : 
        raw_picstyle == 0x82 ? "Portrait" :
        raw_picstyle == 0x83 ? "Landscape" :
        raw_picstyle == 0x84 ? "Neutral" :
        raw_picstyle == 0x85 ? "Faithful" :
        raw_picstyle == 0x86 ? "Monochrom" :
        raw_picstyle == 0x87 ? "Auto" :
        raw_picstyle == 0x21 ? (picstyle_of_user1 < 0x80 ? user_picstyle_name_1 : "UserDef1") :
        raw_picstyle == 0x22 ? (picstyle_of_user2 < 0x80 ? user_picstyle_name_2 : "UserDef2") :
        raw_picstyle == 0x23 ? (picstyle_of_user3 < 0x80 ? user_picstyle_name_3 : "UserDef3") : 
                                "Unknown";
}

const char* get_picstyle_shortname(int raw_picstyle)
{
    return
        raw_picstyle == 0x81 ? "Std." : 
        raw_picstyle == 0x82 ? "Port." :
        raw_picstyle == 0x83 ? "Land." :
        raw_picstyle == 0x84 ? "Neut." :
        raw_picstyle == 0x85 ? "Fait." :
        raw_picstyle == 0x86 ? "Mono." :
        raw_picstyle == 0x87 ? "Auto" :
        raw_picstyle == 0x21 ? (picstyle_of_user1 < 0x80 ? user_picstyle_shortname_1 : "User1") :
        raw_picstyle == 0x22 ? (picstyle_of_user2 < 0x80 ? user_picstyle_shortname_2 : "User2") :
        raw_picstyle == 0x23 ? (picstyle_of_user3 < 0x80 ? user_picstyle_shortname_3 : "User3") : 
                            "Unk.";
}
static void 
picstyle_display( void * priv, int x, int y, int selected )
{
    int i = picstyle_rec && recording ? picstyle_before_rec : (int)lens_info.picstyle;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "PictureStyle: %s%s(%d,%d,%d,%d)",
        get_picstyle_name(get_prop_picstyle_from_index(i)),
        picstyle_before_rec ? "*" : " ",
        lens_get_from_other_picstyle_sharpness(i),
        lens_get_from_other_picstyle_contrast(i),
        ABS(lens_get_from_other_picstyle_saturation(i)) < 10 ? lens_get_from_other_picstyle_saturation(i) : 0,
        ABS(lens_get_from_other_picstyle_color_tone(i)) < 10 ? lens_get_from_other_picstyle_color_tone(i) : 0
    );
    menu_draw_icon(x, y, MNI_ON, 0);
}

static void 
picstyle_display_submenu( void * priv, int x, int y, int selected )
{
    int p = get_prop_picstyle_from_index(lens_info.picstyle);
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "PictureStyle: %s%s",
        get_picstyle_name(p),
        picstyle_before_rec ? " (REC)" : ""
    );
    menu_draw_icon(x, y, MNI_ON, 0);
}

static void
picstyle_toggle(void* priv, int sign )
{
    if (recording) return;
    int p = lens_info.picstyle;
    p = mod(p + sign - 1, NUM_PICSTYLES) + 1;
    if (p)
    {
        p = get_prop_picstyle_from_index(p);
        prop_request_change(PROP_PICTURE_STYLE, &p, 4);
    }
}

static void 
picstyle_rec_display( void * priv, int x, int y, int selected )
{
    if (!picstyle_rec)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "REC PicStyle: Don't change"
        );
    }
    else
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "REC PicStyle: %s (%d,%d,%d,%d)",
            get_picstyle_name(get_prop_picstyle_from_index(picstyle_rec)),
            lens_get_from_other_picstyle_sharpness(picstyle_rec),
            lens_get_from_other_picstyle_contrast(picstyle_rec),
            ABS(lens_get_from_other_picstyle_saturation(picstyle_rec)) < 10 ? lens_get_from_other_picstyle_saturation(picstyle_rec) : 0,
            ABS(lens_get_from_other_picstyle_color_tone(picstyle_rec)) < 10 ? lens_get_from_other_picstyle_color_tone(picstyle_rec) : 0
        );
    }
}

static void 
picstyle_rec_sub_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "REC PicStyle: %s\n"
        "              (%d,%d,%d,%d)",
        get_picstyle_name(get_prop_picstyle_from_index(picstyle_rec_sub)),
        lens_get_from_other_picstyle_sharpness(picstyle_rec_sub),
        lens_get_from_other_picstyle_contrast(picstyle_rec_sub),
        ABS(lens_get_from_other_picstyle_saturation(picstyle_rec_sub)) < 10 ? lens_get_from_other_picstyle_saturation(picstyle_rec_sub) : 0,
        ABS(lens_get_from_other_picstyle_color_tone(picstyle_rec_sub)) < 10 ? lens_get_from_other_picstyle_color_tone(picstyle_rec_sub) : 0
    );
}

static void
picstyle_rec_toggle( void * priv, int delta )
{
    if (recording) return;
    if (picstyle_rec) picstyle_rec = 0;
    else picstyle_rec = picstyle_rec_sub;
}

static void
picstyle_rec_sub_toggle( void * priv, int delta )
{
    if (recording) return;
    picstyle_rec_sub = mod(picstyle_rec_sub + delta - 1, NUM_PICSTYLES) + 1;
    if (picstyle_rec) picstyle_rec = picstyle_rec_sub;
}
static void redraw_after_task(int msec)
{
    msleep(msec);
    redraw();
}

void redraw_after(int msec)
{
    task_create("redraw", 0x1d, 0, redraw_after_task, (void*)msec);
}

static void rec_picstyle_change(int rec)
{
    static int prev = -1;

    if (picstyle_rec)
    {
        if (prev == 0 && rec) // will start recording
        {
            picstyle_before_rec = lens_info.picstyle;
            int p = get_prop_picstyle_from_index(picstyle_rec);
            if (p)
            {
                NotifyBox(2000, "Picture Style : %s", get_picstyle_name(p));
                prop_request_change(PROP_PICTURE_STYLE, &p, 4);
            }
        }
        else if (prev == 2 && rec == 0) // recording => will stop
        {
            int p = get_prop_picstyle_from_index(picstyle_before_rec);
            if (p)
            {
                NotifyBox(2000, "Picture Style : %s", get_picstyle_name(p));
                prop_request_change(PROP_PICTURE_STYLE, &p, 4);
            }
            picstyle_before_rec = 0;
        }
    }
    prev = rec;
}

#ifdef CONFIG_50D
PROP_HANDLER(PROP_SHOOTING_TYPE)
{
    int rec = (shooting_type == 4 ? 2 : 0);
    rec_picstyle_change(rec);
    shutter_btn_rec_do(rec);
    rec_notify_trigger(rec);
}
void mvr_rec_start_shoot(){}
#else
void mvr_rec_start_shoot(int rec)
{
    rec_notify_trigger(rec);
    rec_picstyle_change(rec);
}
#endif


PROP_INT(PROP_STROBO_AECOMP, flash_ae);

static void
flash_ae_toggle(void* priv, int sign )
{
    int ae = (int8_t)flash_ae;
    int newae = ae + sign * (ABS(ae + sign) <= 24 ? 4 : 8);
    if (newae > FLASH_MAX_EV * 8) newae = FLASH_MIN_EV * 8;
    if (newae < FLASH_MIN_EV * 8) newae = FLASH_MAX_EV * 8;
    ae &= 0xFF;
    prop_request_change(PROP_STROBO_AECOMP, &newae, 4);
}

static void 
flash_ae_display( void * priv, int x, int y, int selected )
{
    int ae_ev = ((int8_t)flash_ae) * 10 / 8;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Flash AEcomp: %s%d.%d EV",
        ae_ev < 0 ? "-" : "",
        ABS(ae_ev) / 10, 
        ABS(ae_ev % 10)
    );
    menu_draw_icon(x, y, MNI_PERCENT, (ae_ev + 80) * 100 / (24+80));
}

// 0 = off, 1 = alo, 2 = htp
static int get_ladj()
{
    int alo = get_alo();
    if (get_htp()) return 4;
    if (alo == ALO_LOW) return 1;
    if (alo == ALO_STD) return 2;
    if (alo == ALO_HIGH) return 3;
    return 0;
}

/*
#if defined(CONFIG_500D) || defined(CONFIG_5D2) || defined(CONFIG_50D)
static void
alo_toggle( void * priv )
{
    int alo = get_alo();
    switch (alo)
    {
        case ALO_OFF:
            set_alo(ALO_STD);
            break;
        case ALO_STD:
            set_alo(ALO_LOW);
            break;
        case ALO_LOW:
            set_alo(ALO_HIGH);
            break;
        case ALO_HIGH:
            set_alo(ALO_OFF);
            break;
    }
}

static void
htp_toggle( void * priv )
{
    int htp = get_htp();
    if (htp)
        set_htp(0);
    else
        set_htp(1);
}

#endif

static void
ladj_toggle(void* priv, int sign )
{
    int ladj = get_ladj();
    ladj = mod(ladj + sign, 5);
    if (ladj == 0)
    {
        set_htp(0);
        set_alo(ALO_OFF);
    }
    else if (ladj == 1)
    {
        set_htp(0);
        set_alo(ALO_LOW);
    }
    else if (ladj == 2)
    {
        set_htp(0);
        set_alo(ALO_STD);
    }
    else if (ladj == 3)
    {
        set_htp(0);
        set_alo(ALO_HIGH);
    }
    else
    {
        set_htp(1); // this disables ALO
    }
}

#if defined(CONFIG_500D) || defined(CONFIG_5D2) || defined(CONFIG_50D)
static void 
ladj_display( void * priv, int x, int y, int selected )
{
    int htp = get_htp();
    int alo = get_alo();
    bmp_printf(
               selected ? MENU_FONT_SEL : MENU_FONT,
               x, y,
               "HTP / ALO   : %s/%s",
               (htp ? "ON" : "OFF"),
               (alo == ALO_STD ? "Standard" :
                alo == ALO_LOW ? "Low" :
                alo == ALO_HIGH ? "Strong" :
                alo == ALO_OFF ? "OFF" : "err")
               );
    menu_draw_icon(x, y, MNI_BOOL_GDR_EXPSIM(htp || (alo != ALO_OFF)));
}
#else
static void 
ladj_display( void * priv, int x, int y, int selected )
{
    int htp = get_htp();
    int alo = get_alo();
    bmp_printf(
               selected ? MENU_FONT_SEL : MENU_FONT,
               x, y,
               "HTP / ALO   : %s",
               (htp ? "HTP" :
                (alo == ALO_STD ? "ALO std" :
                 (alo == ALO_LOW ? "ALO low" : 
                  (alo == ALO_HIGH ? "ALO strong " :
                   (alo == ALO_OFF ? "OFF" : "err")))))
               );
    menu_draw_icon(x, y, alo != ALO_OFF ? MNI_ON : htp ? MNI_AUTO : MNI_OFF, 0);
}
#endif
*/

static void
htp_toggle( void * priv )
{
    int htp = get_htp();
    if (htp)
        set_htp(0);
    else
        set_htp(1);
}

static void 
htp_display( void * priv, int x, int y, int selected )
{
    int htp = get_htp();
    //int alo = get_alo();
    bmp_printf(
               selected ? MENU_FONT_SEL : MENU_FONT,
               x, y,
               "Highlight Tone P.: %s",
               htp ? "ON" : "OFF"
               );
    menu_draw_icon(x, y, MNI_BOOL(htp), 0);
}


static void 
zoom_auto_exposure_print( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Auto exposure on Zoom : %s",
        zoom_auto_exposure ? "ON" : "OFF"
    );
    #ifndef CONFIG_5D2
    if (zoom_auto_exposure && is_movie_mode())
        menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Only works in photo mode.");
    #endif
}

static void zoom_x5_x10_toggle(void* priv, int delta)
{
    *(int*)priv = ! *(int*)priv;
    
    if (zoom_disable_x5 && zoom_disable_x10) // can't disable both at the same time
    {
        if (priv == &zoom_disable_x5) zoom_disable_x10 = 0;
        else zoom_disable_x5 = 0;
    }
}

static void zoom_lv_face_step()
{
#ifdef AFFRAME_PROP_LEN
    if (!lv) return;
    if (recording) return;
    if (face_zoom_request && lv_dispsize == 1 && !recording)
    {
        if (lvaf_mode == 2 && wait_for_lv_err_msg(200)) // zoom request in face detect mode; temporary switch to live focus mode
        {
            int afmode = 1;
            int afx = afframe[2];
            int afy = afframe[3];
            prop_request_change(PROP_LVAF_MODE, &afmode, 4);
            msleep(100);
            afframe[2] = afx;
            afframe[3] = afy;
            prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);
            msleep(1);
            set_lv_zoom(5);
            msleep(1);
        }
        else if (lvaf_mode == 1) // back from temporary live focus mode
        {
            int afmode = 2;
            prop_request_change(PROP_LVAF_MODE, &afmode, 4);
            msleep(100);
            face_zoom_request = 0;
            //~ bmp_printf(FONT_LARGE, 10, 50, "       ");
        }
        else // cancel zoom request
        {
            msleep(100);
            face_zoom_request = 0;
            //~ bmp_printf(FONT_LARGE, 10, 50, "Zoom :(");
        }
    }
    
    if ((zoom_halfshutter == 1 && is_manual_focus()) || (zoom_halfshutter == 2))
    {
        int hs = get_halfshutter_pressed();
        if (hs && lv_dispsize == 1)
        {
            zoom_was_triggered_by_halfshutter = 1;
            int zoom = zoom_disable_x5 ? 10 : 5;
            set_lv_zoom(zoom);
            msleep(100);
        }
        if (!hs && lv_dispsize > 1 && zoom_was_triggered_by_halfshutter)
        {
            zoom_was_triggered_by_halfshutter = 0;
            set_lv_zoom(1);
            msleep(100);
        }
    }
#endif
}

static int zoom_focus_ring_disable_time = 0;
static int zoom_focus_ring_flag = 0;
void zoom_focus_ring_trigger() // called from prop handler
{
    int zfr = ((zoom_focus_ring == 1 && is_manual_focus()) || (zoom_focus_ring == 2));
    if (!zfr) return;
    if (recording) return;
    if (lv_dispsize > 1) return;
    zoom_focus_ring_flag = 1;
}
void zoom_focus_ring_engage() // called from shoot_task
{
    int zfr = ((zoom_focus_ring == 1 && is_manual_focus()) || (zoom_focus_ring == 2));
    if (!zfr) return;
    if (recording) return;
    if (!DISPLAY_IS_ON) return;
    zoom_focus_ring_disable_time = ms100_clock + 4000;
    int zoom = zoom_disable_x5 ? 10 : 5;
    set_lv_zoom(zoom);
}
static void zoom_focus_ring_step()
{
    int zfr = ((zoom_focus_ring == 1 && is_manual_focus()) || (zoom_focus_ring == 2));
    if (!zfr) return;
    if (recording) return;
    if (!DISPLAY_IS_ON) return;
    if (zoom_focus_ring_disable_time && ms100_clock > zoom_focus_ring_disable_time && !get_halfshutter_pressed())
    {
        if (lv_dispsize > 1) set_lv_zoom(1);
        zoom_focus_ring_disable_time = 0;
    }
}
/*
int zoom_x5_x10_step()
{
    if (zoom_disable_x5 && lv_dispsize == 5)
    {
        set_lv_zoom(10);
        return 1;
    }
    if (zoom_disable_x10 && lv_dispsize == 10)
    {
        set_lv_zoom(1);
        return 1;
    }
    return 0;
}*/

int handle_zoom_x5_x10(struct event * event)
{
    if (!lv) return 1;
    if (recording) return 1;
    if (!zoom_disable_x5 && !zoom_disable_x10) return 1;
    
    if (event->param == BGMT_PRESS_ZOOMIN_MAYBE && liveview_display_idle() && !gui_menu_shown())
    {
        set_lv_zoom(lv_dispsize > 1 ? 1 : zoom_disable_x5 ? 10 : 5);
        return 0;
    }
    return 1;
}

static void 
zoom_sharpen_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Zoom SharpContrast++: %s",
        zoom_sharpen ? "ON" : "OFF"
    );
}

// called from some prop_handlers (shoot.c and zebra.c)
void zoom_sharpen_step()
{
    if (!zoom_sharpen) return;

    static int co = 100;
    static int sa = 100;
    static int sh = 100;
    
    if (zoom_sharpen && lv && lv_dispsize > 1 && (!HALFSHUTTER_PRESSED || zoom_was_triggered_by_halfshutter) && !gui_menu_shown() && !bulb_ramp_calibration_running) // bump contrast/sharpness
    {
        if (co == 100)
        {
            co = lens_get_contrast();
            sh = lens_get_sharpness();
            sa = lens_get_saturation();
            lens_set_contrast(4);
            lens_set_sharpness(7);
            lens_set_saturation(MAX(0, sa));
        }
    }
    else // restore contrast/sharpness
    {
        if (co < 100)
        {
            lens_set_contrast(co);
            lens_set_sharpness(sh);
            lens_set_saturation(sa);
            co = sa = sh = 100;
        }
    }
}

void restore_expsim_task(int es)
{
    for (int i = 0; i < 50; i++)
    {
        lens_wait_readytotakepic(64);
        set_expsim(es);
        msleep(300);
        if (expsim == es) return;
    }
    NotifyBox(5000, "Could not restore ExpSim :(");
    info_led_blink(5, 50, 50);
}
// to be called from the same places as zoom_sharpen_step
void zoom_auto_exposure_step()
{
    if (!zoom_auto_exposure) return;

    static int es = -1;
    // static int aem = -1;
    
    if (lv && lv_dispsize > 1 && (!HALFSHUTTER_PRESSED || zoom_was_triggered_by_halfshutter) && !gui_menu_shown() && !bulb_ramp_calibration_running)
    {
        // photo mode: disable ExpSim
        // movie mode 5D2: disable ExpSim
        // movie mode small cams: change PROP_AE_MODE_MOVIE
        if (is_movie_mode())
        {
            #ifdef CONFIG_5D2
            if (es == -1)
            {
                es = expsim;
                set_expsim(0);
            }
            /* #else // unstable
                #ifndef CONFIG_50D
                if (aem == -1)
                {
                    aem = ae_mode_movie;
                    int x = 0;
                    prop_request_change(PROP_AE_MODE_MOVIE, &x, 4);
                }
                #endif */
            #endif
        }
        else // photo mode
        {
            if (es == -1)
            {
                es = expsim;
                set_expsim(0);
            }
        }
    }
    else // restore things back
    {
        if (es >= 0)
        {
            // not sure why, but when taking a picture, expsim can't be restored;
            // workaround: create a task that retries a few times
            task_create("restore_expsim", 0x1a, 0, restore_expsim_task, (void*)es);
            es = -1;
        }
        /* if (aem >= 0)
        {
            prop_request_change(PROP_AE_MODE_MOVIE, &aem, 4);
            aem = -1;
        }*/
    }
}

static void 
hdr_display( void * priv, int x, int y, int selected )
{
    if (!hdr_enabled)
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "HDR Bracketing  : OFF"
        );
    }
    else
    {
        bmp_printf(
            selected ? MENU_FONT_SEL : MENU_FONT,
            x, y,
            "HDR Bracketing  : %Xx%d%sEV,%s%s%s",
            hdr_steps == 1 ? 10 : hdr_steps, // trick: when steps=1 (auto) it will display A :)
            hdr_stepsize / 8,
            ((hdr_stepsize/4) % 2) ? ".5" : "", 
            hdr_sequence == 0 ? "0--" : hdr_sequence == 1 ? "0-+" : "0++",
            hdr_delay ? ",2s" : "",
            hdr_iso == 1 ? ",ISO" : hdr_iso == 2 ? ",iso" : ""
        );
        
        if (aeb_setting)
        {
            #ifdef CONFIG_60D
            if (drive_mode == DRIVE_HISPEED_CONTINUOUS)
                menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Canon AEB settings will be used. Press shutter once.");
            #endif
            menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Turn off Canon bracketing (AEB)!");
        }
    }
}

#if !defined(CONFIG_50D) && !defined(CONFIG_5D3) && !defined(CONFIG_1100D)
void hdr_display_status(int fnt)
{
    if (HDR_ENABLED)
        bmp_printf(fnt, HDR_STATUS_POS_X , HDR_STATUS_POS_Y, 
            "HDR %Xx%d%sEV",
            hdr_steps == 1 ? 10 : hdr_steps, // trick: when steps=1 (auto) it will display A :)
            hdr_stepsize / 8,
            ((hdr_stepsize/4) % 2) ? ".5" : "");
}
#endif

// 0,4,8,12,16, 24, 32, 40
static void
hdr_stepsize_toggle( void * priv, int delta )
{
    int h = hdr_stepsize;
    delta *= (h+delta < 16 ? 4 : 8);
    h += delta;
    if (h > 40) h = 0;
    if (h < 0) h = 40;
    hdr_stepsize = h;
}

int is_bulb_mode()
{
    //~ bmp_printf(FONT_LARGE, 0, 0, "%d %d %d %d ", bulb_ramping_enabled, intervalometer_running, shooting_mode, lens_info.raw_shutter);
    //~ msleep(0); // what the duck?!
    if (BULB_EXPOSURE_CONTROL_ACTIVE) return 1; // this will force bulb mode when needed
    if (shooting_mode == SHOOTMODE_BULB) return 1;
    if (shooting_mode != SHOOTMODE_M) return 0;
    if (lens_info.raw_shutter != 0xC) return 0;
    return 1;
}

void ensure_bulb_mode()
{
    lens_wait_readytotakepic(64);
    #if defined(CONFIG_60D) || defined(CONFIG_5D2)
    int a = lens_info.raw_aperture;
    set_shooting_mode(SHOOTMODE_BULB);
    if (expsim == 2) set_expsim(1);
    lens_set_rawaperture(a);
    #else
    if (shooting_mode != SHOOTMODE_M)
        set_shooting_mode(SHOOTMODE_M);
    int shutter = 12; // huh?!
    prop_request_change( PROP_SHUTTER, &shutter, 4 );
    prop_request_change( PROP_SHUTTER_ALSO, &shutter, 4 );
    #endif
}

// goes to Bulb mode and takes a pic with the specified duration (ms)
void
bulb_take_pic(int duration)
{
    //~ NotifyBox(2000,  "Bulb: %d ", duration); msleep(2000);
    duration = MAX(duration, BULB_MIN_EXPOSURE) + BULB_EXPOSURE_CORRECTION;
    int s0r = lens_info.raw_shutter; // save settings (for restoring them back)
    int m0r = shooting_mode;
    ensure_bulb_mode();
    
    //~ #ifdef CONFIG_600D
    assign_af_button_to_star_button();
    //~ #endif
    
    msleep(100);
    //~ if (beep_enabled) beep();
    
    int d0 = drive_mode;
    lens_set_drivemode(DRIVE_SINGLE);
    //~ NotifyBox(3000, "BulbStart (%d)", duration); msleep(1000);
    mlu_lock_mirror_if_needed();
    //~ SW1(1,50);
    //~ SW2(1,0);

    //~ SW1(1,100);
    //~ SW1(0,100);
    SW1(1,100);
    wait_till_next_second();
    
    //~ int x = 0;
    //~ prop_request_change(PROP_REMOTE_BULB_RELEASE_START, &x, 4);
    SW2(1,0);
    
    //~ msleep(duration);
    int d = duration/1000;
    for (int i = 0; i < d; i++)
    {
        // for 550D and other cameras that may keep the display on during bulb exposures -> always turn it off
        if (DISPLAY_IS_ON) fake_simple_button(BGMT_INFO);
        
        // turn off the LED - no light pollution, please :)
        // but blink it quickly every 10 seconds to have some feedback
        if (i % 10 == 1) { _card_led_on(); msleep(10); _card_led_off(); }

        // blink twice every minute
        if (i % 60 == 1) { msleep(200); _card_led_on(); msleep(10); _card_led_off(); }
        
        // count one second (sync'ed to RTC)
        wait_till_next_second();
        
        // exposure was canceled earlier by user
        if (lens_info.job_state == 0) break;
    }
    msleep(duration % 1000);
    //~ prop_request_change(PROP_REMOTE_BULB_RELEASE_END, &x, 4);
    //~ NotifyBox(3000, "BulbEnd");
    SW2(0,0);
    SW1(0,0);
    //~ msleep(100);
    //~ #ifdef CONFIG_600D
    lens_wait_readytotakepic(64);
    //~ if (beep_enabled) beep();
    restore_af_button_assignment();
    //~ #endif
    //~ get_out_of_play_mode(1000);
    lens_set_drivemode(d0);
    prop_request_change( PROP_SHUTTER, &s0r, 4 );
    prop_request_change( PROP_SHUTTER_ALSO, &s0r, 4);
    set_shooting_mode(m0r);
    msleep(200);
}

static void bulb_toggle(void* priv, int delta)
{
    bulb_duration_index = mod(bulb_duration_index + delta - 1, COUNT(timer_values) - 1) + 1;
    bulb_shutter_valuef = (float)timer_values[bulb_duration_index];
}

static void
bulb_display( void * priv, int x, int y, int selected )
{
    int d = BULB_SHUTTER_VALUE_S;
    if (!bulb_duration_index) d = 0;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Bulb Timer      : %s",
        bulb_timer ? format_time_hours_minutes_seconds(d) : "OFF"
    );
    menu_draw_icon(x, y, !bulb_timer ? MNI_OFF : is_bulb_mode() ? MNI_PERCENT : MNI_WARNING, is_bulb_mode() ? (intptr_t)( bulb_duration_index * 100 / COUNT(timer_values)) : (intptr_t) "Bulb timer only works in BULB mode");
    if (selected && is_bulb_mode() && intervalometer_running) timelapse_calc_display(&interval_timer_index, x - font_large.width*2, y + font_large.height * 9, selected);
}

static void
bulb_display_submenu( void * priv, int x, int y, int selected )
{
    int d = BULB_SHUTTER_VALUE_S;
    if (!bulb_duration_index) d = 0;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Bulb Exposure : %s",
        format_time_hours_minutes_seconds(d)
    );
    menu_draw_icon(x, y, MNI_PERCENT, (intptr_t)( bulb_duration_index * 100 / COUNT(timer_values)));
}

// like expsim_toggle
static void
    mlu_toggle( void * priv, int delta )
{
    // off, on, auto
    if (!mlu_auto && !get_mlu()) // off->on
    {
        set_mlu(1);
    }
    else if (!mlu_auto && get_mlu()) // on->auto
    {
        set_mlu(0);
        mlu_auto = 1;
    }
    else // auto->off
    {
        mlu_auto = 0;
        set_mlu(0);
    }
}

static void
mlu_display( void * priv, int x, int y, int selected )
{
    //~ int d = timer_values[bulb_duration_index];
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Mirror Lockup   : %s",
        #if defined(CONFIG_550D) || defined(CONFIG_500D) || defined(CONFIG_5D2)
        mlu_auto ? "Timer+LCDremote"
        #else
        mlu_auto ? "Self-timer only"
        #endif
        : get_mlu() ? "ON" : "OFF"
    );
    if (get_mlu() && lv) menu_draw_icon(x, y, MNI_WARNING, (intptr_t) "Mirror Lockup does not work in LiveView");
    else menu_draw_icon(x, y, mlu_auto ? MNI_AUTO : MNI_BOOL(get_mlu()), 0);
}

#if 0
static void
picq_display( void * priv, int x, int y, int selected )
{
    int raw = pic_quality & 0x60000;
    int rawsize = pic_quality & 0xF;
    int jpegtype = pic_quality >> 24;
    int jpegsize = (pic_quality >> 8) & 0xF;
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Picture Quality : %s%s%s%s%s",
        rawsize == 1 ? "M" : rawsize == 2 ? "S" : "",
        raw ? "RAW" : "",
        jpegtype != 4 && raw ? "+" : "",
        jpegtype == 4 ? "" : jpegsize == 0 ? "Large" : jpegsize == 1 ? "Med" : "Small",
        jpegtype == 2 ? "Coarse" : jpegtype == 3 ? "Fine" : ""
    );
    menu_draw_icon(x, y, MNI_ON, 0);
}

static void picq_toggle_rawsize(void* priv)
{
    int p = pic_quality;
    int r = p & 0xF;
    r = mod(r+1, 3);
    int newp = (p & 0xfffffff0) | r;
    set_pic_quality(newp);
}

static void picq_toggle_raw_on_off(void* priv)
{
    int raw = pic_quality & 0x60000;
    int newp;
    if (raw)
    {
        int jt = (pic_quality >> 24) & 0xF;
        if (jt == 4) newp = PICQ_LARGE_FINE;
        else newp = (pic_quality & 0xf0f1fff0) | (jt << 24);
    }
    else newp = pic_quality | 0x60000;
    console_printf("%x\n", newp);
    set_pic_quality(newp);
}

static void picq_toggle_raw(void* priv)
{
    int raw = pic_quality & 0x60000;
    int rsize = pic_quality & 0xF;
    if (raw && rsize < 2) picq_toggle_rawsize(0);
    else picq_toggle_raw_on_off(0);
}

static void picq_toggle_jpegsize(void* priv)
{
    int js = (pic_quality >> 8) & 0xF;
    js = mod(js+1, 3);
    int newp = (pic_quality & 0xfffff0ff) | (js << 8);
    set_pic_quality(newp);
}

static void picq_toggle_jpegtype(void* priv)
{
    int jt = (pic_quality >> 24) & 0xF;
    jt = mod(jt-1, 3) + 2;
    int newp = (pic_quality & 0xf0ffffff) | (jt << 24);
    int raw = pic_quality & 0x60000;
    int rawsize = pic_quality & 0xF;
    if (jt == 4) newp = PICQ_RAW + rawsize;
    set_pic_quality(newp);
}

static int picq_next(int p)
{
    switch(pic_quality)
    {
        case PICQ_RAW: return PICQ_MRAW;
        case PICQ_MRAW: return PICQ_SRAW;
        case PICQ_SRAW: return PICQ_RAW_JPG_LARGE_FINE;
        case PICQ_RAW_JPG_LARGE_FINE: return PICQ_MRAW_JPG_LARGE_FINE;
        case PICQ_MRAW_JPG_LARGE_FINE: return PICQ_SRAW_JPG_LARGE_FINE;
        case PICQ_SRAW_JPG_LARGE_FINE: return PICQ_SRAW_JPG_MED_FINE;
        case PICQ_SRAW_JPG_MED_FINE: return PICQ_SRAW_JPG_SMALL_FINE;
        case PICQ_SRAW_JPG_SMALL_FINE: return PICQ_LARGE_FINE;
        case PICQ_LARGE_FINE: return PICQ_MED_FINE;
        case PICQ_MED_FINE: return PICQ_SMALL_FINE;
    }
    return PICQ_RAW;
}

static void picq_toggle(void* priv)
{
    int newp = picq_next(pic_quality);
    set_pic_quality(newp);
}
#endif

static int bulb_ramping_adjust_iso_180_rule_without_changing_exposure(int intervalometer_delay)
{
    int raw_shutter_0 = shutter_ms_to_raw(BULB_SHUTTER_VALUE_MS);
    int raw_iso_0 = lens_info.raw_iso;
    
    int ideal_shutter_speed_ms = intervalometer_delay * 1000 / 2; // 180 degree rule => ideal value
    int ideal_shutter_speed_raw = shutter_ms_to_raw(ideal_shutter_speed_ms);

    int delta = 0;  // between 90 and 180 degrees => OK

    if (ideal_shutter_speed_raw > raw_shutter_0 + 4)
        delta = 8; // shutter too slow (more than 270 degrees -- ideal value) => boost ISO

    if (ideal_shutter_speed_raw < raw_shutter_0 - 4)
        delta = -8; // shutter too fast (less than 128 degrees) => lower ISO
    
    if (delta) // should we change something?
    {
        int max_auto_iso = auto_iso_range & 0xFF;
        int new_raw_iso = COERCE(lens_info.raw_iso + delta, get_htp() ? 78 : 72, max_auto_iso); // Allowed values: ISO 100 (or 200 with HTP) ... max auto ISO from Canon menu
        delta = new_raw_iso - raw_iso_0;
        if (delta == 0) return 0; // nothing to change
        float new_bulb_shutter = 
            delta ==  8 ? bulb_shutter_valuef / 2 :
            delta == -8 ? bulb_shutter_valuef * 2 :
            bulb_shutter_valuef;
        
        lens_set_rawiso(new_raw_iso); // try to set new iso
        msleep(100);
        if (lens_info.raw_iso == new_raw_iso) // new iso accepted
        {
            bulb_shutter_valuef = new_bulb_shutter;
            return 1;
        }
        // if we are here, either iso was refused
        // => restore old iso, just to be sure
        lens_set_rawiso(raw_iso_0); 
    }
    return 0; // nothing changed
}

static FILE* bramp_log_file = 0;
static int bramp_init_state = 0;
static int bramp_init_done = 0;
static int bramp_reference_level = 0;
static int bramp_measured_level = 0;
//~ int bramp_level_ev_ratio = 0;
static int bramp_hist_dirty = 0;
static int bramp_ev_reference_x1000 = 0;
static int bramp_prev_shot_was_bad = 1;
static float bramp_u1 = 0; // for the feedback controller: command at previous step
static int bramp_last_exposure_rounding_error_evx1000;

static int seconds_clock = 0;
int get_seconds_clock() { return seconds_clock; } 

static void
seconds_clock_task( void* unused )
{
    TASK_LOOP 
    {
        wait_till_next_second();
        seconds_clock++;

        if (BULB_EXPOSURE_CONTROL_ACTIVE && !gui_menu_shown())
            bulb_ramping_showinfo();

        if (intervalometer_running && lens_info.job_state == 0 && !gui_menu_shown() && !get_halfshutter_pressed())
            info_led_blink(1, 50, 0);
        
        #if defined(CONFIG_60D) || defined(CONFIG_5D2)
        RefreshBatteryLevel_1Hz();
        #endif
    }
}
TASK_CREATE( "seconds_clock_task", seconds_clock_task, 0, 0x19, 0x1000 );

static int measure_brightness_level(int initial_wait)
{
    msleep(initial_wait);
    if (bramp_hist_dirty)
    {
        struct vram_info * vram = get_yuv422_vram();
        hist_build(vram->vram, vram->width, vram->pitch);
        bramp_hist_dirty = 0;
    }
    int ans = hist_get_percentile_level(bramp_percentile);
    //~ get_out_of_play_mode(500);
    return ans;
}

static void bramp_change_percentile(int dir)
{
    ASSERT(PLAY_MODE);
    NotifyBoxHide();
    bramp_percentile = COERCE(bramp_percentile + dir * 5, 5, 95);
    
    int i;
    for (i = 0; i <= 20; i++)
    {
        bramp_reference_level = measure_brightness_level(0); // at bramp_percentile
        if (bramp_reference_level > 230) bramp_percentile = COERCE(bramp_percentile - 5, 5, 95);
        else if (bramp_reference_level < 25) bramp_percentile = COERCE(bramp_percentile + 5, 5, 95);
        else break;
    }
    if (i >= 20) { NotifyBox(1000, "Image not properly exposed"); return; }

    int level_8bit = bramp_reference_level;
    int level_8bit_plus = level_8bit + 5; //hist_get_percentile_level(bramp_percentile + 5) * 255 / 100;
    int level_8bit_minus = level_8bit - 5; //hist_get_percentile_level(bramp_percentile - 5) * 255 / 100;
    clrscr();
    highlight_luma_range(level_8bit_minus, level_8bit_plus, COLOR_BLUE, COLOR_WHITE);
    hist_highlight(level_8bit);
    bmp_printf(FONT_LARGE, 50, 400, 
        "Meter for %s\n"
        "(%2d%% luma at %dth percentile)",
        bramp_percentile < 40 ? "shadows" : bramp_percentile < 70 ? "midtones" : "highlights",
        bramp_reference_level*100/255, 0,
        bramp_percentile);
}

int handle_bulb_ramping_keys(struct event * event)
{
    if (intervalometer_running && bramp_init_state && PLAY_MODE)
    {
        switch (event->param)
        {
            case BGMT_PRESS_SET:
            {
                bramp_init_state = 0; // OK :)
                NotifyBox(1000, "OK");
                return 1;
            }
            case BGMT_WHEEL_LEFT:
            case BGMT_WHEEL_RIGHT:
            {
                int dir = event->param == BGMT_WHEEL_LEFT ? -1 : 1;
                bramp_change_percentile(dir);
                //~ NotifyBoxHide();
                return 0;
            }
        }
    }
    
    // test interpolation on luma-ev curve
    //~ for (int i = 0; i < 255; i += 5)
        //~ bramp_plot_luma_ev_point(i, COLOR_GREEN1);

    return 1;
}

static void flip_zoom()
{
    if (!lv) return;
    if (is_movie_mode())
    {
        if (recording) return;
        if (video_mode_crop) return;
    }
    
    // flip zoom mode back and forth to apply settings instantly
    int zoom0 = lv_dispsize;
    int zoom1 = zoom0 == 10 ? 5 : zoom0 == 5 ? 1 : 10;
    set_lv_zoom(zoom1);
    set_lv_zoom(zoom0);
}


static int bramp_measure_luma(int delay)
{
    ASSERT(lv);
    ASSERT(lv_dispsize > 1);
    ASSERT(expsim);
    ASSERT(shooting_mode == SHOOTMODE_M);
    //~ ASSERT(LVAE_DISP_GAIN); // display gain can also be zero, no problem
    
    msleep(delay);
    // we are in zoom mode, histogram not normally updated => we can reuse the buffer
    //~ struct vram_info * vram = get_yuv422_vram();
    //~ hist_build(vram->vram, vram->width, vram->pitch);
    //~ bramp_hist_dirty = 0;
    //~ return hist_get_percentile_level(50) * 255/100; // median => much more robust in cluttered scenes, but more sensitive to noise
    int Y,U,V;
    get_spot_yuv(200, &Y, &U, &V);
    return Y;
}

// still useful for bulb ramping
int bramp_zoom_toggle_needed = 0; // for 600D and some new lenses?!
static int bramp_set_display_gain_and_measure_luma(int gain)
{
    gain = COERCE(gain, 0, 65534);
    //~ bmp_printf(FONT_MED, 100, 100, "%d ", gain);
    //~ set_display_gain_equiv(gain);
    call("lvae_setdispgain", gain);
    if (lv_dispsize == 1) set_lv_zoom(5);
    if (bramp_zoom_toggle_needed)
    {
        flip_zoom();
        msleep(1000);
    }
    msleep(500);
    return bramp_measure_luma(0);
}

static int crit_dispgain_50(int gain)
{
    if (!lv) return 0;
    int Y = bramp_set_display_gain_and_measure_luma(gain);
    NotifyBox(1000, "Gain=%d => Luma=%d ", gain, Y);
    return 128 - Y;
}


static int bramp_luma_ev[11];

static void bramp_plot_luma_ev()
{
    for (int i = -5; i < 5; i++)
    {
        int luma1 = bramp_luma_ev[i+5];
        int luma2 = bramp_luma_ev[i+6];
        int x1 =  350 + i * 20;
        int x2 =  350 + (i+1) * 20;
        int y1 =  240 - (luma1-128)/2;
        int y2 =  240 - (luma2-128)/2;
        draw_line(x1, y1, x2, y2, COLOR_RED);
        draw_line(x1, y1+1, x2, y2+1, COLOR_RED);
        draw_line(x1, y1+2, x2, y2+2, COLOR_WHITE);
        draw_line(x1, y1-1, x2, y2-1, COLOR_WHITE);
    }
    int x1 =  350 - 5 * 20;
    int x2 =  350 + 5 * 20;
    int y1 =  240 - 128/2;
    int y2 =  240 + 128/2;
    bmp_draw_rect(COLOR_WHITE, x1, y1, x2-x1, y2-y1);
}

static int bramp_luma_to_ev_x100(int luma)
{
    int i;
    for (i = -5; i < 5; i++)
        if (luma <= bramp_luma_ev[i+5]) break;
    i = COERCE(i-1, -5, 4);
    // now, luma is between luma1 and luma2
    // EV correction is between i EV and (i+1) EV => linear approximation
    int luma1 = bramp_luma_ev[i+5];
    int luma2 = bramp_luma_ev[i+6];
    int k = (luma-luma1) * 1000 / (luma2-luma1);
    //~ return i * 100;
    int ev_x100 = ((1000-k) * i + k * (i+1))/10;
    //~ NotifyBox(1000, "%d,%d=>%d", luma, i, ev_x100);
    return COERCE(ev_x100, -500, 500);
}

static void bramp_plot_luma_ev_point(int luma, int color)
{
    luma = COERCE(luma, 0, 255);
    int ev = bramp_luma_to_ev_x100(luma);
    ev = COERCE(ev, -500, 500);
    int x = 350 + ev * 20 / 100;
    int y = 240 - (luma-128)/2;
    for (int r = 0; r < 5; r++)
    {
        draw_circle(x, y, r, color);
        draw_circle(x+1, y, r, color);
    }
    draw_circle(x, y, 6, COLOR_WHITE);
}

#define BRAMP_SHUTTER_0 56 // 1 second exposure => just for entering compensation
//~ static int bramp_temporary_exposure_compensation_ev_x100 = 0;

// bulb ramping calibration cache
static CONFIG_INT("bramp.calib.sig", bramp_calib_sig, 0);
static CONFIG_INT("bramp.calib.m5", bramp_calib_cache_m5, 0);
static CONFIG_INT("bramp.calib.m4", bramp_calib_cache_m4, 0);
static CONFIG_INT("bramp.calib.m3", bramp_calib_cache_m3, 0);
static CONFIG_INT("bramp.calib.m2", bramp_calib_cache_m2, 0);
static CONFIG_INT("bramp.calib.m1", bramp_calib_cache_m1, 0);
static CONFIG_INT("bramp.calib.0", bramp_calib_cache_0, 0);
static CONFIG_INT("bramp.calib.1", bramp_calib_cache_1, 0);
static CONFIG_INT("bramp.calib.2", bramp_calib_cache_2, 0);
static CONFIG_INT("bramp.calib.3", bramp_calib_cache_3, 0);
static CONFIG_INT("bramp.calib.4", bramp_calib_cache_4, 0);
static CONFIG_INT("bramp.calib.5", bramp_calib_cache_5, 0);

void bramp_cleanup()
{
    if (bramp_log_file)
    {
        FIO_CloseFile(bramp_log_file);
        bramp_log_file = 0;
    }
}

void bulb_ramping_init()
{
    if (bramp_init_done) return;
    if (BULB_EXPOSURE_CONTROL_ACTIVE) set_shooting_mode(SHOOTMODE_M);

    static char fn[50];
    for (int i = 0; i < 100; i++)
    {
        snprintf(fn, sizeof(fn), CARD_DRIVE "ML/LOGS/BRAMP%02d.LOG", i);
        unsigned size;
        if( FIO_GetFileSize( fn, &size ) != 0 ) break;
        if (size == 0) break;
    }
    bramp_log_file = FIO_CreateFileEx(fn);

    bulb_duration_index = 0; // disable bulb timer to avoid interference
    bulb_shutter_valuef = raw2shutterf(lens_info.raw_shutter);
    bramp_ev_reference_x1000 = 0;
    bramp_last_exposure_rounding_error_evx1000 = 0;
    //~ bramp_temporary_exposure_compensation_ev_x100 = 0;
    bramp_prev_shot_was_bad = 1; // force full correction at first step
    bramp_u1 = 0.0;
    
    if (!bramp_auto_exposure) 
    {
        bramp_init_done = 1;
        return;
    }

    // if calibration is cached, load it from config file
    int calib_sig = lens_info.picstyle * 123 + lens_get_contrast() + (get_htp() ? 17 : 23);
    if (calib_sig == (int)bramp_calib_sig)
    {
        bramp_luma_ev[0] = bramp_calib_cache_m5;
        bramp_luma_ev[1] = bramp_calib_cache_m4;
        bramp_luma_ev[2] = bramp_calib_cache_m3;
        bramp_luma_ev[3] = bramp_calib_cache_m2;
        bramp_luma_ev[4] = bramp_calib_cache_m1;
        bramp_luma_ev[5] = bramp_calib_cache_0;
        bramp_luma_ev[6] = bramp_calib_cache_1;
        bramp_luma_ev[7] = bramp_calib_cache_2;
        bramp_luma_ev[8] = bramp_calib_cache_3;
        bramp_luma_ev[9] = bramp_calib_cache_4;
        bramp_luma_ev[10] = bramp_calib_cache_5;

        my_fprintf(bramp_log_file, "Luma curve: cached: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", bramp_calib_cache_m5, bramp_calib_cache_m4, bramp_calib_cache_m3, bramp_calib_cache_m2, bramp_calib_cache_m1, bramp_calib_cache_0, bramp_calib_cache_1, bramp_calib_cache_2, bramp_calib_cache_3, bramp_calib_cache_4, bramp_calib_cache_5);

    }
    else // compute calibration from scratch
    {

        NotifyBox(100000, "Calibration...");
        bulb_ramp_calibration_running = 1;

        set_shooting_mode(SHOOTMODE_M);
        if (!lv) force_liveview();
        int e0 = expsim;
        int iso0 = lens_info.raw_iso;
        int s0 = lens_info.raw_shutter;
        set_expsim(1);

    calib_start:
        SW1(1,50); // reset power management timers
        SW1(0,50);
        set_lv_zoom(lv_dispsize == 10 ? 5 : 10);
        
        lens_set_rawiso(COERCE(iso0, 80, 120));

        NotifyBox(2000, "Testing display gain...");
        int Y;
        int Yn = bramp_set_display_gain_and_measure_luma(100);
        int Yp = bramp_set_display_gain_and_measure_luma(65535);
        bramp_zoom_toggle_needed = (ABS(Yn - Yp) < 10);
        if (bramp_zoom_toggle_needed)
        {
            Yn = bramp_set_display_gain_and_measure_luma(100);
            Yp = bramp_set_display_gain_and_measure_luma(65535);
        }
        bramp_set_display_gain_and_measure_luma(0);
        int ok = (ABS(Yn - Yp) > 10);
        if (!ok)
        {
            set_expsim(e0);
            NotifyBox(5000, "Cannot calibrate.        \n"
                            "Please report to ML devs."); msleep(5000);
            intervalometer_stop();
            goto end;
        }
        
        // first try to brighten the image
        while (bramp_measure_luma(500) < 128)
        {
            if (lens_info.raw_iso+8 <= 120) // 6400
            {
                NotifyBox(2000, "Too dark, increasing ISO...");
                lens_set_rawiso(lens_info.raw_iso + 8);
                continue;
            }
            else if (lens_info.raw_shutter-8 >= 20)
            {
                NotifyBox(2000, "Too dark, increasing exp.time...");
                lens_set_rawshutter(lens_info.raw_shutter - 8);
                continue;
            }
            else break;
        }
        
        // then try to darken 
        while (bramp_measure_luma(500) > 150)
        {
            if (lens_info.raw_iso-8 >= 80) // 200
            {
                NotifyBox(2000, "Too bright, decreasing ISO...");
                lens_set_rawiso(lens_info.raw_iso - 8);
                continue;
            }
            else if (lens_info.raw_shutter <= 152) // 1/4000
            {
                NotifyBox(2000, "Too bright, decreasing exp.time...");
                lens_set_rawshutter(lens_info.raw_shutter + 8);
                continue;
            }
            else break;
        }
        
        // at this point, the image should be roughly OK exposed
        // we can now play only with display gain
        
        
        int gain0 = bin_search(128, 2000, crit_dispgain_50);
        Y = bramp_set_display_gain_and_measure_luma(gain0);
        if (ABS(Y-128) > 2) 
        {
            NotifyBox(1000, "Scene %s, retrying...", 
                gain0 > 2450 ? "too dark" :
                gain0 < 150 ? "too bright" : 
                "not static"
            ); 
            
            goto calib_start;
        }
        
        for (int i = -5; i <= 5; i++)
        {
            Y = bramp_set_display_gain_and_measure_luma(gain0 * (1 << (i+10)) / 1024);
            NotifyBox(500, "%d EV => luma=%d  ", i, Y);
            if (i == 0) // here, luma should be 128
            {
                if (ABS(Y-128) > 2) {msleep(500); NotifyBox(1000, "Middle check failed, retrying..."); msleep(1000); goto calib_start;}
                else Y = 128;
            }
            int prev_Y = i > -5 ? bramp_luma_ev[i+5-1] : 0;
            if (Y < prev_Y-3) 
            {
                msleep(500); NotifyBox(1000, "Decreasing curve (%d->%d), retrying...", prev_Y, Y); msleep(1000); 
                goto calib_start;
            }
            bramp_luma_ev[i+5] = MAX(Y, prev_Y);
            bramp_plot_luma_ev();
            //~ set_display_gain(1<<i);
        }
        
        // final check
        Y = bramp_set_display_gain_and_measure_luma(gain0);
        msleep(500);
        if (ABS(Y-128) > 2) { msleep(500); NotifyBox(1000, "Final check failed (%d), retrying...", Y); msleep(1000); goto calib_start;}

        // calibration accepted :)

        bulb_ramp_calibration_running = 0;
        bramp_set_display_gain_and_measure_luma(0);
        set_expsim(e0);
        lens_set_rawiso(iso0);
        lens_set_rawshutter(s0);

        #ifdef CONFIG_500D
            fake_simple_button(BGMT_Q);
        #else
            fake_simple_button(BGMT_LV);
        #endif
        msleep(1000);

        // save calibration results in config file
        bramp_calib_sig = calib_sig;
        bramp_calib_cache_m5 = bramp_luma_ev[0];
        bramp_calib_cache_m4 = bramp_luma_ev[1];
        bramp_calib_cache_m3 = bramp_luma_ev[2];
        bramp_calib_cache_m2 = bramp_luma_ev[3];
        bramp_calib_cache_m1 = bramp_luma_ev[4];
        bramp_calib_cache_0  = bramp_luma_ev[5];
        bramp_calib_cache_1  = bramp_luma_ev[6];
        bramp_calib_cache_2  = bramp_luma_ev[7];
        bramp_calib_cache_3  = bramp_luma_ev[8];
        bramp_calib_cache_4  = bramp_luma_ev[9];
        bramp_calib_cache_5  = bramp_luma_ev[10];
        my_fprintf(bramp_log_file, "Luma curve: %d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", bramp_calib_cache_m5, bramp_calib_cache_m4, bramp_calib_cache_m3, bramp_calib_cache_m2, bramp_calib_cache_m1, bramp_calib_cache_0, bramp_calib_cache_1, bramp_calib_cache_2, bramp_calib_cache_3, bramp_calib_cache_4, bramp_calib_cache_5);

    }

    fake_simple_button(BGMT_PLAY);
    msleep(1000);
    
    if (!PLAY_MODE) { NotifyBox(1000, "BRamp: could not go to PLAY mode"); msleep(2000); intervalometer_stop(); goto end; }
    
    //~ bramp_level_ev_ratio = 0;
    bramp_measured_level = 0;
    
    bramp_init_state = 1;
    
    msleep(200);
    bramp_hist_dirty = 1;
    bramp_change_percentile(0); // show current selection;

    NotifyBox(100000, "Choose a well-exposed photo  \n"
                      "and tonal range to meter for.\n"
                      "Keys: arrows, main dial, SET.");
    
    void* play_buf = get_yuv422_vram()->vram;
    while (PLAY_MODE && bramp_init_state == 1)
    {
        if (get_yuv422_vram()->vram != play_buf) // another image selected
        {
            bramp_hist_dirty = 1;
            bramp_change_percentile(0); // update current selection
            play_buf = get_yuv422_vram()->vram;
        }
        msleep(100);
    }
    if (!PLAY_MODE) { intervalometer_stop(); goto end; }
    
    bramp_init_done = 1; // OK :)

    set_shooting_mode(SHOOTMODE_M);
    lens_set_rawshutter(BRAMP_SHUTTER_0);
    if (lv) fake_simple_button(BGMT_LV);
    msleep(1000);
    set_shooting_mode(SHOOTMODE_M);

    my_fprintf(bramp_log_file, "Reference level: %d at %d-th percentile\n", bramp_reference_level, bramp_percentile);

end:
    bulb_ramp_calibration_running = 0;
}

// monitor shutter speed and aperture and consider your changes as exposure compensation for bulb ramping
/*
static void bramp_temporary_exposure_compensation_update()
{
    if (!bramp_init_done) return;
    if (!bulb_ramping_enabled) return;
    int shutter = (int)lens_info.raw_shutter;
    int aperture = (int)lens_info.raw_aperture;

    static int prev_shutter = 0;
    static int prev_aperture = 0;

    int ec_rounded_abs = (ABS(bramp_temporary_exposure_compensation_ev_x100) + 5) / 10;

    if (prev_shutter > 0xC && shutter > 0xC)
    {
        int ec_delta_shutter = -(shutter - prev_shutter) * 100/8;
        int ec_delta_aperture = (aperture - prev_aperture) * 100/8;
        int ec_delta = ec_delta_shutter + ec_delta_aperture;
        if (ec_delta)
        {
            bramp_temporary_exposure_compensation_ev_x100 += ec_delta;
            ec_rounded_abs = (ABS(bramp_temporary_exposure_compensation_ev_x100) + 5) / 10;
            bmp_printf(FONT_LARGE, 0, 0, 
                "Exp.Comp for next shot: %s%d.%d EV",
                bramp_temporary_exposure_compensation_ev_x100 > 0 ? "+" : "-",
                ec_rounded_abs / 10, ec_rounded_abs % 10
            );
        }
    }

    // extend compensation range beyond normal shutter speed limits
    if (ec_rounded_abs == 0 && shutter != BRAMP_SHUTTER_0) // cancel drift
    {
        lens_set_rawshutter(BRAMP_SHUTTER_0);
        shutter = lens_info.raw_shutter;
    }
    else if (prev_shutter > 144) // 1/2000
    {
        lens_set_rawshutter(prev_shutter - 32);
        shutter = lens_info.raw_shutter;
    }
    else if (prev_shutter < 24) // 16 seconds
    {
        lens_set_rawshutter(prev_shutter + 32);
        shutter = lens_info.raw_shutter;
    }

    prev_shutter = shutter;
    prev_aperture = aperture;
}*/

/*
static int brightness_samples_a[11][11];
static int brightness_samples_b[11][11];
static int brightness_samples_delta[11][11];

int measure_brightness_difference()
{
    for (int i = 0; i <= 10; i++)
    {
        for (int j = 0; j <= 10; j++)
        {
            brightness_samples_a[i][j] = brightness_samples_b[i][j];
            int Y,U,V;
            int dx = (j-5) * 65;
            int dy = (i-5) * 40;
            get_spot_yuv_ex(10, dx, dy, &Y, &U, &V);
            brightness_samples_b[i][j] = Y;
            brightness_samples_delta[i][j] = bramp_luma_to_ev_x100(brightness_samples_b[i][j]) - bramp_luma_to_ev_x100(brightness_samples_a[i][j]);
            int xcb = os.x0 + os.x_ex/2 + dx;
            int ycb = os.y0 + os.y_ex/2 + dy;
            bmp_printf(SHADOW_FONT(FONT_SMALL), xcb, ycb, "%d ", brightness_samples_delta[i][j]);
        }
    }
}*/

static void compute_exposure_for_next_shot()
{
    
    static int prev_file_number = 12345;
    if (prev_file_number == file_number)
    {
        my_fprintf(bramp_log_file, "Picture not taken\n");
        NotifyBox(2000, "Picture not taken :("); msleep(2000);
        return;
    }
    prev_file_number = file_number;
    
    int mf_steps = (int)bramp_manual_speed_focus_steps_per_shot - 1000;
    int manual_evx1000 = (int)bramp_manual_speed_evx1000_per_shot - 1000;

    // don't go slower than intervalometer, and reserve 2 seconds just in case
    float shutter_max = (float)MAX(2, timer_values[interval_timer_index] - 2);
    // also, don't go faster than 1/4000 (or 1/8000)
    float shutter_min = 1.0 / (FASTEST_SHUTTER_SPEED_RAW == 160 ? 8000 : 4000);
    
    if (bramp_auto_exposure)
    {
        //~ msleep(200);
        ensure_play_or_qr_mode_after_shot();
        //~ draw_livev_for_playback();

        //~ NotifyBox(2000, "Exposure for next shot..."); msleep(1000);

        //~ NotifyBoxHide();
        //~ msleep(500);
        bramp_hist_dirty = 1;
        bramp_measured_level = measure_brightness_level(0);
        int mev = bramp_luma_to_ev_x100(bramp_measured_level);
        //~ NotifyBox(1000, "Brightness level: %d (%s%d.%02d EV)", bramp_measured_level, mev > 0 ? "" : "-", ABS(mev)/100, ABS(mev)%100); msleep(1000);

        my_fprintf(bramp_log_file, "%04d luma=%3d rounderr=%3d", file_number, bramp_measured_level, bramp_last_exposure_rounding_error_evx1000);

        /**
         * Use a discrete feedback controller, designed such as the closed loop system 
         * has two real poles placed at f, where f is the smoothing factor (0.1 ... 0.9).
         *
         *  r = expo reference (0, unless manual ramping is active)
         *  e = expo difference          
         *  u = expo correction
         *  T = log2(exposure time)                  +----------< brightness change from real world (sunrise, sunset)
         *                                           |   +------< measurement noise (let's say around 0.03 EV stdev)
         *              _______        ______        |   |
         * r     _  e  |       |   u  |  1   |   T   V   V
         * -----( )----| Bramp |------|(z-1) |------(+)-(+)----+----> picture
         *     - ^     |_______|      |______|                 |
         *       |_____________________________________________| y = brightness level (EV); luma=bramp_reference_level => 0 EV.
         * 
         * P = 1/(z-1) - integrator. 
         * Rationale: the exposure correction at each step is accumulated.
         * 
         * Closed loop system:
         * S = B*P / (1 + B*P)
         *
         * We want to fix the closed loop response (S), so we try to find out the controller B by inverting the process P
         * => B = S / ( 1 - S) / P
         *
         * We will place both closed-loop poles at smoothing factor value f in range [0.1 ... 0.9] and keep the static gain at 1.
         * S = z / (z-f) / (z-f) / (1 / (1-f) / (1-f))
         *
         * Result:
         *
         *      b*z          b
         * B = -----  = -----------
         *     z + a     1 + a*z^-1
         *
         * with:
         *    b = f^2 - 2f + 1
         *    a = f^2
         * 
         * Computing exposure correction:
         * 
         * u = B/A * e
         *    => u(k) = b e(k) - a u(k-1)
         * 
         * Exception: if ABS(e) > 2 EV, apply almost-full correction (B = 0.9) to bring it quickly back on track, 
         * without caring about flicker.
         * 
         */

        // unit: 0.01 EV
        int y_x100 = bramp_luma_to_ev_x100(bramp_measured_level) - bramp_luma_to_ev_x100(bramp_reference_level) - bramp_last_exposure_rounding_error_evx1000/10;
        int r_x100 = bramp_ev_reference_x1000/10;
        int e_x100 = COERCE(r_x100 - y_x100, -mev-500, -mev+500);
        // positive e => picture should be brightened

        // a difference of more than 2 EV will be fully corrected right away
        int expo_diff_too_big = 
            (e_x100 > 200 && bulb_shutter_valuef < shutter_max) ||
            (e_x100 < -200 && bulb_shutter_valuef > shutter_min);
        int should_apply_full_correction_immediately = expo_diff_too_big || bramp_prev_shot_was_bad;
        bramp_prev_shot_was_bad = expo_diff_too_big;

        my_fprintf(bramp_log_file, "y=%4d r=%4d e=%4d => ", y_x100, r_x100, e_x100);

        if (should_apply_full_correction_immediately)
        {
            // big change in brightness - request a new picture without waiting, and apply full correction
            // most probably, user changed ND filters or moved the camera
            
            NotifyBox(1000, "Exposure difference: %s%d.%02d EV ", e_x100 < 0 ? "-" : "+", ABS(e_x100)/100, ABS(e_x100)%100);
            msleep(500);

            float cor = COERCE((float)e_x100 / 111.0f, -3.0f, 3.0f);
            bulb_shutter_valuef *= powf(2, cor); // apply 90% of correction, but not more than 3 EV, to keep things stable
            
            // use high iso to adjust faster, then go back at low iso
            for (int i = 0; i < 5; i++)
                bulb_ramping_adjust_iso_180_rule_without_changing_exposure(expo_diff_too_big ? 1 : timer_values[interval_timer_index]);
                
            bulb_shutter_valuef = COERCE(bulb_shutter_valuef, shutter_min, shutter_max);

            // set Canon shutter speed close to bulb one (just for display)
            lens_set_rawshutter(shutterf_to_raw(bulb_shutter_valuef));

            my_fprintf(bramp_log_file, "harsh: cor=%d shutter=%6dms iso=%4d\n", (int)roundf(cor * 100.0), BULB_SHUTTER_VALUE_MS, lens_info.iso);

            // force next shot to be taken quicker
            intervalometer_next_shot_time = seconds_clock;
            return;
        }
        else // small change in brightness - apply only a small amount of correction to keep things smooth
        {    // see comments above for the feedback loop design
            bramp_ev_reference_x1000 += manual_evx1000;

            //~ float f = (float)bramp_auto_smooth / 100.0;
            int fi = get_smooth_factor_from_max_ev_speed(bramp_auto_ramp_speed);
            float f = (float)fi / 100.0;
            float e = (float)e_x100 / 100.0;

            if (bramp_auto_exposure == 1) // sunset - only increase exposure
                e = MAX(e, 0);

            if (bramp_auto_exposure == 2) // sunrise - only decrease exposure
                e = MIN(e, 0);
            
            float b = f*f - 2*f + 1;
            float a = f*f;
            float u = b*e - a*bramp_u1;
            bramp_u1 = u;
            bulb_shutter_valuef *= powf(2, u);

            // display some info
            int corr_x100 = (int) roundf(u * 100.0);
            NotifyBox(2000, "Exposure difference: %s%d.%02d EV \n"
                            "Exposure correction: %s%d.%02d EV ",
                e_x100 < 0 ? "-" : "+", ABS(e_x100)/100, ABS(e_x100)%100,
                corr_x100 < 0 ? "-" : "+", ABS(corr_x100)/100, ABS(corr_x100)%100
                );  

            my_fprintf(bramp_log_file, "soft: f=%2d e=%4d u=%4d ", fi, (int)roundf(e*100), corr_x100);

            msleep(500);
        }
    }

    // apply manual exposure ramping, if any
    if (manual_evx1000)
        bulb_shutter_valuef *= powf(2, (float)manual_evx1000 / 1000.0);
    
    if (BULB_EXPOSURE_CONTROL_ACTIVE)
    {
        // adjust ISO if needed, and check shutter speed limits
        bulb_ramping_adjust_iso_180_rule_without_changing_exposure(timer_values[interval_timer_index]);
        bulb_shutter_valuef = COERCE(bulb_shutter_valuef, shutter_min, shutter_max);
        
        // set Canon shutter speed close to bulb one (just for display)
        lens_set_rawshutter(shutterf_to_raw(bulb_shutter_valuef));

        my_fprintf(bramp_log_file, "shutter=%6dms iso=%4d\n", BULB_SHUTTER_VALUE_MS, lens_info.iso);
    }
        
    if (mf_steps && !is_manual_focus())
    {
        while (lens_info.job_state) msleep(100);
        msleep(300);
        get_out_of_play_mode(500);
        if (!lv) 
        {
            msleep(500);
            if (!lv) force_liveview();
        }
        set_lv_zoom(5);
        msleep(1000);
        NotifyBox(1000, "Focusing...");
        lens_focus_enqueue_step(-mf_steps);
        msleep(1000);
        set_lv_zoom(1);
        msleep(500);
    }
}

static void bulb_ramping_showinfo()
{
    int s = BULB_SHUTTER_VALUE_MS;
    //~ int manual_evx1000 = (int)bramp_manual_speed_evx1000_per_shot - 1000;
    //~ int rate_x1000 = bramp_light_changing_rate_evx1000 + manual_evx1000;

    bmp_printf(FONT_MED, 50, 350, 
        //~ "Reference level (%2dth prc) :%3d%%    \n"
        //~ "Measured  level (%2dth prc) :%3d%%    \n"
        //~ "Level/EV ratio             :%3d%%/EV \n"
        //~ " EV rate :%s%d.%03d/shot\n"
        " Shutter :%3d.%03d s  \n"
        " ISO     :%5d (range: %d...%d)",
        //~ bramp_percentile, bramp_reference_level, 0,
        //~ bramp_percentile, bramp_measured_level, 0,
        //~ bramp_level_ev_ratio, 0,
        //~ rate_x1000 > 0 ? "+" : rate_x1000 < 0 ? "-" : " ",  ABS(rate_x1000)/1000, ABS(rate_x1000)%1000,
        s / 1000, s % 1000,
        lens_info.iso, get_htp() ? 200 : 100, raw2iso(auto_iso_range & 0xFF)
        );
    
    if (bramp_auto_exposure && (PLAY_MODE || QR_MODE))
    {
        bramp_plot_luma_ev();
        bramp_plot_luma_ev_point(bramp_measured_level, COLOR_RED);
        bramp_plot_luma_ev_point(bramp_reference_level, COLOR_BLUE);
    }
}


/*static void 
bulb_ramping_display( void * priv, int x, int y, int selected )
{
    bmp_printf(
        selected ? MENU_FONT_SEL : MENU_FONT,
        x, y,
        "Bulb Ramping    : %s", 
        bulb_ramping_enabled ? "ON" : "OFF"
    );
}*/

static struct menu_entry shoot_menus[] = {
    {
        .name = "HDR Bracketing",
        .priv = &hdr_enabled,
        .display    = hdr_display,
        .select     = menu_binary_toggle,
        .help = "Exposure bracketing for HDR images. Press shutter once.",
        .essential = FOR_PHOTO,
        .children =  (struct menu_entry[]) {
            {
                .name = "Frames",
                .priv       = &hdr_steps,
                .min = 1,
                .max = 9,
                .choices = (const char *[]) {"err", "Autodetect", "2", "3", "4", "5", "6", "7", "8", "9"},
                .help = "Number of bracketed shots. Can be computed automatically.",
            },
            {
                .name = "EV increment",
                .priv       = &hdr_stepsize,
                .select     = hdr_stepsize_toggle,
                .max = 40,
                .unit = UNIT_1_8_EV,
                .help = "Exposure difference between two frames.",
            },
            {
                .name = "Sequence",
                .priv       = &hdr_sequence,
                .max = 2,
                .help = "Bracketing sequence order / type.",
                .icon_type = IT_DICE,
                .choices = (const char *[]) {"0 - --", "0 - + -- ++", "0 + ++"},
            },
            {
                .name = "2-second delay",
                .priv       = &hdr_delay,
                .max = 1,
                .help = "Delay before starting the exposure.",
                .choices = (const char *[]) {"OFF", "Auto"},
            },
            {
                .name = "ISO shifting",
                .priv       = &hdr_iso,
                .max = 2,
                .help = "First adjust ISO instead of Tv. Range: 100 .. max AutoISO.",
                .choices = (const char *[]) {"OFF", "Full, M only", "Half, M only"},
            },
            {
                .name = "Post scripts",
                .priv       = &hdr_scripts,
                .max = 2,
                .help = "ML can write enfuse scripts (also used for focus stacking).",
                .choices = (const char *[]) {"OFF", "Enfuse", "Align+Enfuse"},
            },
            MENU_EOL
        },
    },
    {
        .name = "Intervalometer",
        .priv       = &intervalometer_running,
        .select     = menu_binary_toggle,
        .display    = intervalometer_display,
        .help = "Take pictures or movies at fixed intervals (for timelapse).",
        .essential = FOR_PHOTO,
        .children =  (struct menu_entry[]) {
            {
                //~ .name = "Take a pic every",
                .priv       = &interval_timer_index,
                .display    = interval_timer_display,
                .select     = interval_timer_toggle,
                .help = "Duration between two shots.",
            },
            {
                .name = "Start after",
                .priv       = &interval_start_timer_index,
                .display    = interval_start_after_display,
                .select     = interval_timer_toggle,
                .help = "Start the intervalometer after X seconds / minutes / hours.",
            },
            {
                .name = "Stop after",
                .priv       = &interval_stop_after,
                .max = 50, // 5000 shots
                .display    = interval_stop_after_display,
                //~ .select     = intervalometer_stop_after_toggle,
                .help = "Stop the intervalometer after taking X shots.",
            },
            {
                //~ .name = "Stop REC after",
                .priv       = &interval_movie_duration_index,
                .display    = interval_movie_stop_display,
                .select     = interval_timer_toggle,
                .help = "Duration for each video clip (in movie mode only).",
            },
            MENU_EOL
        },
    },
    {
        .name = "Bulb/Focus Ramp ",
        .priv       = &bulb_ramping_enabled,
        .display = bulb_ramping_print,
        .max = 1,
        .help = "Exposure / focus ramping for advanced timelapse sequences.",
        .children =  (struct menu_entry[]) {
            {
                .name = "Auto ExpoRamp\b",
                .priv       = &bramp_auto_exposure,
                .max = 3,
                .choices = (const char *[]) {"OFF", "Sunset", "Sunrise", "Auto"},
                .help = "Auto exposure ramping (Tv+ISO) for day<->night timelapse.",
            },
            /*{
                .name = "Smooth Factor\b",
                .priv = &bramp_auto_smooth,
                .max = 90,
                .unit = UNIT_PERCENT,
                .select = bramp_smooth_toggle,
                .display = bramp_auto_smooth_print,
                .help = "For auto ramping. Higher = less flicker, slower ramping."
            },*/
            {
                .name = "MAX RampSpeed",
                .priv       = &bramp_auto_ramp_speed,
                .max = 1000,
                .min = 1,
                .select = bramp_auto_ramp_speed_toggle,
                .display = bramp_auto_ramp_speed_print,
                .help = "For auto ramp. Lower: less flicker. Too low: 2EV exp jumps.",
            },
            {
                .name = "Manual Expo. Ramp",
                .priv       = &bramp_manual_speed_evx1000_per_shot,
                .max = 1000+1000,
                .min = 1000-1000,
                .select = bramp_manual_evx1000_toggle,
                .display = manual_expo_ramp_print,
                .help = "Manual exposure ramping (Tv+ISO), in EV per shot.",
            },
            {
                .name = "Manual Focus Ramp",
                .priv       = &bramp_manual_speed_focus_steps_per_shot,
                .max = 1000+100,
                .min = 1000-100,
                .display = manual_focus_ramp_print,
                .help = "Manual focus ramping, in steps per shot. LiveView only.",
            },
            MENU_EOL,
        }
    },
    {
        .name = "Bulb Timer",
        .priv = &bulb_timer,
        .display = bulb_display, 
        .select = menu_binary_toggle, 
        .help = "For very long exposures. Hold shutter half-pressed for 1s.",
        .essential = FOR_PHOTO,
        .children =  (struct menu_entry[]) {
            {
                //~ .name = "Bulb exposure",
                .select = bulb_toggle,
                .display = bulb_display_submenu,
            },
            MENU_EOL
        },
    },
    #if defined(CONFIG_550D) || defined(CONFIG_500D) || defined(CONFIG_5D2)
    {
        .name = "LCDsensor Remote",
        .priv       = &lcd_release_running,
        .select     = menu_quaternary_toggle, 
        .display    = lcd_release_display,
         #if defined(CONFIG_5D2)
        .help = "Use the ambient light sensor as a simple remote (no shake).",
         #else
        .help = "Use the LCD face sensor as a simple remote (avoids shake).",
         #endif
        //~ .essential = FOR_PHOTO,
        //~ .edit_mode = EM_MANY_VALUES,
    },
    #endif
    #if !defined(CONFIG_600D) && !defined(CONFIG_50D)
    {
        .name = "Audio RemoteShot",
        .priv       = &audio_release_running,
        .select     = menu_binary_toggle,
        .display    = audio_release_display,
        .help = "Clap your hands or pop a balloon to take a picture.",
        .essential = FOR_PHOTO,
        .children =  (struct menu_entry[]) {
            {
                .name = "Trigger level (dB)",
                .priv = &audio_release_level, 
                .min = 1,
                .max = 20,
                .help = "Picture taken when sound level becomes X dB above average.",
            },
            MENU_EOL
        },
    },
    #endif
    {
        .name = "Motion Detect",
        .priv       = &motion_detect,
        .select     = menu_binary_toggle,
        .display    = motion_detect_display,
        .help = "Take a picture when subject is moving or exposure changes.",
        .essential = FOR_PHOTO,
        .children =  (struct menu_entry[]) {
            {
                .name = "Trigger by",
                .priv = &motion_detect_trigger, 
                .max = 1,
                .choices = (const char *[]) {"Expo. change", "Frame diff."},
                .icon_type = IT_DICE,
                .help = "How to compute the difference between two frames.",
            },
            {
                .name = "Trigger level",
                .priv = &motion_detect_level, 
                .min = 1,   
                .max = 30,
                .help = "Picture is taken when frame difference is above threshold.",
            },
            MENU_EOL
        },
    },
    #if !defined(CONFIG_5D2) && !defined(CONFIG_5D3)
    {
        //~ .select     = flash_and_no_flash_toggle,
        .display    = flash_and_no_flash_display,
        .priv = &flash_and_no_flash,
        .max = 1,
        .help = "Take odd pictures with flash, even pictures without flash."
    },
    #endif
    #if defined(CONFIG_550D) || defined(CONFIG_600D) || defined(CONFIG_500D)
    {
        .name = "3rd p. flash LV ",
        .priv = &lv_3rd_party_flash,
        .max = 1,
        .help = "A trick to allow 3rd party flashes to fire in LiveView."
    },
    #endif
    {
        .name = "Silent Picture",
        .priv = &silent_pic_enabled,
        .select = menu_binary_toggle,
        .display = silent_pic_display,
        .help = "Take pics in LiveView without moving the shutter mechanism.",
        .children =  (struct menu_entry[]) {
            {
                .name = "Mode",
                .priv = &silent_pic_mode, 
                .max = 1,
                .choices = (const char *[]) {"Simple", "Hi-Res", "SlitScan"},
                .icon_type = IT_DICE,
                .help = "Silent picture mode: simple or high-resolution."
            },
            {
                .name = "Flags", 
                .priv = &silent_pic_submode,
                .max = 2,
                .choices = (const char *[]) {"None", "Burst","FullHD"},
                .help = "Enables burst mode (for simple pics) or FullHD resolution."
            },
            {
                .name = "Hi-Res", 
                .priv = &silent_pic_highres,
                .max = 7,
                .choices = (const char *[]) {"2x1", "2x2", "2x3", "3x3", "3x4", "4x4", "4x5", "5x5"},
                .icon_type = IT_SIZE,
                .help = "For hi-res matrix mode: select number of subpictures."
            },
            /*{
                .name = "Slit Skip", 
                .priv = &silent_pic_slitscan_skipframes,
                .min = 1,
                .max = 4,
                .icon_type = IT_PERCENT,
                .help = "For slit-scan: how many frames to skip between two lines."
            },*/
            MENU_EOL
        },
    },
    {
        .name = "Mirror Lockup",
        .priv = &mlu_auto,
        .display = mlu_display, 
        .select = mlu_toggle,
        .help = "MLU setting can be linked with self-timer and LCD remote.",
        .essential = FOR_PHOTO,
    },
    /*{
        .display = picq_display, 
        .select = picq_toggle_raw,
        .select_reverse = picq_toggle_jpegsize, 
        .select_auto = picq_toggle_jpegtype,
    }
    {
        .display = picq_display, 
        .select = picq_toggle, 
        .help = "Experimental SRAW/MRAW mode. You may get corrupted files."
    }*/
};

struct menu_entry tweak_menus_shoot[] = {
    {
        .name = "LV Zoom Settings...",
        .select = menu_open_submenu,
        //~ .display = zoom_display,
        .icon_type = IT_SUBMENU,
        .help = "Disable x5 or x10, boost contrast/sharpness...",
        .children =  (struct menu_entry[]) {
            {
                .name = "Zoom x5",
                .priv = &zoom_disable_x5, 
                .max = 1,
                .choices = (const char *[]) {"ON", "Disable"},
                .select = zoom_x5_x10_toggle,
                .help = "Disable x5 zoom in LiveView.",
                .icon_type = IT_DISABLE_SOME_FEATURE,
            },
            {
                .name = "Zoom x10",
                .priv = &zoom_disable_x10, 
                .max = 1,
                .select = zoom_x5_x10_toggle,
                .choices = (const char *[]) {"ON", "Disable"},
                .help = "Disable x10 zoom in LiveView.",
                .icon_type = IT_DISABLE_SOME_FEATURE,
            },
            #if !defined(CONFIG_50D) && !defined(CONFIG_5D2)
            {
                .name = "Zoom :-)",
                .priv = &zoom_enable_face, 
                .max = 1,
                .icon_type = IT_BOOL,
                .help = "Enable zoom when Face Detection is active."
            },
            #endif
            {
                .name = "Auto exposure on Zoom ",
                .priv = &zoom_auto_exposure,
                .max = 1,
                .display = zoom_auto_exposure_print,
                .help = "Auto adjusts exposure, so you can focus manually wide open."
            },
            {
                .name = "Increase SharpContrast",
                .priv = &zoom_sharpen,
                .max = 1,
                .help = "Increase sharpness and contrast when you zoom in LiveView."
            },
            {
                .name = "Zoom on HalfShutter   ",
                .priv = &zoom_halfshutter,
                .max = 2,
                .choices = (const char *[]) {"OFF", "MF", "AF+MF"},
                .help = "Enable zoom when you hold the shutter halfway pressed."
            },
            {
                .name = "Zoom with Focus Ring  ",
                .priv = &zoom_focus_ring,
                .max = 2,
                .choices = (const char *[]) {"OFF", "MF", "AF+MF"},
                .help = "Zoom when you turn the focus ring (only some Canon lenses)."
            },
            MENU_EOL
        },
    },
};

extern int lvae_iso_max;
extern int lvae_iso_min;
extern int lvae_iso_speed;

extern void digic_iso_print( void * priv, int x, int y, int selected);
extern void digic_iso_toggle(void* priv, int delta);

extern int digic_black_level;
extern void digic_black_print( void * priv, int x, int y, int selected);
//~ extern void menu_open_submenu();

extern int digic_shadow_lift;

static struct menu_entry expo_menus[] = {
    {
        .name = "WhiteBalance",
        .display    = kelvin_wbs_display,
        .select     = kelvin_toggle,
        .help = "Adjust Kelvin white balance and GM/BA WBShift.",
        .essential = FOR_PHOTO | FOR_MOVIE,
        .edit_mode = EM_MANY_VALUES_LV,
        //~ .show_liveview = 1,
        .children =  (struct menu_entry[]) {
            {
                .name = "WhiteBalance",
                .display    = kelvin_display,
                .select     = kelvin_toggle,
                .help = "Adjust Kelvin white balance.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "WBShift G/M",
                .display = wbs_gm_display, 
                .select = wbs_gm_toggle,
                //~ .select_auto = wbs_gm_auto,
                .help = "Green-Magenta white balance shift, for fluorescent lights.",
                //~ .show_liveview = 1,
                .essential = FOR_MOVIE,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "WBShift B/A",
                .display = wbs_ba_display, 
                .select = wbs_ba_toggle, 
                .help = "Blue-Amber WBShift; 1 unit = 5 mireks on Kelvin axis.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .priv = (void *)(1),
                .display = wb_custom_gain_display,
                .select = wb_custom_gain_toggle,
                .help = "RED channel multiplier, for custom white balance.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .priv = (void *)(2),
                .display = wb_custom_gain_display,
                .select = wb_custom_gain_toggle,
                .help = "GREEN channel multiplier, for custom white balance.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .priv = (void *)(3),
                .display = wb_custom_gain_display,
                .select = wb_custom_gain_toggle,
                .help = "BLUE channel multiplier, for custom white balance.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
        #ifndef CONFIG_5DC
            {
                .name = "Black Level", 
                .priv = &digic_black_level,
                .min = 0,
                .max = 200,
                .display = digic_black_print,
                .edit_mode = EM_MANY_VALUES_LV,
                .help = "Adjust dark level, as with 'dcraw -k'. Fixes green shadows.",
            },
        #endif
            /*{
                .name = "UniWB\b\b",
                .priv = &uniwb_mode,
                .max = 3,
                .choices = (const char *[]) {"OFF", "Always ON", "on HalfShutter", "not HalfShutter"},
                .help = "Cancels white balance => good RAW histogram approximation.",
            },
            */
            /*{
                .name = "Auto adjust Kelvin",
                .select = kelvin_auto,
                .help = "LiveView: adjust Kelvin value once for the current scene."
            },
            {
                .name = "Auto adjust Green-Magenta",
                .select = wbs_gm_auto,
                .help = "LiveView: adjust Green-Magenta once for the current scene."
            },*/
            {
                .name = "Auto adjust Kelvin + G/M",
                .select = kelvin_n_gm_auto,
                .help = "LiveView: adjust Kelvin and G-M once (Push-button WB)."
            },
            MENU_EOL
        },
    },
    {
        .name = "ISO",
        .display    = iso_display,
        .select     = iso_toggle,
        .help = "Adjust and fine-tune ISO.",
        .essential = FOR_PHOTO | FOR_MOVIE,
        .edit_mode = EM_MANY_VALUES_LV,
        //~ .show_liveview = 1,

        .children =  (struct menu_entry[]) {
            {
                .name = "Equivalent ISO   ",
                .help = "ISO equivalent (analog + digital components).",
                .priv = &lens_info.iso_equiv_raw,
                .unit = UNIT_ISO,
                .select     = iso_toggle,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "Canon analog ISO ",
                .help = "Analog ISO component (ISO at which the sensor is driven).",
                .priv = &lens_info.iso_analog_raw,
                .unit = UNIT_ISO,
                .select     = analog_iso_toggle,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "Canon digital ISO",
                .help = "Canon's digital ISO component. Strongly recommended: 0.",
                .priv = &lens_info.iso_digital_ev,
                .unit = UNIT_1_8_EV,
                .select     = digital_iso_toggle,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "ML digital ISO",
                .display = digic_iso_print,
                .select = digic_iso_toggle,
                .help = "Movie: use negative gain. Photo: use it for night vision.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "Highlight Tone Priority",
                .select = (void (*)(void *,int))htp_toggle,
                .display = htp_display,
                .help = "Highlight Tone Priority. Use with negative DIGIC gain.",
            },
            {
                .name = "ISO Selection    ",
                .priv = &iso_selection,
                .max = 1,
                .help = "What ISOs should be available from main menu and shortcuts.",
                .choices = (const char *[]) {"C 100/160x", "ML ISOs"},
                .icon_type = IT_DICE,
            },
            /*{
                .name = "Lift Shadows",
                .priv = &digic_shadow_lift,
                .min = 0,
                .max = 50,
                .help = "Raises shadow level.",
            },*/
            #ifndef CONFIG_500D
            {
                .name = "Min Movie AutoISO",
                .priv = &lvae_iso_min,
                .min = 72,
                .max = 120,
                .unit = UNIT_ISO,
                .help = "Minimum value for Auto ISO in movie mode.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "Max Movie AutoISO",
                .priv = &lvae_iso_max,
                .min = 72,
                .max = 120,
                .unit = UNIT_ISO,
                .help = "Maximum value for Auto ISO in movie mode.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                .name = "A-ISO smoothness ",
                .priv = &lvae_iso_speed,
                .min = 3,
                .max = 30,
                .help = "Speed for movie Auto ISO. Low values = smooth transitions.",
                .edit_mode = EM_MANY_VALUES_LV,
            },
            #endif
            MENU_EOL
        },
    },
    {
        .name = "Shutter",
        .display    = shutter_display,
        .select     = shutter_toggle,
        .help = "Fine-tune shutter value.",
        .essential = FOR_PHOTO | FOR_MOVIE,
        .edit_mode = EM_MANY_VALUES_LV,
        //~ .show_liveview = 1,
    },
    {
        .name = "Aperture",
        .display    = aperture_display,
        .select     = aperture_toggle,
        .help = "Adjust aperture. Also displays APEX aperture (AV) in stops.",
        .essential = FOR_PHOTO | FOR_MOVIE,
        .edit_mode = EM_MANY_VALUES_LV,
        //~ .show_liveview = 1,
    },
    {
        .name = "PictureStyle",
        .display    = picstyle_display,
        .select     = picstyle_toggle,
        .help = "Change current picture style.",
        .edit_mode = EM_MANY_VALUES_LV,
        //~ .show_liveview = 1,
        //~ .essential = FOR_PHOTO | FOR_MOVIE,
        .children =  (struct menu_entry[]) {
            {
                //~ .name = "PictureStyle",
                .display    = picstyle_display_submenu,
                .select     = picstyle_toggle,
                .help = "Change current picture style.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                //~ .name = "Contrast/Saturation/Sharpness",
                .display    = sharpness_display,
                .select     = sharpness_toggle,
                .help = "Adjust sharpness in current picture style.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                //~ .name = "Contrast/Saturation/Sharpness",
                .display    = contrast_display,
                .select     = contrast_toggle,
                .help = "Adjust contrast in current picture style.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                //~ .name = "Contrast/Saturation/Sharpness",
                .display    = saturation_display,
                .select     = saturation_toggle,
                .help = "Adjust saturation in current picture style.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            {
                //~ .name = "Contrast/Saturation/Sharpness",
                .display    = color_tone_display,
                .select     = color_tone_toggle,
                .help = "Adjust color tone in current picture style.",
                //~ .show_liveview = 1,
                .edit_mode = EM_MANY_VALUES_LV,
            },
            MENU_EOL
        },
    },
    {
        .priv = &picstyle_rec,
        .name = "REC PicStyle",
        .display    = picstyle_rec_display,
        .select     = picstyle_rec_toggle,
        .help = "You can use a different picture style when recording.",
        //~ .essential = FOR_MOVIE,
        .children =  (struct menu_entry[]) {
            {
                //~ .name = "PictureStyle",
                .display    = picstyle_rec_sub_display,
                .select     = picstyle_rec_sub_toggle,
                .help = "Select the picture style for recording.",
                //~ .show_liveview = 1,
            },
            MENU_EOL
        },
    },

/*#if defined(CONFIG_500D) || defined(CONFIG_50D) || defined(CONFIG_5D2)
    {
        .name        = "HTP / ALO",
        .select      = htp_toggle,
        .select_reverse = alo_toggle,
        .display     = ladj_display,
        .help = "Enable/disable HTP [SET] and ALO [PLAY]."
    }, 
#else
    {
        .name = "HTP / ALO",
        .display    = ladj_display,
        .select     = ladj_toggle,
        .help = "Enable/disable HTP and ALO from the same place.",
        .edit_mode = EM_MANY_VALUES_LV,
    },
#endif
*/
#ifndef CONFIG_5D2 // no built-in flash; external flashes have their own EV compensation
    {
        .name = "Flash AEcomp",
        .display    = flash_ae_display,
        .select     = flash_ae_toggle,
        .help = "Flash exposure compensation, from -5EV to +3EV.",
        .essential = FOR_PHOTO,
        .edit_mode = EM_MANY_VALUES,
    },
#endif
};

// for firing HDR shots - avoids random misfire due to low polling frequency
int picture_was_taken_flag = 0;
PROP_HANDLER(PROP_LAST_JOB_STATE)
{
    if (buf[0] > 10) picture_was_taken_flag = 1;
}

void hdr_create_script(int steps, int skip0, int focus_stack, int f0)
{
    if (steps <= 1) return;
    
    if (hdr_scripts == 1)
    {
        FILE * f = INVALID_PTR;
        char name[100];
        snprintf(name, sizeof(name), "%s/%s_%04d.sh", get_dcim_dir(), focus_stack ? "FST" : "HDR", f0);
        f = FIO_CreateFileEx(name);
        if ( f == INVALID_PTR )
        {
            bmp_printf( FONT_LARGE, 30, 30, "FCreate: Err %s", name );
            return;
        }
        my_fprintf(f, "#!/usr/bin/env bash\n");
        my_fprintf(f, "\n# %s_%04d.JPG from IMG_%04d.JPG ... IMG_%04d.JPG\n\n", focus_stack ? "FST" : "HDR", f0, f0, mod(f0 + steps - 1, 10000));
        my_fprintf(f, "enfuse \"$@\" %s --output=%s_%04d.JPG ", focus_stack ? "--exposure-weight=0 --saturation-weight=0 --contrast-weight=1 --hard-mask" : "", focus_stack ? "FST" : "HDR", f0);
        for(int i = 0; i < steps; i++ )
        {
            my_fprintf(f, "IMG_%04d.JPG ", mod(f0 + i, 10000));
        }
        my_fprintf(f, "\n");
        FIO_CloseFile(f);
    }
   
    if (hdr_scripts == 2)
    {
        FILE * f = INVALID_PTR;
        char name[100];
        snprintf(name, sizeof(name), "%s/%s_%04d.sh", get_dcim_dir(), focus_stack ? "FST" : "HDR", f0);
        f = FIO_CreateFileEx(name);
        if ( f == INVALID_PTR )
        {
            bmp_printf( FONT_LARGE, 30, 30, "FCreate: Err %s", name );
            return;
        }
        my_fprintf(f, "#!/usr/bin/env bash\n");
        my_fprintf(f, "\n# %s_%04d.JPG from IMG_%04d.JPG ... IMG_%04d.JPG with aligning first\n\n", focus_stack ? "FST" : "HDR", f0, f0, mod(f0 + steps - 1, 10000));
        my_fprintf(f, "align_image_stack -m -a %s_AIS_%04d", focus_stack ? "FST" : "HDR", f0);
        for(int i = 0; i < steps; i++ )
        {
            my_fprintf(f, " IMG_%04d.JPG", mod(f0 + i, 10000));
        }
        my_fprintf(f, "\n");
        my_fprintf(f, "enfuse \"$@\" %s --output=%s_%04d.JPG %s_AIS_%04d*\n", focus_stack ? "--contrast-window-size=9 --exposure-weight=0 --saturation-weight=0 --contrast-weight=1 --hard-mask" : "", focus_stack ? "FST" : "HDR", f0, focus_stack ? "FST" : "HDR", f0);
        my_fprintf(f, "rm %s_AIS_%04d*\n", focus_stack ? "FST" : "HDR", f0);
        FIO_CloseFile(f);
    }
}

// normal pic, silent pic, bulb pic...
static void take_a_pic(int allow_af)
{
    if (silent_pic_enabled)
    {
        msleep(500);
        silent_pic_take(0); 
    }
    else
    {
        //~ beep();
        if (is_bulb_mode()) bulb_take_pic(BULB_SHUTTER_VALUE_MS);
        else lens_take_picture(64, allow_af);
    }
    lens_wait_readytotakepic(64);
}

// Here, you specify the correction in 1/8 EV steps (for shutter or exposure compensation)
// The function chooses the best method for applying this correction (as exposure compensation, altering shutter value, or bulb timer)
// And then it takes a picture
// .. and restores settings back

// Return value: 1 if OK, 0 if it couldn't set some parameter (but it will still take the shot)
static int hdr_shutter_release(int ev_x8, int allow_af)
{
    int ans = 1;
    //~ NotifyBox(2000, "hdr_shutter_release: %d", ev_x8); msleep(2000);
    lens_wait_readytotakepic(64);

    int manual = (shooting_mode == SHOOTMODE_M || is_movie_mode() || is_bulb_mode());
    int dont_change_exposure = ev_x8 == 0 && !HDR_ENABLED && !bulb_ramping_enabled;

    if (dont_change_exposure)
    {
        take_a_pic(allow_af);
    }
    else if (!manual) // auto modes
    {
        int ae0 = lens_get_ae();
        ans = hdr_set_ae(ae0 + ev_x8);
        take_a_pic(allow_af);
        hdr_set_ae(ae0);
    }
    else // manual mode or bulb
    {
        int iso00 = lens_info.raw_iso;
        int iso0 = iso00;
        if (!iso0) iso0 = lens_info.raw_iso_auto;

        if (hdr_iso && iso0) // dynamic range optimization
        {
            if (ev_x8 < 0)
            {
                int iso_delta = MIN(iso0 - 72, -ev_x8 / (hdr_iso == 2 ? 2 : 1)); // lower ISO, down to ISO 100

                // if we are going to hit shutter speed limit, use more iso shifting, to get the correct bracket
                int rs = get_exposure_time_raw();
                int rc = rs - (ev_x8 + iso_delta);
                if (rc >= FASTEST_SHUTTER_SPEED_RAW)
                    iso_delta = MIN(iso0 - 72, iso_delta + rc - FASTEST_SHUTTER_SPEED_RAW + 1);

                iso_delta = (iso_delta+7)/8*8; // round to full stops; also, prefer lower ISOs
                ev_x8 += iso_delta;
                hdr_set_rawiso(iso0 - iso_delta);
            }
            else if (ev_x8 > 0)
            {
                int max_auto_iso = auto_iso_range & 0xFF;
                int iso_delta = MIN(max_auto_iso - iso0, ev_x8 / (hdr_iso == 2 ? 2 : 1)); // raise ISO, up to max auto iso
                iso_delta = (iso_delta)/8*8; // round to full stops; also, prefer lower ISOs
                if (iso_delta < 0) iso_delta = 0;
                ev_x8 -= iso_delta;
                hdr_set_rawiso(iso0 + iso_delta);
            }
        }

        // apply EV correction in both "domains" (milliseconds and EV)
        int ms = get_exposure_time_ms();
        int msc = ms * roundf(1000.0*powf(2, ev_x8 / 8.0))/1000;
        
        int rs = (BULB_EXPOSURE_CONTROL_ACTIVE) ? shutterf_to_raw_noflicker(bulb_shutter_valuef) : get_exposure_time_raw();
        int rc = rs - ev_x8;

        int s0r = lens_info.raw_shutter; // save settings (for restoring them back)
        #if defined(CONFIG_5D2) || defined(CONFIG_50D)
        int expsim0 = expsim;
        #endif
        
        //~ NotifyBox(2000, "ms=%d msc=%d rs=%x rc=%x", ms,msc,rs,rc); msleep(2000);

        // then choose the best option (bulb for long exposures, regular for short exposures)
        if (msc >= 10000 || (BULB_EXPOSURE_CONTROL_ACTIVE && msc > BULB_MIN_EXPOSURE))
        {
            bulb_take_pic(msc);
            bramp_last_exposure_rounding_error_evx1000 = 0; // bulb ramping assumed to be exact
        }
        else
        {
            int b = bulb_ramping_enabled;
            bulb_ramping_enabled = 0; // to force a pic in manual mode

            #if defined(CONFIG_5D2) || defined(CONFIG_50D)
            if (expsim == 2) { set_expsim(1); msleep(100); } // can't set shutter slower than 1/30 in movie mode
            #endif
            ans = hdr_set_rawshutter(rc);
            take_a_pic(allow_af);
            
            bulb_ramping_enabled = b;
            
            if (BULB_EXPOSURE_CONTROL_ACTIVE)
            {
                // since actual shutter speed differs from float value quite a bit, 
                // we will need this to correct metering readings
                bramp_last_exposure_rounding_error_evx1000 = (int)roundf(log2f(raw2shutterf(rs) / bulb_shutter_valuef) * 1000.0);
                ASSERT(ABS(bramp_last_exposure_rounding_error_evx1000) < 500);
            }
            else bramp_last_exposure_rounding_error_evx1000 = 0;
        }
        
        if (drive_mode == DRIVE_SELFTIMER_2SEC) msleep(2500);
        if (drive_mode == DRIVE_SELFTIMER_REMOTE) msleep(10500);

        // restore settings back
        //~ set_shooting_mode(m0r);
        hdr_set_rawshutter(s0r);
        hdr_set_rawiso(iso00);
        #if defined(CONFIG_5D2) || defined(CONFIG_50D)
        if (expsim0 == 2) set_expsim(expsim0);
        #endif
    }
    lens_wait_readytotakepic(64);
    return ans;
}

static int hdr_check_cancel(int init)
{
    static int m;
    if (init)
    {
        m = shooting_mode;
        return 0;
    }
    
    extern int ml_started;
    if (!ml_started)
        return 0;

    // cancel bracketing
    if (shooting_mode != m || MENU_MODE) 
    { 
        beep(); 
        lens_wait_readytotakepic(64);
        NotifyBox(5000, "Bracketing stopped.");
        return 1; 
    }
    return 0;
}

void ensure_play_or_qr_mode_after_shot()
{
    msleep(300);
    #define QR_OR_PLAY (DISPLAY_IS_ON && (QR_MODE || PLAY_MODE))
    for (int i = 0; i < 20; i++)
    {
        msleep(100);
        if (QR_OR_PLAY)
            break;
        if (display_idle())
            break;
    }
    
    if (!QR_OR_PLAY) // image review disabled?
    {
        lens_wait_readytotakepic(64);
        fake_simple_button(BGMT_PLAY);
        for (int i = 0; i < 50; i++)
        {
            msleep(100);
            if (PLAY_MODE) break;
        }
        msleep(1000);
    }
}

void hdr_check_for_under_or_over_exposure(int* under, int* over)
{
    if (!silent_pic_enabled) ensure_play_or_qr_mode_after_shot();

    int under_numpix, over_numpix;
    int total_numpix = get_under_and_over_exposure(20, 235, &under_numpix, &over_numpix);
    *under = under_numpix > 10;
    *over = over_numpix > 10;
    int po = over_numpix * 10000 / total_numpix;
    int pu = under_numpix * 10000 / total_numpix;
    if (*under) pu = MAX(pu, 1);
    if (*over) po = MAX(po, 1);
    bmp_printf(
        FONT_LARGE, 50, 50, 
        "Under:%3d.%02d%%\n"
        "Over :%3d.%02d%%", 
        pu/100, pu%100, 0, 
        po/100, po%100, 0
    ); 
    msleep(500);
}

static int hdr_shutter_release_then_check_for_under_or_over_exposure(int ev_x8, int allow_af, int* under, int* over)
{
    int ans = hdr_shutter_release(ev_x8, allow_af);
    hdr_check_for_under_or_over_exposure(under, over);
    return ans;
}

static void hdr_auto_take_pics(int step_size, int skip0)
{
    int i;
    
    // make sure it won't autofocus
    // change it only once per HDR sequence to avoid slowdown
    assign_af_button_to_star_button();
    // be careful: don't return without restoring the setting back!
    
    hdr_check_cancel(1);
    
    int UNDER = 1;
    int OVER = 1;
    int under, over;
    
    // first exposure is always at 0 EV (and might be skipped)
    if (!skip0) hdr_shutter_release_then_check_for_under_or_over_exposure(0, 1, &under, &over);
    else hdr_check_for_under_or_over_exposure(&under, &over);
    if (!under) UNDER = 0; if (!over) OVER = 0;
    if (hdr_check_cancel(0)) goto end;
    
    int steps = 1;
    switch (hdr_sequence)
    {
        case 1: // 0 - + -- ++ 
        {
            for( i = 1; i <= 20; i ++  )
            {
                if (OVER)
                {
                    int ok = hdr_shutter_release_then_check_for_under_or_over_exposure(-step_size * i, 1, &under, &over);
                    if (!under) UNDER = 0; if (!over) OVER = 0;
                    if (!ok) OVER = 0; // Canon limit reached, don't continue this sequence
                    steps++;
                    if (hdr_check_cancel(0)) goto end;
                }
                
                if (UNDER)
                {
                    int ok = hdr_shutter_release_then_check_for_under_or_over_exposure(step_size * i, 1, &under, &over);
                    if (!under) UNDER = 0; if (!over) OVER = 0;
                    if (!ok) UNDER = 0; // Canon limit reached, don't continue this sequence
                    steps++;
                    if (hdr_check_cancel(0)) goto end;
                }
            }
            break;
        }
        case 0: // 0 - -- => will only check highlights
        {
            for( i = 1; i < 20; i ++  )
            {
                if (OVER)
                {
                    int ok = hdr_shutter_release_then_check_for_under_or_over_exposure(-step_size * i, 1, &under, &over);
                    if (!under) UNDER = 0; if (!over) OVER = 0;
                    if (!ok) OVER = 0;
                    steps++;
                    if (hdr_check_cancel(0)) goto end;
                }
            }
            break;
        }
        case 2: // 0 + ++
        {
            for( i = 1; i < 20; i ++  )
            {
                if (UNDER)
                {
                    int ok = hdr_shutter_release_then_check_for_under_or_over_exposure(step_size * i, 1, &under, &over);
                    if (!under) UNDER = 0; if (!over) OVER = 0;
                    if (!ok) UNDER = 0;
                    steps++;
                    if (hdr_check_cancel(0)) goto end;
                }
            }
            break;
        }
    }

    hdr_create_script(steps, skip0, 0, file_number - steps + 1);

end:
    restore_af_button_assignment();
}

// skip0: don't take the middle exposure
static void hdr_take_pics(int steps, int step_size, int skip0)
{
    if (steps < 2)  // auto number of steps, based on highlight/shadow levels
    {
        hdr_auto_take_pics(step_size, skip0);
        return;
    }
    //~ NotifyBox(2000, "hdr_take_pics: %d, %d, %d", steps, step_size, skip0); msleep(2000);
    //~ NotifyBox(2000, "HDR script created"); msleep(2000);
    int i;
    
    // make sure it won't autofocus
    // change it only once per HDR sequence to avoid slowdown
    assign_af_button_to_star_button();
    // be careful: don't return without restoring the setting back!
    
    hdr_check_cancel(1);
    
    // first exposure is always at 0 EV (and might be skipped)
    if (!skip0) hdr_shutter_release(0, 1);
    if (hdr_check_cancel(0)) goto end;
    
    switch (hdr_sequence)
    {
        case 1: // 0 - + -- ++ 
        {
            for( i = 1; i <= steps/2; i ++  )
            {
                hdr_shutter_release(-step_size * i, 1);
                if (hdr_check_cancel(0)) goto end;

                if (steps % 2 == 0 && i == steps/2) break;
                
                hdr_shutter_release(step_size * i, 1);
                if (hdr_check_cancel(0)) goto end;
            }
            break;
        }
        case 0: // 0 - --
        case 2: // 0 + ++
        {
            for( i = 1; i < steps; i ++  )
            {
                hdr_shutter_release(step_size * i * (hdr_sequence == 2 ? 1 : -1), 1);
                if (hdr_check_cancel(0)) goto end;
            }
            break;
        }
    }

    hdr_create_script(steps, skip0, 0, file_number - steps + 1);

end:
    restore_af_button_assignment();
}

static void press_rec_button()
{
#if defined(CONFIG_50D) || defined(CONFIG_5D2)
    fake_simple_button(BGMT_PRESS_SET);
#else
    fake_simple_button(BGMT_LV);
#endif
}

void movie_start()
{
    while (get_halfshutter_pressed()) msleep(100);
    if (lens_info.job_state >= 10) return; 

    ensure_movie_mode();
    
    if (recording)
    {
        NotifyBox(2000, "Already recording ");
        return;
    }
    
    #if defined(CONFIG_500D) || defined(CONFIG_50D) || defined(CONFIG_5D2) // record button is used in ML menu => won't start recording
    //~ menu_stop(); msleep(1000);
    while (gui_menu_shown())
    {
        menu_stop();
        msleep(1000);
    }
    #endif
    
    while (get_halfshutter_pressed()) msleep(100);
    
    press_rec_button();
    
    for (int i = 0; i < 30; i++)
    {
        msleep(100);
        if (recording == 2) break; // recording started
    }
    msleep(500);
}

void movie_end()
{
    if (shooting_type != 3 && !is_movie_mode())
    {
        NotifyBox(2000, "movie_end: not movie mode (%d,%d) ", shooting_type, shooting_mode);
        return;
    }
    if (!recording)
    {
        NotifyBox(2000, "movie_end: not recording ");
        return;
    }

    while (get_halfshutter_pressed()) msleep(100);

    msleep(500);

    press_rec_button();

    // wait until it stops recording, but not more than 2s
    for (int i = 0; i < 20; i++)
    {
        msleep(100);
        if (!recording) break;
    }
    msleep(500);
}


static void
short_movie()
{
    movie_start();
    msleep(timer_values[interval_movie_duration_index] * 1000);
    movie_end();
}

// take one picture or a HDR / focus stack sequence
// to be used with the intervalometer
void hdr_shot(int skip0, int wait)
{
    NotifyBoxHide();
    if (HDR_ENABLED)
    {
        //~ NotifyBox(1000, "HDR shot (%dx%dEV)...", hdr_steps, hdr_stepsize/8); msleep(1000);
        int drive_mode_bak = 0;
        lens_wait_readytotakepic(64);
        if (drive_mode != DRIVE_SINGLE) 
        {
            drive_mode_bak = drive_mode;
            lens_set_drivemode(DRIVE_SINGLE);
        }

        hdr_take_pics(hdr_steps, hdr_stepsize, skip0);

        lens_wait_readytotakepic(64);
        if (drive_mode_bak) lens_set_drivemode(drive_mode_bak);
    }
    else // regular pic (not HDR)
    {
        hdr_shutter_release(0, !intervalometer_running); // disable AF on intervalometer, allow it otherwise
    }

    lens_wait_readytotakepic(64);
    picture_was_taken_flag = 0;
}

int remote_shot_flag = 0;
void schedule_remote_shot() { remote_shot_flag = 1; }

static int mlu_lock_flag = 0;
void schedule_mlu_lock() { mlu_lock_flag = 1; }

static int movie_start_flag = 0;
void schedule_movie_start() { movie_start_flag = 1; }
int is_movie_start_scheduled() { return movie_start_flag; }

static int movie_end_flag = 0;
void schedule_movie_end() { movie_end_flag = 1; }

void get_out_of_play_mode(int extra_wait)
{
    if (gui_state == GUISTATE_QR)
    {
        fake_simple_button(BGMT_PLAY);
        msleep(200);
        fake_simple_button(BGMT_PLAY);
    }
    else if (PLAY_MODE) 
    {
        fake_simple_button(BGMT_PLAY);
    }
    while (PLAY_MODE) msleep(100);
    msleep(extra_wait);
}

// take one shot, a sequence of HDR shots, or start a movie
// to be called by remote triggers
void remote_shot(int wait)
{
    // save zoom value (x1, x5 or x10)
    int zoom = lv_dispsize;
    
    if (is_focus_stack_enabled())
    {
        focus_stack_run(0);
    }
    else if (is_movie_mode())
    {
        movie_start();
    }
    else
    {
        hdr_shot(0, wait);
    }
    if (!wait) return;
    
    lens_wait_readytotakepic(64);
    msleep(500);
    while (gui_state != GUISTATE_IDLE) msleep(100);
    msleep(500);
    // restore zoom
    if (lv && !recording && zoom > 1) set_lv_zoom(zoom);

    picture_was_taken_flag = 0;
}

static void display_expsim_status()
{
    get_yuv422_vram();
    static int prev_expsim = 0;
    int x = 610 + font_med.width;
    int y = os.y_max - os.off_169 - font_med.height - 5;
    if (!expsim)
    {
        bmp_printf( FONT(FONT_MED, COLOR_WHITE, 0), x, y, " ExpSim " );
        draw_line(x-5 + font_med.width, y + font_med.height * 3/4, x + font_med.width * 7, y + font_med.height * 1/4, COLOR_WHITE);
    }
    else
    {
        if (expsim != prev_expsim)// redraw();
            bmp_printf( FONT(FONT_MED, COLOR_WHITE, 0), x, y, "        " );
    }
    prev_expsim = expsim;
}


void display_shooting_info_lv()
{
#ifndef CONFIG_5D2
    int screen_layout = get_screen_layout();
    int audio_meters_at_top = audio_meters_are_drawn() 
        && (screen_layout == SCREENLAYOUT_3_2);

    display_lcd_remote_icon(450, audio_meters_at_top ? 25 : 3);
#endif
    display_trap_focus_info();
    display_expsim_status();
}

void display_trap_focus_info()
{
    int show, fg, bg, x, y;
    static int show_prev = 0;
    if (lv)
    {
        show = trap_focus && can_lv_trap_focus_be_active();
        int active = show && get_halfshutter_pressed();
        bg = active ? COLOR_BG : 0;
        fg = active ? COLOR_RED : COLOR_BG;
        x = 8; y = 160;
        if (show || show_prev) bmp_printf(FONT(FONT_MED, fg, bg), x, y, show ? "TRAP \nFOCUS" : "     \n     ");
    }
    else
    {
        show = (trap_focus && ((af_mode & 0xF) == 3) && lens_info.raw_aperture);
        bg = bmp_getpixel(DISPLAY_TRAP_FOCUS_POS_X, DISPLAY_TRAP_FOCUS_POS_Y);
        fg = HALFSHUTTER_PRESSED ? COLOR_RED : COLOR_FG_NONLV;
        x = DISPLAY_TRAP_FOCUS_POS_X; y = DISPLAY_TRAP_FOCUS_POS_Y;
        if (show || show_prev) bmp_printf(FONT(FONT_MED, fg, bg), x, y, show ? DISPLAY_TRAP_FOCUS_MSG : DISPLAY_TRAP_FOCUS_MSG_BLANK);
    }
    show_prev = show;
}

int wait_for_lv_err_msg(int wait) // 1 = msg appeared, 0 = did not appear
{
    extern thunk ErrCardForLVApp_handler;
    for (int i = 0; i <= wait/20; i++)
    {
        if ((intptr_t)get_current_dialog_handler() == (intptr_t)&ErrCardForLVApp_handler) return 1;
        msleep(20);
    }
    return 0;
}

void intervalometer_stop()
{
    if (intervalometer_running)
    {
        intervalometer_running = 0;
        bramp_init_state = 0;
        NotifyBox(2000, "Intervalometer stopped.");
        //~ display_on();
    }
}

int handle_intervalometer(struct event * event)
{
    // stop intervalometer with MENU or PLAY
    if (!IS_FAKE(event) && (event->param == BGMT_MENU || event->param == BGMT_PLAY) && !gui_menu_shown())
        intervalometer_stop();
    return 1;
}

// this syncs with real-time clock
void wait_till_next_second()
{
    struct tm now;
    LoadCalendarFromRTC( &now );
    int s = now.tm_sec;
    
    while (now.tm_sec == s)
    {
        LoadCalendarFromRTC( &now );
        msleep(DISPLAY_IS_ON ? 100 : 300);
    }
}

static void mlu_step()
{
    if (!mlu_auto) return;
    
    int mlu_current_value = get_mlu() ? 1 : 0;
    static int mlu_prev_value = -1;
    if (MENU_MODE && !gui_menu_shown()) // Canon menu => let's see if user changes MLU setting
    {
        if (mlu_prev_value != -1 && mlu_prev_value != mlu_current_value)
        {
            mlu_auto = 0;
            NotifyBox(2000, "ML: Auto MLU disabled");
        }
        mlu_prev_value = mlu_current_value;
    }
    else // not in Canon menu
    {
        mlu_prev_value = -1;
    }

    if (!lv && display_idle() && !get_halfshutter_pressed()) // normal shooting mode, non-liveview
    {
        int mlu_auto_value = ((drive_mode == DRIVE_SELFTIMER_2SEC || drive_mode == DRIVE_SELFTIMER_REMOTE || lcd_release_running == 2) && (!HDR_ENABLED)) ? 1 : 0;
        if (mlu_auto_value != mlu_current_value)
        {
            set_mlu(mlu_auto_value); // shooting mode, ML decides to toggle MLU
        }
    }
}

static void
shoot_task( void* unused )
{
    #ifdef AFFRAME_PROP_LEN
    if (!lv)
    {   // center AF frame at startup in photo mode
        afframe[2] = (afframe[0] - afframe[4])/2;
        afframe[3] = (afframe[1] - afframe[5])/2;
        prop_request_change(PROP_LV_AFFRAME, afframe, AFFRAME_PROP_LEN);
    }
    #endif

    bulb_shutter_valuef = (float)timer_values[bulb_duration_index];
    
    TASK_LOOP
    {
        msleep(MIN_MSLEEP);
        
        if (kelvin_auto_flag)
        {
            kelvin_auto_run();
            kelvin_auto_flag = 0;
        }
        if (wbs_gm_auto_flag)
        {
            wbs_gm_auto_run();
            wbs_gm_auto_flag = 0;
        }
        
        //~ if (gui_menu_shown()) continue; // be patient :)
        
        lcd_release_step();
        
        if (remote_shot_flag)
        {
            remote_shot(1);
            remote_shot_flag = 0;
        }
        if (mlu_lock_flag)
        {
            mlu_lock_mirror_if_needed();
            mlu_lock_flag = 0;
        }
        if (movie_start_flag)
        {
            movie_start();
            movie_start_flag = 0;
        }
        if (movie_end_flag)
        {
            movie_end();
            movie_end_flag = 0;
        }
        if (zoom_focus_ring_flag)
        {
            zoom_focus_ring_engage();
            zoom_focus_ring_flag = 0;
        }
        
        mlu_step();
        zoom_lv_face_step();
        zoom_focus_ring_step();
        //~ uniwb_step();

        if (center_lv_aff)
        {
            center_lv_afframe_do();
            center_lv_aff = 0;
        }

        // avoid camera shake for HDR shots => force self timer
        static int drive_mode_bk = -1;
        if (((HDR_ENABLED && hdr_delay) || is_focus_stack_enabled()) && get_halfshutter_pressed() && drive_mode != DRIVE_SELFTIMER_2SEC && drive_mode != DRIVE_SELFTIMER_REMOTE)
        {
            drive_mode_bk = drive_mode;
            lens_set_drivemode(DRIVE_SELFTIMER_2SEC);
            info_led_on();
        }
        
        // restore drive mode if it was changed
        if (!get_halfshutter_pressed() && drive_mode_bk >= 0)
        {
            msleep(50);
            lens_set_drivemode(drive_mode_bk);
            drive_mode_bk = -1;
            info_led_off();
        }
    
        if (bulb_timer && is_bulb_mode() && !gui_menu_shown())
        {
            // look for a transition of half-shutter during idle state
            static int was_idle_not_pressed = 0;
            int is_idle_not_pressed = !get_halfshutter_pressed() && display_idle();
            int is_idle_and_pressed = get_halfshutter_pressed() && display_idle();
            int trigger_condition = was_idle_not_pressed && is_idle_and_pressed;
            was_idle_not_pressed = is_idle_not_pressed;

            if (trigger_condition)
            {
                info_led_on();
                // need to keep halfshutter pressed for one second
                for (int i = 0; i < 10; i++)
                {
                    msleep(100);
                    if (!get_halfshutter_pressed() || lens_info.job_state >= 10) break;
                }
                if (!get_halfshutter_pressed() || lens_info.job_state >= 10) { info_led_off(); continue; }
                
                info_led_blink(1,50,50); // short blink so you know bulb timer was triggered
                info_led_on();
                
                int d = BULB_SHUTTER_VALUE_S;
                //~ NotifyBoxHide();
                NotifyBox(10000, "[HalfShutter] Bulb timer: %s", format_time_hours_minutes_seconds(d));
                while (get_halfshutter_pressed())
                {
                    msleep(100);
                }
                int m0 = shooting_mode;
                wait_till_next_second();
                //~ NotifyBoxHide();
                NotifyBox(2000, "[2s] Bulb timer: %s", format_time_hours_minutes_seconds(d));
                info_led_on();
                wait_till_next_second();
                if (get_halfshutter_pressed()) continue;
                if (!display_idle()) continue;
                if (m0 != shooting_mode) continue;
                if (lens_info.job_state >= 10) continue;
                //~ NotifyBoxHide();
                NotifyBox(2000, "[1s] Bulb timer: %s", format_time_hours_minutes_seconds(d));
                info_led_on();
                wait_till_next_second();
                if (get_halfshutter_pressed()) continue;
                if (!display_idle()) continue;
                if (m0 != shooting_mode) continue;
                if (lens_info.job_state >= 10) continue;
                info_led_off();
                bulb_take_pic(d * 1000);
            }
        }
        
        if (picture_was_taken_flag) // just took a picture, maybe we should take another one
        {
            if (!recording)
            {
                if (is_focus_stack_enabled())
                {
                    lens_wait_readytotakepic(64);
                    focus_stack_run(1); // skip first exposure, we already took it
                    lens_wait_readytotakepic(64); 
                }
                else if (HDR_ENABLED)
                {
                    lens_wait_readytotakepic(64);
                    hdr_shot(1,1); // skip the middle exposure, which was just taken
                    lens_wait_readytotakepic(64); 
                }
                #ifdef CONFIG_60D
                // smarter trigger for Canon bracketing in high-speed mode
                else if (hdr_enabled && aeb_setting && drive_mode == DRIVE_HISPEED_CONTINUOUS)
                {
                    lens_wait_readytotakepic(64);
                    SW1(1,0);
                    SW2(1,1000);
                    SW2(0,0);
                    SW1(0,0);
                    lens_wait_readytotakepic(64);
                    info_led_blink(1,50,50);
                }
                #endif
            }
            picture_was_taken_flag = 0;
        }

        #if !defined(CONFIG_5D2) && !defined(CONFIG_5D3)
        // toggle flash on/off for next picture
        if (!is_movie_mode() && flash_and_no_flash && strobo_firing < 2 && strobo_firing != file_number % 2)
        {
            strobo_firing = file_number % 2;
            set_flash_firing(strobo_firing);
        }
        
        static int prev_flash_and_no_flash;
        if (!flash_and_no_flash && prev_flash_and_no_flash && strobo_firing==1)
            set_flash_firing(0);
        prev_flash_and_no_flash = flash_and_no_flash;
        #endif

        #if defined(CONFIG_550D) || defined(CONFIG_600D) || defined(CONFIG_500D)
        if (lv_3rd_party_flash && !is_movie_mode())
        {
            if (lv && HALFSHUTTER_PRESSED)
            {
                fake_simple_button(BGMT_LV);
                while (lv) msleep(100);
                SW1(1,10);
                msleep(500);
                while (HALFSHUTTER_PRESSED) msleep(100);
                fake_simple_button(BGMT_LV);
            }
        }
        #endif
                
        // trap focus (outside LV) and all the preconditions
        int tfx = trap_focus && is_manual_focus() && display_idle() && !intervalometer_running && !is_movie_mode();

        if (trap_focus == 2 && tfx && !HALFSHUTTER_PRESSED && cfn_get_af_button_assignment()==0) 
        {
            info_led_off();
            msleep(1000);
            if (!display_idle()) continue;
            if (gui_menu_shown()) continue;
            if (HALFSHUTTER_PRESSED) continue;
            msleep(1000);
            if (!display_idle()) continue;
            if (gui_menu_shown()) continue;
            if (HALFSHUTTER_PRESSED) continue;
            SW1(1,200);
        }

        // same for motion detect
        int mdx = motion_detect && liveview_display_idle() && !recording;
        
        if (!tfx && !DISPLAY_IS_ON) msleep(200); // no need to react very fast, can powersave a bit
        
        //Reset the counter so that if you go in and out of live view, it doesn't start clicking away right away.
        static int K = 0;

        if(!mdx) K = 0;

        if (tfx) // MF
        {
            if (HALFSHUTTER_PRESSED) info_led_on(); else info_led_off();
            if ((!lv && FOCUS_CONFIRMATION) || get_lv_focus_confirmation())
            {
                lens_take_picture(64,0);
                if (trap_focus==2) // engage half-shutter for next shot
                {
                    if (image_review_time) msleep(2000);
                    SW1(1,0);
                }
                //~ call("Release");
                //~ remote_shot(0);
            }
        }
        
        if (mdx)
        {
            K = COERCE(K+1, 0, 1000);
            //~ bmp_printf(FONT_MED, 0, 50, "K= %d   ", K);

            if (motion_detect_trigger == 0)
            {
                int aev = 0;
                //If the new value has changed by more than the detection level, shoot.
                static int old_ae_avg = 0;
                int y,u,v;
                //TODO: maybe get the spot yuv of the target box
                get_spot_yuv(100, &y, &u, &v);
                aev = y / 2;
                if (K > 50) bmp_printf(FONT_MED, 0, 50, "Average exposure: %3d    New exposure: %3d   ", old_ae_avg/100, aev);
                if (K > 50 && ABS(old_ae_avg/100 - aev) >= (int)motion_detect_level)
                {
                    lens_take_picture(64,1);
                    //~ msleep(trap_focus_delay);
                    K = 0;
                }
                old_ae_avg = old_ae_avg * 90/100 + aev * 10;
            }
            else if (motion_detect_trigger == 1)
            {
                int d = get_spot_motion(100, get_global_draw());
                if (K > 50) bmp_printf(FONT_MED, 0, 50, "Motion level: %d   ", d);
                if (K > 50 && d >= (int)motion_detect_level)
                {
                    //~ remote_shot(1);
                    lens_take_picture(64,1);
                    //~ msleep(trap_focus_delay);
                    K = 0;
                }
            }
        }

        static int silent_pic_countdown;
        if (!display_idle())
        {
            silent_pic_countdown = 10;
        }
        else if (!get_halfshutter_pressed())
        {
            if (silent_pic_countdown) silent_pic_countdown--;
        }

        if (lv && silent_pic_enabled && get_halfshutter_pressed())
        {
            if (silent_pic_countdown) // half-shutter was pressed while in playback mode, for example
                continue;
            if (is_focus_stack_enabled()) focus_stack_run(0); // shoot all frames
            else if (!HDR_ENABLED) silent_pic_take(1);
            else 
            {
                NotifyBox(5000, "HDR silent picture...");
                //~ if (beep_enabled) Beep();
                while (get_halfshutter_pressed()) msleep(100);
                if (!lv) force_liveview();
                hdr_shot(0,1);
            }
        }
        
        #define SECONDS_REMAINING (intervalometer_next_shot_time - seconds_clock)
        #define SECONDS_ELAPSED (seconds_clock - seconds_clock_0)

        if (intervalometer_running)
        {
            int seconds_clock_0 = seconds_clock;
            int display_turned_off = 0;
            //~ int images_compared = 0;
            msleep(100);
            while (SECONDS_REMAINING > 0)
            {
                msleep(300);

                if (!intervalometer_running) break; // from inner loop only
                
                if (gui_menu_shown() || get_halfshutter_pressed())
                {
                    intervalometer_next_shot_time++;
                    wait_till_next_second();
                    continue;
                }
                static char msg[50];
                snprintf(msg, sizeof(msg),
                                " Intervalometer:%4d \n"
                                " Pictures taken:%4d ", 
                                SECONDS_REMAINING,
                                intervalometer_pictures_taken);
                if (interval_stop_after) { STR_APPEND(msg, "/ %d", interval_stop_after*100); }
                bmp_printf(FONT_MED, 50, 310, msg);

                if (interval_stop_after && (int)intervalometer_pictures_taken >= (int)(interval_stop_after*100))
                    intervalometer_stop();

                //~ if (bulb_ramping_enabled)
                //~ {
                    //~ bramp_temporary_exposure_compensation_update();
                //~ }

                //~ if (!images_compared && SECONDS_ELAPSED >= 2 && SECONDS_REMAINING >= 2 && image_review_time - SECONDS_ELAPSED >= 1 && bramp_init_done)
                //~ {
                    //~ playback_compare_images(0);
                    //~ images_compared = 1; // do this only once
                //~ }
                if (PLAY_MODE && SECONDS_ELAPSED >= image_review_time)
                {
                    get_out_of_play_mode(0);
                }

                extern int idle_display_turn_off_after;
                if (idle_display_turn_off_after && lens_info.job_state == 0 && liveview_display_idle() && intervalometer_running && !display_turned_off)
                {
                    // stop LiveView and turn off display to save power
                    msleep(500);
                    PauseLiveView();
                    msleep(200);
                    display_off();
                    display_turned_off = 1; // ... but only once per picture (don't be too aggressive)
                }
            }

            if (interval_stop_after && (int)intervalometer_pictures_taken >= (int)(interval_stop_after*100))
                intervalometer_stop();

            if (PLAY_MODE) get_out_of_play_mode(500);
            if (LV_PAUSED) ResumeLiveView();

            if (!intervalometer_running) continue; // back to start of shoot_task loop
            if (gui_menu_shown() || get_halfshutter_pressed()) continue;

            if (bulb_ramping_enabled)
            {
                bulb_ramping_init();
            }
            
            if (lv && silent_pic_enabled) // half-press shutter to disable power management
            {
                assign_af_button_to_halfshutter();
                SW1(1,10);
                SW1(0,50);
                restore_af_button_assignment();
            }

            if (!intervalometer_running) continue;
            if (gui_menu_shown() || get_halfshutter_pressed()) continue;

            int dt = timer_values[interval_timer_index];
            // compute the moment for next shot; make sure it stays somewhat in sync with the clock :)
            intervalometer_next_shot_time = COERCE(intervalometer_next_shot_time + dt, seconds_clock, seconds_clock + dt);
            
            //~ info_led_blink(2,50,50);
            
            if (dt == 0) // crazy mode - needs to be fast
            {
                take_a_pic(0);
            }
            else if (!is_movie_mode() || silent_pic_enabled || bulb_ramping_enabled)
            {
                hdr_shot(0, 1);
            }
            else
            {
                short_movie();
            }
            intervalometer_next_shot_time = MAX(intervalometer_next_shot_time, seconds_clock);
            intervalometer_pictures_taken++;

            if (bulb_ramping_enabled)
            {
                compute_exposure_for_next_shot();
            }
        }
        else // intervalometer not running
        {
            bramp_init_done = 0;
            bramp_cleanup();
            intervalometer_pictures_taken = 0;
            intervalometer_next_shot_time = seconds_clock + timer_values[interval_start_timer_index];

#if !defined(CONFIG_50D) && !defined(CONFIG_5D3) && !defined(CONFIG_5DC) // no audio module on these cameras
            if (audio_release_running) 
            {
                static int countdown = 0;
                if (!display_idle()) countdown = 20;
                if (countdown) { countdown--; }

                extern struct audio_level audio_levels[];

                static int avg_prev0 = 1000;
                static int avg_prev1 = 1000;
                static int avg_prev2 = 1000;
                static int avg_prev3 = 1000;
                int current_pulse_level = audio_level_to_db(audio_levels[0].peak_fast) - audio_level_to_db(avg_prev3);
    
                if (countdown == 0)
                {
                    bmp_printf(FONT_MED, 20,  (lv ? 40 : 3), "Audio release ON (%d / %d)   ", current_pulse_level, audio_release_level);
                    if (current_pulse_level > (int)audio_release_level) 
                    {
                        remote_shot(1);
                        msleep(100);
                        /* Initial forced sleep is necesarry when using camera self timer,
                         * otherwise remote_shot returns right after the countdown 
                         * and the loop below seems to miss the actual picture taking.
                         * This means we will trigger again on the sound of the shutter
                         * (and again, and again, ...)
                         * TODO: should this be fixed in remote_shot itself? */
                        while (lens_info.job_state) msleep(100);
                        countdown = 20;
                    }
                }
                avg_prev3 = avg_prev2;
                avg_prev2 = avg_prev1;
                avg_prev1 = avg_prev0;
                avg_prev0 = audio_levels[0].avg;
            }
#endif
        }
    }
}

TASK_CREATE( "shoot_task", shoot_task, 0, 0x1a, 0x8000 );

void shoot_init()
{
    set_maindial_sem = create_named_semaphore("set_maindial_sem", 1);
    menu_add( "Shoot", shoot_menus, COUNT(shoot_menus) );
    menu_add( "Expo", expo_menus, COUNT(expo_menus) );
    //~ menu_add( "Tweaks", vid_menus, COUNT(vid_menus) );

#ifndef CONFIG_5DC
	extern struct menu_entry expo_override_menus[];
    menu_add( "Expo", expo_override_menus, 1 );
#endif

#ifndef CONFIG_600D // expsim doesn't work
    extern struct menu_entry expo_tweak_menus[];
    menu_add( "Expo", expo_tweak_menus, 1 );
#endif
}

INIT_FUNC("shoot", shoot_init);
back to top