xmlDocPtr create_new_editdoc(char *action, flam3_genome *parent0, flam3_genome *parent1) { xmlDocPtr doc = NULL, comment_doc = NULL; xmlNodePtr root_node = NULL, node = NULL, nodecopy = NULL; xmlNodePtr root_comment = NULL; struct tm *localt; time_t mytime; char *ai; char timestring[100]; char *nick = getenv("nick"); char *url = getenv("url"); char *id = getenv("id"); char *comment = getenv("comment"); int sheep_gen = argi("sheep_gen",-1); int sheep_id = argi("sheep_id",-1); char buffer[100]; char comment_string[100]; doc = xmlNewDoc( (const xmlChar *)"1.0"); /* Create the root node, called "edit" */ root_node = xmlNewNode(NULL, (const xmlChar *)"edit"); xmlDocSetRootElement(doc,root_node); /* Add the edit attributes */ /* date */ mytime = time(NULL); localt = localtime(&mytime); /* XXX use standard time format including timezone */ strftime(timestring, 100, "%a %b %e %H:%M:%S %z %Y", localt); xmlNewProp(root_node, (const xmlChar *)"date", (const xmlChar *)timestring); /* nick */ if (nick) { xmlNewProp(root_node, (const xmlChar *)"nick", (const xmlChar *)nick); } /* url */ if (url) { xmlNewProp(root_node, (const xmlChar *)"url", (const xmlChar *)url); } if (id) { xmlNewProp(root_node, (const xmlChar *)"id", (const xmlChar *)id); } /* action */ xmlNewProp(root_node, (const xmlChar *)"action", (const xmlChar *)action); /* sheep info */ if (sheep_gen > 0 && sheep_id > 0) { /* Create a child node of the root node called sheep */ node = xmlNewChild(root_node, NULL, (const xmlChar *)"sheep", NULL); /* Create the sheep attributes */ sprintf(buffer, "%d", sheep_gen); xmlNewProp(node, (const xmlChar *)"generation", (const xmlChar *)buffer); sprintf(buffer, "%d", sheep_id); xmlNewProp(node, (const xmlChar *)"id", (const xmlChar *)buffer); } /* Check for the parents */ /* If Parent 0 not specified, this is a randomly generated genome. */ if (parent0) { if (parent0->edits) { /* Copy the node from the parent */ node = xmlDocGetRootElement(parent0->edits); nodecopy = xmlCopyNode(node, 1); xmlNewProp(nodecopy,(const xmlChar *)"filename", (const xmlChar *)parent0->parent_fname); sprintf(buffer,"%d",parent0->genome_index); xmlNewProp(nodecopy,(const xmlChar *)"index", (const xmlChar *)buffer); xmlAddChild(root_node, nodecopy); } else { /* Insert a (parent has no edit) message */ nodecopy = xmlNewChild(root_node, NULL, (const xmlChar *)"edit",NULL); xmlNewProp(nodecopy,(const xmlChar *)"filename", (const xmlChar *)parent0->parent_fname); sprintf(buffer,"%d",parent0->genome_index); xmlNewProp(nodecopy,(const xmlChar *)"index", (const xmlChar *)buffer); } } if (parent1) { if (parent1->edits) { /* Copy the node from the parent */ node = xmlDocGetRootElement(parent1->edits); nodecopy = xmlCopyNode(node, 1); xmlNewProp(nodecopy,(const xmlChar *)"filename", (const xmlChar *)parent1->parent_fname); sprintf(buffer,"%d",parent1->genome_index); xmlNewProp(nodecopy,(const xmlChar *)"index", (const xmlChar *)buffer); xmlAddChild(root_node, nodecopy); } else { /* Insert a (parent has no edit) message */ nodecopy = xmlNewChild(root_node, NULL, (const xmlChar *)"edit",NULL); xmlNewProp(nodecopy,(const xmlChar *)"filename", (const xmlChar *)parent1->parent_fname); sprintf(buffer,"%d",parent1->genome_index); xmlNewProp(nodecopy,(const xmlChar *)"index", (const xmlChar *)buffer); } } /* Comment string */ /* This one's hard, since we have to treat the comment string as */ /* a valid XML document. Create a new document using the comment */ /* string as the in-memory document, and then copy all children of */ /* the root node into the edit structure */ /* Parsing the comment string should be done once and then copied */ /* for each call to create_new_editdoc, but that's for later. */ if (comment) { sprintf(comment_string,"<comm>%s</comm>",comment); comment_doc = xmlReadMemory(comment_string, strlen(comment_string), "comment.env", NULL, XML_PARSE_NONET); /* Check for errors */ if (comment_doc==NULL) { fprintf(stderr, "Failed to parse comment into XML!\n"); exit(1); } /* Loop through the children of the new document and copy */ /* them into the root_node */ root_comment = xmlDocGetRootElement(comment_doc); for (node=root_comment->children; node; node = node->next) { nodecopy = xmlCopyNode(node,1); xmlAddChild(root_node, nodecopy); } /* Free the created document */ xmlFreeDoc(comment_doc); } /* return the xml doc */ return(doc); }
void write_jpeg(FILE *file, unsigned char *image, int width, int height, flam3_img_comments *fpc) { struct jpeg_compress_struct info; struct jpeg_error_mgr jerr; size_t i; char *nick = getenv("nick"); char *url = getenv("url"); char *id = getenv("id"); char *ai; /* For argi */ int jpegcom_enable = argi("enable_jpeg_comments",1); char nick_string[64], url_string[128], id_string[128]; char bv_string[64],ni_string[64],rt_string[64]; char genome_string[65536], ver_string[64]; /* Create the mandatory comment strings */ snprintf(genome_string,65536,"flam3_genome: %s",fpc->genome); snprintf(bv_string,64,"flam3_error_rate: %s",fpc->badvals); snprintf(ni_string,64,"flam3_samples: %s",fpc->numiters); snprintf(rt_string,64,"flam3_time: %s",fpc->rtime); snprintf(ver_string,64,"flam3_version: %s",flam3_version()); info.err = jpeg_std_error(&jerr); jpeg_create_compress(&info); jpeg_stdio_dest(&info, file); info.in_color_space = JCS_RGB; info.input_components = 3; info.image_width = width; info.image_height = height; jpeg_set_defaults(&info); if (getenv("jpeg")) { int quality = atoi(getenv("jpeg")); jpeg_set_quality(&info, quality, TRUE); } jpeg_start_compress(&info, TRUE); /* Write comments to jpeg */ if (jpegcom_enable==1) { jpeg_write_marker(&info, JPEG_COM, (unsigned char *)ver_string, (int)strlen(ver_string)); if (0 != nick) { snprintf(nick_string,64,"flam3_nickname: %s",nick); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)nick_string, (int)strlen(nick_string)); } if (0 != url) { snprintf(url_string,128,"flam3_url: %s",url); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)url_string, (int)strlen(url_string)); } if (0 != id) { snprintf(id_string,128,"flam3_id: %s",id); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)id_string, (int)strlen(id_string)); } jpeg_write_marker(&info, JPEG_COM, (unsigned char *)bv_string, (int)strlen(bv_string)); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)ni_string, (int)strlen(ni_string)); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)rt_string, (int)strlen(rt_string)); jpeg_write_marker(&info, JPEG_COM, (unsigned char *)genome_string, (int)strlen(genome_string)); } for (i = 0; i < height; i++) { JSAMPROW row_pointer[1]; row_pointer[0] = (unsigned char *) image + (3 * width * i); jpeg_write_scanlines(&info, row_pointer, 1); } jpeg_finish_compress(&info); jpeg_destroy_compress(&info); }
int main(int argc, char **argv) { char *ai, *fname; char flamename[256]; char *prefix = args("prefix", ""); int first_frame = argi("begin", 0); int last_frame = argi("end", 0); int frame_time = argi("time", 0); int dtime = argi("dtime", 1); int do_fields = argi("fields", 0); int write_genome = argi("write_genome",0); double qs = argf("qs", 1.0); double ss = argf("ss", 1.0); char *format = getenv("format"); int verbose = argi("verbose", 1); int transparency = argi("transparency", 0); int bits = argi("bits", 33); int sub_batch_size = argi("sub_batch_size", 10000); int bpc = argi("bpc",8); int earlyclip = argi("earlyclip",0); int ftime, channels; unsigned char *image; flam3_genome *cps,center_cp; int i, ncps = 0; char *inf = getenv("in"); double pixel_aspect = argf("pixel_aspect", 1.0); int num_threads = argi("nthreads",0); FILE *in,*fp,*genfp; flam3_frame f; flam3_img_comments fpc; stat_struct stats,stats2; char badval_string[64]; char numiter_string[64]; char rtime_string[64]; #ifdef WIN32 char *slashloc; char exepath[256]; char palpath[256]; memset(exepath,0,256); memset(palpath,0,256); slashloc = strrchr(argv[0],'\\'); if (NULL==slashloc) { sprintf(palpath,"flam3_palettes=flam3-palettes.xml"); } else { strncpy(exepath,argv[0],slashloc-argv[0]+1); sprintf(palpath,"flam3_palettes=%sflam3-palettes.xml",exepath); } putenv(palpath); #endif memset(¢er_cp,0, sizeof(flam3_genome)); if (1 != argc) { docstring(); exit(0); } /* Init random number generators */ flam3_init_frame(&f); flam3_srandom(); /* Set the number of threads */ if (num_threads==0) { num_threads = flam3_count_nthreads(); if (verbose > 1) fprintf(stderr,"Automatically detected %d core(s)...\n",num_threads); } else{ if (verbose) fprintf(stderr,"Manually specified %d thread(s)...\n",num_threads); } if (getenv("frame")) { if (getenv("time")) { fprintf(stderr, "cannot specify both time and frame.\n"); exit(1); } if (getenv("begin") || getenv("end")) { fprintf(stderr, "cannot specify both frame and begin or end.\n"); exit(1); } first_frame = last_frame = atoi(getenv("frame")); } if (getenv("time")) { if (getenv("begin") || getenv("end")) { fprintf(stderr, "cannot specify both time and begin or end.\n"); exit(1); } first_frame = last_frame = frame_time; } if (NULL == format) format = "png"; if (strcmp(format, "jpg") && strcmp(format, "ppm") && strcmp(format, "png")) { fprintf(stderr, "format must be either jpg, ppm, or png, not %s.\n", format); exit(1); } if (pixel_aspect <= 0.0) { fprintf(stderr, "pixel aspect ratio must be positive, not %g.\n", pixel_aspect); exit(1); } if (inf) in = fopen(inf, "rb"); else in = stdin; if (NULL == in) { perror(inf); exit(1); } cps = flam3_parse_from_file(in, inf, flam3_defaults_on, &ncps); if (inf) fclose(in); if (NULL == cps) { fprintf(stderr," error reading genomes.\n"); exit(1); } if (0 == ncps) { fprintf(stderr, "error: no genomes.\n"); exit(1); } for (i = 0; i < ncps; i++) { cps[i].sample_density *= qs; cps[i].height = (int)(cps[i].height * ss); cps[i].width = (int)(cps[i].width * ss); cps[i].pixels_per_unit *= ss; if (cps[i].height<=0 || cps[i].width<=0) { fprintf(stderr,"output image has dimension <=0, aborting.\n"); exit(1); } if (i > 0 && cps[i].time <= cps[i-1].time) { fprintf(stderr, "error: control points must be sorted by time, but %g <= %g, index %d.\n", cps[i].time, cps[i-1].time, i); exit(1); } if ((cps[i].width != cps[0].width) || (cps[i].height != cps[0].height)) { fprintf(stderr, "warning: flame %d at time %g size mismatch. " "(%d,%d) should be (%d,%d).\n", i, cps[i].time, cps[i].width, cps[i].height, cps[0].width, cps[0].height); cps[i].width = cps[0].width; cps[i].height = cps[0].height; } } if (!getenv("time") && !getenv("frame")) { if (!getenv("begin")) { first_frame = (int) cps[0].time; } if (!getenv("end")) { last_frame = (int) cps[ncps-1].time - 1; if (last_frame < first_frame) last_frame = first_frame; } } channels = strcmp(format, "png") ? 3 : 4; /* Check for 16-bit-per-channel processing */ if ( (16 == bpc) && (strcmp(format,"png") != 0)) { fprintf(stderr,"Support for 16 bpc images is only present for the png format.\n"); exit(1); } else if (bpc != 8 && bpc != 16) { fprintf(stderr,"Unexpected bpc specified (%d)\n",bpc); exit(1); } // f.temporal_filter_radius = argf("blur", 0.5); f.pixel_aspect_ratio = pixel_aspect; f.genomes = cps; f.ngenomes = ncps; f.verbose = verbose; f.bits = bits; f.progress = 0; f.earlyclip = earlyclip; f.nthreads = num_threads; f.sub_batch_size = sub_batch_size; if (16==bpc) f.bytes_per_channel = 2; else f.bytes_per_channel = 1; image = (void *) calloc((size_t)channels * (size_t)cps[0].width * (size_t)cps[0].height * f.bytes_per_channel, sizeof(char)); if (dtime < 1) { fprintf(stderr, "dtime must be positive, not %d.\n", dtime); exit(1); } for (ftime = first_frame; ftime <= last_frame; ftime += dtime) { f.time = (double) ftime; if (verbose && ((last_frame-first_frame)/dtime) >= 1) { fprintf(stderr, "time = %d/%d/%d\n", ftime, last_frame, dtime); } if (do_fields) { if (flam3_render(&f, image, flam3_field_even, channels, transparency,&stats)) { fprintf(stderr,"error rendering image: aborting.\n"); exit(1); } f.time += 0.5; if (flam3_render(&f, image, flam3_field_odd, channels, transparency,&stats2)) { fprintf(stderr,"error rendering image: aborting.\n"); exit(1); } stats.badvals+=stats2.badvals; stats.render_seconds+=stats2.render_seconds; stats.num_iters+=stats2.num_iters; } else { if (flam3_render(&f, image, flam3_field_both, channels, transparency,&stats)) { fprintf(stderr,"error rendering image: aborting.\n"); exit(1); } } if (getenv("out")) fname = getenv("out"); else { fname = malloc(strlen(prefix) + 20); sprintf(fname, "%s%05d.%s", prefix, ftime, format); } if (verbose) fprintf(stderr, "writing %s...", fname); if (write_genome) { sprintf(flamename,"%s.flam3",fname); /* get center genome */ flam3_interpolate(f.genomes, f.ngenomes, f.time, 0, ¢er_cp); /* write it out */ genfp = fopen(flamename,"w"); if (NULL == genfp) { perror(flamename); exit(1); } flam3_print(genfp, ¢er_cp, NULL, flam3_print_edits); fclose(genfp); } fp = fopen(fname, "wb"); if (NULL == fp) { perror(fname); exit(1); } /* Get center cp for embedding in png file */ flam3_interpolate(f.genomes, f.ngenomes, f.time, 0, ¢er_cp); /* Convert to string */ fpc.genome = flam3_print_to_string(¢er_cp); sprintf(badval_string, "%g",stats.badvals/(double)stats.num_iters); fpc.badvals = badval_string; sprintf(numiter_string,"%g",(double)stats.num_iters); fpc.numiters = numiter_string; sprintf(rtime_string,"%d",stats.render_seconds); fpc.rtime = rtime_string; if (!strcmp(format, "png")) { write_png(fp, image, cps[0].width, cps[0].height, &fpc, f.bytes_per_channel); } else if (!strcmp(format, "jpg")) { write_jpeg(fp, image, cps[0].width, cps[0].height, &fpc); } else { int size = 3 * cps[0].width * cps[0].height; fprintf(fp, "P6\n"); fprintf(fp, "%d %d\n255\n", cps[0].width, cps[0].height); if (size != fwrite(image, 1, size, fp)) { perror(fname); } } /* Free string */ free(fpc.genome); clear_cp(¢er_cp,0); fclose(fp); if (!getenv("out")) free(fname); } for (i = 0; i < ncps; i++) { xmlFreeDoc(cps[i].edits); clear_cp(&cps[i],0); } free(cps); free(image); if (verbose) fprintf(stderr, "done.\n"); fflush(stderr); return 0; }
static void iter_thread(void *fth) { double sub_batch; int j; flam3_thread_helper *fthp = (flam3_thread_helper *)fth; flam3_iter_constants *ficp = fthp->fic; struct timespec pauset; int SBS = ficp->spec->sub_batch_size; int fuse; int cmap_size = ficp->cmap_size; int cmap_size_m1 = ficp->cmap_size-1; double eta = 0.0; fuse = (ficp->spec->earlyclip) ? FUSE_28 : FUSE_27; pauset.tv_sec = 0; pauset.tv_nsec = 100000000; if (fthp->timer_initialize) { *(ficp->progress_timer) = 0; memset(ficp->progress_timer_history,0,64*sizeof(time_t)); memset(ficp->progress_history,0,64*sizeof(double)); *(ficp->progress_history_mark) = 0; } for (sub_batch = 0; sub_batch < ficp->batch_size; sub_batch+=SBS) { int sub_batch_size, badcount; time_t newt = time(NULL); /* sub_batch is double so this is sketchy */ sub_batch_size = (sub_batch + SBS > ficp->batch_size) ? (ficp->batch_size - sub_batch) : SBS; if (fthp->first_thread && newt != *(ficp->progress_timer)) { double percent = 100.0 * ((((sub_batch / (double) ficp->batch_size) + ficp->temporal_sample_num) / ficp->ntemporal_samples) + ficp->batch_num)/ficp->nbatches; int old_mark = 0; int ticker; if (ficp->spec->verbose) fprintf(stderr, "\rchaos: %5.1f%%", percent); *(ficp->progress_timer) = newt; if (ficp->progress_timer_history[*(ficp->progress_history_mark)] && ficp->progress_history[*(ficp->progress_history_mark)] < percent) old_mark = *(ficp->progress_history_mark); if (percent > 0) { eta = (100 - percent) * (*(ficp->progress_timer) - ficp->progress_timer_history[old_mark]) / (percent - ficp->progress_history[old_mark]); if (ficp->spec->verbose) { ticker = (*(ficp->progress_timer)&1)?':':'.'; if (eta < 1000) ticker = ':'; if (eta > 100) fprintf(stderr, " ETA%c %.1f minutes", ticker, eta / 60); else fprintf(stderr, " ETA%c %ld seconds ", ticker, (long) ceil(eta)); fprintf(stderr, " \r"); fflush(stderr); } } ficp->progress_timer_history[*(ficp->progress_history_mark)] = *(ficp->progress_timer); ficp->progress_history[*(ficp->progress_history_mark)] = percent; *(ficp->progress_history_mark) = (*(ficp->progress_history_mark) + 1) % 64; } /* Custom progress function */ if (ficp->spec->progress) { if (fthp->first_thread) { int rv; /* Recalculate % done, as the other calculation only updates once per second */ double percent = 100.0 * ((((sub_batch / (double) ficp->batch_size) + ficp->temporal_sample_num) / ficp->ntemporal_samples) + ficp->batch_num)/ficp->nbatches; rv = (*ficp->spec->progress)(ficp->spec->progress_parameter, percent, 0, eta); if (rv==2) { /* PAUSE */ time_t tnow = time(NULL); time_t tend; int lastpt; ficp->aborted = -1; do { #if defined(_WIN32) /* mingw or msvc */ Sleep(100); #else nanosleep(&pauset,NULL); #endif rv = (*ficp->spec->progress)(ficp->spec->progress_parameter, percent, 0, eta); } while (rv==2); /* modify the timer history to compensate for the pause */ tend = time(NULL)-tnow; ficp->aborted = 0; for (lastpt=0;lastpt<64;lastpt++) { if (ficp->progress_timer_history[lastpt]) { ficp->progress_timer_history[lastpt] += tend; } } } if (rv==1) { /* ABORT */ ficp->aborted = 1; #ifdef HAVE_LIBPTHREAD pthread_exit((void *)0); #else return; #endif } } else { if (ficp->aborted<0) { do { #if defined(_WIN32) /* mingw or msvc */ Sleep(100); #else nanosleep(&pauset,NULL); #endif } while (ficp->aborted==-1); } #ifdef HAVE_LIBPTHREAD if (ficp->aborted>0) pthread_exit((void *)0); #else if (ficp->aborted>0) return; #endif } } /* Seed iterations */ fthp->iter_storage[0] = flam3_random_isaac_11(&(fthp->rc)); fthp->iter_storage[1] = flam3_random_isaac_11(&(fthp->rc)); fthp->iter_storage[2] = flam3_random_isaac_01(&(fthp->rc)); fthp->iter_storage[3] = flam3_random_isaac_01(&(fthp->rc)); /* Execute iterations */ badcount = flam3_iterate(&(fthp->cp), sub_batch_size, fuse, fthp->iter_storage, ficp->xform_distrib, &(fthp->rc)); #if defined(HAVE_LIBPTHREAD) && defined(USE_LOCKS) /* Lock mutex for access to accumulator */ pthread_mutex_lock(&ficp->bucket_mutex); #endif /* Add the badcount to the counter */ ficp->badvals += badcount; /* Put them in the bucket accumulator */ for (j = 0; j < sub_batch_size*4; j+=4) { double p0, p1, p00, p11; double dbl_index0,dbl_frac; double interpcolor[4]; int ci, color_index0; double *p = &(fthp->iter_storage[j]); bucket *b; if (fthp->cp.rotate != 0.0) { p00 = p[0] - fthp->cp.rot_center[0]; p11 = p[1] - fthp->cp.rot_center[1]; p0 = p00 * ficp->rot[0][0] + p11 * ficp->rot[0][1] + fthp->cp.rot_center[0]; p1 = p00 * ficp->rot[1][0] + p11 * ficp->rot[1][1] + fthp->cp.rot_center[1]; } else { p0 = p[0]; p1 = p[1]; } if (p0 >= ficp->bounds[0] && p1 >= ficp->bounds[1] && p0 <= ficp->bounds[2] && p1 <= ficp->bounds[3]) { double logvis=1.0; bucket *buckets = (bucket *)(ficp->buckets); /* Skip if invisible */ if (p[3]==0) continue; else logvis = p[3]; b = buckets + (int)(ficp->ws0 * p0 - ficp->wb0s0) + ficp->width * (int)(ficp->hs1 * p1 - ficp->hb1s1); #ifdef USE_FLOAT_INDICES color_index0 = 0; //fprintf(stdout,"%.16f\n",p[2]*256.0); while(color_index0 < cmap_size_m1) { if (ficp->dmap[color_index0+1].index > p[2]) break; else color_index0++; } if (p[3]==1.0) { bump_no_overflow(b[0][0], ficp->dmap[color_index0].color[0]); bump_no_overflow(b[0][1], ficp->dmap[color_index0].color[1]); bump_no_overflow(b[0][2], ficp->dmap[color_index0].color[2]); bump_no_overflow(b[0][3], ficp->dmap[color_index0].color[3]); bump_no_overflow(b[0][4], 255.0); } else { bump_no_overflow(b[0][0], logvis*ficp->dmap[color_index0].color[0]); bump_no_overflow(b[0][1], logvis*ficp->dmap[color_index0].color[1]); bump_no_overflow(b[0][2], logvis*ficp->dmap[color_index0].color[2]); bump_no_overflow(b[0][3], logvis*ficp->dmap[color_index0].color[3]); bump_no_overflow(b[0][4], logvis*255.0); #else dbl_index0 = p[2] * cmap_size; color_index0 = (int) (dbl_index0); if (flam3_palette_mode_linear == fthp->cp.palette_mode) { if (color_index0 < 0) { color_index0 = 0; dbl_frac = 0; } else if (color_index0 >= cmap_size_m1) { color_index0 = cmap_size_m1-1; dbl_frac = 1.0; } else { /* interpolate between color_index0 and color_index0+1 */ dbl_frac = dbl_index0 - (double)color_index0; } for (ci=0;ci<4;ci++) { interpcolor[ci] = ficp->dmap[color_index0].color[ci] * (1.0-dbl_frac) + ficp->dmap[color_index0+1].color[ci] * dbl_frac; } } else { /* Palette mode step */ if (color_index0 < 0) { color_index0 = 0; } else if (color_index0 >= cmap_size_m1) { color_index0 = cmap_size_m1; } for (ci=0;ci<4;ci++) interpcolor[ci] = ficp->dmap[color_index0].color[ci]; } if (p[3]==1.0) { bump_no_overflow(b[0][0], interpcolor[0]); bump_no_overflow(b[0][1], interpcolor[1]); bump_no_overflow(b[0][2], interpcolor[2]); bump_no_overflow(b[0][3], interpcolor[3]); bump_no_overflow(b[0][4], 255.0); } else { bump_no_overflow(b[0][0], logvis*interpcolor[0]); bump_no_overflow(b[0][1], logvis*interpcolor[1]); bump_no_overflow(b[0][2], logvis*interpcolor[2]); bump_no_overflow(b[0][3], logvis*interpcolor[3]); bump_no_overflow(b[0][4], logvis*255.0); } #endif } } #if defined(HAVE_LIBPTHREAD) && defined(USE_LOCKS) /* Release mutex */ pthread_mutex_unlock(&ficp->bucket_mutex); #endif } #ifdef HAVE_LIBPTHREAD pthread_exit((void *)0); #endif } static int render_rectangle(flam3_frame *spec, void *out, int field, int nchan, int transp, stat_struct *stats) { long nbuckets; int i, j, k, batch_num, temporal_sample_num; double nsamples, batch_size; bucket *buckets; abucket *accumulate; double *points; double *filter, *temporal_filter, *temporal_deltas, *batch_filter; double ppux=0, ppuy=0; int image_width, image_height; /* size of the image to produce */ int out_width; int filter_width=0; int bytes_per_channel = spec->bytes_per_channel; int oversample; double highpow; int nbatches; int ntemporal_samples; flam3_palette dmap; int gutter_width; double vibrancy = 0.0; double gamma = 0.0; double background[3]; int vib_gam_n = 0; time_t progress_began=0; int verbose = spec->verbose; int gnm_idx,max_gnm_de_fw,de_offset; flam3_genome cp; unsigned short *xform_distrib; flam3_iter_constants fic; flam3_thread_helper *fth; #ifdef HAVE_LIBPTHREAD pthread_attr_t pt_attr; pthread_t *myThreads=NULL; #endif int thread_status; int thi; time_t tstart,tend; double sumfilt; char *ai; int cmap_size; char *last_block; size_t memory_rqd; /* Per-render progress timers */ time_t progress_timer=0; time_t progress_timer_history[64]; double progress_history[64]; int progress_history_mark = 0; tstart = time(NULL); fic.badvals = 0; fic.aborted = 0; stats->num_iters = 0; /* correct for apophysis's use of 255 colors in the palette rather than all 256 */ cmap_size = 256 - argi("apo_palette",0); memset(&cp,0, sizeof(flam3_genome)); /* interpolate and get a control point */ flam3_interpolate(spec->genomes, spec->ngenomes, spec->time, 0, &cp); oversample = cp.spatial_oversample; highpow = cp.highlight_power; nbatches = cp.nbatches; ntemporal_samples = cp.ntemporal_samples; if (nbatches < 1) { fprintf(stderr, "nbatches must be positive, not %d.\n", nbatches); return(1); } if (oversample < 1) { fprintf(stderr, "oversample must be positive, not %d.\n", oversample); return(1); } /* Initialize the thread helper structures */ fth = (flam3_thread_helper *)calloc(spec->nthreads,sizeof(flam3_thread_helper)); for (i=0;i<spec->nthreads;i++) fth[i].cp.final_xform_index=-1; /* Set up the output image dimensions, adjusted for scanline */ image_width = cp.width; out_width = image_width; if (field) { image_height = cp.height / 2; if (field == flam3_field_odd) out = (unsigned char *)out + nchan * bytes_per_channel * out_width; out_width *= 2; } else image_height = cp.height; /* Spatial Filter kernel creation */ filter_width = flam3_create_spatial_filter(spec, field, &filter); /* handle error */ if (filter_width<0) { fprintf(stderr,"flam3_create_spatial_filter returned error: aborting\n"); return(1); } /* note we must free 'filter' at the end */ /* batch filter */ /* may want to revisit this at some point */ batch_filter = (double *) malloc(sizeof(double) * nbatches); for (i=0; i<nbatches; i++) batch_filter[i]=1.0/(double)nbatches; /* temporal filter - we must free temporal_filter and temporal_deltas at the end */ sumfilt = flam3_create_temporal_filter(nbatches*ntemporal_samples, cp.temporal_filter_type, cp.temporal_filter_exp, cp.temporal_filter_width, &temporal_filter, &temporal_deltas); /* the number of additional rows of buckets we put at the edge so that the filter doesn't go off the edge */ gutter_width = (filter_width - oversample) / 2; /* Check the size of the density estimation filter. If the 'radius' of the density estimation filter is greater than the gutter width, we have to pad with more. Otherwise, we can use the same value. */ max_gnm_de_fw=0; for (gnm_idx = 0; gnm_idx < spec->ngenomes; gnm_idx++) { int this_width = (int)ceil(spec->genomes[gnm_idx].estimator) * oversample; if (this_width > max_gnm_de_fw) max_gnm_de_fw = this_width; } /* Add OS-1 for the averaging at the edges, if it's > 0 already */ if (max_gnm_de_fw>0) max_gnm_de_fw += (oversample-1); /* max_gnm_de_fw is now the number of pixels of additional gutter */ /* necessary to appropriately perform the density estimation filtering */ /* Check to see if it's greater than the gutter_width */ if (max_gnm_de_fw > gutter_width) { de_offset = max_gnm_de_fw - gutter_width; gutter_width = max_gnm_de_fw; } else de_offset = 0; /* Allocate the space required to render the image */ fic.height = oversample * image_height + 2 * gutter_width; fic.width = oversample * image_width + 2 * gutter_width; nbuckets = (long)fic.width * (long)fic.height; memory_rqd = (sizeof(bucket) * nbuckets + sizeof(abucket) * nbuckets + 4 * sizeof(double) * (size_t)(spec->sub_batch_size) * spec->nthreads); last_block = (char *) malloc(memory_rqd); if (NULL == last_block) { fprintf(stderr, "render_rectangle: cannot malloc %g bytes.\n", (double)memory_rqd); fprintf(stderr, "render_rectangle: w=%d h=%d nb=%ld.\n", fic.width, fic.height, nbuckets); return(1); } /* Just free buckets at the end */ buckets = (bucket *) last_block; accumulate = (abucket *) (last_block + sizeof(bucket) * nbuckets); points = (double *) (last_block + (sizeof(bucket) + sizeof(abucket)) * nbuckets); if (verbose) { fprintf(stderr, "chaos: "); progress_began = time(NULL); } background[0] = background[1] = background[2] = 0.0; memset((char *) accumulate, 0, sizeof(abucket) * nbuckets); /* Batch loop - outermost */ for (batch_num = 0; batch_num < nbatches; batch_num++) { double de_time; double sample_density=0.0; double k1, area, k2; flam3_de_helper de; de_time = spec->time + temporal_deltas[batch_num*ntemporal_samples]; memset((char *) buckets, 0, sizeof(bucket) * nbuckets); /* interpolate and get a control point */ /* ONLY FOR DENSITY FILTER WIDTH PURPOSES */ /* additional interpolation will be done in the temporal_sample loop */ flam3_interpolate(spec->genomes, spec->ngenomes, de_time, 0, &cp); /* if instructed to by the genome, create the density estimation */ /* filter kernels. Check boundary conditions as well. */ if (cp.estimator < 0.0 || cp.estimator_minimum < 0.0) { fprintf(stderr,"density estimator filter widths must be >= 0\n"); return(1); } if (spec->bits <= 32) { if (cp.estimator > 0.0) { fprintf(stderr, "warning: density estimation disabled with %d bit buffers.\n", spec->bits); cp.estimator = 0.0; } } /* Create DE filters */ if (cp.estimator > 0.0) { de = flam3_create_de_filters(cp.estimator,cp.estimator_minimum, cp.estimator_curve,oversample); if (de.kernel_size<0) { fprintf(stderr,"de.kernel_size returned 0 - aborting.\n"); return(1); } } else de.max_filter_index = 0; /* Temporal sample loop */ for (temporal_sample_num = 0; temporal_sample_num < ntemporal_samples; temporal_sample_num++) { double temporal_sample_time; double color_scalar = temporal_filter[batch_num*ntemporal_samples + temporal_sample_num]; temporal_sample_time = spec->time + temporal_deltas[batch_num*ntemporal_samples + temporal_sample_num]; /* Interpolate and get a control point */ flam3_interpolate(spec->genomes, spec->ngenomes, temporal_sample_time, 0, &cp); /* Get the xforms ready to render */ if (prepare_precalc_flags(&cp)) { fprintf(stderr,"prepare xform pointers returned error: aborting.\n"); return(1); } xform_distrib = flam3_create_xform_distrib(&cp); if (xform_distrib==NULL) { fprintf(stderr,"create xform distrib returned error: aborting.\n"); return(1); } /* compute the colormap entries. */ /* the input colormap is 256 long with entries from 0 to 1.0 */ for (j = 0; j < CMAP_SIZE; j++) { dmap[j].index = cp.palette[(j * 256) / CMAP_SIZE].index / 256.0; for (k = 0; k < 4; k++) dmap[j].color[k] = (cp.palette[(j * 256) / CMAP_SIZE].color[k] * WHITE_LEVEL) * color_scalar; } /* compute camera */ if (1) { double t0, t1, shift=0.0, corner0, corner1; double scale; if (cp.sample_density <= 0.0) { fprintf(stderr, "sample density (quality) must be greater than zero," " not %g.\n", cp.sample_density); return(1); } scale = pow(2.0, cp.zoom); sample_density = cp.sample_density * scale * scale; ppux = cp.pixels_per_unit * scale; ppuy = field ? (ppux / 2.0) : ppux; ppux /= spec->pixel_aspect_ratio; switch (field) { case flam3_field_both: shift = 0.0; break; case flam3_field_even: shift = -0.5; break; case flam3_field_odd: shift = 0.5; break; } shift = shift / ppux; t0 = (double) gutter_width / (oversample * ppux); t1 = (double) gutter_width / (oversample * ppuy); corner0 = cp.center[0] - image_width / ppux / 2.0; corner1 = cp.center[1] - image_height / ppuy / 2.0; fic.bounds[0] = corner0 - t0; fic.bounds[1] = corner1 - t1 + shift; fic.bounds[2] = corner0 + image_width / ppux + t0; fic.bounds[3] = corner1 + image_height / ppuy + t1 + shift; fic.size[0] = 1.0 / (fic.bounds[2] - fic.bounds[0]); fic.size[1] = 1.0 / (fic.bounds[3] - fic.bounds[1]); fic.rot[0][0] = cos(cp.rotate * 2 * M_PI / 360.0); fic.rot[0][1] = -sin(cp.rotate * 2 * M_PI / 360.0); fic.rot[1][0] = -fic.rot[0][1]; fic.rot[1][1] = fic.rot[0][0]; fic.ws0 = fic.width * fic.size[0]; fic.wb0s0 = fic.ws0 * fic.bounds[0]; fic.hs1 = fic.height * fic.size[1]; fic.hb1s1 = fic.hs1 * fic.bounds[1]; } /* number of samples is based only on the output image size */ nsamples = sample_density * image_width * image_height; /* how many of these samples are rendered in this loop? */ batch_size = nsamples / (nbatches * ntemporal_samples); stats->num_iters += batch_size; /* Fill in the iter constants */ fic.xform_distrib = xform_distrib; fic.spec = spec; fic.batch_size = batch_size / (double)spec->nthreads; fic.temporal_sample_num = temporal_sample_num; fic.ntemporal_samples = ntemporal_samples; fic.batch_num = batch_num; fic.nbatches = nbatches; fic.cmap_size = cmap_size; fic.dmap = (flam3_palette_entry *)dmap; fic.color_scalar = color_scalar; fic.buckets = (void *)buckets; /* Need a timer per job */ fic.progress_timer = &progress_timer; fic.progress_timer_history = &(progress_timer_history[0]); fic.progress_history = &(progress_history[0]); fic.progress_history_mark = &progress_history_mark; /* Initialize the thread helper structures */ for (thi = 0; thi < spec->nthreads; thi++) { int rk; /* Create a new isaac state for this thread */ for (rk = 0; rk < RANDSIZ; rk++) fth[thi].rc.randrsl[rk] = irand(&spec->rc); irandinit(&(fth[thi].rc),1); if (0==thi) { fth[thi].first_thread=1; if (0==batch_num && 0==temporal_sample_num) fth[thi].timer_initialize = 1; else fth[thi].timer_initialize = 0; } else { fth[thi].first_thread=0; fth[thi].timer_initialize = 0; } fth[thi].iter_storage = &(points[thi*(spec->sub_batch_size)*4]); fth[thi].fic = &fic; flam3_copy(&(fth[thi].cp),&cp); } #ifdef HAVE_LIBPTHREAD /* Let's make some threads */ myThreads = (pthread_t *)malloc(spec->nthreads * sizeof(pthread_t)); #if defined(USE_LOCKS) pthread_mutex_init(&fic.bucket_mutex, NULL); #endif pthread_attr_init(&pt_attr); pthread_attr_setdetachstate(&pt_attr,PTHREAD_CREATE_JOINABLE); for (thi=0; thi <spec->nthreads; thi ++) pthread_create(&myThreads[thi], &pt_attr, (void *)iter_thread, (void *)(&(fth[thi]))); pthread_attr_destroy(&pt_attr); /* Wait for them to return */ for (thi=0; thi < spec->nthreads; thi++) pthread_join(myThreads[thi], (void **)&thread_status); #if defined(USE_LOCKS) pthread_mutex_destroy(&fic.bucket_mutex); #endif free(myThreads); #else for (thi=0; thi < spec->nthreads; thi++) iter_thread( (void *)(&(fth[thi])) ); #endif /* Free the xform_distrib array */ free(xform_distrib); if (fic.aborted) { if (verbose) fprintf(stderr, "\naborted!\n"); goto done; } vibrancy += cp.vibrancy; gamma += cp.gamma; background[0] += cp.background[0]; background[1] += cp.background[1]; background[2] += cp.background[2]; vib_gam_n++; } k1 =(cp.contrast * cp.brightness * PREFILTER_WHITE * 268.0 * batch_filter[batch_num]) / 256; area = image_width * image_height / (ppux * ppuy); k2 = (oversample * oversample * nbatches) / (cp.contrast * area * WHITE_LEVEL * sample_density * sumfilt); #if 0 printf("iw=%d,ih=%d,ppux=%f,ppuy=%f\n",image_width,image_height,ppux,ppuy); printf("contrast=%f, brightness=%f, PREFILTER=%d, temporal_filter=%f\n", cp.contrast, cp.brightness, PREFILTER_WHITE, temporal_filter[batch_num]); printf("oversample=%d, nbatches=%d, area = %f, WHITE_LEVEL=%d, sample_density=%f\n", oversample, nbatches, area, WHITE_LEVEL, sample_density); printf("k1=%f,k2=%15.12f\n",k1,k2); #endif if (de.max_filter_index == 0) { for (j = 0; j < fic.height; j++) { for (i = 0; i < fic.width; i++) { abucket *a = accumulate + i + j * fic.width; bucket *b = buckets + i + j * fic.width; double c[4], ls; c[0] = (double) b[0][0]; c[1] = (double) b[0][1]; c[2] = (double) b[0][2]; c[3] = (double) b[0][3]; if (0.0 == c[3]) continue; ls = (k1 * log(1.0 + c[3] * k2))/c[3]; c[0] *= ls; c[1] *= ls; c[2] *= ls; c[3] *= ls; abump_no_overflow(a[0][0], c[0]); abump_no_overflow(a[0][1], c[1]); abump_no_overflow(a[0][2], c[2]); abump_no_overflow(a[0][3], c[3]); } } } else { de_thread_helper *deth; int de_aborted=0; int myspan = (fic.height-2*(oversample-1)+1); int swath = myspan/(spec->nthreads); /* Create the de helper structures */ deth = (de_thread_helper *)calloc(spec->nthreads,sizeof(de_thread_helper)); for (thi=0;thi<(spec->nthreads);thi++) { /* Set up the contents of the helper structure */ deth[thi].b = buckets; deth[thi].accumulate = accumulate; deth[thi].width = fic.width; deth[thi].height = fic.height; deth[thi].oversample = oversample; deth[thi].progress_size = spec->sub_batch_size/10; deth[thi].de = &de; deth[thi].k1 = k1; deth[thi].k2 = k2; deth[thi].curve = cp.estimator_curve; deth[thi].spec = spec; deth[thi].aborted = &de_aborted; if ( (spec->nthreads)>myspan) { /* More threads than rows */ deth[thi].start_row=0; if (thi==spec->nthreads-1) { deth[thi].end_row=myspan; deth[thi].last_thread=1; } else { deth[thi].end_row=-1; deth[thi].last_thread=0; } } else { /* Normal case */ deth[thi].start_row=thi*swath; deth[thi].end_row=(thi+1)*swath; if (thi==spec->nthreads-1) { deth[thi].end_row=myspan; deth[thi].last_thread=1; } else { deth[thi].last_thread=0; } } } #ifdef HAVE_LIBPTHREAD /* Let's make some threads */ myThreads = (pthread_t *)malloc(spec->nthreads * sizeof(pthread_t)); pthread_attr_init(&pt_attr); pthread_attr_setdetachstate(&pt_attr,PTHREAD_CREATE_JOINABLE); for (thi=0; thi <spec->nthreads; thi ++) pthread_create(&myThreads[thi], &pt_attr, (void *)de_thread, (void *)(&(deth[thi]))); pthread_attr_destroy(&pt_attr); /* Wait for them to return */ for (thi=0; thi < spec->nthreads; thi++) pthread_join(myThreads[thi], (void **)&thread_status); free(myThreads); #else for (thi=0; thi <spec->nthreads; thi ++) de_thread((void *)(&(deth[thi]))); #endif free(deth); if (de_aborted) { if (verbose) fprintf(stderr, "\naborted!\n"); goto done; } } /* End density estimation loop */ /* If allocated, free the de filter memory for the next batch */ if (de.max_filter_index > 0) { free(de.filter_coefs); free(de.filter_widths); } } if (verbose) { fprintf(stderr, "\rchaos: 100.0%% took: %ld seconds \n", time(NULL) - progress_began); fprintf(stderr, "filtering..."); } /* filter the accumulation buffer down into the image */ if (1) { int x, y; double t[4],newrgb[3]; double g = 1.0 / (gamma / vib_gam_n); double tmp,a; double alpha,ls; int rgbi; double linrange = cp.gam_lin_thresh; vibrancy /= vib_gam_n; background[0] /= vib_gam_n/256.0; background[1] /= vib_gam_n/256.0; background[2] /= vib_gam_n/256.0; /* If we're in the early clip mode, perform this first step to */ /* apply the gamma correction and clipping before the spat filt */ if (spec->earlyclip) { for (j = 0; j < fic.height; j++) { for (i = 0; i < fic.width; i++) { abucket *ac = accumulate + i + j*fic.width; if (ac[0][3]<=0) { alpha = 0.0; ls = 0.0; } else { tmp=ac[0][3]/PREFILTER_WHITE; alpha = flam3_calc_alpha(tmp,g,linrange); ls = vibrancy * 256.0 * alpha / tmp; if (alpha<0.0) alpha = 0.0; if (alpha>1.0) alpha = 1.0; } t[0] = (double)ac[0][0]; t[1] = (double)ac[0][1]; t[2] = (double)ac[0][2]; t[3] = (double)ac[0][3]; flam3_calc_newrgb(t, ls, highpow, newrgb); for (rgbi=0;rgbi<3;rgbi++) { a = newrgb[rgbi]; a += (1.0-vibrancy) * 256.0 * pow( t[rgbi] / PREFILTER_WHITE, g); if (nchan<=3 || transp==0) a += ((1.0 - alpha) * background[rgbi]); else { if (alpha>0) a /= alpha; else a = 0; } /* Clamp here to ensure proper filter functionality */ if (a>255) a = 255; if (a<0) a = 0; /* Replace values in accumulation buffer with these new ones */ ac[0][rgbi] = a; } ac[0][3] = alpha; } } } /* Apply the spatial filter */ y = de_offset; for (j = 0; j < image_height; j++) { x = de_offset; for (i = 0; i < image_width; i++) { int ii, jj,rgbi; void *p; unsigned short *p16; unsigned char *p8; t[0] = t[1] = t[2] = t[3] = 0.0; for (ii = 0; ii < filter_width; ii++) { for (jj = 0; jj < filter_width; jj++) { double k = filter[ii + jj * filter_width]; abucket *ac = accumulate + x+ii + (y+jj)*fic.width; t[0] += k * ac[0][0]; t[1] += k * ac[0][1]; t[2] += k * ac[0][2]; t[3] += k * ac[0][3]; } } p = (unsigned char *)out + nchan * bytes_per_channel * (i + j * out_width); p8 = (unsigned char *)p; p16 = (unsigned short *)p; /* The old way, spatial filter first and then clip after gamma */ if (!spec->earlyclip) { tmp=t[3]/PREFILTER_WHITE; if (t[3]<=0) { alpha = 0.0; ls = 0.0; } else { alpha = flam3_calc_alpha(tmp,g,linrange); ls = vibrancy * 256.0 * alpha / tmp; if (alpha<0.0) alpha = 0.0; if (alpha>1.0) alpha = 1.0; } flam3_calc_newrgb(t, ls, highpow, newrgb); for (rgbi=0;rgbi<3;rgbi++) { a = newrgb[rgbi]; a += (1.0-vibrancy) * 256.0 * pow( t[rgbi] / PREFILTER_WHITE, g); if (nchan<=3 || transp==0) a += ((1.0 - alpha) * background[rgbi]); else { if (alpha>0) a /= alpha; else a = 0; } /* Clamp here to ensure proper filter functionality */ if (a>255) a = 255; if (a<0) a = 0; /* Replace values in accumulation buffer with these new ones */ t[rgbi] = a; } t[3] = alpha; } for (rgbi=0;rgbi<3;rgbi++) { a = t[rgbi]; if (a > 255) a = 255; if (a < 0) a = 0; if (2==bytes_per_channel) { a *= 256.0; /* Scales to [0-65280] */ p16[rgbi] = (unsigned short) a; } else { p8[rgbi] = (unsigned char) a; } } if (t[3]>1) t[3]=1; if (t[3]<0) t[3]=0; /* alpha */ if (nchan>3) { if (transp==1) { if (2==bytes_per_channel) p16[3] = (unsigned short) (t[3] * 65535); else p8[3] = (unsigned char) (t[3] * 255); } else { if (2==bytes_per_channel) p16[3] = 65535; else p8[3] = 255; } } x += oversample; } y += oversample; } } done: stats->badvals = fic.badvals; free(temporal_filter); free(temporal_deltas); free(batch_filter); free(filter); free(buckets); // free(accumulate); // free(points); /* We have to clear the cps in fth first */ for (thi = 0; thi < spec->nthreads; thi++) { clear_cp(&(fth[thi].cp),0); } free(fth); clear_cp(&cp,0); if (getenv("insert_palette")) { int ph = 100; if (ph >= image_height) ph = image_height; /* insert the palette into the image */ for (j = 0; j < ph; j++) { for (i = 0; i < image_width; i++) { unsigned char *p = (unsigned char *)out + nchan * (i + j * out_width); p[0] = (unsigned char)dmap[i * 256 / image_width].color[0]; p[1] = (unsigned char)dmap[i * 256 / image_width].color[1]; p[2] = (unsigned char)dmap[i * 256 / image_width].color[2]; } } } tend = time(NULL); stats->render_seconds = (int)(tend-tstart); return(0); }
void write_png(FILE *file, void *image, int width, int height, flam3_img_comments *fpc, int bpc) { png_structp png_ptr; png_infop info_ptr; png_text text[FLAM3_PNG_COM]; size_t i; unsigned short testbe = 1; void **rows = malloc(sizeof(void *) * height); char *nick = getenv("nick"); char *url = getenv("url"); char *id = getenv("id"); char *ai; /* For argi */ int pngcom_enable = argi("enable_png_comments", 1); text[0].compression = PNG_TEXT_COMPRESSION_NONE; text[0].key = "flam3_version"; text[0].text = flam3_version(); text[1].compression = PNG_TEXT_COMPRESSION_NONE; text[1].key = "flam3_nickname"; text[1].text = nick; text[2].compression = PNG_TEXT_COMPRESSION_NONE; text[2].key = "flam3_url"; text[2].text = url; text[3].compression = PNG_TEXT_COMPRESSION_NONE; text[3].key = "flam3_id"; text[3].text = id; text[4].compression = PNG_TEXT_COMPRESSION_NONE; text[4].key = "flam3_error_rate"; text[4].text = fpc->badvals; text[5].compression = PNG_TEXT_COMPRESSION_NONE; text[5].key = "flam3_samples"; text[5].text = fpc->numiters; text[6].compression = PNG_TEXT_COMPRESSION_NONE; text[6].key = "flam3_time"; text[6].text = fpc->rtime; text[7].compression = PNG_TEXT_COMPRESSION_zTXt; text[7].key = "flam3_genome"; text[7].text = fpc->genome; for (i = 0; i < height; i++) rows[i] = (unsigned char *)image + i * width * 4 * bpc; png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); info_ptr = png_create_info_struct(png_ptr); if (setjmp(png_jmpbuf(png_ptr))) { fclose(file); png_destroy_write_struct(&png_ptr, &info_ptr); perror("writing file"); return; } png_init_io(png_ptr, file); png_set_IHDR(png_ptr, info_ptr, width, height, 8*bpc, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); if (pngcom_enable==1) png_set_text(png_ptr, info_ptr, text, FLAM3_PNG_COM); png_write_info(png_ptr, info_ptr); /* Must set this after the write_info */ if (2==bpc && testbe != htons(testbe)) { png_set_swap(png_ptr); } png_write_image(png_ptr, (png_bytepp) rows); png_write_end(png_ptr, info_ptr); png_destroy_write_struct(&png_ptr, &info_ptr); free(rows); }