/* Copyright (c) 1999 Surendar Chandra and Duke University * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Duke University * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE */ /* Usage: Compile with IJG JPEG Library version 6a or higher. */ #include #include #include #include #include #include #include #include #include #include "common.h" /* Is progressive format preserved? Preserving progressive format takes longer as the custom Huffman table creation is expensive. Disabled by default */ #undef PROGRESSIVE_PRESERVED /* Optimize the transcoding operations by avoiding integer divisions. Runs a little faster at the expensive of more integer roundoff errors */ #define OPTIMIZE #ifdef OPTIMIZE /* To offset the effects of the integer optimization we scale it using 2^(SCALE) */ #define SCALE 2 #endif /* OPTIMIZE */ /* * The main program. */ typedef struct { struct jpeg_destination_mgr pub; /* public fields */ unsigned char **buf; size_t *size; } my_destination_mgr; typedef my_destination_mgr * my_dest_ptr; static void init_destination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr) cinfo->dest; /* Allocate the output buffer --- it will be released when done with image */ *(dest->buf) = malloc(1024); *(dest->size) = 1024; dest->pub.next_output_byte = *(dest->buf); dest->pub.free_in_buffer = 1024; } static boolean empty_output_buffer (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr) cinfo->dest; unsigned char *ret; ret = realloc(*(dest->buf), *(dest->size) + 1024); *(dest->buf) = ret; (*dest->size) += 1024; dest->pub.next_output_byte = *(dest->buf) + *(dest->size) - 1024 ; dest->pub.free_in_buffer = 1024; return TRUE; } static void term_destination (j_compress_ptr cinfo) { my_dest_ptr dest = (my_dest_ptr) cinfo->dest; *(dest->size)-=dest->pub.free_in_buffer; } static void jpeg_memory_dest (j_compress_ptr cinfo, caddr_t *out_buf, size_t *out_size) { my_dest_ptr dest; if (cinfo->dest == NULL) { cinfo->dest = (struct jpeg_destination_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(my_destination_mgr)); } dest = (my_dest_ptr) cinfo->dest; dest->pub.init_destination = init_destination; dest->pub.empty_output_buffer = empty_output_buffer; dest->pub.term_destination = term_destination; dest->buf = (unsigned char **)out_buf; dest->size = out_size; } struct my_error_mgr { struct jpeg_error_mgr pub; /* "public" fields */ jmp_buf setjmp_buffer; /* for return to caller */ }; typedef struct my_error_mgr * my_error_ptr; /* * Here's the routine that will replace the standard error_exit method: */ static void my_error_exit (j_common_ptr cinfo) { /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */ my_error_ptr myerr = (my_error_ptr) cinfo->err; /* Always display the message. */ /* We could postpone this until after returning, if we chose. */ (*cinfo->err->output_message) (cinfo); /* Return control to the setjmp point */ longjmp(myerr->setjmp_buffer, 1); } static void init_source (j_decompress_ptr cinfo) { /* NOOP */ } static boolean fill_input_buffer (j_decompress_ptr cinfo) { struct jpeg_source_mgr *src = cinfo->src; WARNMS(cinfo, JWRN_JPEG_EOF); /* Insert a fake EOI marker */ src->next_input_byte[0] = (JOCTET) 0xFF; src->next_input_byte[1] = (JOCTET) JPEG_EOI; src->bytes_in_buffer = 2; return TRUE; } static void skip_input_data (j_decompress_ptr cinfo, long num_bytes) { struct jpeg_source_mgr *src = cinfo->src; if (num_bytes > 0) { while (num_bytes > (long) src->bytes_in_buffer) { num_bytes -= (long) src->bytes_in_buffer; (void) fill_input_buffer(cinfo); /* note we assume that fill_input_buffer will never return FALSE, * so suspension need not be handled. */ } src->next_input_byte += (size_t) num_bytes; src->bytes_in_buffer -= (size_t) num_bytes; } } static void term_source (j_decompress_ptr cinfo) { /* no work necessary here */ } static void jpeg_memory_src (j_decompress_ptr cinfo, caddr_t buffer, size_t nbyte) { struct jpeg_source_mgr *src; if (cinfo->src == NULL) { /* first time for this JPEG object? */ cinfo->src = (struct jpeg_source_mgr *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, SIZEOF(struct jpeg_source_mgr)); } src = cinfo->src; src->next_input_byte = (JOCTET *) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT, nbyte * SIZEOF(JOCTET)); src->init_source = init_source; src->fill_input_buffer = fill_input_buffer; src->skip_input_data = skip_input_data; src->resync_to_restart = jpeg_resync_to_restart; /* use default method */ src->term_source = term_source; src->bytes_in_buffer = nbyte; /* forces fill_input_buffer on first read */ memcpy(src->next_input_byte, buffer, nbyte); } /* scale the initial JPEG Quality factor by %. Scale is 0..100. For scale >= 100, nothing happens. Return Value: -1 - Error 0 - Nothing happened 1 - Success */ int scaleJPEGQualityFactor ( caddr_t in_buf, /* IN: Input image */ size_t in_size, /* IN: Image buffer size */ caddr_t *out_buf, /* OUT: Output image, will be allocated internally */ size_t *out_size, /* OUT: Output image size */ unsigned int *qual, /* OUT: Image Quality Factor, returned*/ unsigned int *scaled_quality, /* OUT: Image Quality of output image*/ unsigned int scale /* IN: Scale percentage 0..100 */ ) { struct jpeg_decompress_struct srcinfo; struct jpeg_compress_struct dstinfo; jvirt_barray_ptr * src_coef_arrays; jpeg_component_info *compptr; JDIMENSION dst_blk; int ci, i, offset_x, offset_y; JBLOCKARRAY src_buffer; JCOEFPTR src_ptr; int qtblno; /* Quantization table */ JQUANT_TBL *src_qtbl, *dst_qtbl; /* JPEG Quantization tables */ struct my_error_mgr mcerr, mderr; /* Error manager */ int computations = 0; /* The number of computational blocks */ /* Initialize the JPEG decompression object with default error handling. */ srcinfo.err = jpeg_std_error(&mderr.pub); mderr.pub.error_exit = my_error_exit; if (setjmp(mderr.setjmp_buffer)) { jpeg_destroy_decompress(&srcinfo); return -1; /* Something failed */ } jpeg_create_decompress(&srcinfo); /* Initialize the JPEG compression object with default error handling. */ dstinfo.err = jpeg_std_error(&mcerr.pub); mcerr.pub.error_exit = my_error_exit; jpeg_create_compress(&dstinfo); srcinfo.mem->max_memory_to_use = dstinfo.mem->max_memory_to_use; jpeg_memory_src(&srcinfo, in_buf, in_size); /* Read file header */ (void) jpeg_read_header(&srcinfo, TRUE); *qual = initialQualityFactor(srcinfo); *scaled_quality = (*qual * scale)/100; if (*scaled_quality >= *qual) { out_buf = malloc(in_size); memcpy(out_buf, in_buf, in_size); *out_size = in_size; jpeg_destroy_compress(&dstinfo); jpeg_destroy_decompress(&srcinfo); return 0; } /* Read source file as DCT coefficients */ src_coef_arrays = jpeg_read_coefficients(&srcinfo); /* Initialize destination compression parameters from source values */ jpeg_copy_critical_parameters(&srcinfo, &dstinfo); /* Adjust destination parameters if required by transform options; * also find out which set of coefficient arrays will hold the output. */ jpeg_set_quality((j_compress_ptr) &dstinfo, *scaled_quality, 1); #ifdef PROGRESSIVE_PRESERVED if (srcinfo.progressive_mode) { dstinfo.progressive_mode = srcinfo.progressive_mode; jpeg_simple_progression(&dstinfo); } #endif /* PROGRESSIVE_PRESERVED */ /* Specify data destination for compression */ jpeg_memory_dest(&dstinfo, out_buf, out_size); /* Start compressor (note no image data is actually written here) */ jpeg_write_coefficients(&dstinfo, src_coef_arrays); /* Execute image transformation, if any */ #ifdef OPTIMIZE for (qtblno = 0; qtblno < NUM_QUANT_TBLS; qtblno++) { if (srcinfo.quant_tbl_ptrs[qtblno] != NULL) { src_qtbl = srcinfo.quant_tbl_ptrs[qtblno]; dst_qtbl = dstinfo.quant_tbl_ptrs[qtblno]; for (i = 0; i < DCTSIZE2; i++) src_qtbl->quantval[i] = (src_qtbl->quantval[i] << SCALE) / dst_qtbl->quantval[i]; } } #endif /* OPTIMIZE */ for (ci = 0; ci < dstinfo.num_components; ci++) { compptr = dstinfo.comp_info + ci; qtblno = compptr->quant_tbl_no; if (qtblno < 0 || qtblno >= NUM_QUANT_TBLS || srcinfo.quant_tbl_ptrs[qtblno] == NULL) { jpeg_destroy_compress(&dstinfo); jpeg_destroy_decompress(&srcinfo); return -1; } computations += (compptr->height_in_blocks * compptr->width_in_blocks); src_qtbl = srcinfo.quant_tbl_ptrs[qtblno]; #ifndef OPTIMIZE dst_qtbl = dstinfo.quant_tbl_ptrs[qtblno]; #endif /* OPTIMIZE */ for (dst_blk = 0; dst_blk < compptr->height_in_blocks; dst_blk += 1 /* compptr->h_samp_factor */) { src_buffer = (*srcinfo.mem->access_virt_barray) ((j_common_ptr) &srcinfo, src_coef_arrays[ci], dst_blk, (JDIMENSION) 1, TRUE); for (offset_x = 0; offset_x < 1/* compptr->h_samp_factor */; offset_x++) { for (offset_y = 0; offset_y < compptr->width_in_blocks; offset_y++) { src_ptr = src_buffer[offset_x][offset_y]; for (i = 0; i < DCTSIZE2; i++) { #ifdef OPTIMIZE src_ptr[i] = (src_ptr[i] * src_qtbl->quantval[i]) >> SCALE; #else src_ptr[i] = src_ptr[i] * src_qtbl->quantval[i] / dst_qtbl->quantval[i]; #endif /* OPTIMIZE */ } } } } } /* Finish compression and release memory */ jpeg_finish_compress(&dstinfo); jpeg_destroy_compress(&dstinfo); (void) jpeg_finish_decompress(&srcinfo); jpeg_destroy_decompress(&srcinfo); return 1; } /* Return Value: 1 if it is an efficient image 0 otherwise */ int efficientJPEG(caddr_t in_buf,/* JPEG Image */ size_t in_size) /* Size of in_buf */ { struct jpeg_decompress_struct srcinfo; jvirt_barray_ptr * src_coef_arrays; JDIMENSION src_blk; jpeg_component_info *compptr; int ci, j, offset_y; JBLOCKARRAY src_buffer; JCOEFPTR src_ptr; int qtblno; struct my_error_mgr mderr; int pdf = 0; unsigned long num_mults; extern const int jpeg_natural_order[]; unsigned int qual; double good = 0.0; /* Initialize the JPEG decompression object with default error handling. */ srcinfo.err = jpeg_std_error(&mderr.pub); mderr.pub.error_exit = my_error_exit; if (setjmp(mderr.setjmp_buffer)) { jpeg_destroy_decompress(&srcinfo); return 0; } jpeg_create_decompress(&srcinfo); jpeg_memory_src(&srcinfo, in_buf, in_size); /* Read file header */ (void) jpeg_read_header(&srcinfo, TRUE); qual = initialQualityFactor(srcinfo); if (qual > 80) { jpeg_destroy_decompress(&srcinfo); return 1; } /* Read source file as DCT coefficients */ src_coef_arrays = jpeg_read_coefficients(&srcinfo); num_mults = 0; for (ci = 0; ci < srcinfo.num_components; ci++) { compptr = srcinfo.comp_info + ci; qtblno = compptr->quant_tbl_no; if (qtblno != 0) continue; if (srcinfo.quant_tbl_ptrs[qtblno] == NULL) { jpeg_destroy_decompress(&srcinfo); return 0; } for (src_blk = 0; src_blk < compptr->height_in_blocks; src_blk += 1) { src_buffer = (*srcinfo.mem->access_virt_barray) ((j_common_ptr) &srcinfo, src_coef_arrays[ci], src_blk,(JDIMENSION)1, FALSE); num_mults += compptr->width_in_blocks * 5; for (offset_y = 0; offset_y < compptr->width_in_blocks; offset_y++) { src_ptr = src_buffer[0][offset_y]; for (j = 5; j > 0 ; j--) { if (abs((int) src_ptr[jpeg_natural_order[j]]) > 80) pdf++; } } } } /* Finish compression and release memory */ (void) jpeg_finish_decompress(&srcinfo); jpeg_destroy_decompress(&srcinfo); good = (double) pdf / (double)num_mults; if (good > 0.03) return 1; return 0; } #define DEBUG #ifdef DEBUG int main(int argc, char *argv[], char *envp[]) { struct stat stat_buf; size_t jpeg_size; caddr_t jpeg_buffer; caddr_t c_buffer; size_t c_size; int fd; int qual, scaled_qual; int src, dest; int scale = 50; if (argc != 3) { fprintf(stderr, "Usage: %s <%%Q>\n", *argv); exit(1); } if (stat(argv[1], &stat_buf) != 0) { fprintf(stderr, "Cannot stat file\n"); exit(1); } if (argc > 2) scale = atoi(argv[2]); jpeg_size = stat_buf.st_size; jpeg_buffer = malloc(jpeg_size); fd = open(argv[1], O_RDONLY, 0644); if (fd < 0) { fprintf(stderr, "Cannot open file\n"); exit(1); } read(fd, jpeg_buffer, jpeg_size); close(fd); scaleJPEGQualityFactor(jpeg_buffer, jpeg_size, &c_buffer, &c_size, &qual, &scaled_qual, scale); // write(1, c_buffer, c_size); src = efficientJPEG(jpeg_buffer, jpeg_size); dest = efficientJPEG(c_buffer, c_size); fprintf(stderr, "Input Image is %s. Output Image is %s\n", (src==1) ? "Efficient" : "Not Efficient", (dest==1) ? "Efficient" : "Not Efficient"); exit(0); } #endif /* DEBUG */