/* * 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 (!enough_colors(mw, opt)) { fprintf(stdout, " Color count is too low, skipping...\n"); return CloneMagickWand(mw); } if (quality(mw) < opt->quality_in_min) { fprintf(stdout, " Quality < %u, won't second-guess...\n", opt->quality_in_min); return CloneMagickWand(mw); } size_t width = MagickGetImageWidth(mw); size_t height = MagickGetImageHeight(mw); dssim_info *dssim = dssim_init(1); void *convert_data = convert_row_start(mw); dssim_set_original_float_callback(dssim, width, height, convert_row_callback, convert_data); convert_row_finish(convert_data); { 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 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); void *convert_data = convert_row_start(tmp); dssim_set_modified_float_callback(dssim, width, height, convert_row_callback, convert_data); convert_row_finish(convert_data); double error = 20.0 * dssim_compare(dssim, NULL); // scaled to threshold of previous implementation density_ratio = fabs(color_density(tmp) - original_density) / original_density; /* color density ratio threshold is an alternative quality measure. If it's exceeded, pretend MSE was higher to increase quality */ if (density_ratio > opt->color_density_ratio) { error *= 1.25 + density_ratio; // fudge factor } /* eliminate half search space based on whether distortion within thresholds */ if (error > opt->error_threshold) { qmin = q; } else { qmax = q; } if (opt->show_progress) { fprintf(stdout, "%.2f/%.2f@%u ", error, density_ratio, q); } /* Stop searching if close enough to the target */ if (fabs(error - opt->error_threshold) < opt->error_threshold * ERROR_THRESHOLD_INACCURACY) { qmax = q; break; } } if (opt->show_progress) { putc('\n', stdout); } 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] */ #if MagickLibVersion >= 0x630 /* FIXME: available in 0x660, not available in 0x628, not sure which version it was introduced in */ (void) MagickSetImageProperty(mw, "jpeg:sampling-factor", "2x2"); #endif /* 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; }
/* * 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; }