From 16c3cdb956ecfc00bae368646c1af5b0f808e6ab Mon Sep 17 00:00:00 2001 From: fbossen Date: Thu, 13 Feb 2014 16:55:56 -0500 Subject: [PATCH] Initial version of scan optimisation First implementation of scan optimisation as done by jpgcrush. Many parameters are currently hardcoded which should be changed. Implementation is missing for monochrome. --- jcinit.c | 4 +- jcmaster.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++ jcparam.c | 162 +++++++++++++++++++++++++++++++++++++++ jpeglib.h | 7 +- 4 files changed, 388 insertions(+), 2 deletions(-) diff --git a/jcinit.c b/jcinit.c index de0ade2a..6dfb4f70 100644 --- a/jcinit.c +++ b/jcinit.c @@ -3,6 +3,8 @@ * * Copyright (C) 1991-1997, Thomas G. Lane. * This file is part of the Independent JPEG Group's software. + * mozjpeg Modifications: + * Copyright (C) 2014, Mozilla Corporation. * For conditions of distribution and use, see the accompanying README file. * * This file contains initialization logic for the JPEG compressor. @@ -60,7 +62,7 @@ jinit_compress_master (j_compress_ptr cinfo) /* Need a full-image coefficient buffer in any multi-pass mode. */ jinit_c_coef_controller(cinfo, - (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding)); + (boolean) (cinfo->num_scans > 1 || cinfo->optimize_coding || cinfo->optimize_scans)); jinit_c_main_controller(cinfo, FALSE /* never need full buffer here */); jinit_marker_writer(cinfo); diff --git a/jcmaster.c b/jcmaster.c index dca03156..8f55ce68 100644 --- a/jcmaster.c +++ b/jcmaster.c @@ -6,6 +6,8 @@ * Modified 2003-2010 by Guido Vollbeding. * libjpeg-turbo Modifications: * Copyright (C) 2010, D. R. Commander. + * mozjpeg Modifications: + * Copyright (C) 2014, Mozilla Corporation. * For conditions of distribution and use, see the accompanying README file. * * This file contains master control logic for the JPEG compressor. @@ -177,6 +179,14 @@ validate_script (j_compress_ptr cinfo) /* -1 until that coefficient has been seen; then last Al for it */ #endif + if (cinfo->optimize_scans) { + cinfo->progressive_mode = TRUE; + /* When we optimize scans, there is redundancy in the scan list + * and this function will fail. Therefore skip all this checking + */ + return; + } + if (cinfo->num_scans <= 0) ERREXIT1(cinfo, JERR_BAD_SCAN_SCRIPT, 0); @@ -488,6 +498,14 @@ prepare_for_pass (j_compress_ptr cinfo) select_scan_parameters(cinfo); per_scan_setup(cinfo); } + if (cinfo->optimize_scans) { + cinfo->saved_dest = cinfo->dest; + cinfo->dest = NULL; + cinfo->scan_buffer[master->scan_number] = NULL; + cinfo->scan_size[master->scan_number] = 0; + jpeg_mem_dest(cinfo, &cinfo->scan_buffer[master->scan_number], &cinfo->scan_size[master->scan_number]); + (*cinfo->dest->init_destination)(cinfo); + } (*cinfo->entropy->start_pass) (cinfo, FALSE); (*cinfo->coef->start_pass) (cinfo, JBUF_CRANK_DEST); /* We emit frame/scan headers now */ @@ -530,6 +548,27 @@ pass_startup (j_compress_ptr cinfo) } +LOCAL(void) +copy_buffer (j_compress_ptr cinfo, int scan_idx) +{ + int size = cinfo->scan_size[scan_idx]; + unsigned char * src = cinfo->scan_buffer[scan_idx]; + + while (size >= cinfo->dest->free_in_buffer) + { + MEMCOPY(cinfo->dest->next_output_byte, src, cinfo->dest->free_in_buffer); + src += cinfo->dest->free_in_buffer; + size -= cinfo->dest->free_in_buffer; + cinfo->dest->next_output_byte += cinfo->dest->free_in_buffer; + cinfo->dest->free_in_buffer = 0; + (*cinfo->dest->empty_output_buffer)(cinfo); + } + + MEMCOPY(cinfo->dest->next_output_byte, src, size); + cinfo->dest->next_output_byte += size; + cinfo->dest->free_in_buffer -= size; +} + /* * Finish up at end of pass. */ @@ -562,6 +601,184 @@ finish_pass_master (j_compress_ptr cinfo) /* next pass is either optimization or output of next scan */ if (cinfo->optimize_coding) master->pass_type = huff_opt_pass; + if (cinfo->optimize_scans) { + (*cinfo->dest->term_destination)(cinfo); + cinfo->dest = cinfo->saved_dest; + switch (master->scan_number) { + case 0: + { + copy_buffer(cinfo, 0); + break; + } + case 11: + { + int size[4]; + size[0] = cinfo->scan_size[1] + cinfo->scan_size[2]; + size[1] = cinfo->scan_size[3] + cinfo->scan_size[4] + cinfo->scan_size[5]; + size[2] = cinfo->scan_size[6] + cinfo->scan_size[7] + cinfo->scan_size[8] + cinfo->scan_size[5]; + size[3] = cinfo->scan_size[9] + cinfo->scan_size[10] + cinfo->scan_size[11] + cinfo->scan_size[8] + cinfo->scan_size[5]; + + int Al = 0; + int i; + for (i = 1; i <= 3; i++) + if (size[i] < size[Al]) + Al = i; + cinfo->best_Al = Al; + + /* HACK! casting a const to a non-const :-( */ + jpeg_scan_info *scanptr = (jpeg_scan_info *)&cinfo->scan_info[12]; + + for (i = 0; i < 11; i++) + scanptr[i].Al = Al; + break; + } + + case 22: + { + int size[6]; + size[0] = cinfo->scan_size[12]; + size[1] = cinfo->scan_size[13] + cinfo->scan_size[14]; + size[2] = cinfo->scan_size[15] + cinfo->scan_size[16]; + size[3] = cinfo->scan_size[17] + cinfo->scan_size[18]; + size[4] = cinfo->scan_size[19] + cinfo->scan_size[20]; + size[5] = cinfo->scan_size[21] + cinfo->scan_size[22]; + int best = 0; + if (size[1] < size[best]) + best = 1; + if (size[2] < size[best]) + best = 2; + if (best != 0) { + if (size[3] < size[best]) + best = 3; + if (best == 2) { + if (size[4] < size[best]) { + best = 4; + if (size[5] < size[best]) + best = 5; + } + } + } + + if (best == 0) + copy_buffer(cinfo, 12); + else { + copy_buffer(cinfo, 11+2*best); + copy_buffer(cinfo, 12+2*best); + } + + if (cinfo->best_Al >= 3) + copy_buffer(cinfo, 11); + if (cinfo->best_Al >= 2) + copy_buffer(cinfo, 8); + if (cinfo->best_Al >= 1) + copy_buffer(cinfo, 5); + + int i; + for (i = 0; i < 23; i++) + free(cinfo->scan_buffer[i]); + } + break; + + case 25: + { + int size[2]; + size[0] = cinfo->scan_size[23]; + size[1] = cinfo->scan_size[24] + cinfo->scan_size[25]; + if (size[0] <= size[1]) + copy_buffer(cinfo, 23); + else { + copy_buffer(cinfo, 24); + copy_buffer(cinfo, 25); + } + break; + } + + case 41: + { + int size[3]; + size[0] = cinfo->scan_size[26] + cinfo->scan_size[27]; + size[0] += cinfo->scan_size[28] + cinfo->scan_size[29]; + size[1] = cinfo->scan_size[30] + cinfo->scan_size[31] + cinfo->scan_size[32]; + size[1] += cinfo->scan_size[33] + cinfo->scan_size[34] + cinfo->scan_size[35]; + size[2] = cinfo->scan_size[36] + cinfo->scan_size[37] + cinfo->scan_size[38] + cinfo->scan_size[39]; + size[2] += cinfo->scan_size[40] + cinfo->scan_size[41] + cinfo->scan_size[34] + cinfo->scan_size[35]; + + int Al = 0; + int i; + for (i = 1; i <= 3; i++) + if (size[i] < size[Al]) + Al = i; + cinfo->best_Al = Al; + jpeg_scan_info *scanptr = (jpeg_scan_info *)&cinfo->scan_info[42]; + + for (i = 0; i < 22; i++) + scanptr[i].Al = Al; + break; + } + + case 63: + { + int size[6]; + size[0] = cinfo->scan_size[42]; + size[0] += cinfo->scan_size[43]; + size[1] = cinfo->scan_size[44] + cinfo->scan_size[45]; + size[1] += cinfo->scan_size[46] + cinfo->scan_size[47]; + size[2] = cinfo->scan_size[48] + cinfo->scan_size[49]; + size[2] += cinfo->scan_size[50] + cinfo->scan_size[51]; + size[3] = cinfo->scan_size[52] + cinfo->scan_size[53]; + size[3] += cinfo->scan_size[54] + cinfo->scan_size[55]; + size[4] = cinfo->scan_size[56] + cinfo->scan_size[57]; + size[4] += cinfo->scan_size[58] + cinfo->scan_size[59]; + size[5] = cinfo->scan_size[60] + cinfo->scan_size[61]; + size[5] += cinfo->scan_size[62] + cinfo->scan_size[63]; + + int best = 0; + if (size[1] < size[best]) + best = 1; + if (size[2] < size[best]) + best = 2; + if (best != 0) { + if (size[3] < size[best]) + best = 3; + if (best == 2) { + if (size[4] < size[best]) { + best = 4; + if (size[5] < size[best]) + best = 5; + } + } + } + + if (best == 0) { + copy_buffer(cinfo, 42); + copy_buffer(cinfo, 43); + } + else { + copy_buffer(cinfo, 40+4*best); + copy_buffer(cinfo, 41+4*best); + copy_buffer(cinfo, 42+4*best); + copy_buffer(cinfo, 43+4*best); + } + + if (cinfo->best_Al >= 2) { + copy_buffer(cinfo, 40); + copy_buffer(cinfo, 41); + } + if (cinfo->best_Al >= 1) { + copy_buffer(cinfo, 34); + copy_buffer(cinfo, 35); + } + int i; + for (i = 23; i < 64; i++) + free(cinfo->scan_buffer[i]); + } + break; + + default: + break; + } + } + master->scan_number++; break; } diff --git a/jcparam.c b/jcparam.c index c4215c29..5476ae19 100644 --- a/jcparam.c +++ b/jcparam.c @@ -325,6 +325,7 @@ jpeg_set_defaults (j_compress_ptr cinfo) } #ifdef C_PROGRESSIVE_SUPPORTED + cinfo->optimize_scans = TRUE; if (cinfo->use_moz_defaults) jpeg_simple_progression(cinfo); else { @@ -606,6 +607,162 @@ fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) } +/* + * List of scans to be tested + * cinfo->num_components and cinfo->jpeg_color_space must be correct. + */ + +LOCAL(void) +jpeg_search_progression (j_compress_ptr cinfo) +{ + int ncomps = cinfo->num_components; + int nscans; + jpeg_scan_info * scanptr; + + /* Safety check to ensure start_compress not called yet. */ + if (cinfo->global_state != CSTATE_START) + ERREXIT1(cinfo, JERR_BAD_STATE, cinfo->global_state); + + /* Figure space needed for script. Calculation must match code below! */ + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + /* Custom script for YCbCr color images. */ + nscans = 64; + } else { + /* All-purpose script for other color spaces. */ + if (cinfo->use_moz_defaults) { + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 5 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 1 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } else { + if (ncomps > MAX_COMPS_IN_SCAN) + nscans = 6 * ncomps; /* 2 DC + 4 AC scans per component */ + else + nscans = 2 + 4 * ncomps; /* 2 DC scans; 4 AC scans per component */ + } + } + + /* Allocate space for script. + * We need to put it in the permanent pool in case the application performs + * multiple compressions without changing the settings. To avoid a memory + * leak if jpeg_simple_progression is called repeatedly for the same JPEG + * object, we try to re-use previously allocated space, and we allocate + * enough space to handle YCbCr even if initially asked for grayscale. + */ + if (cinfo->script_space == NULL || cinfo->script_space_size < nscans) { + cinfo->script_space_size = MAX(nscans, 10); + cinfo->script_space = (jpeg_scan_info *) + (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, + cinfo->script_space_size * SIZEOF(jpeg_scan_info)); + } + scanptr = cinfo->script_space; + cinfo->scan_info = scanptr; + cinfo->num_scans = nscans; + + if (ncomps == 3 && cinfo->jpeg_color_space == JCS_YCbCr) { + + /* 23 scans for luma */ + /* 1 scan for DC */ + /* 11 scans to determine successive approximation */ + /* 11 scans to determine frequency approximation */ + /* after 12 scans need to update following 11 */ + /* after 23 scans need to determine which to keep */ + /* last 4 done conditionally */ + + /* luma DC by itself */ + scanptr = fill_dc_scans(scanptr, 1, 0, 0); + /* luma approximation 1 */ + scanptr = fill_a_scan(scanptr, 0, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 9, 63, 0, 0); + /* luma approximation 2 */ + scanptr = fill_a_scan(scanptr, 0, 1, 8, 0, 1); + scanptr = fill_a_scan(scanptr, 0, 9, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 0, 1, 63, 1, 0); + /* luma approximation 3 */ + scanptr = fill_a_scan(scanptr, 0, 1, 8, 0, 2); + scanptr = fill_a_scan(scanptr, 0, 9, 63, 0, 2); + scanptr = fill_a_scan(scanptr, 0, 1, 63, 2, 1); + /* luma approximation 4 */ + scanptr = fill_a_scan(scanptr, 0, 1, 8, 0, 3); + scanptr = fill_a_scan(scanptr, 0, 9, 63, 0, 3); + scanptr = fill_a_scan(scanptr, 0, 1, 63, 3, 2); + /* luma frequency split 1 */ + scanptr = fill_a_scan(scanptr, 0, 1, 63, 0, 0); + /* luma frequency split 2 */ + scanptr = fill_a_scan(scanptr, 0, 1, 2, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 3, 63, 0, 0); + /* luma frequency split 3 */ + scanptr = fill_a_scan(scanptr, 0, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 9, 63, 0, 0); + /* luma frequency split 4, don't do if 2 and 3 didn't help */ + scanptr = fill_a_scan(scanptr, 0, 1, 5, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 6, 63, 0, 0); + /* luma frequency split 5, do only if 3 is best */ + scanptr = fill_a_scan(scanptr, 0, 1, 12, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 13, 63, 0, 0); + /* luma frequency split 6, don't do if 5 didn't help */ + scanptr = fill_a_scan(scanptr, 0, 1, 18, 0, 0); + scanptr = fill_a_scan(scanptr, 0, 19, 63, 0, 0); + + /* 41 scans for chroma */ + + /* chroma DC combined */ + scanptr = fill_a_scan_pair(scanptr, 1, 0, 0, 0, 0); + /* chroma DC separate */ + scanptr = fill_a_scan(scanptr, 1, 0, 0, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 0, 0, 0, 0); + /* chroma approximation 1 */ + scanptr = fill_a_scan(scanptr, 1, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 9, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 9, 63, 0, 0); + /* chroma approximation 2 */ + scanptr = fill_a_scan(scanptr, 1, 1, 8, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 9, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 2, 1, 8, 0, 1); + scanptr = fill_a_scan(scanptr, 2, 9, 63, 0, 1); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 1, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 63, 1, 0); + /* chroma approximation 3 */ + scanptr = fill_a_scan(scanptr, 1, 1, 8, 0, 2); + scanptr = fill_a_scan(scanptr, 1, 9, 63, 0, 2); + scanptr = fill_a_scan(scanptr, 2, 1, 8, 0, 2); + scanptr = fill_a_scan(scanptr, 2, 9, 63, 0, 2); + scanptr = fill_a_scan(scanptr, 1, 1, 63, 2, 1); + scanptr = fill_a_scan(scanptr, 2, 1, 63, 2, 1); + /* chroma frequency split 1 */ + scanptr = fill_a_scan(scanptr, 1, 1, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 63, 0, 0); + /* chroma frequency split 2 */ + scanptr = fill_a_scan(scanptr, 1, 1, 2, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 3, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 2, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 3, 63, 0, 0); + /* chroma frequency split 3 */ + scanptr = fill_a_scan(scanptr, 1, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 9, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 8, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 9, 63, 0, 0); + /* chroma frequency split 4 */ + scanptr = fill_a_scan(scanptr, 1, 1, 5, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 6, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 5, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 6, 63, 0, 0); + /* chroma frequency split 5 */ + scanptr = fill_a_scan(scanptr, 1, 1, 12, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 13, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 12, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 13, 63, 0, 0); + /* chroma frequency split 6 */ + scanptr = fill_a_scan(scanptr, 1, 1, 18, 0, 0); + scanptr = fill_a_scan(scanptr, 1, 19, 63, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 1, 18, 0, 0); + scanptr = fill_a_scan(scanptr, 2, 19, 63, 0, 0); + } else { + /* TODO */ + } +} + /* * Create a recommended progressive-JPEG script. * cinfo->num_components and cinfo->jpeg_color_space must be correct. @@ -614,6 +771,10 @@ fill_dc_scans (jpeg_scan_info * scanptr, int ncomps, int Ah, int Al) GLOBAL(void) jpeg_simple_progression (j_compress_ptr cinfo) { + if (cinfo->optimize_scans) { + jpeg_search_progression(cinfo); + return; + } int ncomps = cinfo->num_components; int nscans; jpeg_scan_info * scanptr; @@ -724,4 +885,5 @@ jpeg_simple_progression (j_compress_ptr cinfo) } } + #endif /* C_PROGRESSIVE_SUPPORTED */ diff --git a/jpeglib.h b/jpeglib.h index fb9bf1e6..8b2a1494 100644 --- a/jpeglib.h +++ b/jpeglib.h @@ -377,7 +377,12 @@ struct jpeg_compress_struct { J_DCT_METHOD dct_method; /* DCT algorithm selector */ boolean use_moz_defaults; /* TRUE if using Mozilla defaults */ - + boolean optimize_scans; /* TRUE=optimize progressive coding scans */ + struct jpeg_destination_mgr * saved_dest; /* saved value of dest */ + unsigned char * scan_buffer[64]; /* buffer for a given scan */ + unsigned long scan_size[64]; /* size for a given scan */ + int best_Al; /* best value for Al found in scan search */ + /* The restart interval can be specified in absolute MCUs by setting * restart_interval, or in MCU rows by setting restart_in_rows * (in which case the correct restart_interval will be figured