Beispiel #1
0
static pngquant_error prepare_output_image(liq_result *result, liq_image *input_image, png8_image *output_image)
{
    output_image->width = liq_image_get_width(input_image);
    output_image->height = liq_image_get_height(input_image);
    output_image->gamma = liq_get_output_gamma(result);

    /*
    ** Step 3.7 [GRR]: allocate memory for the entire indexed image
    */

    output_image->indexed_data = malloc(output_image->height * output_image->width);
    output_image->row_pointers = malloc(output_image->height * sizeof(output_image->row_pointers[0]));

    if (!output_image->indexed_data || !output_image->row_pointers) {
        return OUT_OF_MEMORY_ERROR;
    }

    for(size_t row = 0; row < output_image->height; row++) {
        output_image->row_pointers[row] = output_image->indexed_data + row * output_image->width;
    }

    const liq_palette *palette = liq_get_palette(result);
    // tRNS, etc.
    output_image->num_palette = palette->count;
    output_image->num_trans = 0;
    for(unsigned int i=0; i < palette->count; i++) {
        if (palette->entries[i].a < 255) {
            output_image->num_trans = i+1;
        }
    }

    return SUCCESS;
}
void
set_palette(liq_result *rv, png8_image *png)
{
    unsigned int i;
    liq_color px;
    const liq_palette *palette;

    palette = liq_get_palette(rv);
    png->num_palette = palette->count;
    png->num_trans = 0;
    for (i = 0;i < palette->count;i++) {
        px = palette->entries[i];
        if (px.a < 255) {
            png->num_trans = i + 1;
        }
        png->palette[i] = (png_color) {.red=px.r, .green=px.g, .blue=px.b};
        png->trans[i] = px.a;
    }
}

void
png8_image_destroy(png8_image *png)
{
    free(png->indexed_data);
    png->indexed_data = NULL;

    free(png->row_pointers);
    png->row_pointers = NULL;
}
Beispiel #3
0
static void set_palette(liq_result *result, png8_image *output_image)
{
    const liq_palette *palette = liq_get_palette(result);

    // tRNS, etc.
    output_image->num_palette = palette->count;
    output_image->num_trans = 0;
    for(unsigned int i=0; i < palette->count; i++) {
        liq_color px = palette->entries[i];
        if (px.a < 255) {
            output_image->num_trans = i+1;
        }
        output_image->palette[i] = (png_color){.red=px.r, .green=px.g, .blue=px.b};
        output_image->trans[i] = px.a;
    }
}


static bool file_exists(const char *outname)
{
    FILE *outfile = fopen(outname, "rb");
    if ((outfile ) != NULL) {
        fclose(outfile);
        return true;
    }
    return false;
}
void
prepare_output_image(liq_result *rv, liq_image *image, png8_image *png)
{
    unsigned int i;
    const liq_palette *palette;
    
    png->width = liq_image_get_width(image);
    png->height = liq_image_get_height(image);

    png->indexed_data = malloc(png->width * png->height);
    png->row_pointers = malloc(png->height * sizeof(png->row_pointers[0]));

    for (i = 0;i < png->height;i++) {
        png->row_pointers[i] = png->indexed_data + i * png->width;
    }
    
    palette = liq_get_palette(rv);
    png->num_palette = palette->count;
    png->num_trans = 0;
    for (i = 0;i < palette->count;i++) {
        if (palette->entries[i].a < 255) {
            png->num_trans = i + 1;
        }
    }
}
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;
}
Beispiel #6
0
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);
}
Beispiel #7
0
static void set_palette(liq_result *result, png8_image *output_image)
{
    const liq_palette *palette = liq_get_palette(result);

    output_image->num_palette = palette->count;
    for(unsigned int i=0; i < palette->count; i++) {
        const liq_color px = palette->entries[i];
        output_image->palette[i] = (rwpng_rgba){.r=px.r, .g=px.g, .b=px.b, .a=px.a};
    }
}


static bool file_exists(const char *outname)
{
    FILE *outfile = fopen(outname, "rb");
    if ((outfile ) != NULL) {
        fclose(outfile);
        return true;
    }
    return false;
}
Beispiel #8
0
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);
}
Beispiel #10
0
int main(int argc, char *argv[])
{
    struct pngquant_options options = {
        .floyd = 1.f, // floyd-steinberg dithering
        .strip = false,
    };
    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;
                }
                if (strcmp(optarg, "-") == 0) {
                    options.using_stdout = true;
                    break;
                }
                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_strip:
                options.strip = true;
                break;

            case arg_map:
                {
                    png24_image tmp = {};
                    if (SUCCESS != read_image(options.liq, optarg, false, &tmp, &options.fixed_palette_image, true, true, false)) {
                        fprintf(stderr, "  error: unable to load %s", optarg);
                        return INVALID_ARGUMENT;
                    }
                    liq_result *tmp_quantize = liq_quantize_image(options.liq, options.fixed_palette_image);
                    const liq_palette *pal = liq_get_palette(tmp_quantize);
                    if (!pal) {
                        fprintf(stderr, "  error: unable to read colors from %s", optarg);
                        return INVALID_ARGUMENT;
                    }
                    for(unsigned int i=0; i < pal->count; i++) {
                        liq_image_add_fixed_color(options.fixed_palette_image, pal->entries[i]);
                    }
                    liq_result_destroy(tmp_quantize);
                }
                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;
    }
    if (options.using_stdout && !options.using_stdin && num_files != 1) {
        fputs("Only one input file is allowed when using the special output path \"-\" to write to stdout\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);
            opts.log_callback = log_callback_buferred;
            opts.log_callback_user_info = &buf;
        }
        #endif


        pngquant_error retval = SUCCESS;

        const char *outname = output_file_path;
        char *outname_free = NULL;
        if (!opts.using_stdout) {
            if (!outname) {
                outname = outname_free = add_filename_extension(filename, newext);
            }
            if (!opts.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, "Quantized %d image%s.",
                       file_count, (file_count == 1)? "" : "s");
    }

    if (options.fixed_palette_image) 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->strip, 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 (RWPNG_ICCP == input_image_rwpng.input_color) {
            verbose_printf(options, "  used embedded ICC profile to transform image to sRGB colorspace");
        } else if (RWPNG_GAMA_CHRM == input_image_rwpng.input_color) {
            verbose_printf(options, "  used gAMA and cHRM chunks to transform image to sRGB colorspace");
        } else if (RWPNG_ICCP_WARN_GRAY == input_image_rwpng.input_color) {
            verbose_printf(options, "  warning: ignored ICC profile in GRAY colorspace");
        } else if (RWPNG_COCOA == input_image_rwpng.input_color) {
            // No comment
        } else if (RWPNG_SRGB == input_image_rwpng.input_color) {
            verbose_printf(options, "  passing sRGB tag from the input");
        } else if (input_image_rwpng.gamma != 0.45455) {
            verbose_printf(options, "  converted image from gamma %2.1f to gamma 2.2",
                           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_error remap_error = liq_image_quantize(options->fixed_palette_image ? options->fixed_palette_image : input_image, options->liq, &remap);

        if (LIQ_OK == remap_error) {

            // fixed gamma ~2.2 for the web. PNG can't store exact 1/2.2
            // NB: can't change gamma here, because output_color is allowed to be an sRGB tag
            liq_set_output_gamma(remap, 0.45455);
            liq_set_dithering_level(remap, options->floyd);

            retval = prepare_output_image(remap, input_image, input_image_rwpng.output_color, &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 if (LIQ_QUALITY_TOO_LOW == remap_error) {
            retval = TOO_LOW_QUALITY;
        } else {
            retval = INVALID_ARGUMENT; // dunno
        }
    }

    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 raised to 1.5, because even greater savings are needed to justify big quality loss.
            // but >50% savings are considered always worthwile in order to allow low quality conversions to work at all
            const double quality = quality_percent/100.0;
            const double expected_reduced_size = pow(quality, 1.5);
            output_image.maximum_file_size = (input_image_rwpng.file_size-1) * (expected_reduced_size < 0.5 ? 0.5 : expected_reduced_size);
        }

        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 (SUCCESS == retval && output_image.metadata_size > 0) {
            verbose_printf(options, "  copied %dKB of additional PNG metadata", (int)(output_image.metadata_size+999)/1000);
        }
    }

    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;
}
Beispiel #11
0
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;
}