/** \file * Zebra stripes, contrast edge detection and crop marks. * */ /* * Copyright (C) 2009 Trammell Hudson * Edge detection code by Robert Thiel * * 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 "zebra.h" #include "dryos.h" #include "bmp.h" #include "version.h" #include "config.h" #include "menu.h" #include "property.h" #include "gui.h" #include "lens.h" #include "math.h" #include "imgconv.h" #include "histogram.h" #include "falsecolor.h" #define ext_monitor_rca 0 static void waveform_init(); //~ static void histo_init(); static void do_disp_mode_change(); static void show_overlay(); static void transparent_overlay_from_play(); static void transparent_overlay_offset_clear(void* priv, int delta); //~ static void draw_histogram_and_waveform(); static void schedule_transparent_overlay(); //~ static void defish_draw(); //~ static void defish_draw_lv_color(); static int zebra_color_word_row(int c, int y); static void spotmeter_step(); static void cropmark_cache_update_signature(); static int cropmark_cache_is_valid(); static void default_movie_cropmarks(); static void black_bars_16x9(); static void black_bars(); //~ static void defish_draw_play(); extern unsigned int log_length(int x); extern void zoom_sharpen_step(); extern void bv_auto_update(); void lens_display_set_dirty(){}; void cropmark_clear_cache(); void draw_histogram_and_waveform(int); void update_disp_mode_bits_from_params(); void uyvy2yrgb(uint32_t , int* , int* , int* , int* ); int toggle_disp_mode(); void toggle_disp_mode_menu(void *priv, int delta); int get_zoom_overlay_trigger_mode() { return 0; } void zoom_overlay_set_countdown(){} int lv_luma_is_accurate() { return 1; } int should_draw_bottom_graphs() { return 0; } void bmp_mute_flag_reset(){} void PauseLiveView(){}; void ResumeLiveView(){}; void play_422(){}; int get_zoom_overlay_trigger_by_focus_ring(){ return 0; } int get_disp_mode() { return 0; } void bmp_off(){}; void bmp_on(){}; void clear_zebras_from_mirror(){}; void copy_zebras_from_mirror(){}; void cropmark_clear_cache(){}; void update_disp_mode_bits_from_params(){}; int disp_profiles_0 = 0; // color info in 5Dc is not quite yuv422... but almost there (it's probably yuv411) // uYvY yYuY vYyY // => uYvY uYvY uYvY static int bmp_is_on() { return 1; } #define hist_height 54 #define HIST_WIDTH 128 #define WAVEFORM_WIDTH 180 #define WAVEFORM_HEIGHT 120 #define WAVEFORM_FACTOR (1 << waveform_size) // 1, 2 or 4 #define WAVEFORM_OFFSET (waveform_size <= 1 ? 80 : 0) #define WAVEFORM_FULLSCREEN (waveform_draw && waveform_size == 2) #define BVRAM_MIRROR_SIZE (BMPPITCH*540) //~ static CONFIG_INT( "global.draw", global_draw, 1 ); #define global_draw 1 static CONFIG_INT( "global.draw.mode", global_draw_mode, 0 ); PROP_HANDLER(PROP_GUI_STATE) { if (global_draw_mode && buf[0] == GUISTATE_QR) { fake_simple_button(BGMT_PRESS_DIRECT_PRINT); } } static CONFIG_INT( "zebra.draw", zebra_draw, 1 ); static CONFIG_INT( "zebra.colorspace", zebra_colorspace, 0 );// luma/rgb/lumafast static CONFIG_INT( "zebra.thr.hi", zebra_level_hi, 99 ); static CONFIG_INT( "zebra.thr.lo", zebra_level_lo, 1 ); CONFIG_INT( "zebra.rec", zebra_rec, 1 ); int should_draw_zoom_overlay() { return false; } int digic_zoom_overlay_enabled() { return 0; } int nondigic_zoom_overlay_enabled() { return 0; } static CONFIG_INT( "focus.peaking", focus_peaking, 1); //~ static CONFIG_INT( "focus.peaking.method", focus_peaking_method, 1); static CONFIG_INT( "focus.peaking.filter.edges", focus_peaking_filter_edges, 1); // prefer texture details rather than strong edges static CONFIG_INT( "focus.peaking.disp", focus_peaking_disp, 0); // display as dots or blended static CONFIG_INT( "focus.peaking.thr", focus_peaking_pthr, 5); // 1% static CONFIG_INT( "focus.peaking.color", focus_peaking_color, 7); // R,G,B,C,M,Y,cc1,cc2 CONFIG_INT( "focus.peaking.grayscale", focus_peaking_grayscale, 0); // R,G,B,C,M,Y,cc1,cc2 int focus_peaking_as_display_filter() { return lv && focus_peaking && focus_peaking_disp; } int is_focus_peaking_enabled() { return focus_peaking; } //~ static CONFIG_INT( "focus.graph", focus_graph, 0); //~ static CONFIG_INT( "edge.draw", edge_draw, 0 ); static CONFIG_INT( "waveform.draw", waveform_draw, 0); static CONFIG_INT( "waveform.size", waveform_size, 0 ); //~ static CONFIG_INT( "waveform.x", waveform_x, 720 - WAVEFORM_WIDTH ); //~ static CONFIG_INT( "waveform.y", waveform_y, 480 - 50 - WAVEFORM_WIDTH ); static CONFIG_INT( "waveform.bg", waveform_bg, COLOR_ALMOST_BLACK ); // solid black int histogram_or_small_waveform_enabled() { return (hist_draw || (waveform_draw && !waveform_size)) && get_expsim(); } static CONFIG_INT( "vectorscope.draw", vectorscope_draw, 0); /* runtime-configurable size */ uint32_t vectorscope_width = 256; uint32_t vectorscope_height = 256; /* 128 is also a good choice, but 256 is max. U and V are using that resolution */ #define VECTORSCOPE_WIDTH_MAX 256 #define VECTORSCOPE_HEIGHT_MAX 256 CONFIG_INT( "clear.preview", clearscreen_enabled, 0); static CONFIG_INT( "clear.preview.delay", clearscreen_delay, 1000); // ms CONFIG_INT( "clear.preview.mode", clearscreen_mode, 0); // 2 is always // keep old program logic #define clearscreen (clearscreen_enabled ? clearscreen_mode+1 : 0) static CONFIG_INT( "spotmeter.size", spotmeter_size, 5 ); static CONFIG_INT( "spotmeter.draw", spotmeter_draw, 0 ); static CONFIG_INT( "spotmeter.formula", spotmeter_formula, 0 ); // 0 percent, 1 IRE AJ, 2 IRE Piers static CONFIG_INT( "spotmeter.position", spotmeter_position, 1 ); // fixed / attached to AF frame //~ static CONFIG_INT( "unified.loop", unified_loop, 2); // temporary; on/off/auto //~ static CONFIG_INT( "zebra.density", zebra_density, 0); //~ static CONFIG_INT( "hd.vram", use_hd_vram, 0); CONFIG_INT("idle.display.turn_off.after", idle_display_turn_off_after, 0); // this also enables power saving for intervalometer static CONFIG_INT("idle.display.dim.after", idle_display_dim_after, 0); static CONFIG_INT("idle.display.gdraw_off.after", idle_display_global_draw_off_after, 0); static CONFIG_INT("idle.rec", idle_rec, 0); static CONFIG_INT("idle.shortcut.key", idle_shortcut_key, 0); CONFIG_INT("idle.blink", idle_blink, 1); /** * Normal BMP VRAM has its origin in 720x480 center crop * But on HDMI you are allowed to go back 120x30 pixels (BMP_W_MINUS x BMP_H_MINUS). * * For mirror VRAM we'll keep the same addressing mode: * allocate full size (960x540) and use the pointer to 720x480 center crop. */ static uint8_t* bvram_mirror_start = 0; static uint8_t* bvram_mirror = 0; uint8_t* get_bvram_mirror() { return bvram_mirror; } //~ #define bvram_mirror bmp_vram_idle() int cropmark_cache_dirty = 1; int crop_dirty = 0; // redraw cropmarks after some time (unit: 0.1s) int clearscreen_countdown = 20; //~ int recording = 0; void crop_set_dirty(int value) { crop_dirty = MAX(crop_dirty, value); } PROP_HANDLER(PROP_HOUTPUT_TYPE) { extern int ml_started; if (ml_started) redraw(); } #if defined(CONFIG_60D) || defined(CONFIG_600D) volatile int lcd_position = 0; volatile int display_dont_mirror_dirty; PROP_HANDLER(PROP_LCD_POSITION) { if (lcd_position != (int)buf[0]) display_dont_mirror_dirty = 1; lcd_position = buf[0]; redraw_after(100); } #endif /*int gui_state; PROP_HANDLER(PROP_GUI_STATE) { gui_state = buf[0]; if (gui_state == GUISTATE_IDLE) crop_set_dirty(40); return prop_cleanup( token, property ); }*/ static int idle_globaldraw_disable = 0; int get_global_draw() // menu setting, or off if { extern int ml_started; if (!ml_started) return 0; if (!global_draw) return 0; if (PLAY_MODE) return 1; // exception, always draw stuff in play mode return DISPLAY_IS_ON; } int get_global_draw_setting() // whatever is set in menu { return global_draw; } /** Store the waveform data for each of the WAVEFORM_WIDTH bins with * 128 levels */ static uint8_t* waveform = 0; #define WAVEFORM_UNSAFE(x,y) (waveform[(x) + (y) * WAVEFORM_WIDTH]) #define WAVEFORM(x,y) (waveform[COERCE((x), 0, WAVEFORM_WIDTH-1) + COERCE((y), 0, WAVEFORM_HEIGHT-1) * WAVEFORM_WIDTH]) /** Store the histogram data for each of the "HIST_WIDTH" bins */ static uint32_t hist[HIST_WIDTH]; static uint32_t hist_r[HIST_WIDTH]; static uint32_t hist_g[HIST_WIDTH]; static uint32_t hist_b[HIST_WIDTH]; /** Maximum value in the histogram so that at least one entry fills * the box */ static uint32_t hist_max; /** total number of pixels analyzed by histogram */ static uint32_t hist_total_px; static uint8_t *vectorscope = NULL; /* helper to draw pixels at given position. no wrap checks when is greater 1 */ static void vectorscope_putpixel(uint8_t *bmp_buf, int x_pos, int y_pos, uint8_t color, uint8_t count) { int pos = x_pos + y_pos * vectorscope_width; while(count--) { bmp_buf[pos++] = 255 - color; } } /* another helper that draws a color dot at given position. and specify the center of our scope graphic. and are in 1/2048th units and specify the relative dot position. */ static void vectorscope_putblock(uint8_t *bmp_buf, int xc, int yc, uint8_t color, int32_t frac_x, int32_t frac_y) { int x_pos = xc + ((int32_t)vectorscope_width * frac_x) / 4096; int y_pos = yc + (-(int32_t)vectorscope_height * frac_y) / 4096; vectorscope_putpixel(bmp_buf, x_pos + 0, y_pos - 4, color, 1); vectorscope_putpixel(bmp_buf, x_pos + 0, y_pos + 4, color, 1); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos - 3, color, 7); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos - 2, color, 7); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos - 1, color, 7); vectorscope_putpixel(bmp_buf, x_pos - 4, y_pos + 0, color, 9); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos + 1, color, 7); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos + 2, color, 7); vectorscope_putpixel(bmp_buf, x_pos - 3, y_pos + 3, color, 7); } /* draws the overlay: circle with color dots. */ void vectorscope_paint(uint8_t *bmp_buf, uint32_t x_origin, uint32_t y_origin) { //int r = vectorscope_height/2 - 1; int xc = x_origin + vectorscope_width/2; int yc = y_origin + vectorscope_height/2; /* red block at U=-14.7% V=61.5% => U=-304/2048th V=1259/2048th */ vectorscope_putblock(bmp_buf, xc, yc, 8, -302, 1259); /* green block */ vectorscope_putblock(bmp_buf, xc, yc, 7, -593, -1055); /* blue block */ vectorscope_putblock(bmp_buf, xc, yc, 9, 895, -204); /* cyan block */ vectorscope_putblock(bmp_buf, xc, yc, 5, 301, -1259); /* magenta block */ vectorscope_putblock(bmp_buf, xc, yc, 14, 592, 1055); /* yellow block */ vectorscope_putblock(bmp_buf, xc, yc, 15, -893, 204); } void vectorscope_clear() { if(vectorscope != NULL) { bzero32(vectorscope, vectorscope_width * vectorscope_height * sizeof(uint8_t)); } } void vectorscope_init() { if(vectorscope == NULL) { vectorscope = malloc(VECTORSCOPE_WIDTH_MAX * VECTORSCOPE_HEIGHT_MAX * sizeof(uint8_t)); vectorscope_clear(); } } static inline void vectorscope_addpixel(uint8_t y, int8_t u, int8_t v) { if(vectorscope == NULL) { return; } int32_t V = -v; int32_t U = u; /* convert YUV to vectorscope position */ V *= vectorscope_height; V >>= 8; V += vectorscope_height >> 1; U *= vectorscope_width; U >>= 8; U += vectorscope_width >> 1; uint16_t pos = U + V * vectorscope_width; /* increase luminance at this position. when reaching 4*0x2A, we are at maximum. */ if(vectorscope[pos] < (0x2A << 2)) { vectorscope[pos]++; } } /* memcpy the second part of vectorscope buffer. uses only few resources */ static void vectorscope_draw_image(uint32_t x_origin, uint32_t y_origin) { if(vectorscope == NULL) { return; } uint8_t * const bvram = bmp_vram(); if (!bvram) { return; } vectorscope_paint(vectorscope, 0, 0); for(uint32_t y = 0; y < vectorscope_height; y++) { int ys = y * 8/9; for(uint32_t x = 0; x < vectorscope_width; x++) { uint8_t brightness = vectorscope[x + y*vectorscope_width]; int xc = x - vectorscope_height/2; int yc = y - vectorscope_height/2; int r = vectorscope_height/2 - 1; int inside_circle = xc*xc + yc*yc < (r-1)*(r-1); int on_circle = !inside_circle && xc*xc + yc*yc <= (r+1)*(r+1); // kdenlive vectorscope: // center: 175,180 // I: 83,38 => dx=-92, dy=142 // Q: 320,87 => dx=145, dy=93 // let's say 660/1024 is a good approximation of the slope // wikipedia image: // center: 318, 294 // I: 171, 68 => 147,226 // Q: 545, 147 => 227,147 // => 663/1024 is a better approximation int on_axis = (x==vectorscope_width/2) || (y==vectorscope_height/2) || (inside_circle && (xc==yc*663/1024 || -xc*663/1024==yc)); int pixel = 0; if (on_circle || (on_axis && brightness==0)) { pixel = 50; bmp_putpixel_fast(bvram, x_origin + x, y_origin + ys, pixel); } else if (inside_circle) { /* paint (semi)transparent when no pixels in this color range */ if (brightness == 0) { pixel = 70; } else if (brightness > 0x26 + 0x2A * 4) { /* some fake fixed color, for overlays */ pixel = 255 - brightness; } else { /* 0x26 is the palette color for black plus max 0x2A until white */ pixel = 0x26 + (brightness >> 2); } bmp_putpixel_fast(bvram, x_origin + x, y_origin + ys, pixel); } } } } /** Generate the histogram data from the YUV frame buffer. * * Walk the frame buffer two pixels at a time, in 32-bit chunks, * to avoid err70 while recording. * * Average two adjacent pixels to try to reduce noise slightly. * * Update the hist_max for the largest number of bin entries found * to scale the histogram to fit the display box from top to * bottom. */ void hist_build() { struct vram_info * lv = get_yuv422_vram(); uint32_t* buf = (uint32_t*)lv->vram; int x,y; hist_max = 0; hist_total_px = 0; for( x=0 ; x> 16) & 0xFF00) >> 8; uint32_t p2 = ((pixel >> 0) & 0xFF00) >> 8; Y = (p1+p2) / 2; } hist_total_px++; uint32_t hist_level = Y * HIST_WIDTH / 256; // Ignore the 0 bin. It generates too much noise unsigned count = ++ (hist[ hist_level & 0x7F]); if( hist_level && count > hist_max ) hist_max = count; // Update the waveform plot if (waveform_draw) { uint8_t* w = &WAVEFORM(((x-os.x0) * WAVEFORM_WIDTH) / os.x_ex, (Y * WAVEFORM_HEIGHT) / 256); if ((*w) < 250) (*w)++; } if (vectorscope_draw) { uint32_t pixel = yuv411_to_422((uint32_t) p); int8_t U = (pixel >> 0) & 0xFF; int8_t V = (pixel >> 16) & 0xFF; vectorscope_addpixel(Y, U, V); } } } } int get_under_and_over_exposure(int thr_lo, int thr_hi, int* under, int* over) { *under = -1; *over = -1; struct vram_info * lv = get_yuv422_vram(); if (!lv) return -1; *under = 0; *over = 0; int total = 0; void* vram = lv->vram; int x,y; for( y = os.y0 ; y < os.y_max; y ++ ) { uint32_t * const v_row = (uint32_t*)( vram + BM2LV_R(y) ); for( x = os.x0 ; x < os.x_max ; x += 2 ) { uint32_t* p = &v_row[x/2]; int Y, R, G, B; yuv411_to_rgb((uint32_t)p, &Y, &R, &G, &B); //~ uyvy2yrgb(pixel, &Y, &R, &G, &B); //~ COMPUTE_UYVY2YRGB(pixel, Y, R, G, B); int M = MAX(MAX(R,G),B); if (*p && Y < thr_lo) (*under)++; // try to ignore black bars if (M > thr_hi) (*over)++; total++; } } return total; } static int hist_rgb_color(int y, int sizeR, int sizeG, int sizeB) { switch ((y > sizeR ? 0 : 1) | (y > sizeG ? 0 : 2) | (y > sizeB ? 0 : 4)) { case 0b000: return COLOR_ALMOST_BLACK; // almost black case 0b001: return COLOR_RED; case 0b010: return 7; // green case 0b100: return 9; // strident blue case 0b011: return COLOR_YELLOW; case 0b110: return 5; // cyan case 0b101: return 14; // magenta case 0b111: return COLOR_WHITE; } return 0; } #define ZEBRA_COLOR_WORD_SOLID(x) ( (x) | (x)<<8 | (x)<<16 | (x)<<24 ) static int zebra_rgb_color(int underexposed, int clipR, int clipG, int clipB, int y) { if (underexposed) return ZEBRA_COLOR_WORD_SOLID(79); switch ((clipR ? 0 : 1) | (clipG ? 0 : 2) | (clipB ? 0 : 4)) { case 0b000: return ZEBRA_COLOR_WORD_SOLID(COLOR_BLACK); case 0b001: return ZEBRA_COLOR_WORD_SOLID(COLOR_RED); case 0b010: return ZEBRA_COLOR_WORD_SOLID(7); // green case 0b100: return ZEBRA_COLOR_WORD_SOLID(9); // strident blue case 0b011: return ZEBRA_COLOR_WORD_SOLID(COLOR_YELLOW); case 0b110: return ZEBRA_COLOR_WORD_SOLID(5); // cyan case 0b101: return ZEBRA_COLOR_WORD_SOLID(14); // magenta case 0b111: return 0; } return 0; } static void hist_dot(int x, int y, int fg_color, int bg_color, int radius, int label) { if (hist_colorspace == 0 || !label) { for (int r = 0; r < radius; r++) { draw_circle(x, y, r, fg_color); draw_circle(x + 1, y, r, fg_color); } draw_circle(x, y, radius, bg_color); } if (label) { if (hist_colorspace == 1) // RGB { char msg[5]; snprintf(msg, sizeof(msg), "%d", label); bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_WHITE, fg_color)), x - font_med.width + 1, y - font_med.height/2, " "); bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_WHITE, fg_color)), x - font_med.width * strlen(msg) / 2 + 1, y - font_med.height/2, msg); } else { bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_BLACK, COLOR_WHITE)), x + 15, y - font_med.height/2, "%d%%", label); } } } static int hist_dot_radius(int over, int hist_total_px) { if (hist_warn <= 4) return 7; // fixed radius for these modes // overexposures stronger than 1% are displayed at max radius (10) int p = 100 * over / hist_total_px; if (p > 1) return 10; // for smaller overexposure percentages, use dot radius to suggest the amount unsigned p1000 = 100 * 1000 * over / hist_total_px; int plog = p1000 ? (int)log2f(p1000) : 0; return MIN(plog, 10); } static int hist_dot_label(int over, int hist_total_px) { int p = 100 * over / hist_total_px; return hist_warn <= 4 ? 0 : p; } /** Draw the waveform image into the bitmap framebuffer. * * Draw one pixel at a time; it seems to be ok with err70. * Since there is plenty of math per pixel this doesn't * swamp the bitmap framebuffer hardware. */ static void waveform_draw_image( unsigned x_origin, unsigned y_origin, unsigned height ) { if (!PLAY_OR_QR_MODE) { if (!lv_luma_is_accurate()) return; } // Ensure that x_origin is quad-word aligned x_origin &= ~3; uint8_t * const bvram = bmp_vram(); // okay if (!bvram) return; unsigned pitch = BMPPITCH; if( hist_max == 0 ) hist_max = 1; int i, y; // vertical line up to the hist size for (int k = 0; k < WAVEFORM_FACTOR; k++) { for( y=WAVEFORM_HEIGHT-1 ; y>=0 ; y-- ) { int ys = y * height / WAVEFORM_HEIGHT + k; //int y_next = (y-1) * height / WAVEFORM_HEIGHT; uint32_t pixel = 0; int w = WAVEFORM_WIDTH*WAVEFORM_FACTOR; for( i=0 ; i 42 - 5 ) count = COLOR_RED; else if( count > 0 ) count += 38 + 5; else // Draw a series of colored scales if( y == (WAVEFORM_HEIGHT*1)/4 ) count = COLOR_BLUE; else if( y == (WAVEFORM_HEIGHT*2)/4 ) count = 0xE; // pink else if( y == (WAVEFORM_HEIGHT*3)/4 ) count = COLOR_BLUE; else count = COLOR_BLACK; // transparent pixel |= (count << ((i & 3)<<3)); if( (i & 3) != 3 ) continue; bmp_putpixel_fast(bvram, x_origin + i, y_origin + ys, pixel); bmp_putpixel_fast(bvram, x_origin + i + 1, y_origin + ys, pixel>>8); bmp_putpixel_fast(bvram, x_origin + i + 2, y_origin + ys, pixel>>16); bmp_putpixel_fast(bvram, x_origin + i + 3, y_origin + ys, pixel>>24); pixel = 0; } } bmp_draw_rect(60, x_origin-1, y_origin-1, WAVEFORM_WIDTH*WAVEFORM_FACTOR+1, height+1); } } int tic() { struct tm now; LoadCalendarFromRTC(&now); return now.tm_sec + now.tm_min * 60 + now.tm_hour * 3600 + now.tm_mday * 3600 * 24; } /*static void dump_vram() { dump_big_seg(4, "ML/LOGS/4.bin"); dump_big_seg(4, "ML/LOGS/4-1.bin"); //dump_seg(0x1000, 0x100000, "ML/LOGS/ram.bin"); //~ dump_seg(YUV422_IMAGE_BUFFER, 1920*1080*2, "ML/LOGS/VRAM.BIN"); }*/ int fps_ticks = 0; static void waveform_init() { if (!waveform) waveform = malloc(WAVEFORM_WIDTH * WAVEFORM_HEIGHT); bzero32(waveform, WAVEFORM_WIDTH * WAVEFORM_HEIGHT); } void bvram_mirror_clear() { ASSERT(bvram_mirror_start); BMP_LOCK( bzero32(bvram_mirror_start, BMP_VRAM_SIZE); ) cropmark_cache_dirty = 1; } void bvram_mirror_init() { if (!bvram_mirror_start) { bvram_mirror_start = (void*)UNCACHEABLE(malloc(BMP_VRAM_SIZE)); if (!bvram_mirror_start) { while(1) { bmp_printf(FONT_MED, 30, 30, "Failed to allocate BVRAM mirror"); msleep(100); } } // to keep the same addressing mode as with normal BMP VRAM - origin in 720x480 center crop bvram_mirror = bvram_mirror_start + BMP_HDMI_OFFSET; bvram_mirror_clear(); } } static int get_focus_color(int thr, int d) { return focus_peaking_color == 0 ? COLOR_RED : focus_peaking_color == 1 ? 7 : focus_peaking_color == 2 ? COLOR_BLUE : focus_peaking_color == 3 ? 5 : focus_peaking_color == 4 ? 14 : focus_peaking_color == 5 ? 15 : focus_peaking_color == 6 ? (thr > 50 ? COLOR_RED : thr > 40 ? 19 /*orange*/ : thr > 30 ? 15 /*yellow*/ : thr > 20 ? 5 /*cyan*/ : 9 /*light blue*/) : focus_peaking_color == 7 ? ( d > 50 ? COLOR_RED : d > 40 ? 19 /*orange*/ : d > 30 ? 15 /*yellow*/ : d > 20 ? 5 /*cyan*/ : 9 /*light blue*/) : 1; } //~ static unsigned int* bm_hd_r_cache = 0; static unsigned int bm_hd_x_cache[BMP_W_PLUS - BMP_W_MINUS]; static int bm_hd_bm2lv_sx = 0; static int bm_hd_lv2hd_sx = 0; void zebra_update_lut() { int rebuild = 0; if(unlikely(bm_hd_bm2lv_sx != bm2lv.sx)) { bm_hd_bm2lv_sx = bm2lv.sx; rebuild = 1; } if(unlikely(bm_hd_lv2hd_sx != lv2hd.sx)) { bm_hd_lv2hd_sx = lv2hd.sx; rebuild = 1; } if(unlikely(rebuild)) { int xStart = os.x0 + 8; int xEnd = os.x_max - 8; for (int x = xStart; x < xEnd; x += 1) { bm_hd_x_cache[x - BMP_W_MINUS] = (BM2HD_X(x) * 2) + 1; } } } /*static int zebra_color_word_row_thick(int c, int y) { //~ return zebra_color_word_row(c,y); if (!c) return 0; uint32_t cw = 0; switch(y % 4) { case 0: cw = c | c << 8 | c << 16; break; case 1: cw = c << 8 | c << 16 | c << 24; break; case 2: cw = c << 16 | c << 24 | c; break; case 3: cw = c << 24 | c | c << 8; break; } return cw; }*/ static int zebra_digic_dirty = 0; void draw_zebras( int Z ) { uint8_t * const bvram = bmp_vram_real(); int zd = Z && zebra_draw && (lv_luma_is_accurate() || PLAY_OR_QR_MODE) && (zebra_rec || !recording); // when to draw zebras if (zd) { int zlh = zebra_level_hi * 255 / 100 - 1; int zll = zebra_level_lo * 255 / 100; uint8_t * lvram = get_yuv422_vram()->vram; // draw zebra in 16:9 frame // y is in BM coords for(int y = os.y0 + os.off_169; y < os.y_max - os.off_169; y ++ ) { #define color_over ZEBRA_COLOR_WORD_SOLID(COLOR_RED) #define color_under ZEBRA_COLOR_WORD_SOLID(COLOR_BLUE) #define color_rgb_under zebra_rgb_color(1, 0, 0, 0, y) #define color_rgb_clipR zebra_rgb_color(0, 1, 0, 0, y) #define color_rgb_clipG zebra_rgb_color(0, 0, 1, 0, y) #define color_rgb_clipB zebra_rgb_color(0, 0, 0, 1, y) #define color_rgb_clipRG zebra_rgb_color(0, 1, 1, 0, y) #define color_rgb_clipGB zebra_rgb_color(0, 0, 1, 1, y) #define color_rgb_clipRB zebra_rgb_color(0, 1, 0, 1, y) #define color_rgb_clipRGB zebra_rgb_color(0, 1, 1, 1, y) uint32_t * const v_row = (uint32_t*)( lvram + BM2LV_R(y) ); // 2 pixels uint32_t* lvp; // that's a moving pointer through lv vram for (int x = os.x0; x < os.x_max; x ++) { lvp = v_row + BM2LV_X(x)/2; int bp = 0; #define BP bp if (zebra_colorspace == 1 && !ext_monitor_rca) // rgb { int Y, R, G, B; //~ uyvy2yrgb(*lvp, &Y, &R, &G, &B); yuv411_to_rgb((uint32_t)lvp, &Y, &R, &G, &B); //~ COMPUTE_UYVY2YRGB(pixel, Y, R, G, B); if(unlikely(Y < zll)) // underexposed { BP = color_rgb_under; } else { //~ BP = zebra_rgb_color(Y < zll, R > zlh, G > zlh, B > zlh, y); //~ BN = MN = zebra_rgb_color(Y < zll, R > zlh, G > zlh, B > zlh, y+1); if (unlikely(R > zlh)) // R clipped { if (unlikely(G > zlh)) // RG clipped { if (B > zlh) // RGB clipped (all of them) { BP = color_rgb_clipRGB; } else // only R and G clipped { BP = color_rgb_clipRG; } } else // R clipped, G not clipped { if (unlikely(B > zlh)) // only R and B clipped { BP = color_rgb_clipRB; } else // only R clipped { BP = color_rgb_clipR; } } } else // R not clipped { if (unlikely(G > zlh)) // R not clipped, G clipped { if (unlikely(B > zlh)) // only G and B clipped { BP = color_rgb_clipGB; } else // only G clipped { BP = color_rgb_clipG; } } else // R not clipped, G not clipped { if (unlikely(B > zlh)) // only B clipped { BP = color_rgb_clipB; } else // nothing clipped { BP = 0; } } } } } else // luma { int p0 = (*lvp) >> 8 & 0xFF; if (unlikely(p0 > zlh)) { BP = color_over; } else if (unlikely(p0 < zll)) { BP = color_under; } else BP = 0; } bmp_putpixel_fast(bvram, x, y, BP); #undef MP #undef BP #undef BN #undef MN } } } } static int peak_scaling[256]; /* static inline int peak_d1xy(uint8_t* p8) { int p_cc = (int)(*p8); int p_rc = (int)(*(p8 + 2)); int p_cd = (int)(*(p8 + vram_lv.pitch)); int e_dx = ABS(p_rc - p_cc); int e_dy = ABS(p_cd - p_cc); int e = MAX(e_dx, e_dy); return peak_scaling[MIN(e, 255)]; }*/ static inline int peak_d2xy(uint8_t* p8) { // approximate second derivative with a Laplacian kernel: // -1 // -1 4 -1 // -1 int result = ((int)(*p8) * 4) - (int)(*(p8 + 2)); result -= (int)(*(p8 - 2)); result -= (int)(*(p8 + vram_lv.pitch)); result -= (int)(*(p8 - vram_lv.pitch)); int e = ABS(result); if (focus_peaking_filter_edges) { // filter out strong edges where first derivative is strong // as these are usually false positives int d1x = ABS((int)(*(p8 + 2)) - (int)(*(p8 - 2))); int d1y = ABS((int)(*(p8 + vram_lv.pitch)) - (int)(*(p8 - vram_lv.pitch))); int d1 = MAX(d1x, d1y); e = MAX(e - ((d1 << focus_peaking_filter_edges) >> 2), 0) * 2; } return e; } static inline int peak_d2xy_hd(const uint8_t* p8) { // approximate second derivative with a Laplacian kernel: // -1 // -1 4 -1 // -1 int result = ((int)(*p8) * 4) - (int)(*(p8 + 2)); result -= (int)(*(p8 - 2)); result -= (int)(*(p8 + vram_hd.pitch)); result -= (int)(*(p8 - vram_hd.pitch)); int e = ABS(result); if (focus_peaking_filter_edges) { // filter out strong edges where first derivative is strong // as these are usually false positives int d1x = ABS((int)(*(p8 + 2)) - (int)(*(p8 - 2))); int d1y = ABS((int)(*(p8 + vram_hd.pitch)) - (int)(*(p8 - vram_hd.pitch))); int d1 = MAX(d1x, d1y); e = MAX(e - ((d1 << focus_peaking_filter_edges) >> 2), 0) * 2; } return e; } // should be more accurate for 5Dc - there is a hi-res buffer in playback mode static int peak_d2xy_hd_avg2x2(const uint8_t* p8) { int p00 = peak_d2xy((uint8_t *) p8); int p01 = peak_d2xy((uint8_t *) p8 + 4); int p10 = peak_d2xy((uint8_t *) p8 + vram_hd.pitch); int p11 = peak_d2xy((uint8_t *) p8 + vram_hd.pitch + 4); return (p00 + p01 + p10 + p11) / 4; } //~ static inline int peak_blend_solid(uint32_t* s, int e, int thr) { return 0x4C7F4CD5; } //~ static inline int peak_blend_raw(uint32_t* s, int e) { return (e << 8) | (e << 24); } static inline int peak_blend_alpha(uint32_t* s, int e) { return 0; // TODO: ??? } static void focus_found_pixel(int x, int y, int e, int thr, uint8_t * const bvram) { int color = get_focus_color(thr, e); bmp_putpixel_fast(bvram, x, y, color); //~ bmp_putpixel_fast(bvram, x+1, y, color); //~ bmp_putpixel_fast(bvram, x, y+1, color); //~ bmp_putpixel_fast(bvram, x+1, y+1, color); } // returns how the focus peaking threshold changed static int draw_zebra_and_focus( int Z, int F) { if (unlikely(!get_global_draw())) return 0; uint8_t * const bvram = bmp_vram_real(); if (unlikely(!bvram)) return 0; if (unlikely(!bvram_mirror)) return 0; draw_zebras(Z); if (focus_peaking_as_display_filter()) return 0; // it's drawn from display filters routine static int thr = 50; static int thr_increment = 1; static int prev_thr = 50; static int thr_delta = 0; if (F && focus_peaking) { struct vram_info *hd_vram = get_yuv422_hd_vram(); uint32_t hdvram = (uint32_t)hd_vram->vram; int yStart = os.y0 + os.off_169 + 8; int yEnd = os.y_max - os.off_169 - 8; int xStart = os.x0 + 8; int xEnd = os.x_max - 8; int n_over = 0; int n_total = ((yEnd - yStart) * (xEnd - xStart)); const uint8_t* p8; // that's a moving pointer zebra_update_lut(); { for(int y = yStart; y < yEnd; y ++) { uint32_t hd_row = hdvram + BM2HD_R(y); for (int x = xStart; x < xEnd; x ++) { p8 = (uint8_t *)(hd_row + bm_hd_x_cache[x - BMP_W_MINUS]); /** simple Laplacian filter * -1 * -1 4 -1 * -1 * * Big endian: * uyvy uyvy uyvy * uyvy uYvy uyvy * uyvy uyvy uyvy */ int e = peak_d2xy_hd(p8); /* executed for 1% of pixels */ if (unlikely(e >= thr)) { n_over++; if (F == 1) focus_found_pixel(x, y, e, thr, bvram); } } } } //~ bmp_printf(FONT_LARGE, 10, 50, "%d ", thr); if (1000 * n_over / n_total > (int)focus_peaking_pthr) { if (thr_delta > 0) thr_increment++; else thr_increment = 1; thr += thr_increment; } else { if (thr_delta < 0) thr_increment++; else thr_increment = 1; thr -= thr_increment; } thr_increment = COERCE(thr_increment, -5, 5); int thr_min = 10; thr = COERCE(thr, thr_min, 255); thr_delta = thr - prev_thr; prev_thr = thr; } return thr_delta; } void guess_focus_peaking_threshold() { if (!focus_peaking) return; int prev_thr_delta = 1234; for (int i = 0; i < 50; i++) { int thr_delta = draw_zebra_and_focus(0,2); // dummy focus peaking without drawing //~ bmp_printf(FONT_LARGE, 0, 0, "%x ", thr_delta); msleep(1000); if (!thr_delta) break; if (prev_thr_delta != 1234 && SGN(thr_delta) != SGN(prev_thr_delta)) break; prev_thr_delta = thr_delta; } } void highlight_luma_range(int lo, int hi, int color1, int color2) { uint8_t * const bvram = bmp_vram(); if (!bvram) return; if (!bvram_mirror) return; int y; uint8_t * const lvram = get_yuv422_vram()->vram; int lvpitch = get_yuv422_vram()->pitch; for( y = 0; y < 480; y += 2 ) { uint32_t * const v_row = (uint32_t*)( lvram + y * lvpitch ); // 2 pixel uint16_t * const b_row = (uint16_t*)( bvram + y * BMPPITCH); // 2 pixel uint8_t* lvp; // that's a moving pointer through lv vram uint16_t* bp; // through bmp vram for (lvp = ((uint8_t*)v_row)+1, bp = b_row; lvp < (uint8_t*)(v_row + 720/2) ; lvp += 4, bp++) { int x = ((int)lvp) / 2; int color = (y/2 - x/2) % 2 ? color1 | color1 << 8 : color2 | color2 << 8; #define BP (*bp) #define BN (*(bp + BMPPITCH/2)) int pix = (*lvp + *(lvp+2))/2; int c = pix >= lo && *lvp <= hi ? color : 0; BN = BP = c; #undef BP #undef BN } } } #ifdef FEATURE_ZEBRA static MENU_UPDATE_FUNC(zebra_draw_display) { unsigned z = CURRENT_VALUE; int over_disabled = (zebra_level_hi > 100); int under_disabled = (zebra_level_lo == 0); if (z) { MENU_SET_VALUE( "%s, ", zebra_colorspace == 0 ? "Luma" : zebra_colorspace == 1 ? "RGB" : "LumaFast" ); if (over_disabled) { MENU_APPEND_VALUE( "under %d%%", zebra_level_lo ); } else if (under_disabled) { MENU_APPEND_VALUE( "over %d%%", zebra_level_hi ); } else { MENU_APPEND_VALUE( "%d..%d%%", zebra_level_lo, zebra_level_hi ); } } } static MENU_UPDATE_FUNC(zebra_level_display) { int level = CURRENT_VALUE; if (level == 0 || level > 100) { MENU_SET_VALUE("Disabled"); MENU_SET_ICON(MNI_PERCENT_OFF, 0); MENU_SET_ENABLED(0); } else { MENU_SET_VALUE( "%d%% (%d)", level, 0, (level * 255 + 50) / 100 ); } } #endif /*static void zebra_toggle( void* priv, int sign ) { menu_ternary_toggle(priv, -sign); }*/ /* static MENU_UPDATE_FUNC(focus_debug_display) { unsigned fc = *(unsigned*) priv; bmp_printf( selected ? MENU_FONT_SEL : MENU_FONT, x, y, "FPeak debug : %s", focus_peaking_debug ? "ON" : "OFF" ); }*/ static MENU_UPDATE_FUNC(focus_peaking_display) { unsigned f = CURRENT_VALUE; if (f) MENU_SET_VALUE( "ON,%d.%d,%s%s", focus_peaking_pthr / 10, focus_peaking_pthr % 10, focus_peaking_color == 0 ? "R" : focus_peaking_color == 1 ? "G" : focus_peaking_color == 2 ? "B" : focus_peaking_color == 3 ? "C" : focus_peaking_color == 4 ? "M" : focus_peaking_color == 5 ? "Y" : focus_peaking_color == 6 ? "global" : focus_peaking_color == 7 ? "local" : "err", focus_peaking_grayscale ? ",Gray" : "" ); /* else MENU_SET_VALUE ( "Focus Peak : OFF" ); MENU_SET_ICON(MNI_BOOL_GDR(f), 0);*/ } static void focus_peaking_adjust_thr(void* priv, int delta) { focus_peaking_pthr = (int)focus_peaking_pthr + (focus_peaking_pthr < 10 ? 1 : 5) * delta; if ((int)focus_peaking_pthr > 50) focus_peaking_pthr = 1; if ((int)focus_peaking_pthr <= 0) focus_peaking_pthr = 50; } /* static MENU_UPDATE_FUNC(focus_graph_display) { bmp_printf( selected ? MENU_FONT_SEL : MENU_FONT, x, y, "Focus Graph : %s", *(unsigned*) priv ? "ON " : "OFF" ); }*/ /* static MENU_UPDATE_FUNC(edge_display) { bmp_printf( selected ? MENU_FONT_SEL : MENU_FONT, x, y, "Edgedetect : %s", *(unsigned*) priv ? "ON " : "OFF" ); }*/ /* static MENU_UPDATE_FUNC(hist_display) { bmp_printf( selected ? MENU_FONT_SEL : MENU_FONT, x, y, "Histo/Wavefm: %s/%s", hist_draw == 1 ? "Luma" : hist_draw == 2 ? "RGB" : "OFF", //~ hist_draw ? "RGB" : "OFF", waveform_draw == 1 ? "Small" : waveform_draw == 2 ? "Large" : waveform_draw == 3 ? "FullScreen" : "OFF" ); //~ bmp_printf(FONT_MED, x + 460, y+5, "[SET/Q]"); menu_draw_icon(MNI_BOOL_GDR_EXPSIM(hist_draw || waveform_draw), 0); }*/ static MENU_UPDATE_FUNC(waveform_print) { MENU_SET_VALUE( "%s", waveform_draw == 0 ? "OFF" : waveform_size == 0 ? "Small" : waveform_size == 1 ? "Large" : waveform_size == 2 ? "FullScreen" : "err" ); } static MENU_UPDATE_FUNC(global_draw_display) { MENU_SET_VALUE ( "%s", global_draw_mode == 0 ? "DirecPrint btn only" : "After taking a pic " ); } static MENU_UPDATE_FUNC(vectorscope_display) { MENU_SET_VALUE( "%s", CURRENT_VALUE ? "ON " : "OFF" ); } #ifdef CONFIG_40D static MENU_UPDATE_FUNC(clearscreen_display) { int mode = clearscreen; MENU_SET_VALUE( "Clear overlays : %s", //~ mode ? "ON (HalfShutter)" : "OFF" mode == 0 ? "OFF" : mode == 1 ? "HalfShutter/DofP" : mode == 2 ? "WhenIdle" : mode == 3 ? "Always" : "Recording" ); } #endif static MENU_UPDATE_FUNC(spotmeter_menu_display) { MENU_SET_VALUE( "%s", spotmeter_draw == 0 ? "OFF" : spotmeter_formula == 0 ? "Percent" : spotmeter_formula == 1 ? "0..255" : spotmeter_formula == 2 ? "IRE -1..101" : spotmeter_formula == 3 ? "IRE 0..108" : spotmeter_formula == 4 ? "RGB" : spotmeter_formula == 5 ? "HSL" : /*spotmeter_formula == 6*/"HSV" ); } // for surface cleaning int spy_pre_xcb = -1; int spy_pre_ycb = -1; void get_spot_yuv_ex(int size_dxb, int dx, int dy, int* Y, int* U, int* V) { struct vram_info * vram = get_yuv422_vram(); if( !vram->vram ) return; const uint16_t* vr = (void*) vram->vram; const unsigned width = vram->width; //~ const unsigned pitch = vram->pitch; //~ const unsigned height = vram->height; int x, y; int xcb = os.x0 + os.x_ex/2 + dx; int ycb = os.y0 + os.y_ex/2 + dy; int xcl = BM2LV_X(xcb); int ycl = BM2LV_Y(ycb); int dxl = BM2LV_DX(size_dxb); // surface cleaning if ( spy_pre_xcb != -1 && spy_pre_ycb != -1 && (spy_pre_xcb != xcb || spy_pre_ycb != ycb) ) { bmp_draw_rect(0, spy_pre_xcb - size_dxb, spy_pre_ycb - size_dxb, 2*size_dxb, 2*size_dxb); } bmp_draw_rect(COLOR_WHITE, xcb - size_dxb, ycb - size_dxb, 2*size_dxb, 2*size_dxb); unsigned sy = 0; int32_t su = 0, sv = 0; // Y is unsigned, U and V are signed // Sum the values around the center for( y = ycl - dxl ; y <= ycl + dxl ; y++ ) { for( x = xcl - dxl ; x <= xcl + dxl ; x++ ) { uint16_t p = vr[ x + y * width ]; sy += (p >> 8) & 0xFF; if (x % 2) sv += (int8_t)(p & 0x00FF); else su += (int8_t)(p & 0x00FF); } } sy /= (2 * dxl + 1) * (2 * dxl + 1); su /= (dxl + 1) * (2 * dxl + 1); sv /= (dxl + 1) * (2 * dxl + 1); *Y = sy; *U = su; *V = sv; spy_pre_xcb = xcb; spy_pre_ycb = ycb; } void get_spot_yuv(int dxb, int* Y, int* U, int* V) { get_spot_yuv_ex(dxb, 0, 0, Y, U, V); } int get_spot_motion(int dxb, int xcb, int ycb, int draw) { return 0; } #ifdef CONFIG_40D void spotmeter_erase() { #if 0 if (!spotmeter_dirty) return; spotmeter_dirty = 0; int xcb = spot_prev_xcb; int ycb = spot_prev_ycb; int dx = spotmeter_formula <= 3 ? 26 : 52; int y0 = -13; uint32_t* M = (uint32_t*)get_bvram_mirror(); uint32_t* B = (uint32_t*)bmp_vram(); for(int y = (ycb&~1) + y0 ; y <= (ycb&~1) + 36 ; y++ ) { for(int x = xcb - dx ; x <= xcb + dx ; x+=4 ) { uint8_t* m = (uint8_t*)(&(M[BM(x,y)/4])); //32bit to 8bit if (*m == 0x80) *m = 0; m++; if (*m == 0x80) *m = 0; m++; if (*m == 0x80) *m = 0; m++; if (*m == 0x80) *m = 0; B[BM(x,y)/4] = 0; } } #endif } #endif static void spotmeter_step() { if (gui_menu_shown()) return; if (!get_global_draw()) return; if (digic_zoom_overlay_enabled()) return; // incorrect readings //~ if (!lv) return; if (!PLAY_OR_QR_MODE) { if (!lv_luma_is_accurate()) return; } struct vram_info * vram = get_yuv422_vram(); if( !vram->vram ) return; const uint16_t* vr = (uint16_t*) vram->vram; const unsigned width = vram->width; //~ const unsigned pitch = vram->pitch; //~ const unsigned height = vram->height; const unsigned dxb = spotmeter_size; //unsigned sum = 0; int x, y; int xcb = os.x0 + os.x_ex/2; int ycb = os.y0 + os.y_ex/2; int xcl = BM2LV_X(xcb); int ycl = BM2LV_Y(ycb); int dxl = BM2LV_DX(dxb); unsigned sy = 0; int32_t su = 0, sv = 0; // Y is unsigned, U and V are signed // Sum the values around the center for( y = ycl - dxl ; y <= ycl + dxl ; y++ ) { for( x = xcl - dxl ; x <= xcl + dxl ; x += 2 ) { uint32_t uyvy = yuv411_to_422((uint32_t)&vr[ x + y * width ]); sy += UYVY_GET_AVG_Y(uyvy); su += UYVY_GET_U(uyvy); sv += UYVY_GET_V(uyvy); } } sy /= (dxl + 1) * (2 * dxl + 1); su /= (dxl + 1) * (2 * dxl + 1); sv /= (dxl + 1) * (2 * dxl + 1); // Scale to 100% const unsigned scaled = (101 * sy) / 256; // spotmeter color: // black on transparent, if brightness > 60% // white on transparent, if brightness < 50% // previous value otherwise // if false color is active, draw white on semi-transparent gray int fg = scaled < 50 ? COLOR_WHITE : COLOR_BLACK; int bg = fg == COLOR_BLACK ? COLOR_WHITE : COLOR_BLACK; int fnt = FONT(FONT_LARGE, fg, bg); int fnts = FONT(FONT_MED, fg, bg); if (!arrow_keys_shortcuts_active()) { bmp_draw_rect(COLOR_WHITE, xcb - dxb, ycb - dxb, 2*dxb+1, 2*dxb+1); bmp_draw_rect(COLOR_BLACK, xcb - dxb + 1, ycb - dxb + 1, 2*dxb+1-2, 2*dxb+1-2); } ycb += dxb + 25; ycb -= font_large.height/2; xcb -= 2 * font_large.width; if (spotmeter_formula <= 1) { bmp_printf( fnt, xcb, ycb, "%3d%s", spotmeter_formula == 0 ? scaled : sy, spotmeter_formula == 0 ? "%" : "" ); } else if (spotmeter_formula <= 3) { int ire_aj = (((int)sy) - 2) * 102 / 253 - 1; // formula from AJ: (2...255) -> (-1...101) int ire_piers = ((int)sy) * 108/255; // formula from Piers: (0...255) -> (0...108) int ire = (spotmeter_formula == 2) ? ire_aj : ire_piers; bmp_printf( fnt, xcb, ycb, "%s%3d", // why does %4d display garbage?! ire < 0 ? "-" : " ", ire < 0 ? -ire : ire ); bmp_printf( fnts, xcb + font_large.width*4, ycb, "IRE\n%s", spotmeter_formula == 2 ? "-1..101" : "0..108" ); } else { int uyvy = UYVY_PACK(su,sy,sv,sy); int R,G,B,Y; COMPUTE_UYVY2YRGB(uyvy, Y, R, G, B); xcb -= font_med.width * 3/2; bmp_printf( fnt, xcb, ycb, "#%02x%02x%02x", R,G,B ); } } int handle_transparent_overlay(struct event * event) { return 1; } struct menu_entry zebra_menus[] = { { .name = "Show Overlay", .priv = &global_draw_mode, .max = 1, .update = global_draw_display, .icon_type = IT_DICE, .help = "When to display ML overlay graphics (zebra, histogram...)", //.essential = FOR_LIVEVIEW, }, { .name = "Zebras", .priv = &zebra_draw, .update = zebra_draw_display, .max = 1, .help = "Zebra stripes: show overexposed or underexposed areas.", //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, .children = (struct menu_entry[]) { { .name = "Color space", .priv = &zebra_colorspace, .max = 1, .choices = (const char *[]) {"Luma", "RGB", "Luma Fast"}, .icon_type = IT_DICE, .help = "Luma: red/blue. RGB: color is reverse of clipped channel.", }, { .name = "Underexposure", .priv = &zebra_level_lo, .min = 0, .max = 20, .update = zebra_level_display, .help = "Underexposure threshold.", }, { .name = "Overexposure", .priv = &zebra_level_hi, .min = 70, .max = 101, .update = zebra_level_display, .help = "Overexposure threshold.", }, MENU_EOL }, }, { .name = "Focus Peak", .priv = &focus_peaking, .update = focus_peaking_display, .max = 1, .help = "Show which parts of the image are in focus.", .submenu_width = 650, //.essential = FOR_LIVEVIEW, .children = (struct menu_entry[]) { { .name = "Filter bias", .priv = &focus_peaking_filter_edges, .max = 2, .choices = (const char *[]) {"Strong edges", "Balanced", "Fine details"}, .help = "Balance fine texture details vs strong high-contrast edges.", .icon_type = IT_DICE }, { .name = "Threshold", .priv = &focus_peaking_pthr, .select = focus_peaking_adjust_thr, .help = "How many pixels are considered in focus (percentage).", .unit = UNIT_PERCENT_x10 }, { .name = "Color", .priv = &focus_peaking_color, .max = 7, .choices = (const char *[]) {"Red", "Green", "Blue", "Cyan", "Magenta", "Yellow", "Global Focus", "Local Focus"}, .help = "Focus peaking color (fixed or color coding).", .icon_type = IT_DICE, }, { .name = "Grayscale img.", .priv = &focus_peaking_grayscale, .max = 1, .help = "Display the image in grayscale.", }, /*{ .priv = &focus_peaking_debug, .max = 1, .name = "Debug mode", .help = "Displays raw contrast image (grayscale).", },*/ MENU_EOL }, }, { .name = "Spotmeter", .priv = &spotmeter_draw, .max = 1, .update = spotmeter_menu_display, .help = "Exposure aid: display brightness from a small spot.", //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, .children = (struct menu_entry[]) { { .name = "Spotmeter Unit", .priv = &spotmeter_formula, .max = 4, .choices = (const char *[]) {"Percent", "0..255", "IRE -1..101", "IRE 0..108", "RGB (HTML)"}, .icon_type = IT_DICE, .help = "Measurement unit for brightness level(s).", }, MENU_EOL } }, #if 0 { .name = "False color", .priv = &falsecolor_draw, .update = falsecolor_display, .submenu_height = 160, .help = "Exposure aid: each brightness level is color-coded.", //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, .children = (struct menu_entry[]) { { .name = "Palette", .priv = &falsecolor_palette, .max = COUNT(false_colour)-1, .icon_type = IT_DICE, .update = falsecolor_display_palette, .help = "False color palettes for exposure, banding, green screen...", }, MENU_EOL } }, #endif /* { .name = "Histo/Wavefm", .priv = &hist_draw, .select = zebra_toggle, .select_auto = waveform_toggle, .update = hist_display, .help = "Histogram [SET] and Waveform [Q] for evaluating exposure.", //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, }, */ { .name = "Histogram", .priv = &hist_draw, .max = 1, .update = hist_print, .help = "Exposure aid: shows the distribution of brightness levels.", //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, .children = (struct menu_entry[]) { { .name = "Color space", .priv = &hist_colorspace, .max = 1, .choices = (const char *[]) {"Luma", "RGB"}, .icon_type = IT_DICE, .help = "Color space for histogram: Luma channel (YUV) / RGB.", }, { .name = "Scaling", .priv = &hist_log, .max = 1, .choices = (const char *[]) {"Linear", "Logarithmic"}, .help = "Linear or logarithmic histogram.", .icon_type = IT_DICE, }, { .name = "Clip warning", .priv = &hist_warn, .max = 5, .update = hist_warn_display, .help = "Display warning dots when one color channel is clipped.", }, MENU_EOL }, }, { .name = "Waveform", .priv = &waveform_draw, .update = waveform_print, .max = 1, .help = "Exposure aid: useful for checking overall brightness.", .children = (struct menu_entry[]) { { .name = "Size", .priv = &waveform_size, .max = 1, .choices = (const char *[]) {"Small", "Large"}, .icon_type = IT_SIZE, .help = "Waveform size: Small / Large / FullScreen.", }, MENU_EOL }, //.essential = FOR_LIVEVIEW | FOR_PLAYBACK, }, { .name = "Vectorscope", .update = vectorscope_display, .priv = &vectorscope_draw, .max = 1, .help = "Shows color distribution as U-V plot. For grading & WB.", //.essential = FOR_LIVEVIEW, }, //~ { //~ .update = crop_off_display, //~ .select = crop_off_toggle, //~ .select_reverse = crop_off_toggle_rev, //~ }, //~ { //~ .priv = "[debug] HDMI test", //~ .update = menu_print, //~ .select = hdmi_test_toggle, //~ } //~ { //~ .priv = &edge_draw, //~ .select = menu_binary_toggle, //~ .update = edge_display, //~ }, //~ { //~ .priv = &waveform_draw, //~ .select = menu_binary_toggle, //~ .update = waveform_display, //~ }, }; int handle_zoom_overlay(struct event * event) { return 1; } int liveview_display_idle() { return 0; } int livev_for_playback_running = 0; void draw_livev_for_playback() { if (!PLAY_MODE && !QR_MODE) { livev_for_playback_running = 0; return; } extern int quick_review_allow_zoom; if (quick_review_allow_zoom && image_review_time == 0xff) { // wait for the camera to switch from QR to PLAY before drawing anything while (!PLAY_MODE) msleep(100); msleep(500); } while (!DISPLAY_IS_ON) msleep(100); livev_for_playback_running = 1; info_led_on(); get_yuv422_vram(); // just to refresh VRAM params extern int defish_preview; //~ BMP_LOCK( set_ml_palette_if_dirty(); bvram_mirror_clear(); // may be filled with liveview cropmark / masking info, not needed in play mode clrscr(); #if defined(FEATURE_FALSE_COLOR) if (falsecolor_draw) { draw_false_downsampled(); } else #endif { guess_focus_peaking_threshold(); draw_zebra_and_focus(1,1); } if (spotmeter_draw) spotmeter_step(); draw_histogram_and_waveform(1); bvram_mirror_clear(); // may remain filled with playback zebras //~ ) livev_for_playback_running = 0; info_led_off(); } void draw_histogram_and_waveform(int allow_play) { if (!get_global_draw()) return; get_yuv422_vram(); if (hist_draw || waveform_draw || vectorscope_draw) { hist_build(); } if( hist_draw ) { BMP_LOCK( hist_draw_image( os.x_max - HIST_WIDTH - 5, os.y0 + 100, -1); ) } if( waveform_draw) { BMP_LOCK( waveform_draw_image( os.x_max - WAVEFORM_WIDTH*WAVEFORM_FACTOR - (WAVEFORM_FULLSCREEN ? 0 : 4), os.y_max - WAVEFORM_HEIGHT*WAVEFORM_FACTOR - WAVEFORM_OFFSET, WAVEFORM_HEIGHT*WAVEFORM_FACTOR ); ) } if(vectorscope_draw) { /* make sure memory address of bvram will be 4 byte aligned */ BMP_LOCK( vectorscope_draw_image(os.x0 + 32, 64); ) } } int idle_is_powersave_enabled() { return 0; } int idle_is_powersave_active() { return 0; } void idle_force_powersave_in_1s() { } void idle_force_powersave_now() { } int handle_powersave_key(struct event * event) { return 1; } void idle_wakeup_reset_counters(int reason) // called from handle_buttons { } void redraw_do() { //~ clrscr(); } void redraw() { //~ clrscr(); } void draw_cropmark_area() { get_yuv422_vram(); bmp_draw_rect(COLOR_BLUE, os.x0, os.y0, os.x_ex, os.y_ex); draw_line(os.x0, os.y0, os.x_max, os.y_max, COLOR_BLUE); draw_line(os.x0, os.y_max, os.x_max, os.y0, COLOR_BLUE); bmp_draw_rect(COLOR_RED, HD2BM_X(0), HD2BM_Y(0), HD2BM_DX(vram_hd.width), HD2BM_DY(vram_hd.height)); draw_line(HD2BM_X(0), HD2BM_Y(0), HD2BM_X(vram_hd.width), HD2BM_Y(vram_hd.height), COLOR_RED); draw_line(HD2BM_X(0), HD2BM_Y(vram_hd.height), HD2BM_X(vram_hd.width), HD2BM_Y(0), COLOR_RED); } int handle_disp_preset_key(struct event * event) { return 1; } static int livev_playback = 0; static void livev_playback_toggle() { if (livev_for_playback_running) return; livev_playback = !livev_playback; if (livev_playback) { livev_for_playback_running = 1; task_create("lv_playback", 0x1a, 0x4000, draw_livev_for_playback, 0); } else { clrscr(); //~ restore_canon_palette(); } } static void livev_playback_reset() { livev_playback = 0; } // trick for grayscale focus peaking int zebra_should_run() { return livev_playback || QR_MODE; } int handle_livev_playback(struct event * event, int button) { // enable LiveV stuff in Play mode if (PLAY_OR_QR_MODE && !gui_menu_shown()) { if (event->param == button) { livev_playback_toggle(); return 0; } else if (event->param != button+1) { livev_playback_reset(); } } return 1; } static void zebra_init() { precompute_yuv2rgb(); menu_add( "Overlay", zebra_menus, COUNT(zebra_menus) ); } INIT_FUNC(__FILE__, zebra_init); void peaking_benchmark() { msleep(1000); fake_simple_button(BGMT_PLAY); msleep(2000); int a = get_seconds_clock(); for (int i = 0; i < 1000; i++) { draw_zebra_and_focus(0,1); } int b = get_seconds_clock(); NotifyBox(10000, "%d seconds => %d fps", b-a, 1000 / (b-a)); beep(); }