bool load_image(flow_c * c, char * checksum, struct flow_bitmap_bgra ** ref, void * bitmap_owner) { char filename[2048]; if (!create_relative_path(c, false, filename, 2048, "/visuals/%s.png", checksum)) { FLOW_add_to_callstack(c); return false; } struct flow_job * job = flow_job_create(c); if (job == NULL) { FLOW_error_return(c); } size_t bytes_count; uint8_t * bytes = read_all_bytes(c, &bytes_count, filename); if (bytes == NULL) { FLOW_error_return(c); } struct flow_io * input = flow_io_create_from_memory(c, flow_io_mode_read_seekable, bytes, bytes_count, job, NULL); if (input == NULL) { FLOW_error_return(c); } if (!flow_job_add_io(c, job, input, 0, FLOW_INPUT)) { FLOW_error_return(c); } struct flow_graph * g = flow_graph_create(c, 10, 10, 200, 2.0); if (g == NULL) { FLOW_add_to_callstack(c); return false; } int32_t last; last = flow_node_create_decoder(c, &g, -1, 0); last = flow_node_create_bitmap_bgra_reference(c, &g, last, ref); if (flow_context_has_error(c)) { FLOW_add_to_callstack(c); return false; } if (!flow_job_execute(c, job, &g)) { FLOW_add_to_callstack(c); return false; } // Let the bitmap last longer than the job if (!flow_set_owner(c, *ref, bitmap_owner)) { FLOW_add_to_callstack(c); return false; } if (!flow_job_destroy(c, job)) { FLOW_error_return(c); } FLOW_free(c, bytes); 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; }
static bool save_bitmap_to_visuals(flow_c * c, struct flow_bitmap_bgra * bitmap, char * checksum, char * name) { char filename[2048]; if (!create_relative_path(c, true, filename, 2048, "/visuals/%s.png", checksum)) { FLOW_add_to_callstack(c); return false; } if (access(filename, F_OK) != -1) { return true; // Already exists! } if (!write_frame_to_disk(c, &filename[0], bitmap)) { FLOW_add_to_callstack(c); return false; } fprintf(stderr, "%s (%s)\n", &filename[0], name); return true; }
static bool diff_images(flow_c * c, char * checksum_a, char * checksum_b, double * out_dssim, bool generate_visual_diff, const char * storage_relative_to) { char filename_a[2048]; if (!create_path_from_relative(c, storage_relative_to, false, filename_a, 2048, "/visuals/%s.png", checksum_a)) { FLOW_add_to_callstack(c); return false; } char filename_b[2048]; if (!create_path_from_relative(c, storage_relative_to, false, filename_b, 2048, "/visuals/%s.png", checksum_b)) { FLOW_add_to_callstack(c); return false; } char filename_c[2048]; if (!create_path_from_relative(c, storage_relative_to, false, filename_c, 2048, "/visuals/compare_%s_vs_%s.png", checksum_a, checksum_b)) { FLOW_add_to_callstack(c); return false; } char magick_command[4096]; flow_snprintf(magick_command, 4096, "dssim %s %s", filename_b, filename_a); *out_dssim = get_dssim_from_command(c, magick_command); if (*out_dssim > 10 || *out_dssim < 0) { fprintf(stderr, "Failed to execute: %s", magick_command); *out_dssim = 2.23456; // FLOW_error(c, flow_status_IO_error); }; if (generate_visual_diff) { fprintf(stderr, "%s\n", &filename_c[0]); if (access(filename_c, F_OK) != -1) { return true; // Already exists! } flow_snprintf(magick_command, 4096, "composite %s %s -compose difference %s", filename_a, filename_b, filename_c); int result = system(magick_command); if (result != 0) { fprintf(stderr, "unhappy imagemagick\n"); } } return true; }
static bool download_by_checksum(flow_c * c, char * checksum, const char * storage_relative_to) { char filename[2048]; if (!create_path_from_relative(c, storage_relative_to, true, filename, 2048, "/visuals/%s.png", checksum)) { FLOW_add_to_callstack(c); return false; } fprintf(stderr, "%s (trusted)\n", &filename[0]); if (access(filename, F_OK) != -1) { return true; // Already exists! } char url[2048]; flow_snprintf(url, 2048, "http://s3-us-west-2.amazonaws.com/imageflow-resources/visual_test_checksums/%s.png", checksum); // TODO: fix actual URL if (!fetch_image(url, filename)) { FLOW_add_to_callstack(c); return false; } return true; }
bool load_image(flow_c * c, char * checksum, struct flow_bitmap_bgra ** ref, void * bitmap_owner, const char * storage_relative_to) { char filename[2048]; if (!create_path_from_relative(c, storage_relative_to, false, filename, 2048, "/visuals/%s.png", checksum)) { FLOW_add_to_callstack(c); return false; } // load PNG if (!flow_bitmap_bgra_load_png(c, ref, filename)) { FLOW_add_to_callstack(c); return false; } // Let the bitmap last longer than the job if (!flow_set_owner(c, *ref, bitmap_owner)) { FLOW_add_to_callstack(c); return false; } return true; }
struct flow_interpolation_details * flow_interpolation_details_create_custom(flow_c * context, double window, double blur, flow_detailed_interpolation_method filter) { struct flow_interpolation_details * d = flow_interpolation_details_create(context); if (d != NULL) { d->blur = blur; d->filter = filter; d->window = window; } else { FLOW_add_to_callstack(context); } return d; }
struct flow_interpolation_details * flow_interpolation_details_create_bicubic_custom(flow_c * context, double window, double blur, double B, double C) { struct flow_interpolation_details * d = flow_interpolation_details_create(context); if (d != NULL) { d->blur = blur; derive_cubic_coefficients(B, C, d); d->filter = filter_flex_cubic; d->window = window; } else { FLOW_add_to_callstack(context); } return d; }
static char * get_checksum_for(flow_c * c, const char * name, const char * storage_relative_to) { struct named_checksum * checksums = NULL; size_t checksum_count = 0; if (!load_checksums(c, &checksums, &checksum_count, storage_relative_to)) { FLOW_add_to_callstack(c); return NULL; } for (size_t i = 0; i < checksum_count; i++) { if (strcmp(checksums[i].name, name) == 0) { return checksums[i].checksum; } } return NULL; }
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; }
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; }
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; }