struct bufferAndSize pngQuant(MagickWand *output){ unsigned long wid = MagickGetImageWidth(output); unsigned long hei = MagickGetImageHeight(output); unsigned char *bmp_buffer; bmp_buffer = (unsigned char*) malloc(wid*hei*4); MagickGetImagePixels(output,0,0,wid,hei,"RGBA",CharPixel,bmp_buffer); liq_attr *attr = liq_attr_create(); liq_image *qimage = liq_image_create_rgba(attr, bmp_buffer, wid, hei, 0); liq_set_max_colors(attr, 255); liq_set_speed(attr, 10); liq_result *res = liq_quantize_image(attr, qimage); int png_buffer_size = wid*hei; unsigned char *png_buffer = malloc(png_buffer_size); liq_write_remapped_image(res, qimage, png_buffer, png_buffer_size); const liq_palette *pal = liq_get_palette(res); LodePNGState state; lodepng_state_init(&state); /*optionally customize the state*/ state.info_png.color.bitdepth = 8; state.info_png.color.colortype = LCT_PALETTE; state.info_raw.colortype = LCT_PALETTE; state.info_raw.bitdepth = 8; state.encoder.auto_convert = 0; state.encoder.add_id = 0; state.encoder.zlibsettings.nicematch = 258; int i = 0; for (i=0; i < pal->count; ++i) { lodepng_palette_add(&state.info_png.color, pal->entries[i].r , pal->entries[i].g, pal->entries[i].b, pal->entries[i].a); lodepng_palette_add(&state.info_raw, pal->entries[i].r , pal->entries[i].g, pal->entries[i].b, pal->entries[i].a); } unsigned char *data; size_t size = 0; lodepng_encode(&data, &size, png_buffer, wid, hei, &state); // writtendata = io_write(size, data); lodepng_state_cleanup(&state); free(bmp_buffer); free(png_buffer); liq_attr_destroy(attr); liq_image_destroy(qimage); liq_result_destroy(res); lodepng_state_cleanup(&state); struct bufferAndSize bs; bs.data = data; bs.size = size; return bs; }
static void test_abort() { liq_attr *attr = liq_attr_create(); unsigned char dummy[4] = {0}; liq_image *img = liq_image_create_rgba(attr, dummy, 1, 1, 0); liq_attr_set_progress_callback(attr, test_abort_callback, magic); liq_result *res = liq_quantize_image(attr, img); assert(!res); liq_attr_destroy(attr); }
static void test_fixed_colors_order() { liq_attr *attr = liq_attr_create(); unsigned char dummy[4] = {0}; liq_image *img = liq_image_create_rgba(attr, dummy, 1, 1, 0); assert(img); liq_color colors[17] = { {0,0,0,0}, {1,1,1,1}, {2,2,2,2}, {3,3,3,3}, {4,4,4,4}, {5,4,4,4}, {6,4,4,4}, {6,7,4,4}, {6,7,8,4}, {6,7,8,9}, {10,7,8,9}, {10,11,8,9}, {10,11,12,9}, {10,11,12,13}, {14,11,12,13}, {14,15,12,13}, {14,15,16,13}, }; for(int i=0; i < 17; i++) { liq_image_add_fixed_color(img, colors[i]); } liq_result *res = liq_quantize_image(attr, img); assert(res); const liq_palette *pal = liq_get_palette(res); assert(pal); assert(pal->count == 17); for(int i=0; i < 17; i++) { assert(pal->entries[i].r == colors[i].r); assert(pal->entries[i].g == colors[i].g); assert(pal->entries[i].b == colors[i].b); assert(pal->entries[i].a == colors[i].a); } liq_set_dithering_level(res, 1.0); char buf[1]; assert(LIQ_OK == liq_write_remapped_image(res, img, buf, 1)); liq_result_set_progress_callback(res, test_abort_callback, magic); assert(LIQ_ABORTED == liq_write_remapped_image(res, img, buf, 1)); liq_result_destroy(res); liq_image_destroy(img); liq_attr_destroy(attr); }
static void test_fixed_colors() { liq_attr *attr = liq_attr_create(); liq_attr_set_progress_callback(attr, test_continue_callback, magic); unsigned char dummy[4] = {0}; liq_image *img = liq_image_create_rgba(attr, dummy, 1, 1, 0); assert(img); liq_image_add_fixed_color(img, (liq_color){0,0,0,0}); liq_result *res = liq_quantize_image(attr, img); assert(res); assert(progress_called); const liq_palette *pal = liq_get_palette(res); assert(pal); assert(pal->count == 1); liq_result_destroy(res); liq_image_destroy(img); liq_attr_destroy(attr); }
void MedianCut32bitQuantizer::process_() { liq_attr *attr = liq_attr_create(); liq_set_speed(attr, 1); liq_image *image = liq_image_create_rgba(attr, reinterpret_cast<void*>(rawsrc_.get()), width_, height_, 0); liq_result *res = liq_quantize_image(attr, image); liq_set_dithering_level(res, 1); liq_write_remapped_image(res, image, reinterpret_cast<void*>(rawdest_.get()), width_ * height_); const liq_palette *pal = liq_get_palette(res); for (size_t i = 0; i < pal->count; ++i) { const liq_color& color = pal->entries[i]; palette_[i].red = color.r; palette_[i].green = color.g; palette_[i].blue = color.b; trans_[i] = color.a; } liq_attr_destroy(attr); liq_image_destroy(image); liq_result_destroy(res); }
int main(int argc, char *argv[]) { struct pngquant_options options = { .floyd = 1.f, // floyd-steinberg dithering }; options.liq = liq_attr_create(); if (!options.liq) { fputs("SSE-capable CPU is required for this build.\n", stderr); return WRONG_ARCHITECTURE; } unsigned int error_count=0, skipped_count=0, file_count=0; pngquant_error latest_error=SUCCESS; const char *newext = NULL, *output_file_path = NULL; fix_obsolete_options(argc, argv); int opt; do { opt = getopt_long(argc, argv, "Vvqfhs:Q:o:", long_options, NULL); switch (opt) { case 'v': options.verbose = true; break; case 'q': options.verbose = false; break; case arg_floyd: options.floyd = optarg ? atof(optarg) : 1.f; if (options.floyd < 0 || options.floyd > 1.f) { fputs("--floyd argument must be in 0..1 range\n", stderr); return INVALID_ARGUMENT; } break; case arg_ordered: options.floyd = 0; break; case 'f': options.force = true; break; case arg_no_force: options.force = false; break; case arg_ext: newext = optarg; break; case 'o': if (output_file_path) { fputs("--output option can be used only once\n", stderr); return INVALID_ARGUMENT; } output_file_path = optarg; break; case arg_iebug: // opacities above 238 will be rounded up to 255, because IE6 truncates <255 to 0. liq_set_min_opacity(options.liq, 238); fputs(" warning: the workaround for IE6 is deprecated\n", stderr); break; case arg_transbug: liq_set_last_index_transparent(options.liq, true); break; case arg_skip_larger: options.skip_if_larger = true; break; case 's': { int speed = atoi(optarg); if (speed >= 10) { options.fast_compression = true; } if (speed == 11) { options.floyd = 0; speed = 10; } if (LIQ_OK != liq_set_speed(options.liq, speed)) { fputs("Speed should be between 1 (slow) and 11 (fast).\n", stderr); return INVALID_ARGUMENT; } } break; case 'Q': if (!parse_quality(optarg, options.liq, &options.min_quality_limit)) { fputs("Quality should be in format min-max where min and max are numbers in range 0-100.\n", stderr); return INVALID_ARGUMENT; } break; case arg_posterize: if (LIQ_OK != liq_set_min_posterization(options.liq, atoi(optarg))) { fputs("Posterization should be number of bits in range 0-4.\n", stderr); return INVALID_ARGUMENT; } break; case arg_map: { png24_image tmp = {}; if (SUCCESS != read_image(options.liq, optarg, false, &tmp, &options.fixed_palette_image, false, false)) { fprintf(stderr, " error: unable to load %s", optarg); return INVALID_ARGUMENT; } } break; case 'h': print_full_version(stdout); print_usage(stdout); return SUCCESS; case 'V': puts(PNGQUANT_VERSION); return SUCCESS; case -1: break; default: return INVALID_ARGUMENT; } } while (opt != -1); int argn = optind; if (argn >= argc) { if (argn > 1) { fputs("No input files specified.\n", stderr); } else { print_full_version(stderr); } print_usage(stderr); return MISSING_ARGUMENT; } if (options.verbose) { liq_set_log_callback(options.liq, log_callback, NULL); options.log_callback = log_callback; } char *colors_end; unsigned long colors = strtoul(argv[argn], &colors_end, 10); if (colors_end != argv[argn] && '\0' == colors_end[0]) { if (LIQ_OK != liq_set_max_colors(options.liq, colors)) { fputs("Number of colors must be between 2 and 256.\n", stderr); return INVALID_ARGUMENT; } argn++; } if (newext && output_file_path) { fputs("--ext and --output options can't be used at the same time\n", stderr); return INVALID_ARGUMENT; } // new filename extension depends on options used. Typically basename-fs8.png if (newext == NULL) { newext = options.floyd > 0 ? "-ie-fs8.png" : "-ie-or8.png"; if (!options.ie_mode) { newext += 3; /* skip "-ie" */ } } if (argn == argc || (argn == argc-1 && 0==strcmp(argv[argn],"-"))) { options.using_stdin = true; options.using_stdout = !output_file_path; argn = argc-1; } const int num_files = argc-argn; if (output_file_path && num_files != 1) { fputs("Only one input file is allowed when --output is used\n", stderr); return INVALID_ARGUMENT; } #ifdef _OPENMP // if there's a lot of files, coarse parallelism can be used if (num_files > 2*omp_get_max_threads()) { omp_set_nested(0); omp_set_dynamic(1); } else { omp_set_nested(1); } #endif #pragma omp parallel for \ schedule(static, 1) reduction(+:skipped_count) reduction(+:error_count) reduction(+:file_count) shared(latest_error) for(int i=0; i < num_files; i++) { struct pngquant_options opts = options; opts.liq = liq_attr_copy(options.liq); const char *filename = opts.using_stdin ? "stdin" : argv[argn+i]; #ifdef _OPENMP struct buffered_log buf = {}; if (opts.log_callback && omp_get_num_threads() > 1 && num_files > 1) { liq_set_log_callback(opts.liq, log_callback_buferred, &buf); liq_set_log_flush_callback(opts.liq, log_callback_buferred_flush, &buf); options.log_callback = log_callback_buferred; options.log_callback_user_info = &buf; } #endif pngquant_error retval = SUCCESS; const char *outname = output_file_path; char *outname_free = NULL; if (!options.using_stdout) { if (!outname) { outname = outname_free = add_filename_extension(filename, newext); } if (!options.force && file_exists(outname)) { fprintf(stderr, " error: '%s' exists; not overwriting\n", outname); retval = NOT_OVERWRITING_ERROR; } } if (SUCCESS == retval) { retval = pngquant_file(filename, outname, &opts); } free(outname_free); liq_attr_destroy(opts.liq); if (retval) { #pragma omp critical { latest_error = retval; } if (retval == TOO_LOW_QUALITY || retval == TOO_LARGE_FILE) { skipped_count++; } else { error_count++; } } ++file_count; } if (error_count) { verbose_printf(&options, "There were errors quantizing %d file%s out of a total of %d file%s.", error_count, (error_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s"); } if (skipped_count) { verbose_printf(&options, "Skipped %d file%s out of a total of %d file%s.", skipped_count, (skipped_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s"); } if (!skipped_count && !error_count) { verbose_printf(&options, "No errors detected while quantizing %d image%s.", file_count, (file_count == 1)? "" : "s"); } liq_image_destroy(options.fixed_palette_image); liq_attr_destroy(options.liq); return latest_error; } pngquant_error pngquant_file(const char *filename, const char *outname, struct pngquant_options *options) { pngquant_error retval = SUCCESS; verbose_printf(options, "%s:", filename); liq_image *input_image = NULL; png24_image input_image_rwpng = {}; bool keep_input_pixels = options->skip_if_larger || (options->using_stdout && options->min_quality_limit); // original may need to be output to stdout if (SUCCESS == retval) { retval = read_image(options->liq, filename, options->using_stdin, &input_image_rwpng, &input_image, keep_input_pixels, options->verbose); } int quality_percent = 90; // quality on 0-100 scale, updated upon successful remap png8_image output_image = {}; if (SUCCESS == retval) { verbose_printf(options, " read %luKB file", (input_image_rwpng.file_size+1023UL)/1024UL); #if USE_LCMS if (input_image_rwpng.lcms_status == ICCP) { verbose_printf(options, " used embedded ICC profile to transform image to sRGB colorspace"); } else if (input_image_rwpng.lcms_status == GAMA_CHRM) { verbose_printf(options, " used gAMA and cHRM chunks to transform image to sRGB colorspace"); } else if (input_image_rwpng.lcms_status == ICCP_WARN_GRAY) { verbose_printf(options, " warning: ignored ICC profile in GRAY colorspace"); } #endif if (input_image_rwpng.gamma != 0.45455) { verbose_printf(options, " corrected image from gamma %2.1f to sRGB gamma", 1.0/input_image_rwpng.gamma); } // when using image as source of a fixed palette the palette is extracted using regular quantization liq_result *remap = liq_quantize_image(options->liq, options->fixed_palette_image ? options->fixed_palette_image : input_image); if (remap) { liq_set_output_gamma(remap, 0.45455); // fixed gamma ~2.2 for the web. PNG can't store exact 1/2.2 liq_set_dithering_level(remap, options->floyd); retval = prepare_output_image(remap, input_image, &output_image); if (SUCCESS == retval) { if (LIQ_OK != liq_write_remapped_image_rows(remap, input_image, output_image.row_pointers)) { retval = OUT_OF_MEMORY_ERROR; } set_palette(remap, &output_image); double palette_error = liq_get_quantization_error(remap); if (palette_error >= 0) { quality_percent = liq_get_quantization_quality(remap); verbose_printf(options, " mapped image to new colors...MSE=%.3f (Q=%d)", palette_error, quality_percent); } } liq_result_destroy(remap); } else { retval = TOO_LOW_QUALITY; } } if (SUCCESS == retval) { if (options->skip_if_larger) { // this is very rough approximation, but generally avoid losing more quality than is gained in file size. // Quality is squared, because even greater savings are needed to justify big quality loss. double quality = quality_percent/100.0; output_image.maximum_file_size = (input_image_rwpng.file_size-1) * quality*quality; } output_image.fast_compression = options->fast_compression; output_image.chunks = input_image_rwpng.chunks; input_image_rwpng.chunks = NULL; retval = write_image(&output_image, NULL, outname, options); if (TOO_LARGE_FILE == retval) { verbose_printf(options, " file exceeded expected size of %luKB", (unsigned long)output_image.maximum_file_size/1024UL); } } if (options->using_stdout && keep_input_pixels && (TOO_LARGE_FILE == retval || TOO_LOW_QUALITY == retval)) { // when outputting to stdout it'd be nasty to create 0-byte file // so if quality is too low, output 24-bit original pngquant_error write_retval = write_image(NULL, &input_image_rwpng, outname, options); if (write_retval) { retval = write_retval; } } if (input_image) liq_image_destroy(input_image); rwpng_free_image24(&input_image_rwpng); rwpng_free_image8(&output_image); return retval; }
int main(int argc, char *argv[]) { struct pngquant_options options = { .floyd = 1.f, // floyd-steinberg dithering }; options.liq = liq_attr_create(); #if USE_SSE if (!options.liq) { print_full_version(stderr); fputs("SSE2-capable CPU is required for this build.\n", stderr); return WRONG_ARCHITECTURE; } #endif unsigned int error_count=0, skipped_count=0, file_count=0; pngquant_error latest_error=SUCCESS; const char *newext = NULL, *output_file_path = NULL; fix_obsolete_options(argc, argv); int opt; do { opt = getopt_long(argc, argv, "Vvqfhs:Q:o:", long_options, NULL); switch (opt) { case 'v': liq_set_log_callback(options.liq, log_callback, NULL); options.log_callback = log_callback; break; case 'q': liq_set_log_callback(options.liq, NULL, NULL); options.log_callback = NULL; break; case arg_floyd: options.floyd = optarg ? atof(optarg) : 1.0; if (options.floyd < 0 || options.floyd > 1.0) { fputs("--floyd argument must be in 0..1 range\n", stderr); return INVALID_ARGUMENT; } break; case arg_ordered: options.floyd = 0; break; case 'f': options.force = true; break; case arg_no_force: options.force = false; break; case arg_ext: newext = optarg; break; case 'o': if (output_file_path) { fputs("--output option can be used only once\n", stderr); return INVALID_ARGUMENT; } output_file_path = optarg; break; case arg_iebug: // opacities above 238 will be rounded up to 255, because IE6 truncates <255 to 0. liq_set_min_opacity(options.liq, 238); options.ie_mode = true; break; case arg_transbug: liq_set_last_index_transparent(options.liq, true); break; case 's': { int speed = atoi(optarg); if (speed >= 10) { options.fast_compression = true; } if (speed == 11) { options.floyd = 0; speed = 10; } if (LIQ_OK != liq_set_speed(options.liq, speed)) { fputs("Speed should be between 1 (slow) and 11 (fast).\n", stderr); return INVALID_ARGUMENT; } } break; case 'Q': if (!parse_quality(optarg, options.liq, &options.min_quality_limit)) { fputs("Quality should be in format min-max where min and max are numbers in range 0-100.\n", stderr); return INVALID_ARGUMENT; } break; case arg_map: { png24_image tmp = {}; if (SUCCESS != read_image(options.liq, optarg, false, &tmp, &options.fixed_palette_image, false)) { fprintf(stderr, " error: Unable to load %s", optarg); return INVALID_ARGUMENT; } } break; case 'h': print_full_version(stdout); print_usage(stdout); return SUCCESS; case 'V': puts(PNGQUANT_VERSION); return SUCCESS; case -1: break; default: return INVALID_ARGUMENT; } } while (opt != -1); int argn = optind; if (argn >= argc) { if (argn > 1) { fputs("No input files specified. See -h for help.\n", stderr); } else { print_full_version(stderr); print_usage(stderr); } return MISSING_ARGUMENT; } char *colors_end; unsigned long colors = strtoul(argv[argn], &colors_end, 10); if (colors_end != argv[argn] && '\0' == colors_end[0]) { if (LIQ_OK != liq_set_max_colors(options.liq, colors)) { fputs("Number of colors must be between 2 and 256.\n", stderr); return INVALID_ARGUMENT; } argn++; } if (newext && output_file_path) { fputs("--ext and --output options can't be used at the same time\n", stderr); return INVALID_ARGUMENT; } // new filename extension depends on options used. Typically basename-fs8.png if (newext == NULL) { newext = options.floyd > 0 ? "-ie-fs8.png" : "-ie-or8.png"; if (!options.ie_mode) newext += 3; /* skip "-ie" */ } if (argn == argc || (argn == argc-1 && 0==strcmp(argv[argn],"-"))) { options.using_stdin = true; argn = argc-1; } if (options.using_stdin && output_file_path) { fputs("--output can't be mixed with stdin\n", stderr); return INVALID_ARGUMENT; } const int num_files = argc-argn; if (output_file_path && num_files != 1) { fputs("Only one input file is allowed when --output is used\n", stderr); return INVALID_ARGUMENT; } #ifdef _OPENMP // if there's a lot of files, coarse parallelism can be used if (num_files > 2*omp_get_max_threads()) { omp_set_nested(0); omp_set_dynamic(1); } else { omp_set_nested(1); } #endif #pragma omp parallel for \ schedule(dynamic) reduction(+:skipped_count) reduction(+:error_count) reduction(+:file_count) shared(latest_error) for(int i=0; i < num_files; i++) { struct pngquant_options opts = options; opts.liq = liq_attr_copy(options.liq); const char *filename = opts.using_stdin ? "stdin" : argv[argn+i]; #ifdef _OPENMP struct buffered_log buf = {}; if (opts.log_callback && omp_get_num_threads() > 1 && num_files > 1) { liq_set_log_callback(opts.liq, log_callback_buferred, &buf); liq_set_log_flush_callback(opts.liq, log_callback_buferred_flush, &buf); options.log_callback = log_callback_buferred; options.log_callback_user_info = &buf; } #endif pngquant_error retval = SUCCESS; const char *outname = output_file_path; char *outname_free = NULL; if (!options.using_stdin) { if (!outname) { outname = outname_free = add_filename_extension(filename, newext); } if (!options.force && file_exists(outname)) { fprintf(stderr, " error: %s exists; not overwriting\n", outname); retval = NOT_OVERWRITING_ERROR; } } if (!retval) { retval = pngquant_file(filename, outname, &opts); } free(outname_free); liq_attr_destroy(opts.liq); if (retval) { #pragma omp critical { latest_error = retval; } if (retval == TOO_LOW_QUALITY) { skipped_count++; } else { error_count++; } } ++file_count; } if (error_count) { verbose_printf(&options, "There were errors quantizing %d file%s out of a total of %d file%s.", error_count, (error_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s"); } if (skipped_count) { verbose_printf(&options, "Skipped %d file%s out of a total of %d file%s.", skipped_count, (skipped_count == 1)? "" : "s", file_count, (file_count == 1)? "" : "s"); } if (!skipped_count && !error_count) { verbose_printf(&options, "No errors detected while quantizing %d image%s.", file_count, (file_count == 1)? "" : "s"); } liq_image_destroy(options.fixed_palette_image); liq_attr_destroy(options.liq); return latest_error; } static void pngquant_output_image_free(png8_image *output_image) { free(output_image->indexed_data); output_image->indexed_data = NULL; free(output_image->row_pointers); output_image->row_pointers = NULL; }
int quantize_pngquant( Pixel *pixelData, int width, int height, uint32_t quantPixels, Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, int withAlpha) { int result = 0; liq_image *image = NULL; liq_attr *attr = NULL; liq_result *remap = NULL; unsigned char *charMatrix = NULL; unsigned char **charMatrixRows = NULL; unsigned int i, y; *palette = NULL; *paletteLength = 0; *quantizedPixels = NULL; /* configure pngquant */ attr = liq_attr_create(); if (!attr) { goto err; } if (quantPixels) { liq_set_max_colors(attr, quantPixels); } /* prepare input image */ image = liq_image_create_rgba( attr, pixelData, width, height, 0.45455 /* gamma */); if (!image) { goto err; } /* quantize the image */ remap = liq_quantize_image(attr, image); if (!remap) { goto err; } liq_set_output_gamma(remap, 0.45455); liq_set_dithering_level(remap, 1); /* write output palette */ const liq_palette *l_palette = liq_get_palette(remap); *paletteLength = l_palette->count; *palette = malloc(sizeof(Pixel) * l_palette->count); if (!*palette) { goto err; } for (i = 0; i < l_palette->count; i++) { (*palette)[i].c.b = l_palette->entries[i].b; (*palette)[i].c.g = l_palette->entries[i].g; (*palette)[i].c.r = l_palette->entries[i].r; (*palette)[i].c.a = l_palette->entries[i].a; } /* write output pixels (pngquant uses char array) */ charMatrix = malloc(width * height); if (!charMatrix) { goto err; } charMatrixRows = malloc(height * sizeof(unsigned char*)); if (!charMatrixRows) { goto err; } for (y = 0; y < height; y++) { charMatrixRows[y] = &charMatrix[y * width]; } if (LIQ_OK != liq_write_remapped_image_rows(remap, image, charMatrixRows)) { goto err; } /* transcribe output pixels (pillow uses uint32_t array) */ *quantizedPixels = malloc(sizeof(uint32_t) * width * height); if (!*quantizedPixels) { goto err; } for (i = 0; i < width * height; i++) { (*quantizedPixels)[i] = charMatrix[i]; } result = 1; err: if (attr) liq_attr_destroy(attr); if (image) liq_image_destroy(image); if (remap) liq_result_destroy(remap); free(charMatrix); free(charMatrixRows); if (!result) { free(*quantizedPixels); free(*palette); } return result; }
/** * Based on libgd/gd_topal.c */ static int ngx_pngquant_gd_image(gdImagePtr oim, int dither, int colorsWanted, int speed) { int i; int maxColors = gdMaxColors; if (!oim->trueColor) { return 1; } /* If we have a transparent color (the alphaless mode of transparency), we * must reserve a palette entry for it at the end of the palette. */ if (oim->transparent >= 0) { maxColors--; } if (colorsWanted > maxColors) { colorsWanted = maxColors; } oim->pixels = calloc(sizeof (unsigned char *), oim->sy); if (!oim->pixels) { /* No can do */ goto outOfMemory; } for (i = 0; (i < oim->sy); i++) { oim->pixels[i] = (unsigned char *) calloc(sizeof (unsigned char *), oim->sx); if (!oim->pixels[i]) { goto outOfMemory; } } liq_attr *attr = liq_attr_create_with_allocator(malloc, gdFree); liq_image *image; liq_result *remap; int remapped_ok = 0; liq_set_max_colors(attr, colorsWanted); /* by default make it fast to match speed of previous implementation */ liq_set_speed(attr, speed ? speed : 9); if (oim->paletteQuantizationMaxQuality) { liq_set_quality(attr, oim->paletteQuantizationMinQuality, oim->paletteQuantizationMaxQuality); } image = liq_image_create_custom(attr, ngx_pngquant_convert_gd_pixel_to_rgba, oim, oim->sx, oim->sy, 0); remap = liq_quantize_image(attr, image); if (!remap) { /* minimum quality not met, leave image unmodified */ liq_image_destroy(image); liq_attr_destroy(attr); goto outOfMemory; } liq_set_dithering_level(remap, dither ? 1 : 0); if (LIQ_OK == liq_write_remapped_image_rows(remap, image, oim->pixels)) { remapped_ok = 1; const liq_palette *pal = liq_get_palette(remap); oim->transparent = -1; unsigned int icolor; for(icolor=0; icolor < pal->count; icolor++) { oim->open[icolor] = 0; oim->red[icolor] = pal->entries[icolor].r * gdRedMax/255; oim->green[icolor] = pal->entries[icolor].g * gdGreenMax/255; oim->blue[icolor] = pal->entries[icolor].b * gdBlueMax/255; int alpha = pal->entries[icolor].a * gdAlphaMax/255; if (gdAlphaOpaque < gdAlphaTransparent) { alpha = gdAlphaTransparent - alpha; } oim->alpha[icolor] = alpha; if (oim->transparent == -1 && alpha == gdAlphaTransparent) { oim->transparent = icolor; } } oim->colorsTotal = pal->count; } liq_result_destroy(remap); liq_image_destroy(image); liq_attr_destroy(attr); if (remapped_ok) { ngx_pngquant_free_true_color_image_data(oim); return 1; } outOfMemory: if (oim->trueColor) { /* On failure only */ if (oim->pixels) { for (i = 0; i < oim->sy; i++) { if (oim->pixels[i]) { gdFree (oim->pixels[i]); } } gdFree (oim->pixels); } oim->pixels = NULL; } return 0; }