https://bitbucket.org/daniel_fort/magic-lantern
Raw File
Tip revision: 69a45ffe4474b93b19fb913b89318233621615f0 authored by Daniel Fort on 08 September 2015, 16:46:50 UTC
Close branch EOSM_fs_magic_zoom.
Tip revision: 69a45ff
dual_iso.c
/**
 * Dual ISO trick
 * Codenames: ISO-less mode, Nikon mode.
 * 
 * Technical details: https://dl.dropboxusercontent.com/u/4124919/bleeding-edge/isoless/dual_iso.pdf
 * 
 * 5D3 and 7D only.
 * Requires a camera with two analog amplifiers (cameras with 8-channel readout seem to have this).
 * 
 * Samples half of sensor lines at ISO 100 and the other half at ISO 1600 (or other user-defined values)
 * This trick cleans up shadow noise, resulting in a dynamic range improvement of around 3 stops on 5D3,
 * at the cost of reduced vertical resolution, aliasing and moire.
 * 
 * Requires a camera with two separate analog amplifiers that can be configured separately.
 * At the time of writing, the only two cameras known to have this are 5D Mark III and 7D.
 * 
 * Works for both raw photos (CR2 - postprocess with cr2hdr) and raw videos (raw2dng).
 * 
 * After postprocessing, you get a DNG that looks like an ISO 100 shot,
 * with much cleaner shadows (you can boost the exposure in post at +6EV without getting a lot of noise)
 * 
 * You will not get any radioactive HDR look by default;
 * you will get a normal picture with less shadow noise.
 * 
 * To get the HDR look, you need to use the "HDR from a single raw" trick:
 * develop the DNG file at e.g. 0, +3 and +6 EV and blend the resulting JPEGs with your favorite HDR software
 * (mine is Enfuse)
 * 
 * This technique is very similar to http://www.guillermoluijk.com/article/nonoise/index_en.htm
 * 
 * and Guillermo Luijk's conclusion applies here too:
 * """
 *     But differently as in typical HDR tools that apply local microcontrast and tone mapping procedures,
 *     the described technique seeks to rescue all possible information providing it in a resulting image 
 *     with the same overall and local brightness, contrast and tones as the original. It is now a decision
 *     of each user to choose the way to process and make use of it in the most convenient way.
 * """
 */

/*
 * Copyright (C) 2013 Magic Lantern Team
 * 
 * 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 <module.h>
#include <dryos.h>
#include <property.h>
#include <bmp.h>
#include <menu.h>
#include <config.h>
#include <raw.h>
#include <lens.h>
#include <math.h>
#include <fileprefix.h>
#include <raw.h>

static CONFIG_INT("isoless.hdr", isoless_hdr, 0);
static CONFIG_INT("isoless.iso", isoless_recovery_iso, 3);
static CONFIG_INT("isoless.alt", isoless_alternate, 0);
static CONFIG_INT("isoless.prefix", isoless_file_prefix, 0);

extern WEAK_FUNC(ret_0) int raw_lv_is_enabled();
extern WEAK_FUNC(ret_0) int get_dxo_dynamic_range();
extern WEAK_FUNC(ret_0) int is_play_or_qr_mode();
extern WEAK_FUNC(ret_0) int raw_hist_get_percentile_level();
extern WEAK_FUNC(ret_0) int raw_hist_get_overexposure_percentage();
extern WEAK_FUNC(ret_0) void raw_lv_request();
extern WEAK_FUNC(ret_0) void raw_lv_release();
extern WEAK_FUNC(ret_0) float raw_to_ev(int ev);

int dual_iso_set_enabled(bool enabled);
int dual_iso_is_enabled();
int dual_iso_is_active();

/* camera-specific constants */

static int is_7d = 0;
static int is_5d2 = 0;
static int is_50d = 0;
static int is_6d = 0;
static int is_60d = 0; 
static int is_500d = 0;
static int is_550d = 0;
static int is_600d = 0;
static int is_650d = 0;
static int is_700d = 0;
static int is_eosm = 0;
static int is_1100d = 0;

static uint32_t FRAME_CMOS_ISO_START = 0;
static uint32_t FRAME_CMOS_ISO_COUNT = 0;
static uint32_t FRAME_CMOS_ISO_SIZE = 0;

static uint32_t PHOTO_CMOS_ISO_START = 0;
static uint32_t PHOTO_CMOS_ISO_COUNT = 0;
static uint32_t PHOTO_CMOS_ISO_SIZE = 0;

static uint32_t CMOS_ISO_BITS = 0;
static uint32_t CMOS_FLAG_BITS = 0;
static uint32_t CMOS_EXPECTED_FLAG = 0;

#define CMOS_ISO_MASK ((1 << CMOS_ISO_BITS) - 1)
#define CMOS_FLAG_MASK ((1 << CMOS_FLAG_BITS) - 1)

/* CBR macros (to know where they were called from) */
#define CTX_SHOOT_TASK 0
#define CTX_SET_RECOVERY_ISO 1

static int isoless_recovery_iso_index()
{
    /* CHOICES("-6 EV", "-5 EV", "-4 EV", "-3 EV", "-2 EV", "-1 EV", "+1 EV", "+2 EV", "+3 EV", "+4 EV", "+5 EV", "+6 EV", "100", "200", "400", "800", "1600", "3200", "6400", "12800") */

    int max_index = MAX(FRAME_CMOS_ISO_COUNT, PHOTO_CMOS_ISO_COUNT) - 1;
    
    /* absolute mode */
    if (isoless_recovery_iso >= 0)
        return COERCE(isoless_recovery_iso, 0, max_index);
    
    /* relative mode */

    /* auto ISO? idk, fall back to 100 */
    if (lens_info.raw_iso == 0)
        return 0;
    
    int delta = isoless_recovery_iso < -6 ? isoless_recovery_iso + 6 : isoless_recovery_iso + 7;
    int canon_iso_index = (lens_info.iso_analog_raw - 72) / 8;
    return COERCE(canon_iso_index + delta, 0, max_index);
}

int dual_iso_calc_dr_improvement(int iso1, int iso2)
{
    int iso_hi = MAX(iso1, iso2);
    int iso_lo = MIN(iso1, iso2);

    int dr_hi = get_dxo_dynamic_range(iso_hi);
    int dr_lo = get_dxo_dynamic_range(iso_lo);
    int dr_gained = (iso_hi - iso_lo) / 8 * 100;
    int dr_lost = dr_lo - dr_hi;
    int dr_total = dr_gained - dr_lost;
    
    return dr_total;
}

int dual_iso_get_dr_improvement()
{
    if (!dual_iso_is_active())
        return 0;
    
    int iso1 = 72 + isoless_recovery_iso_index() * 8;
    int iso2 = lens_info.iso_analog_raw/8*8;
    return dual_iso_calc_dr_improvement(iso1, iso2);
}

/* 7D: transfer data to/from master memory */
extern WEAK_FUNC(ret_0) uint32_t BulkOutIPCTransfer(int type, uint8_t *buffer, int length, uint32_t master_addr, void (*cb)(uint32_t*, uint32_t, uint32_t), uint32_t cb_parm);
extern WEAK_FUNC(ret_0) uint32_t BulkInIPCTransfer(int type, uint8_t *buffer, int length, uint32_t master_addr, void (*cb)(uint32_t*, uint32_t, uint32_t), uint32_t cb_parm);

static uint8_t* local_buf = 0;

static void bulk_cb(uint32_t *parm, uint32_t address, uint32_t length)
{
    *parm = 0;
}

static int isoless_enable(uint32_t start_addr, int size, int count, uint16_t* backup)
{
        /* for 7D */
        int start_addr_0 = start_addr;
        
        if (is_7d) /* start_addr is on master */
        {
            volatile uint32_t wait = 1;
            BulkInIPCTransfer(0, local_buf, size * count, start_addr, &bulk_cb, (uint32_t) &wait);
            while(wait) msleep(20);
            start_addr = (uint32_t) local_buf + 2; /* our numbers are aligned at 16 bits, but not at 32 */
        }
    
        /* sanity check first */
        
        int prev_iso = 0;
        for (int i = 0; i < count; i++)
        {
            uint16_t raw = *(uint16_t*)(start_addr + i * size);
            uint32_t flag = raw & CMOS_FLAG_MASK;
            int iso1 = (raw >> CMOS_FLAG_BITS) & CMOS_ISO_MASK;
            int iso2 = (raw >> (CMOS_FLAG_BITS + CMOS_ISO_BITS)) & CMOS_ISO_MASK;
            int reg  = (raw >> 12) & 0xF;

            if (reg != 0 && !is_6d)
                return reg;
            
            if (flag != CMOS_EXPECTED_FLAG)
                return 2;
            
            if (is_5d2)
                iso2 += iso1; /* iso2 is 0 by default */
            
            if (iso1 != iso2)
                return 3;
            
            if ( (iso1 < prev_iso) && !is_50d && !is_500d) /* the list should be ascending */
                return 4;
            
            prev_iso = iso1;
        }
        
        /* backup old values */
        for (int i = 0; i < count; i++)
        {
            uint16_t raw = *(uint16_t*)(start_addr + i * size);
            backup[i] = raw;
        }
        
        /* apply our custom amplifier gains */
        for (int i = 0; i < count; i++)
        {
            uint16_t raw = *(uint16_t*)(start_addr + i * size);
            int my_raw = backup[COERCE(isoless_recovery_iso_index(), 0, count-1)];
            
            if (is_5d2)
            {
                /* iso2 is 0 by default, but our algorithm expects two identical values => let's mangle them */
                int iso1 = (raw >> CMOS_FLAG_BITS) & CMOS_ISO_MASK;
                int my_iso1 = (my_raw >> CMOS_FLAG_BITS) & CMOS_ISO_MASK;
                raw |= (iso1 << (CMOS_FLAG_BITS + CMOS_ISO_BITS));
                my_raw |= (my_iso1 << (CMOS_FLAG_BITS + CMOS_ISO_BITS));
                
                /* enable the dual ISO flag */
                raw |= 1 << (CMOS_FLAG_BITS + CMOS_ISO_BITS + CMOS_ISO_BITS);
            }


            int my_iso2 = (my_raw >> (CMOS_FLAG_BITS + CMOS_ISO_BITS)) & CMOS_ISO_MASK;
            raw &= ~(CMOS_ISO_MASK << (CMOS_FLAG_BITS + CMOS_ISO_BITS));
            raw |= (my_iso2 << (CMOS_FLAG_BITS + CMOS_ISO_BITS));

            if (is_eosm || is_650d || is_700d) //TODO: This hack is probably needed on EOSM and 100D
            {
                raw &= 0x7FF; // Clear the MSB to fix line-skipping. 1 -> 8 lines, 0 -> 4 lines
            }  

            *(uint16_t*)(start_addr + i * size) = raw;
        }

        if (is_7d) /* commit the changes on master */
        {
            volatile uint32_t wait = 1;
            BulkOutIPCTransfer(0, (uint8_t*)start_addr - 2, size * count, start_addr_0, &bulk_cb, (uint32_t) &wait);
            while(wait) msleep(20);
        }

        /* success */
        return 0;
}

static int isoless_disable(uint32_t start_addr, int size, int count, uint16_t* backup)
{
    /* for 7D */
    int start_addr_0 = start_addr;
    
    if (is_7d) /* start_addr is on master */
    {
        volatile uint32_t wait = 1;
        BulkInIPCTransfer(0, local_buf, size * count, start_addr, &bulk_cb, (uint32_t) &wait);
        while(wait) msleep(20);
        start_addr = (uint32_t) local_buf + 2;
    }

    /* just restore saved values */
    for (int i = 0; i < count; i++)
    {
        *(uint16_t*)(start_addr + i * size) = backup[i];
    }

    if (is_7d) /* commit the changes on master */
    {
        volatile uint32_t wait = 1;
        BulkOutIPCTransfer(0, (uint8_t*)start_addr - 2, size * count, start_addr_0, &bulk_cb, (uint32_t) &wait);
        while(wait) msleep(20);
    }

    /* success */
    return 0;
}

static struct semaphore * isoless_sem = 0;

/* Photo mode: always enable */
/* LiveView: only enable in movie mode */
/* Refresh the parameters whenever you change something from menu */
static int enabled_lv = 0;
static int enabled_ph = 0;

/* thread safe */
static unsigned int isoless_refresh(unsigned int ctx)
{
    if (!job_state_ready_to_take_pic())
        return 0;

    take_semaphore(isoless_sem, 0);

    static uint16_t backup_lv[20];
    static uint16_t backup_ph[20];
    int mv = is_movie_mode() ? 1 : 0;
    int lvi = lv ? 1 : 0;
    int raw_mv = mv && lv && raw_lv_is_enabled();
    int raw_ph = (pic_quality & 0xFE00FF) == (PICQ_RAW & 0xFE00FF);
    
    if (FRAME_CMOS_ISO_COUNT > COUNT(backup_ph)) goto end;
    if (PHOTO_CMOS_ISO_COUNT > COUNT(backup_lv)) goto end;
    
    static int prev_sig = 0;
    int sig = isoless_recovery_iso + (lvi << 16) + (raw_mv << 17) + (raw_ph << 18) + (isoless_hdr << 24) + (isoless_alternate << 25) + (isoless_file_prefix << 26) + get_shooting_card()->file_number * isoless_alternate + lens_info.raw_iso * 1234;
    int setting_changed = (sig != prev_sig);
    prev_sig = sig;
    
    if (enabled_lv && setting_changed)
    {
        isoless_disable(FRAME_CMOS_ISO_START, FRAME_CMOS_ISO_SIZE, FRAME_CMOS_ISO_COUNT, backup_lv);
        enabled_lv = 0;
    }
    
    if (enabled_ph && setting_changed)
    {
        isoless_disable(PHOTO_CMOS_ISO_START, PHOTO_CMOS_ISO_SIZE, PHOTO_CMOS_ISO_COUNT, backup_ph);
        enabled_ph = 0;
    }

    if (isoless_hdr && raw_ph && !enabled_ph && PHOTO_CMOS_ISO_START && ((get_shooting_card()->file_number % 2) || !isoless_alternate))
    {
        enabled_ph = 1;
        int err = isoless_enable(PHOTO_CMOS_ISO_START, PHOTO_CMOS_ISO_SIZE, PHOTO_CMOS_ISO_COUNT, backup_ph);
        if (err) { NotifyBox(10000, "ISOless PH err(%d)", err); enabled_ph = 0; }
    }
    
    if (isoless_hdr && raw_mv && !enabled_lv && FRAME_CMOS_ISO_START)
    {
        enabled_lv = 1;
        int err = isoless_enable(FRAME_CMOS_ISO_START, FRAME_CMOS_ISO_SIZE, FRAME_CMOS_ISO_COUNT, backup_lv);
        if (err) { NotifyBox(10000, "ISOless LV err(%d)", err); enabled_lv = 0; }
    }

    if (setting_changed)
    {
        /* hack: this may be executed when file_number is updated;
         * if so, it will rename the previous picture, captured with the old setting,
         * so it will mis-label the pics */
        int file_prefix_needs_delay = (ctx == CTX_SHOOT_TASK && lens_info.job_state);

        int iso1 = 72 + isoless_recovery_iso_index() * 8;
        int iso2 = lens_info.iso_analog_raw/8*8;

        static int prefix_key = 0;
        if (isoless_file_prefix && enabled_ph && iso1 != iso2)
        {
            if (!prefix_key)
            {
                //~ NotifyBox(1000, "DUAL");
                if (file_prefix_needs_delay) msleep(500);
                prefix_key = file_prefix_set("DUAL");
            }
        }
        else if (prefix_key)
        {
            if (file_prefix_needs_delay) msleep(500);
            if (file_prefix_reset(prefix_key))
            {
                //~ NotifyBox(1000, "IMG_");
                prefix_key = 0;
            }
        }
    }

end:
    give_semaphore(isoless_sem);
    return 0;
}

int dual_iso_set_enabled(bool enabled)
{
    if (enabled)
        isoless_hdr = 1; 
    else
        isoless_hdr = 0;

    return 1; // module is loaded & responded != ret_0
}

int dual_iso_is_enabled()
{
    return isoless_hdr;
}

int dual_iso_is_active()
{
    return is_movie_mode() ? enabled_lv : enabled_ph;
}

int dual_iso_get_recovery_iso()
{
    if (!dual_iso_is_active())
        return 0;
    
    return 72 + isoless_recovery_iso_index() * 8;
}

int dual_iso_set_recovery_iso(int iso)
{
    if (!dual_iso_is_active())
        return 0;
    
    int max_index = MAX(FRAME_CMOS_ISO_COUNT, PHOTO_CMOS_ISO_COUNT) - 1;
    isoless_recovery_iso = COERCE((iso - 72)/8, 0, max_index);

    /* apply the new settings right now */
    isoless_refresh(CTX_SET_RECOVERY_ISO);
    return 1;
}

static unsigned int isoless_playback_fix(unsigned int ctx)
{
    if (is_7d || is_1100d)
        return 0; /* seems to cause problems, figure out why */
    
    if (!isoless_hdr) return 0;
    if (!is_play_or_qr_mode()) return 0;
    
    static int aux = INT_MIN;
    if (!should_run_polling_action(1000, &aux))
        return 0;

    uint32_t* lv = (uint32_t*)get_yuv422_vram()->vram;
    if (!lv) return 0;

    /* try to guess the period of alternating lines */
    int avg[5];
    int best_score = 0;
    int period = 0;
    int max_i = 0;
    int min_i = 0;
    int max_b = 0;
    int min_b = 0;
    for (int rep = 2; rep <= 5; rep++)
    {
        /* compute average brightness for each line group */
        for (int i = 0; i < rep; i++)
            avg[i] = 0;
        
        int num = 0;
        for(int y = os.y0; y < os.y_max; y ++ )
        {
            for (int x = os.x0; x < os.x_max; x += 32)
            {
                uint32_t uyvy = lv[BM2LV(x,y)/4];
                int luma = (((((uyvy) >> 24) & 0xFF) + (((uyvy) >> 8) & 0xFF)) >> 1);
                avg[y % rep] += luma;
                num++;
            }
        }
        
        /* choose the group with max contrast */
        int min = INT_MAX;
        int max = INT_MIN;
        int mini = 0;
        int maxi = 0;
        for (int i = 0; i < rep; i++)
        {
            avg[i] = avg[i] * rep / num;
            if (avg[i] < min)
            {
                min = avg[i];
                mini = i;
            }
            if (avg[i] > max)
            {
                max = avg[i];
                maxi = i;
            }
        }

        int score = max - min;
        if (score > best_score)
        {
            period = rep;
            best_score = score;
            min_i = mini;
            max_i = maxi;
            max_b = max;
            min_b = min;
        }
    }
    
    if (best_score < 5)
        return 0;

    /* alternate between bright and dark exposures */
    static int show_bright = 0;
    show_bright = !show_bright;
    
    /* one exposure too bright or too dark? no point in showing it */
    int forced = 0;
    if (min_b < 10)
        show_bright = 1, forced = 1;
    if (max_b > 245)
        show_bright = 0, forced = 1;

    bmp_printf(FONT_MED, 0, 0, "%s%s", show_bright ? "Bright" : "Dark", forced ? " only" : "");

    /* only keep one line from each group (not optimal for resolution, but doesn't have banding) */
    for(int y = os.y0; y < os.y_max; y ++ )
    {
        uint32_t* bright = &(lv[BM2LV_R(y)/4]);
        int dark_y = y/period*period + (show_bright ? max_i : min_i);
        if (dark_y < 0) continue;
        if (y == dark_y) continue;
        uint32_t* dark = &(lv[BM2LV_R(dark_y)/4]);
        memcpy(bright, dark, vram_lv.pitch);
    }
    return 0;
}

static MENU_UPDATE_FUNC(isoless_check)
{
    int iso1 = 72 + isoless_recovery_iso_index() * 8;
    int iso2 = lens_info.iso_analog_raw/8*8;
    
    if (!iso2)
        MENU_SET_WARNING(MENU_WARN_ADVICE, "Auto ISO => cannot estimate dynamic range.");
    
    if (!iso2 && iso1 < 0)
        MENU_SET_WARNING(MENU_WARN_NOT_WORKING, "Auto ISO => cannot use relative recovery ISO.");
    
    if (iso1 == iso2)
        MENU_SET_WARNING(MENU_WARN_INFO, "Both ISOs are identical, nothing to do.");
    
    if (iso1 && iso2 && ABS(iso1 - iso2) > 8 * (is_movie_mode() ? MIN(FRAME_CMOS_ISO_COUNT-2, 3) : MIN(PHOTO_CMOS_ISO_COUNT-2, 4)))
        MENU_SET_WARNING(MENU_WARN_INFO, "Consider using a less aggressive setting (e.g. 100/800).");

    if (!get_dxo_dynamic_range(72))
        MENU_SET_WARNING(MENU_WARN_ADVICE, "No dynamic range info available.");

    int mvi = is_movie_mode();

    int raw = mvi ? FRAME_CMOS_ISO_START && raw_lv_is_enabled() : ((pic_quality & 0xFE00FF) == (PICQ_RAW & 0xFE00FF));

    if (!raw)
        menu_set_warning_raw(entry, info);
}

static MENU_UPDATE_FUNC(isoless_dr_update)
{
    isoless_check(entry, info);
    if (info->warning_level >= MENU_WARN_ADVICE)
    {
        MENU_SET_VALUE("N/A");
        return;
    }
    
    int dr_improvement = dual_iso_get_dr_improvement() / 10;
    
    MENU_SET_VALUE("%d.%d EV", dr_improvement/10, dr_improvement%10);
}

static MENU_UPDATE_FUNC(isoless_overlap_update)
{
    int iso1 = 72 + isoless_recovery_iso_index() * 8;
    int iso2 = (lens_info.iso_analog_raw)/8*8;

    int iso_hi = MAX(iso1, iso2);
    int iso_lo = MIN(iso1, iso2);
    
    isoless_check(entry, info);
    if (info->warning_level >= MENU_WARN_ADVICE)
    {
        MENU_SET_VALUE("N/A");
        return;
    }
    
    int iso_diff = (iso_hi - iso_lo) * 10/ 8;
    int dr_lo = (get_dxo_dynamic_range(iso_lo)+5)/10;
    int overlap = dr_lo - iso_diff;
    
    MENU_SET_VALUE("%d.%d EV", overlap/10, overlap%10);
}

static MENU_UPDATE_FUNC(isoless_update)
{
    if (!isoless_hdr)
        return;

    int iso1 = 72 + isoless_recovery_iso_index() * 8;
    int iso2 = (lens_info.iso_analog_raw)/8*8;

    MENU_SET_VALUE("%d/%d", raw2iso(iso2), raw2iso(iso1));

    isoless_check(entry, info);
    if (info->warning_level >= MENU_WARN_ADVICE)
        return;
    
    int dr_improvement = dual_iso_get_dr_improvement() / 10;
    
    MENU_SET_RINFO("DR+%d.%d", dr_improvement/10, dr_improvement%10);
}

static struct menu_entry isoless_menu[] =
{
    {
        .name = "Dual ISO",
        .priv = &isoless_hdr,
        .update = isoless_update,
        .max = 1,
        .help  = "Alternate ISO for every 2 sensor scan lines.",
        .help2 = "With some clever post, you get less shadow noise (more DR).",
        .submenu_width = 710,
        .children =  (struct menu_entry[]) {
            {
                .name = "Recovery ISO",
                .priv = &isoless_recovery_iso,
                .update = isoless_check,
                .min = -12,
                .max = 6,
                .unit = UNIT_ISO,
                .choices = CHOICES("-6 EV", "-5 EV", "-4 EV", "-3 EV", "-2 EV", "-1 EV", "+1 EV", "+2 EV", "+3 EV", "+4 EV", "+5 EV", "+6 EV", "100", "200", "400", "800", "1600", "3200", "6400"),
                .help  = "ISO for half of the scanlines (usually to recover shadows).",
                .help2 = "Can be absolute or relative to primary ISO from Canon menu.",
            },
            {
                .name = "Dynamic range gained",
                .update = isoless_dr_update,
                .icon_type = IT_ALWAYS_ON,
                .help  = "[READ-ONLY] How much more DR you get with current settings",
                .help2 = "(upper theoretical limit, estimated from DxO measurements)",
            },
            {
                .name = "Midtone overlapping",
                .update = isoless_overlap_update,
                .icon_type = IT_ALWAYS_ON,
                .help  = "[READ-ONLY] How much of midtones will get better resolution",
                .help2 = "Highlights/shadows will be half res, with aliasing/moire.",
            },
            {
                .name = "Alternate frames only",
                .priv = &isoless_alternate,
                .max = 1,
                .help = "Shoot one image with the hack, one without.",
            },
            {
                .name = "Custom file prefix",
                .priv = &isoless_file_prefix,
                .max = 1,
                .choices = CHOICES("OFF", "DUAL (unreliable!)"),
                .help  = "Change file prefix for dual ISO photos (e.g. DUAL0001.CR2).",
                .help2 = "Will not sync properly in burst mode or when taking pics quickly."
            },
            MENU_EOL,
        },
    },
};

static unsigned int isoless_init()
{
    if (is_camera("5D3", "1.1.3") || is_camera("5D3", "1.2.3"))
    {
        FRAME_CMOS_ISO_START = 0x40452C72; // CMOS register 0000 - for LiveView, ISO 100 (check in movie mode, not photo!)
        FRAME_CMOS_ISO_COUNT =          9; // from ISO 100 to 25600
        FRAME_CMOS_ISO_SIZE  =         30; // distance between ISO 100 and ISO 200 addresses, in bytes

        PHOTO_CMOS_ISO_START = 0x40451120; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          8; // from ISO 100 to 12800
        PHOTO_CMOS_ISO_SIZE  =         18; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 4;
        CMOS_FLAG_BITS = 4;
        CMOS_EXPECTED_FLAG = 3;
    }
    else if (is_camera("7D", "2.0.3"))
    {
        is_7d = 1;
        
        PHOTO_CMOS_ISO_START = 0x406944f4; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        PHOTO_CMOS_ISO_SIZE  =         14; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 0;
        
        local_buf = fio_malloc(PHOTO_CMOS_ISO_COUNT * PHOTO_CMOS_ISO_SIZE + 4);
    }
    else if (is_camera("5D2", "2.1.2"))
    {
        is_5d2 = 1;
        
        PHOTO_CMOS_ISO_START = 0x404b3b5c; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          5; // from ISO 100 to 1600
        PHOTO_CMOS_ISO_SIZE  =         14; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 3;
    }
    else if (is_camera("6D", "1.1.6"))
    {
        is_6d = 1;

        FRAME_CMOS_ISO_START = 0x40452196; // CMOS register 0003 - for LiveView, ISO 100 (check in movie mode, not photo!)
        FRAME_CMOS_ISO_COUNT =          7; // from ISO 100 to 6400
        FRAME_CMOS_ISO_SIZE  =         32; // distance between ISO 100 and ISO 200 addresses, in bytes

        PHOTO_CMOS_ISO_START = 0x40450E08; // CMOS register 0003 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          7; // from ISO 100 to 6400 (last real iso!)
        PHOTO_CMOS_ISO_SIZE  =         18; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 4;
        CMOS_FLAG_BITS = 0;
        CMOS_EXPECTED_FLAG = 0;
    }
    else if (is_camera("50D", "1.0.9"))
    {  
        // 100 - 0x04 - 160 - 0x94
        /* 00:00:04.078911     100   0004 404B548E */
        /* 00:00:14.214376     160   0094 404B549C */
        /* 00:00:26.551116     320   01B4 404B54AA */
        /*                     640   01FC 404B54B8 */
        /* 00:00:47.349194     1250+ 016C 404B54C6 */

        is_50d = 1;    

        PHOTO_CMOS_ISO_START = 0x404B548E; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          5; // from ISO 100 to 1600
        PHOTO_CMOS_ISO_SIZE  =         14; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 3;
        CMOS_EXPECTED_FLAG = 4;
    }
    else if (is_camera("60D", "1.1.1"))
    {  
        /*
        100 - 0
        200 - 0x024
        400 - 0x048
        800 - 0x06c
        1600 -0x090
        3200 -0x0b4
        */
        is_60d = 1;    

        FRAME_CMOS_ISO_START = 0x407458fc; // CMOS register 0000 - for LiveView, ISO 100 (check in movie mode, not photo!)
        FRAME_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        FRAME_CMOS_ISO_SIZE  =         30; // distance between ISO 100 and ISO 200 addresses, in bytes

        PHOTO_CMOS_ISO_START = 0x4074464c; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        PHOTO_CMOS_ISO_SIZE  =         18; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 0; 
    }
    else if (is_camera("500D", "1.1.1"))
    {  
        is_500d = 1;    

        PHOTO_CMOS_ISO_START = 0x405C56C2; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          5; // from ISO 100 to 1600
        PHOTO_CMOS_ISO_SIZE  =         14; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 3;
        CMOS_EXPECTED_FLAG = 0;
    }
    else if (is_camera("550D", "1.0.9"))
    {
    	is_550d = 1;
		
		FRAME_CMOS_ISO_START = 0x40695494; // CMOS register 0000 - for LiveView, ISO 100 (check in movie mode, not photo!)
		FRAME_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
		FRAME_CMOS_ISO_SIZE  =         30; // distance between ISO 100 and ISO 200 addresses, in bytes
		
		//  00 0000 406941E4  = 100
		//  00 0024 406941F6  = 200
		//  00 0048 40694208  = 400
		//  00 006C 4069421A  = 800
		//  00 0090 4069422C  = 1600
		//  00 00B4 4069423E  = 3200
		
		PHOTO_CMOS_ISO_START = 0x406941E4; // CMOS register 0000 - for photo mode, ISO 100
		PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
		PHOTO_CMOS_ISO_SIZE  =         18; // distance between ISO 100 and ISO 200 addresses, in bytes
		
		CMOS_ISO_BITS = 3;
		CMOS_FLAG_BITS = 2;
		CMOS_EXPECTED_FLAG = 0;
    }
    else if (is_camera("600D", "1.0.2"))
    {  
        /*
        100 - 0
        200 - 0x024
        400 - 0x048
        800 - 0x06c
        1600 -0x090
        3200 -0x0b4
        */
        is_600d = 1;    

        FRAME_CMOS_ISO_START = 0x406957C8; // CMOS register 0000 - for LiveView, ISO 100 (check in movie mode, not photo!)
        FRAME_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        FRAME_CMOS_ISO_SIZE  =         30; // distance between ISO 100 and ISO 200 addresses, in bytes

        PHOTO_CMOS_ISO_START = 0x4069464C; // CMOS register 0000 - for photo mode, ISO 100
        PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        PHOTO_CMOS_ISO_SIZE  =         18; // distance between ISO 100 and ISO 200 addresses, in bytes

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 0;
    }
    else if (is_camera("700D", "1.1.4"))
    {
        is_700d = 1;    

        FRAME_CMOS_ISO_START = 0x4045328E;
        FRAME_CMOS_ISO_COUNT =          6;
        FRAME_CMOS_ISO_SIZE  =         34;

        PHOTO_CMOS_ISO_START = 0x40452044;
        PHOTO_CMOS_ISO_COUNT =          6;
        PHOTO_CMOS_ISO_SIZE  =         16;

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 3;
    }
    else if (is_camera("650D", "1.0.4"))
    {
        is_650d = 1;    

        FRAME_CMOS_ISO_START = 0x404a038e;
        FRAME_CMOS_ISO_COUNT =          6;
        FRAME_CMOS_ISO_SIZE  =       0x22;

        PHOTO_CMOS_ISO_START = 0x4049f144;
        PHOTO_CMOS_ISO_COUNT =          6;
        PHOTO_CMOS_ISO_SIZE  =       0x10;

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 3;
    }

    else if (is_camera("EOSM", "2.0.2"))
    {
        is_eosm = 1;    
        
        /*   00 0803 40502516 */
		/*   00 0827 40502538 */
		/*   00 084B 4050255A */
		/*   00 086F 4050257C */
		/*   00 0893 4050259E */
		/*   00 08B7 405025C0 */


        FRAME_CMOS_ISO_START = 0x40482516;
        FRAME_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        FRAME_CMOS_ISO_SIZE  =         34;


        /*
        00 0803 4050124C
        00 0827 4050125C
        00 084B 4050126C
        00 086F 4050127C
        00 0893 4050128C
        00 08B7 4050129C
        */

        PHOTO_CMOS_ISO_START = 0x4048124C;
        PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to 3200
        PHOTO_CMOS_ISO_SIZE  =         16;

        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 2;
        CMOS_EXPECTED_FLAG = 3;
    }
    else if (is_camera("1100D", "1.0.5"))
    {
        is_1100d = 1;
        /*
         100 - 0     0x407444B2
         200 - 0x120 0x407444C6
         400 - 0x240 0x407444DA
         800 - 0x360 0x407444EE
         1600 -0x480 0x40744502
         3200 -0x5A0 0x40744516
         */
        
        PHOTO_CMOS_ISO_START = 0x407444B2; // CMOS register 00    00 - for photo mode, ISO
        PHOTO_CMOS_ISO_COUNT =          6; // from ISO 100 to     3200
        PHOTO_CMOS_ISO_SIZE  =         20; // distance between     ISO 100 and ISO 200 addresses, in bytes
        
        CMOS_ISO_BITS = 3;
        CMOS_FLAG_BITS = 5;
        CMOS_EXPECTED_FLAG = 0;
    }




    if (FRAME_CMOS_ISO_START || PHOTO_CMOS_ISO_START)
    {
        menu_add("Expo", isoless_menu, COUNT(isoless_menu));
    }
    else
    {
        isoless_hdr = 0;
        return 1;
    }
    return 0;
}

static unsigned int isoless_deinit()
{
    return 0;
}

MODULE_INFO_START()
    MODULE_INIT(isoless_init)
    MODULE_DEINIT(isoless_deinit)
MODULE_INFO_END()

MODULE_CBRS_START()
    MODULE_CBR(CBR_SHOOT_TASK, isoless_refresh, CTX_SHOOT_TASK)
    MODULE_CBR(CBR_SHOOT_TASK, isoless_playback_fix, CTX_SHOOT_TASK)
MODULE_CBRS_END()

MODULE_CONFIGS_START()
    MODULE_CONFIG(isoless_hdr)
    MODULE_CONFIG(isoless_recovery_iso)
    MODULE_CONFIG(isoless_alternate)
    MODULE_CONFIG(isoless_file_prefix)
MODULE_CONFIGS_END()
back to top