// Tests the FFT implementation against the naive DFT, returning the base-10 logarithm of the RMS error. This number should be less than -10. static double test_fft_log_error(int n) { double *inputreal, *inputimag; double *refoutreal, *refoutimag; double *actualoutreal, *actualoutimag; inputreal = random_reals(n); inputimag = random_reals(n); refoutreal = malloc(n * sizeof(double)); refoutimag = malloc(n * sizeof(double)); naive_dft(inputreal, inputimag, refoutreal, refoutimag, 0, n); actualoutreal = memdup(inputreal, n * sizeof(double)); actualoutimag = memdup(inputimag, n * sizeof(double)); void *fftTables = fft_init(n); if (fftTables == NULL) return 99; fft_transform(fftTables, actualoutreal, actualoutimag); fft_destroy(fftTables); double result = log10_rms_err(refoutreal, refoutimag, actualoutreal, actualoutimag, n); free(inputreal); free(inputimag); free(refoutreal); free(refoutimag); free(actualoutreal); free(actualoutimag); return result; }
int main(int argc, char **argv) { // Self-test to check correct computation of values srand(time(NULL)); int i; for (i = 2; i <= 10; i++) { // Test FFT sizes 4, 8, 16, ..., 512, 1024 if (test_fft_log_error(1 << i) > -10) { printf("Self-test failed\n"); return 1; } } printf("Self-test passed\n"); // Speed benchmark const int64_t TARGET_TIME = 100000000; // In nanoseconds const int TRIALS = 10; printf("%9s %s\n", "Size", "Time per FFT (ns)"); size_t n; for (n = 4; n <= (size_t)1 << 26; n *= 2) { // Initialize data sets void *fftTables = fft_init(n); double *real = random_reals(n); double *imag = random_reals(n); if (fftTables == NULL || real == NULL || imag == NULL) { printf("Memory allocation failed\n"); return 1; } // Determine number of iterations to run to spend TARGET_TIME uint64_t iterations = 1; while (1) { int64_t time = benchmark_time(fftTables, real, imag, iterations); if (time >= TARGET_TIME) { iterations = (uint64_t)((double)TARGET_TIME / time * iterations + 0.5); if (iterations == 0) iterations = 1; break; } iterations *= 2; } // Run trials and store timing double *runtimes = malloc(TRIALS * sizeof(double)); int i; for (i = 0; i < TRIALS; i++) runtimes[i] = (double)benchmark_time(fftTables, real, imag, iterations) / iterations; fft_destroy(fftTables); free(real); free(imag); // Compute statistics double min = 1e300; double sum = 0; for (i = 0; i < TRIALS; i++) { double t = runtimes[i]; if (t < min) min = t; sum += t; } double mean = sum / TRIALS; double sqrdiffsum = 0; for (i = 0; i < TRIALS; i++) { double t = runtimes[i]; sqrdiffsum += (t - mean) * (t - mean); } double stddev = sqrt(sqrdiffsum / TRIALS); free(runtimes); printf("%9zu min=%"PRIu64" mean=%"PRIu64" sd=%.2f%%\n", n, (uint64_t)(min + 0.5), (uint64_t)(mean + 0.5), stddev / mean * 100); } return 0; }
// Run the plugin static void run (const gchar *name, gint nparams, const GimpParam *param, gint *nreturn_vals, GimpParam **return_vals) { // Return values static GimpParam values[1]; gint sel_x1, sel_y1, sel_x2, sel_y2, w, h, padding; PluginData pd; GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; *nreturn_vals = 1; *return_vals = values; if (param[0].type!= GIMP_PDB_INT32) status=GIMP_PDB_CALLING_ERROR; if (param[2].type!=GIMP_PDB_DRAWABLE) status=GIMP_PDB_CALLING_ERROR; run_mode = (GimpRunMode) param[0].data.d_int32; pd.drawable = gimp_drawable_get(param[2].data.d_drawable); gimp_drawable_mask_bounds(pd.drawable->drawable_id, &sel_x1, &sel_y1, &sel_x2, &sel_y2); pd.selection_width = sel_x2 - sel_x1; pd.selection_height = sel_y2 - sel_y1; pd.selection_offset_x = sel_x1; pd.selection_offset_y = sel_y1; pd.image_width = gimp_drawable_width(pd.drawable->drawable_id); pd.image_height = gimp_drawable_height(pd.drawable->drawable_id); pd.channel_count = gimp_drawable_bpp(pd.drawable->drawable_id); pd.point_grabbed = -1; if (run_mode == GIMP_RUN_INTERACTIVE) { // Interactive call with dialog dialog(&pd); if (pd.curve_user.count > 0) { gimp_set_data (PLUG_IN_BINARY, pd.curve_user.user_points, sizeof (GdkPoint) * pd.curve_user.count); } } else if (run_mode == GIMP_RUN_WITH_LAST_VALS) { // Read a saved curve and apply it fft_prepare(&pd); gimp_get_data(PLUG_IN_BINARY, pd.curve_user.user_points); pd.curve_user.count = gimp_get_data_size(PLUG_IN_BINARY) / sizeof (GdkPoint); gimp_pixel_rgn_init(&pd.region, pd.drawable, 0, 0, pd.image_width, pd.image_height, TRUE, TRUE); fft_apply(&pd); gimp_pixel_rgn_set_rect(&pd.region, pd.img_pixels, 0, 0, pd.image_width, pd.image_height); gimp_drawable_flush(pd.drawable); gimp_drawable_merge_shadow(pd.drawable->drawable_id, TRUE); gimp_drawable_update(pd.drawable->drawable_id, pd.selection_offset_x, pd.selection_offset_y, pd.selection_width, pd.selection_height); fft_destroy(&pd); gimp_displays_flush(); } else { status = GIMP_PDB_CALLING_ERROR; } values[0].type = GIMP_PDB_STATUS; values[0].data.d_status = status; gimp_drawable_detach(pd.drawable); }
// Create and handle the plugin's dialog gboolean dialog(PluginData *pd) { gimp_ui_init (PLUG_IN_BINARY, FALSE); GtkWidget *dialog, *main_hbox, *hbox_buttons, *preview, *graph, *vbox, *preview_button, *preview_hd_checkbox; dialog = gimp_dialog_new ("Frequency Curves", PLUG_IN_BINARY, NULL, (GtkDialogFlags)0, gimp_standard_help_func, PLUG_IN_NAME, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, GTK_STOCK_OK, GTK_RESPONSE_OK, NULL); main_hbox = gtk_hbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (main_hbox), 12); gtk_container_add (GTK_CONTAINER(GTK_DIALOG(dialog)->vbox), main_hbox); curve_init(&pd->curve_user); curve_copy(&pd->curve_user, &pd->curve_fft); pd->preview = gimp_drawable_preview_new (pd->drawable, 0); gtk_box_pack_start (GTK_BOX (main_hbox), pd->preview, TRUE, TRUE, 0); gtk_widget_show (pd->preview); g_signal_connect_swapped (pd->preview, "invalidated", G_CALLBACK (preview_invalidated), pd); vbox = gtk_vbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); gtk_container_add (GTK_CONTAINER(main_hbox), vbox); gtk_widget_show(vbox); graph = pd->graph = gtk_drawing_area_new(); pd->graph_pixmap = NULL; gtk_widget_set_size_request (graph, GRAPH_WIDTH, GRAPH_HEIGHT); gtk_widget_set_events (graph, GDK_EXPOSURE_MASK | GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_ENTER_NOTIFY_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_BUTTON1_MOTION_MASK); gtk_container_add (GTK_CONTAINER (vbox), graph); gtk_widget_show (graph); g_signal_connect (graph, "event", G_CALLBACK (graph_events), pd); hbox_buttons = gtk_hbox_new (FALSE, 12); gtk_container_set_border_width (GTK_CONTAINER (hbox_buttons), 12); gtk_container_add (GTK_CONTAINER(vbox), hbox_buttons); gtk_widget_show(hbox_buttons); preview_button = gtk_button_new_with_mnemonic ("HD _Preview"); gtk_box_pack_start (GTK_BOX (hbox_buttons), preview_button, FALSE, FALSE, 0); gtk_widget_show (preview_button); g_signal_connect (preview_button, "clicked", G_CALLBACK (preview_hd), pd); preview_hd_checkbox = gtk_check_button_new_with_label("Always preview HD"); gtk_box_pack_start (GTK_BOX (hbox_buttons), preview_hd_checkbox, FALSE, FALSE, 0); gtk_widget_show (preview_hd_checkbox); pd->do_preview_hd = FALSE; gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(preview_hd_checkbox), FALSE); g_signal_connect (preview_hd_checkbox, "toggled", G_CALLBACK (preview_hd_toggled), pd); gtk_widget_show(main_hbox); gtk_widget_show(dialog); fft_prepare(pd); histogram_generate(pd); wavelet_prepare(pd); gboolean run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); if (run) { // set the region mode to actual writing gimp_pixel_rgn_init(&pd->region, pd->drawable, 0, 0, pd->image_width, pd->image_height, TRUE, TRUE); fft_apply(pd); gimp_pixel_rgn_set_rect(&pd->region, pd->img_pixels, 0, 0, pd->image_width, pd->image_height); // show the result gimp_drawable_flush(pd->drawable); gimp_drawable_merge_shadow(pd->drawable->drawable_id, TRUE); gimp_drawable_update(pd->drawable->drawable_id, pd->selection_offset_x, pd->selection_offset_y, pd->selection_width, pd->selection_height); gimp_displays_flush(); } fft_destroy(pd); wavelet_destroy(pd); gtk_widget_destroy (dialog); return run; }