static int enough_colors(MagickWand *mw, const struct imgmin_options *opt) { return unique_colors(mw) >= opt->min_unique_colors || /* * most color photos end up as the TrueColor type... * for Grayscale we ignore color count... * haven't run into many of the other types yet, be aggressive for now */ MagickGetType(mw) != TrueColorType || /* * if a full-color JPEGs has exactly 256 colors it's likely * been (poorly) converted from a GIF or PNG, these often look * terrible anyway but can often be compressed quite a bit */ unique_colors(mw) == 256; }
static void report_before(MagickWand *mw, size_t size_in) { const double ks = size_in / 1024.; fprintf(stdout, "Before quality:%lu colors:%lu size:%5.1fkB type:%s format:%s ", quality(mw), (unsigned long)unique_colors(mw), ks, type2str(MagickGetImageType(mw)), MagickGetImageFormat(mw)); }
static void report_after(MagickWand *mw, size_t size_in, size_t size_out) { const double ks = size_in / 1024.; const double kd = size_out / 1024.; const double ksave = ks - kd; const double kpct = ksave * 100. / ks; fprintf(stdout, "After quality:%lu colors:%lu size:%5.1fkB saved:%5.1fkB (%.1f%%)\n", (unsigned long)quality(mw), (unsigned long)unique_colors(mw), kd, ksave, kpct); }
static double color_density(MagickWand *mw) { const size_t area = MagickGetImageHeight(mw) * MagickGetImageWidth(mw); double density = (double)unique_colors(mw) / area; return density; }
static void doit(const char *src, const char *dst, size_t oldsize, const struct imgmin_options *opt) { MagickWand *mw, *tmp; MagickBooleanType status; double ks; size_t newsize = oldsize + 1; MagickWandGenesis(); mw = NewMagickWand(); /* load image... */ if (0 == strcmp("-", src)) { /* ...from stdin */ # define BIGBUF (16 * 1024 * 1024) char *blob = malloc(BIGBUF); oldsize = read(STDIN_FILENO, blob, BIGBUF); if (BIGBUF == oldsize) { fprintf(stderr, "Image too large for hardcoded imgmin stdin buffer\n"); exit(1); } MagickReadImageBlob(mw, blob, oldsize); free(blob); } else { /* ...from disk */ status = MagickReadImage(mw, src); if (status == MagickFalse) ThrowWandException(mw); } ks = oldsize / 1024.; fprintf(stderr, "Before quality:%lu colors:%lu size:%5.1fkB type:%s ", quality(mw), (unsigned long)unique_colors(mw), ks, type2str(MagickGetImageType(mw))); tmp = search_quality(mw, dst, opt); /* output image... */ { unsigned char *blob = MagickGetImageBlob(tmp, &newsize); /* if resulting image is larger than original, use original instead */ if (newsize > oldsize) { (void) MagickRelinquishMemory(blob); blob = MagickGetImageBlob(mw, &newsize); } { int fd; if (0 == strcmp("-", dst)) { fd = STDOUT_FILENO; } else { fd = open(dst, O_WRONLY | O_CREAT, 0644); if (-1 == fd) { perror("open"); exit(1); } } if ((ssize_t)newsize != write(fd, blob, newsize)) { perror("write"); exit(1); } (void) MagickRelinquishMemory(blob); if (fd != STDOUT_FILENO) close(fd); } } { double kd = newsize / 1024.; double ksave = ks - kd; double kpct = ksave * 100. / ks; fprintf(stderr, "After quality:%lu colors:%lu size:%5.1fkB saved:%5.1fkB (%.1f%%)\n", (unsigned long)quality(tmp), (unsigned long)unique_colors(tmp), kd, ksave, kpct); } /* tear it down */ DestroyMagickWand(tmp); DestroyMagickWand(mw); MagickWandTerminus(); }
/* * given a source image, a destination filepath and a set of image metadata thresholds, * search for the lowest-quality version of the source image whose properties fall within our * thresholds. * this will produce an image file that looks the same to the casual observer, but which * contains much less information and results in a smaller file. * typical savings on unoptimized images vary widely from 10-80%, with 25-50% being most common. */ MagickWand * search_quality(MagickWand *mw, const char *dst, const struct imgmin_options *opt) { MagickWand *tmp = NULL; char tmpfile[MAX_PATH] = "/tmp/imgminXXXXXX"; if (0 == strcmp("-", dst)) { if (-1 == mkstemp(tmpfile)) { perror("mkstemp"); return CloneMagickWand(mw); } } else { strcpy(tmpfile, dst); } /* * The overwhelming majority of JPEGs are TrueColorType; it is those types, with a low * unique color count, that we must avoid. */ if (unique_colors(mw) < opt->min_unique_colors && MagickGetType(mw) != GrayscaleType) { fprintf(stderr, " Color count is too low, skipping...\n"); return CloneMagickWand(mw); } if (quality(mw) < opt->quality_in_min) { fprintf(stderr, " Quality < %u, won't second-guess...\n", opt->quality_in_min); return CloneMagickWand(mw); } { ExceptionInfo *exception = AcquireExceptionInfo(); const double original_density = color_density(mw); unsigned qmax = min(quality(mw), opt->quality_out_max); unsigned qmin = opt->quality_out_min; unsigned steps = 0; /* * binary search of quality space for optimally lowest quality that * produces an acceptable level of distortion */ while (qmax > qmin + 1 && steps < opt->max_steps) { double distortion[CompositeChannels+1]; double error; double density_ratio; unsigned q; steps++; q = (qmax + qmin) / 2; /* change quality */ tmp = CloneMagickWand(mw); MagickSetImageCompressionQuality(tmp, q); /* apply quality change */ MagickWriteImages(tmp, tmpfile, MagickTrue); DestroyMagickWand(tmp); tmp = NewMagickWand(); MagickReadImage(tmp, tmpfile); /* quantify distortion produced by quality change * NOTE: writes to distortion[], which we don't care about * and to image(tmp)->error, which we do care about */ (void) GetImageDistortion(GetImageFromMagickWand(tmp), GetImageFromMagickWand(mw), MeanErrorPerPixelMetric, distortion, exception); /* FIXME: in perlmagick i was getting back a number [0,255.0], * here something else is happening... the hardcoded divisor * is an imperfect attempt to scale the number back to the * perlmagick results. I really need to look into this. */ error = GetImageFromMagickWand(tmp)->error.mean_error_per_pixel / 380.; density_ratio = fabs(color_density(tmp) - original_density) / original_density; /* eliminate half search space based on whether distortion within thresholds */ if (error > opt->error_threshold || density_ratio > opt->color_density_ratio) { qmin = q; } else { qmax = q; } if (opt->show_progress) { fprintf(stderr, "%.2f/%.2f@%u ", error, density_ratio, q); } } if (opt->show_progress) { putc('\n', stderr); } MagickSetImageCompressionQuality(mw, qmax); /* "Chroma sub-sampling works because human vision is relatively insensitive to * small areas of colour. It gives a significant reduction in file sizes, with * little loss of perceived quality." [3] */ (void) MagickSetImageProperty(mw, "jpeg:sampling-factor", "2x2"); /* strip an image of all profiles and comments */ (void) MagickStripImage(mw); MagickWriteImages(mw, tmpfile, MagickTrue); (void) DestroyMagickWand(tmp); tmp = NewMagickWand(); MagickReadImage(tmp, tmpfile); exception = DestroyExceptionInfo(exception); } return tmp; }