#include "dryos.h" #include "vram.h" #include "bmp.h" #include "version.h" #include "config.h" #include "lens.h" #include "math.h" #include "raw.h" #include "menu.h" #include "imgconv.h" #include "histogram.h" #include "module.h" #include "zebra.h" #include "falsecolor.h" #if defined(FEATURE_HISTOGRAM) extern int FAST get_y_skip_offset_for_overlays(); extern int nondigic_zoom_overlay_enabled(); CONFIG_INT( "hist.draw", hist_draw, 1 ); #ifdef FEATURE_RAW_HISTOGRAM CONFIG_INT( "hist.type", hist_type, 2 ); #else CONFIG_INT( "hist.type", hist_type, 1 ); #endif CONFIG_INT( "hist.warn", hist_warn, 1 ); CONFIG_INT( "hist.log", hist_log, 1 ); CONFIG_INT( "hist.meter", hist_meter, 2); struct Histogram histogram; #ifdef FEATURE_RAW_HISTOGRAM #define HIST_METER_DYNAMIC_RANGE 1 #define HIST_METER_ETTR_HINT 2 static void histobar_refresh(); /*** lv_playback, menu_redraw_task, livev_loprio_task ***/ void FAST hist_build_raw() { if (!raw_update_params()) return; memset(&histogram, 0, sizeof(histogram)); histogram.is_raw = 1; int step = lv ? 4 : 2; char r2ev[16384]; for (int i = 0; i < 16384; i++) r2ev[i] = COERCE((raw_to_ev(i) + 12) * (HIST_WIDTH-1) / 12, 0, HIST_WIDTH-1); for (int i = os.y0; i < os.y_max; i += step) { int y = BM2RAW_Y(i); if (y < raw_info.active_area.y1+8 || y > raw_info.active_area.y2-8) continue; for (int j = os.x0; j < os.x_max; j += 8) { int x = BM2RAW_X(j); if (x < raw_info.active_area.x1+8 || x > raw_info.active_area.x2-8) continue; int r = raw_red_pixel_dark(x, y); int g = raw_green_pixel_dark(x, y); int b = raw_blue_pixel_dark(x, y); /* ignore bad pixels */ if (r == 0 || g == 0 || b == 0) continue; int ir = r2ev[r]; int ig = r2ev[g]; int ib = r2ev[b]; histogram.hist_r[ir]++; histogram.hist_g[ig]++; histogram.hist_b[ib]++; histogram.total_px++; } } /* in dark areas, spread the histogram count to show solid histogram instead of isolated bars */ for (int i = 0; i < 5000; i++) { int ev0 = r2ev[i]; int evplus = r2ev[i+1]; int evminus = r2ev[i-1]; if (evplus - evminus > 2) /* will there be a gap? fill it */ { int num_bins = evplus - evminus - 1; int delta_r = histogram.hist_r[ev0] / num_bins; int delta_g = histogram.hist_g[ev0] / num_bins; int delta_b = histogram.hist_b[ev0] / num_bins; for (int e = evminus+1; e <= evplus-1; e++) { histogram.hist_r[e] += delta_r; histogram.hist_g[e] += delta_g; histogram.hist_b[e] += delta_b; histogram.hist_r[ev0] -= delta_r; histogram.hist_g[ev0] -= delta_g; histogram.hist_b[ev0] -= delta_b; } } } for (int i = 0; i < HIST_WIDTH; i++) { histogram.max = MAX(histogram.max, histogram.hist_r[i]); histogram.max = MAX(histogram.max, histogram.hist_g[i]); histogram.max = MAX(histogram.max, histogram.hist_b[i]); } histobar_refresh(); } /*** menu_redraw_task ***/ MENU_UPDATE_FUNC(raw_histo_update) { if (RAW_HISTOGRAM_ENABLED) { menu_checkdep_raw(entry, info); } if (RAW_HISTOBAR_ENABLED) { MENU_SET_WARNING(MENU_WARN_INFO, "Will use Histobar in LiveView, RAW histogram after taking a pic."); } else if (RAW_HISTOGRAM_ENABLED) { MENU_SET_WARNING(MENU_WARN_INFO, "Will use RAW histogram in LiveView and after taking a pic."); } } #endif /*** lv_playback, menu_redraw_task, livev_loprio_task ***/ 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; case 0b001: return COLOR_RED; case 0b010: return COLOR_GREEN2; case 0b100: return COLOR_LIGHT_BLUE; case 0b011: return COLOR_YELLOW; case 0b110: return COLOR_CYAN; case 0b101: return COLOR_MAGENTA; case 0b111: return COLOR_WHITE; } return 0; } /*** lv_playback, menu_redraw_task, livev_loprio_task ***/ static void hist_dot(int x, int y, int fg_color, int bg_color, int radius, int label) { x &= ~3; y &= ~3; 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 (label < 10) bmp_printf( SHADOW_FONT(FONT(FONT_MED, COLOR_WHITE, fg_color)), x - 4, y - font_med.height/2, "%d", label ); else bmp_printf( SHADOW_FONT(FONT(FONT_SMALL, COLOR_WHITE, fg_color)), x - 8, y - font_small.height/2, "%d", label ); } } /*** lv_playback, menu_redraw_task, livev_loprio_task ***/ static int hist_dot_radius(int over, int hist_total_px) { // 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); } /*** lv_playback, menu_redraw_task, livev_loprio_task ***/ static int hist_dot_label(int over, int hist_total_px) { return 100 * over / hist_total_px; } static int (*auto_ettr_export_correction)(int* out) = MODULE_FUNCTION(auto_ettr_export_correction); /** Draw the histogram 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. */ /*** lv_playback, livev_loprio_task, menu_redraw_task ***/ void hist_draw_image( unsigned x_origin, unsigned y_origin ) { #ifdef FEATURE_RAW_HISTOGRAM if (RAW_HISTOBAR_ENABLED && lv && can_use_raw_overlays_menu()) return; #endif if (!PLAY_OR_QR_MODE) { if (!lv_luma_is_accurate()) return; } uint8_t * const bvram = bmp_vram(); if (!bvram) return; // Align the x origin, just in case x_origin &= ~3; uint8_t * row = bvram + x_origin + y_origin * BMPPITCH; if( histogram.max == 0 ) histogram.max = 1; unsigned i, y; int log_max = log_length(histogram.max); #ifdef FEATURE_RAW_HISTOGRAM const int v = (1200 - raw_info.dynamic_range) * HIST_WIDTH / 1200; int underexposed_level = COERCE(v, 0, HIST_WIDTH-1); int stops_until_overexposure = 0; #endif for( i=0 ; i < HIST_WIDTH ; i++ ) { // Scale by the maximum bin value const uint32_t size = hist_log ? log_length(histogram.hist[i]) * hist_height / log_max : (histogram.hist[i] * hist_height) / histogram.max; const uint32_t sizeR = hist_log ? log_length(histogram.hist_r[i]) * hist_height / log_max : (histogram.hist_r[i] * hist_height) / histogram.max; const uint32_t sizeG = hist_log ? log_length(histogram.hist_g[i]) * hist_height / log_max : (histogram.hist_g[i] * hist_height) / histogram.max; const uint32_t sizeB = hist_log ? log_length(histogram.hist_b[i]) * hist_height / log_max : (histogram.hist_b[i] * hist_height) / histogram.max; uint8_t * col = row + i; // vertical line up to the hist size for( y=hist_height ; y>0 ; y-- , col += BMPPITCH ) { if (histogram.is_rgb) *col = hist_rgb_color(y, sizeR, sizeG, sizeB); else *col = y > size ? COLOR_BG : #if defined(FEATURE_FALSE_COLOR) falsecolor_fordraw(((i << 8) / HIST_WIDTH) & 0xFF); #else COLOR_WHITE; #endif /* defined(FEATURE_FALSE_COLOR) */ } #if defined(FEATURE_HISTOGRAM) /* draw clip warnings */ if (hist_warn && i == HIST_WIDTH - 1) { unsigned int thr = histogram.total_px / 100000; // start at 0.0001 with a tiny dot thr = MAX(thr, 1); int yw = y_origin + 12 + (hist_log ? hist_height - 24 : 0); int bg = (hist_log ? COLOR_WHITE : COLOR_BLACK); if (histogram.is_rgb) { unsigned int over_r = histogram.hist_r[i] + histogram.hist_r[i-1]; unsigned int over_g = histogram.hist_g[i] + histogram.hist_g[i-1]; unsigned int over_b = histogram.hist_b[i] + histogram.hist_b[i-1]; if (over_r > thr) hist_dot(x_origin + HIST_WIDTH/2 - 25, yw, COLOR_RED, bg, hist_dot_radius(over_r, histogram.total_px), hist_dot_label(over_r, histogram.total_px)); if (over_g > thr) hist_dot(x_origin + HIST_WIDTH/2 , yw, COLOR_GREEN1, bg, hist_dot_radius(over_g, histogram.total_px), hist_dot_label(over_g, histogram.total_px)); if (over_b > thr) hist_dot(x_origin + HIST_WIDTH/2 + 25, yw, COLOR_LIGHT_BLUE, bg, hist_dot_radius(over_b, histogram.total_px), hist_dot_label(over_b, histogram.total_px)); } else { unsigned int over = histogram.hist[i] + histogram.hist[i-1]; if (over > thr) hist_dot(x_origin + HIST_WIDTH/2, yw, COLOR_RED, bg, hist_dot_radius(over, histogram.total_px), hist_dot_label(over, histogram.total_px)); } } #endif #ifdef FEATURE_RAW_HISTOGRAM /* divide the histogram in 12 equal slices - each slice is 1 EV */ if (histogram.is_raw) { static unsigned bar_pos; if (i == 0) bar_pos = 0; int H = hist_height - MAX(MAX(sizeR, sizeG), sizeB) - 1; int h = hist_height - MIN(MIN(sizeR, sizeG), sizeB) - 1; /* mark what's below the noise floor with... noise */ if ((int)i <= underexposed_level && i%2==0) { for (int y = y_origin + ((i/2)%2)*2; y < (int)y_origin + hist_height; y += 4) { int noise_color = (y < (int)y_origin + H) ? COLOR_GRAY(60) : /* noise color on top of histogram */ (y < (int)y_origin + h) ? COLOR_WHITE : /* noise color where histogram has a solid color, but not white */ COLOR_BLACK ; /* noise color where histogram is white */ bmp_putpixel(x_origin + i, y, noise_color); } } /* draw full-stop (EV) bars */ if (i == bar_pos) { int dy = (i < font_med.width * 4) ? font_med.height : 0; draw_line(x_origin + i, y_origin + dy, x_origin + i, y_origin + h, COLOR_GRAY(50)); bar_pos = (((bar_pos+1)*12/HIST_WIDTH) + 1) * HIST_WIDTH/12; } /* compute a basic ETTR hint */ unsigned int thr = histogram.total_px / 10000; if (histogram.hist_r[i] > thr || histogram.hist_g[i] > thr || histogram.hist_b[i] > thr) stops_until_overexposure = 120 - (i * 120 / (HIST_WIDTH-1)); } #endif } /* draw histogram border */ bmp_draw_rect(60, x_origin-1, y_origin-1, HIST_WIDTH+2, hist_height+2); #ifdef FEATURE_RAW_HISTOGRAM if (histogram.is_raw) { char msg[10]; switch (hist_meter) { case HIST_METER_DYNAMIC_RANGE: { int dr = (raw_info.dynamic_range + 5) / 10; snprintf(msg, sizeof(msg), "D%d.%d", dr/10, dr%10); break; } case HIST_METER_ETTR_HINT: { if (!stops_until_overexposure) stops_until_overexposure = INT_MIN; #ifdef CONFIG_MODULES int ettr_stops = INT_MIN; if (auto_ettr_export_correction(&ettr_stops) == 1) if (ettr_stops != INT_MIN) stops_until_overexposure = (ettr_stops+5)/10; #endif if (stops_until_overexposure != INT_MIN) snprintf(msg, sizeof(msg), "E%s%d.%d", FMT_FIXEDPOINT1(stops_until_overexposure)); else snprintf(msg, sizeof(msg), "OVER"); break; } default: snprintf(msg, sizeof(msg), "RAW"); break; } bmp_printf(SHADOW_FONT(FONT_MED), x_origin+4, y_origin, msg); } #endif } /*** menu_redraw_task ***/ MENU_UPDATE_FUNC(hist_print) { #if defined(FEATURE_HISTOGRAM) if (hist_draw) { int raw = 0; #ifdef FEATURE_RAW_HISTOGRAM raw = RAW_HISTOGRAM_ENABLED && can_use_raw_overlays_menu(); #endif MENU_SET_VALUE( "%s%s", raw ? (RAW_HISTOBAR_ENABLED ? "RAW HistoBar" : "RAW RGB") : hist_type == 0 ? "Luma (YUV)" : hist_type == 1 ? "RGB (YUV)" : "RAW N/A", hist_log ? ", Log" : (raw && RAW_HISTOBAR_ENABLED) ? "" : ", Linear" ); } #endif } #endif /* defined(FEATURE_HISTOGRAM) */ /*** ? ***/ MENU_UPDATE_FUNC(hist_warn_display) { #if defined(FEATURE_HISTOGRAM) MENU_SET_VALUE( "Clip warning : %s", hist_warn == 0 ? "OFF" : hist_warn == 1 ? "0.001% px" : hist_warn == 2 ? "0.01% px" : hist_warn == 3 ? "0.1% px" : hist_warn == 4 ? "1% px" : "Gradual" ); #endif /* defined(FEATURE_HISTOGRAM) */ } #ifdef FEATURE_RAW_HISTOGRAM /* speed: * 0 = slowest, but 100% accurate (only for GRAY_PROJECTION_GREEN for now) * 1 = sample at LiveView resolution (720x480) * 2 = LiveView resolution downsampled by 2 on each axis * 3 = LiveView resolution downsampled by 3 on each axis * and so on, until 16 */ /*** deflicker_task, livev_loprio_task, lv_playback, ettr_task, menu_redraw_task, PropMgr ***/ int FAST raw_hist_get_percentile_levels(int* percentiles_x10, int* output_raw_values, int n, int gray_projection, int speed) { if (!raw_update_params()) goto err; get_yuv422_vram(); int* hist = malloc(16384*4); if (!hist) goto err; memset(hist, 0, 16384*4); int off = get_y_skip_offset_for_histogram(); if (speed == 0 && gray_projection == GRAY_PROJECTION_GREEN) { /* time: 1-2 seconds on full raw 5D3 */ //~ int t0 = get_ms_clock_value(); for (struct raw_pixblock * row = (struct raw_pixblock *) raw_info.buffer + raw_info.active_area.y1 * raw_info.width / 8 + (raw_info.active_area.x1 + 7) / 8; (void*)row < (void*)raw_info.buffer + raw_info.pitch * raw_info.active_area.y2; row += 2 * raw_info.width / 8) { struct raw_pixblock * row2 = row + raw_info.pitch / sizeof(struct raw_pixblock); struct raw_pixblock * p; struct raw_pixblock * q; for (p = row, q = row2; (void*)p < (void*)row + raw_info.jpeg.width * 14/8; p++, q++) { /** * p: abcdefgh abcdefgh * q: abcdefgh abcdefgh * * rgrgrgrg rgrgrgrg * gbgbgbgb gbgbgbgb */ //~ int pa = ((int)(p->a)); int pb = ((int)(p->b_lo | (p->b_hi << 12))); //~ int pc = ((int)(p->c_lo | (p->c_hi << 10))); int pd = ((int)(p->d_lo | (p->d_hi << 8))); //~ int pe = ((int)(p->e_lo | (p->e_hi << 6))); int pf = ((int)(p->f_lo | (p->f_hi << 4))); //~ int pg = ((int)(p->g_lo | (p->g_hi << 2))); int ph = ((int)(p->h)); int qa = ((int)(q->a)); //~ int qb = ((int)(q->b_lo | (q->b_hi << 12))); int qc = ((int)(q->c_lo | (q->c_hi << 10))); //~ int qd = ((int)(q->d_lo | (q->d_hi << 8))); int qe = ((int)(q->e_lo | (q->e_hi << 6))); //~ int qf = ((int)(q->f_lo | (q->f_hi << 4))); int qg = ((int)(q->g_lo | (q->g_hi << 2))); //~ int qh = ((int)(q->h)); hist[pb]++; hist[pd]++; hist[pf]++; hist[ph]++; hist[qa]++; hist[qc]++; hist[qe]++; hist[qg]++; /* to check if we sample only the active area */ //~ p->a = rand(); } } //~ int t1 = get_ms_clock_value(); //~ NotifyBox(5000, "%d ", t1 - t0); //~ save_dng("A:/foo.dng"); } else { speed = COERCE(speed, 1, 16); for (int i = os.y0 + off; i < os.y_max - off; i += speed) { int y = BM2RAW_Y(i); for (int j = os.x0; j < os.x_max; j += speed) { int x = BM2RAW_X(j); int px = raw_get_gray_pixel(x, y, gray_projection); hist[px & 16383]++; } } } int total = 0; int i; for( i=0 ; i < 16384 ; i++ ) total += hist[i]; for (int k = 0; k < n; k++) { int thr = (uint64_t)total * percentiles_x10[k] / 1000 - 2; // 50% => median; allow up to 2 stuck pixels int n = 0; int ans = -1; for( i=0 ; i < 16384; i++ ) { n += hist[i]; if (n >= thr) { ans = i; break; } } output_raw_values[k] = ans; } free(hist); return 1; err: for (int k = 0; k < n; k++) { output_raw_values[k] = -1; } return -1; } /*** deflicker_task ***/ int raw_hist_get_percentile_level(int percentile_x10, int gray_projection, int speed) { int ans; raw_hist_get_percentile_levels(&percentile_x10, &ans, 1, gray_projection, speed); return ans; } /*** lv_playback, menu_redraw_task, PropMgr, ettr_task, livev_loprio_task ***/ int raw_hist_get_overexposure_percentage(int gray_projection) { if (!raw_update_params()) return -1; get_yuv422_vram(); /* use some tolerance when checking for overexposure, because white level might vary a little */ int white = raw_info.white_level * 80 / 100; int over = 0; int total = 0; int step = lv ? 4 : 2; for (int i = os.y0; i < os.y_max; i += step) { int y = BM2RAW_Y(i); for (int j = os.x0; j < os.x_max; j += step) { int x = BM2RAW_X(j); int px = raw_get_gray_pixel(x, y, gray_projection); if (px >= white) over++; total++; } } /* percentage x100 */ return over * 10000 / total; } #include "lvinfo.h" static int histobar_ev[15]; static int histobar_stops; static int histobar_clipped; static int histobar_stops_until_overexposure; static int histobar_midtone_level; /* median */ static int histobar_shadow_level; /* at 5th percentile, as in ETTR */ /*** lv_playback, livev_loprio_task, menu_redraw_task ***/ static void histobar_refresh() { int stops = COERCE((raw_info.dynamic_range + 50) / 100, 0, 14); int i_prev = 0; for(int ev = 0 ; ev < stops ; ev++ ) { int evh = COERCE(ev - stops + 12, 0, 12); int i = evh * HIST_WIDTH / 12; int pixels_in_this_stop = 0; for (int j = i_prev; j < i; j++) { int max = MAX(MAX(histogram.hist_r[i], histogram.hist_g[i]), histogram.hist_b[i]); pixels_in_this_stop += max; } histobar_ev[ev] = pixels_in_this_stop; i_prev = i; } histobar_stops = stops; int i = HIST_WIDTH-1; histobar_clipped = histogram.hist_r[i] + histogram.hist_g[i] + histogram.hist_b[i]; int stops_until_overexposure = INT_MIN; int acc = 0; int shadow_px = histogram.total_px / 20; int midtone_px = histogram.total_px / 2; for( i=0 ; i < HIST_WIDTH ; i++ ) { int thr = histogram.total_px / 10000; int max = MAX(MAX(histogram.hist_r[i], histogram.hist_g[i]), histogram.hist_b[i]); int ev_till_right_x10 = 120 - (i * 120 / (HIST_WIDTH-1)); int ev_till_right = ev_till_right_x10 / 10; int ev_from_left = histobar_stops - ev_till_right; if (max > thr) { stops_until_overexposure = ev_till_right_x10; } if (acc < midtone_px && acc + max >= midtone_px) { histobar_midtone_level = ev_from_left; } if (acc < shadow_px && acc + max >= shadow_px) { histobar_shadow_level = ev_from_left; } acc += max; } #ifdef CONFIG_MODULES int ettr_stops = INT_MIN; if (auto_ettr_export_correction(&ettr_stops) == 1) if (ettr_stops != INT_MIN) stops_until_overexposure = (ettr_stops+5)/10; #endif histobar_stops_until_overexposure = stops_until_overexposure; #ifdef CONFIG_QEMU /* double-check median and shadow levels (compare with histobar readings) */ int prctiles[2] = {500, 50}; int raw_levels[2]; int ev_levels[2]; raw_hist_get_percentile_levels(prctiles, raw_levels, 2, GRAY_PROJECTION_MAX_RGB, 0); ev_levels[0] = (int)roundf(raw_to_ev(raw_levels[0]) * 10); ev_levels[1] = (int)roundf(raw_to_ev(raw_levels[1]) * 10); printf("Median : %d = %s%d.%d EV\n", raw_levels[0], FMT_FIXEDPOINT1(ev_levels[0])); printf("Shadow 5%%: %d = %s%d.%d EV\n", 0, raw_levels[1], FMT_FIXEDPOINT1(ev_levels[1])); #endif lens_display_set_dirty(); } /*** ? ***/ static LVINFO_UPDATE_FUNC(histobar_update) { #ifdef FEATURE_RAW_HISTOGRAM if (!RAW_HISTOBAR_ENABLED) return; #endif if (!lv_luma_is_accurate()) return; if (!can_use_raw_overlays()) return; int w = fontspec_font(item->fontspec)->width - 4; int h = fontspec_font(item->fontspec)->height - 4; item->width = histobar_stops * w; item->height = h; item->custom_drawing = 1; if (can_draw) { for(int ev = 0 ; ev < histobar_stops ; ev++ ) { int pixels_in_this_stop = histobar_ev[ev]; int thr = histogram.total_px / 10000; int full = (pixels_in_this_stop > thr); int x = item->x - item->width/2 + ev * w + 1; int y = item->y-2; int h = item->height; int fh = h; /* fill height */ int fg = item->color_fg; /* outline color */ int bg0 = item->color_bg; /* background color */ int bg = bg0; /* fill color */ if (full) { bg = fg; } if (ev == histobar_midtone_level) { fg = bg = COLOR_YELLOW; } if (ev < histobar_shadow_level && full) { fh = h/3; } if (ev == histobar_stops-1 && histobar_clipped > thr) { fg = bg = COLOR_RED; fh = h; } else if (ev == 0 && full) { fg = bg = COLOR_LIGHT_BLUE; } bmp_fill(bg0, x, y, w-2, h-fh); bmp_fill(bg, x, y+h-fh, w-2, fh); bmp_draw_rect(fg, x, y, w-2, h); } } } /*** ? ***/ static LVINFO_UPDATE_FUNC(histobar_indic_update) { LVINFO_BUFFER(10); if (!hist_meter) return; #ifdef FEATURE_RAW_HISTOGRAM if (!RAW_HISTOBAR_ENABLED) return; #endif if (!lv_luma_is_accurate()) return; if (!can_use_raw_overlays()) return; switch (hist_meter) { case HIST_METER_DYNAMIC_RANGE: { int dr = (raw_info.dynamic_range + 5) / 10; snprintf(buffer, sizeof(buffer), SYM_DR"%d.%d", dr/10, dr%10); break; } case HIST_METER_ETTR_HINT: { int stops_until_overexposure = histobar_stops_until_overexposure; if (stops_until_overexposure != INT_MIN) snprintf(buffer, sizeof(buffer), SYM_ETTR"%s%d.%d", FMT_FIXEDPOINT1(stops_until_overexposure)); else snprintf(buffer, sizeof(buffer), "OVER"); break; } } } static struct lvinfo_item info_items[] = { { .name = "HistoBar", .which_bar = LV_PREFER_TOP_BAR, .update = histobar_update, .preferred_position = -1, }, { .name = "EV indic", .which_bar = LV_PREFER_TOP_BAR, .update = histobar_indic_update, .preferred_position = -1, }, }; /*** ml_init ***/ static void hist_init() { lvinfo_add_items(info_items, COUNT(info_items)); } INIT_FUNC("hist", hist_init); #endif