static struct flow_interpolation_line_contributions * LineContributions_alloc(flow_c * context, const uint32_t line_length, const uint32_t windows_size) { struct flow_interpolation_line_contributions * res = (struct flow_interpolation_line_contributions *)FLOW_malloc( context, sizeof(struct flow_interpolation_line_contributions)); if (res == NULL) { FLOW_error(context, flow_status_Out_of_memory); return NULL; } res->WindowSize = windows_size; res->LineLength = line_length; res->ContribRow = (struct flow_interpolation_pixel_contributions *)FLOW_malloc( context, line_length * sizeof(struct flow_interpolation_pixel_contributions)); if (!res->ContribRow) { FLOW_free(context, res); FLOW_error(context, flow_status_Out_of_memory); return NULL; } float * allWeights = FLOW_calloc_array(context, windows_size * line_length, float); if (!allWeights) { FLOW_free(context, res->ContribRow); FLOW_free(context, res); FLOW_error(context, flow_status_Out_of_memory); return NULL; } for (uint32_t i = 0; i < line_length; i++) res->ContribRow[i].Weights = allWeights + (i * windows_size); return res; }
bool visual_compare_two(flow_c * c, struct flow_bitmap_bgra * a, struct flow_bitmap_bgra * b, const char * comparison_title, double * out_dssim, bool save_bitmaps, bool generate_visual_diff, const char * file_, const char * func_, int line_number, const char * storage_relative_to) { char checksum_a[34]; char checksum_b[34]; // compute checksum of bitmap (two checksums, actually - one for configuration, another for bitmap bytes) if (!checksum_bitmap(c, a, checksum_a, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } if (!checksum_bitmap(c, b, checksum_b, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } // Compare if (strcmp(checksum_a, checksum_b) == 0) { if (memcmp(a->pixels, b->pixels, a->stride * a->h) == 0) { *out_dssim = 0; return true; // It matches! } else { // Checksum collsion exit(901); } } if (save_bitmaps) { // They differ if (!save_bitmap_to_visuals(c, a, checksum_a, "A", storage_relative_to)) { FLOW_error_return(c); } if (!save_bitmap_to_visuals(c, b, checksum_b, "B", storage_relative_to)) { FLOW_error_return(c); } // Diff the two, generate a third PNG. Also get PSNR metrics from imagemagick if (!diff_images(c, checksum_a, checksum_b, out_dssim, generate_visual_diff && save_bitmaps, storage_relative_to)) { FLOW_error_return(c); } // Dump to HTML= if (!append_html(c, comparison_title, checksum_a, checksum_b, storage_relative_to)) { FLOW_error_return(c); } } return false; }
bool diff_image_pixels(flow_c * c, struct flow_bitmap_bgra * a, struct flow_bitmap_bgra * b, size_t * diff_count, size_t * total_delta, size_t print_this_many_differences, size_t stop_counting_at) { if (a->w != b->w || a->h != b->h || a->fmt != b->fmt || a->fmt != flow_bgra32) { FLOW_error(c, flow_status_Invalid_argument); return false; } *diff_count = 0; *total_delta = 0; for (size_t pixel_index = 0; pixel_index < a->h * a->stride; pixel_index++) { if (a->pixels[pixel_index] != b->pixels[pixel_index]) { int x = (pixel_index % (a->stride)) / 4; int y = pixel_index / a->stride; int channel = pixel_index % 4; const char channels[] = { "BGRA" }; if (*diff_count < print_this_many_differences) { fprintf(stderr, " (%d,%d) %ca=%d %cb=%d ", x, y, channels[channel], a->pixels[pixel_index], channels[channel], b->pixels[pixel_index]); } (*diff_count)++; (*total_delta) += abs(a->pixels[pixel_index] - b->pixels[pixel_index]); if (stop_counting_at == *diff_count) return true; // We stop comparing after X many differences } } return true; }
static bool append_html(flow_c * c, const char * name, const char * checksum_a, const char * checksum_b, const char * storage_relative_to) { char filename[2048]; if (!create_path_from_relative(c, storage_relative_to, true, filename, 2048, "/visuals/visuals.html")) { FLOW_add_to_callstack(c); return false; } static bool first_write = true; FILE * fp = fopen(filename, first_write ? "w" : "a"); if (fp == NULL) { FLOW_error(c, flow_status_IO_error); return false; } if (first_write) { // Write the header here } if (checksum_b == NULL) { fprintf(fp, "<h1>%s</h2>\n<img class=\"current\" src=\"%s.png\"/>", name, checksum_a); } else { fprintf(fp, "<h1>%s</h2>\n<img class=\"current\" src=\"%s.png\"/><img class=\"correct\" src=\"%s.png\"/><img " "class=\"diff\" src=\"compare_%s_vs_%s.png\"/>", name, checksum_a, checksum_b, checksum_a, checksum_b); } fclose(fp); first_write = false; return true; }
bool flow_job_link_codecs(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (graph_ref == NULL || *graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } struct flow_graph * g = *graph_ref; int32_t i; for (i = 0; i < g->next_node_id; i++) { if (g->nodes[i].type == flow_ntype_decoder || g->nodes[i].type == flow_ntype_encoder) { uint8_t * info_bytes = &g->info_bytes[g->nodes[i].info_byte_index]; struct flow_nodeinfo_codec * info = (struct flow_nodeinfo_codec *)info_bytes; if (info->codec == NULL) { info->codec = flow_job_get_codec_instance(c, job, info->placeholder_id); if (info->codec == NULL) FLOW_error_msg(c, flow_status_Graph_invalid, "No matching codec or io found for placeholder id %d (node #%d).", info->placeholder_id, i); } } } return true; }
struct flow_interpolation_details * flow_interpolation_details_create(flow_c * context) { struct flow_interpolation_details * d = FLOW_calloc_array(context, 1, struct flow_interpolation_details); if (d == NULL) { FLOW_error(context, flow_status_Out_of_memory); return NULL; } d->blur = 1; d->window = 2; d->p1 = d->q1 = 0; d->p2 = d->q2 = d->p3 = d->q3 = d->q4 = 1; d->sharpen_percent_goal = 0; return d; }
bool flow_graph_pre_optimize_flatten(flow_c * c, struct flow_graph ** graph_ref) { if (*graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } bool re_walk; do { re_walk = false; if (!flow_graph_walk_dependency_wise(c, NULL, graph_ref, node_visitor_flatten, NULL, &re_walk)) { FLOW_error_return(c); } } while (re_walk); return true; }
bool flow_graph_optimize(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (*graph_ref == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } bool re_walk; do { re_walk = false; if (!flow_graph_walk(c, job, graph_ref, node_visitor_optimize, NULL, &re_walk)) { FLOW_error_return(c); } } while (re_walk); return true; }
bool append_checksum(flow_c * c, char checksum[34], const char * name, const char * storage_relative_to) { char filename[2048]; if (!create_path_from_relative(c, storage_relative_to, true, filename, 2048, "/visuals/checksums.list")) { FLOW_add_to_callstack(c); return false; } FILE * fp = fopen(filename, "a"); if (fp == NULL) { FLOW_error(c, flow_status_IO_error); return false; } fprintf(fp, "%s\n%s\n", name, &checksum[0]); fclose(fp); return true; }
bool flow_job_execute_where_certain(flow_c * c, struct flow_job * job, struct flow_graph ** g) { if (*g == NULL) { FLOW_error(c, flow_status_Null_argument); return false; } // //Resets and creates state tracking for this graph // if (!flow_job_create_state(c,job, *g)){ // FLOW_error_return(c); // } if (!flow_graph_walk_dependency_wise(c, job, g, node_visitor_execute, NULL, NULL)) { FLOW_error_return(c); } return true; }
static bool load_checksums(flow_c * c, struct named_checksum ** checksums, size_t * checksum_count, const char * storage_relative_to) { static struct named_checksum * list = NULL; static size_t list_size = 0; if (list == NULL) { char filename[2048]; if (!create_path_from_relative(c,storage_relative_to, false, filename, 2048, "/visuals/checksums.list")) { FLOW_add_to_callstack(c); return false; } //fprintf(stderr, "Using checkums from %s.", filename); FILE * fp; char * line_a = NULL; size_t len_a = 0; int64_t read_a; char * line_b = NULL; size_t len_b = 0; int64_t read_b; fp = fopen(filename, "r"); if (fp == NULL) { FLOW_error(c, flow_status_IO_error); return false; } list_size = 200; list = (struct named_checksum *)calloc(list_size, sizeof(struct named_checksum)); size_t index = 0; while (true) { // Read lines in pairs read_a = flow_getline(&line_a, &len_a, fp); if (read_a == -1) { break; } read_b = flow_getline(&line_b, &len_b, fp); if (read_b == -1) { free(line_a); break; } // Drop newlines if present if (line_a[read_a - 1] == '\n') { line_a[read_a - 1] = '\0'; } if (line_b[read_b - 1] == '\n') { line_b[read_b - 1] = '\0'; } // Save list[index].name = line_a; list[index].checksum = line_b; line_a = NULL; line_b = NULL; index++; if (index >= list_size) { FLOW_error_msg(c, flow_status_IO_error, "Could not read in entire checksum file. Please increase list_size above %ul.", list_size); fclose(fp); return false; } } list_size = index; fclose(fp); } *checksum_count = list_size; *checksums = list; return true; }
bool visual_compare(flow_c * c, struct flow_bitmap_bgra * bitmap, const char * name, bool store_checksums, size_t off_by_one_byte_differences_permitted, const char * file_, const char * func_, int line_number, const char * storage_relative_to) { char checksum[34]; // compute checksum of bitmap (two checksums, actually - one for configuration, another for bitmap bytes) if (!checksum_bitmap(c, bitmap, checksum, 34)) { FLOW_error(c, flow_status_Invalid_argument); return false; } // Load stored checksum char * stored_checksum = get_checksum_for(c, name, storage_relative_to); // Compare if (stored_checksum != NULL && strcmp(checksum, stored_checksum) == 0) { // Make sure the file is created for later if (!save_bitmap_to_visuals(c, bitmap, checksum, "trusted", storage_relative_to)) { FLOW_error_return(c); } return true; // It matches! } if (stored_checksum == NULL) { // No stored checksum for this name if (store_checksums) { if (!append_checksum(c, checksum, name, storage_relative_to)) { FLOW_error_return(c); } fprintf(stderr, "===============\n%s\nStoring checksum %s, since FLOW_STORE_CHECKSUMS was set.\n ", name, &checksum[0]); } else { fprintf(stderr, "===============\n%s\nThere is no stored checksum for this test; #define " "FLOW_STORE_CHECKSUMS and rerun to set the initial value to %s.\n ", name, &checksum[0]); } fprintf(stderr, "%s:%d in function %s\n", file_, line_number, func_); } else { fprintf(stderr, "===============\n%s\nThe stored checksum [%s] differs from the current result [%s]. Open " "visuals/visuals.html to comapre.\n ", name, stored_checksum, checksum); fprintf(stderr, "%s:%d in function %s\n", file_, line_number, func_); } // The hash differs // Save ours so we can see it if (!save_bitmap_to_visuals(c, bitmap, checksum, "current", storage_relative_to)) { FLOW_error_return(c); } if (stored_checksum == NULL && store_checksums) { // Don't fail the test for an non-stored checksum. We may be trying to commit several new checksums return true; } if (stored_checksum != NULL) { // Try to download "old" png from S3 using the checksums as an address. if (!download_by_checksum(c, stored_checksum, storage_relative_to)) { FLOW_error_return(c); } // First try a byte-by-byte comparison to eliminate floating-point error struct flow_bitmap_bgra * old; if (!load_image(c, stored_checksum, &old, c, storage_relative_to)) { FLOW_error_return(c); } size_t differences; size_t differences_to_print = 0; // 100 size_t total_delta; if (!diff_image_pixels(c, old, bitmap, &differences, &total_delta, 0, off_by_one_byte_differences_permitted + 4096)) { FLOW_error_return(c); } // If the difference is a handful of off-by-one rounding changes, just print the different bytes. if (differences < off_by_one_byte_differences_permitted && total_delta == differences) { if (!diff_image_pixels(c, old, bitmap, &differences, &total_delta, differences_to_print, off_by_one_byte_differences_permitted + 4096)) { FLOW_error_return(c); } fprintf(stderr, "\nA total of %lu bytes (of %lu) differed between the bitmaps (off by one). Less than " "failure threshold of %lu\n", differences, (size_t)old->stride * (size_t)old->h, off_by_one_byte_differences_permitted); return true; } else { double dssim; // Diff the two, generate a third PNG. Also get PSNR metrics from imagemagick if (!diff_images(c, checksum, stored_checksum, &dssim, true, storage_relative_to)) { FLOW_error_return(c); } fprintf(stdout, "DSSIM %.20f between %s and %s\n", dssim, stored_checksum, checksum); // Dump to HTML= if (!append_html(c, name, checksum, stored_checksum, storage_relative_to)) { FLOW_error_return(c); } } flow_bitmap_bgra_destroy(c, old); } return false; }
bool flow_job_execute(flow_c * c, struct flow_job * job, struct flow_graph ** graph_ref) { if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_link_codecs(c, job, graph_ref)) { FLOW_error_return(c); } // States for a node // New // OutboundDimensionsKnown // Flattened // Optimized // LockedForExecution // Executed int32_t passes = 0; while (!flow_job_graph_fully_executed(c, job, *graph_ref)) { if (passes >= job->max_calc_flatten_execute_passes) { FLOW_error(c, flow_status_Maximum_graph_passes_exceeded); return false; } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_pre_optimize_flatten(c, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_optimize(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_graph_post_optimize_flatten(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_populate_dimensions_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } if (!flow_job_execute_where_certain(c, job, graph_ref)) { FLOW_error_return(c); } passes++; if (!flow_job_notify_graph_changed(c, job, *graph_ref)) { FLOW_error_return(c); } } if (job->next_graph_version > 0 && job->render_last_graph && !flow_job_render_graph_to_png(c, job, *graph_ref, job->next_graph_version - 1)) { FLOW_error_return(c); } return true; }
struct flow_interpolation_line_contributions * flow_interpolation_line_contributions_create(flow_c * context, const uint32_t output_line_size, const uint32_t input_line_size, const struct flow_interpolation_details * details) { const double sharpen_ratio = flow_interpolation_details_percent_negative_weight(details); const double desired_sharpen_ratio = details->sharpen_percent_goal / 100.0; const double scale_factor = (double)output_line_size / (double)input_line_size; const double downscale_factor = fmin(1.0, scale_factor); const double half_source_window = (details->window + 0.5) / downscale_factor; const uint32_t allocated_window_size = (int)ceil(2 * (half_source_window - TONY)) + 1; uint32_t u, ix; struct flow_interpolation_line_contributions * res = LineContributions_alloc(context, output_line_size, allocated_window_size); if (res == NULL) { FLOW_add_to_callstack(context); return NULL; } double negative_area = 0; double positive_area = 0; for (u = 0; u < output_line_size; u++) { const double center_src_pixel = ((double)u + 0.5) / scale_factor - 0.5; const int left_edge = (int)floor(center_src_pixel) - ((allocated_window_size - 1) / 2); const int right_edge = left_edge + allocated_window_size - 1; const uint32_t left_src_pixel = (uint32_t)int_max(0, left_edge); const uint32_t right_src_pixel = (uint32_t)int_min(right_edge, (int)input_line_size - 1); double total_weight = 0.0; double total_negative_weight = 0.0; const uint32_t source_pixel_count = right_src_pixel - left_src_pixel + 1; if (source_pixel_count > allocated_window_size) { flow_interpolation_line_contributions_destroy(context, res); FLOW_error(context, flow_status_Invalid_internal_state); return NULL; } res->ContribRow[u].Left = left_src_pixel; res->ContribRow[u].Right = right_src_pixel; float * weights = res->ContribRow[u].Weights; // commented: additional weight for edges (doesn't seem to be too effective) // for (ix = left_edge; ix <= right_edge; ix++) { for (ix = left_src_pixel; ix <= right_src_pixel; ix++) { int tx = ix - left_src_pixel; // int tx = min(max(ix, left_src_pixel), right_src_pixel) - left_src_pixel; double add = (*details->filter)(details, downscale_factor *((double)ix - center_src_pixel)); if (fabs(add) <= 0.00000002){ add = 0.0; // Weights below a certain threshold make consistent x-plat // integration test results impossible. pos/neg zero, etc. // They should be rounded down to zero at the threshold at which results are consistent. } weights[tx] = (float)add; total_weight += add; total_negative_weight -= fmin(0,add); } float neg_factor, pos_factor; if (total_weight <= 0 || desired_sharpen_ratio > sharpen_ratio){ float total_positive_weight = total_weight + total_negative_weight; float target_negative_weight = desired_sharpen_ratio * total_positive_weight; pos_factor = 1; neg_factor = target_negative_weight / total_negative_weight; }else{ neg_factor = pos_factor = (float)(1.0f / total_weight); } for (ix = 0; ix < source_pixel_count; ix++) { if (weights[ix] < 0) { weights[ix] *= neg_factor; negative_area -= weights[ix]; } else { weights[ix] *= pos_factor; positive_area += weights[ix]; } } //Shrink to improve perf & result consistency int32_t iix; //Shrink region from the right for (iix = source_pixel_count - 1; iix >= 0; iix--){ if (weights[iix] != 0) break; res->ContribRow[u].Right--; } //Shrink region from the left for (iix = 0; iix < (int32_t)source_pixel_count; iix++){ if (weights[0] != 0) break; res->ContribRow[u].Weights++; weights++; res->ContribRow[u].Left++; } } res->percent_negative = negative_area / positive_area; return res; }