blob: 8b689d3aee0d178c998f75c785a5934f70e3cf8b [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Author: Satyanarayana Manyam
#include "pagespeed/kernel/image/jpeg_utils.h"
#include <setjmp.h> // for longjmp
// 'stdio.h' provides FILE for jpeglib (needed for certain builds)
#include <stdio.h>
#include "pagespeed/kernel/image/jpeg_reader.h"
extern "C" {
#ifdef USE_SYSTEM_LIBJPEG
#include "jpeglib.h" // NOLINT
#else
#include "third_party/libjpeg_turbo/src/jpeglib.h"
#endif
}
namespace net_instaweb {
class MessageHandler;
}
namespace {
static double ComputeQualityEntriesSum(JQUANT_TBL* quantization_table,
const unsigned int* std_table) {
double quality_entries_sum = 0;
// Quality is defined in terms of the base quantization tables used by
// encoder. Q = quant table, q = compression quality and S = table used by
// encoder, Encoder does the following.
// if q > 0.5 then Q = 2 - 2*q*S otherwise Q = (0.5/q)*S.
//
// Refer 'jpeg_add_quant_table (...)' in jcparam.c for more details.
//
// Since we don't have access to the table used by encoder. But it is
// generally close to the standard table defined by JPEG. Hence, we apply
// inverse function of the above to using standard table and compute the input
// image jpeg quality.
for (int i = 0; i < DCTSIZE2; i++) {
if (quantization_table->quantval[i] == 1) {
// 1 is the minimum denominator allowed for any value in the quantization
// matrix and it implies that quality is set 100.
quality_entries_sum += 1;
} else {
double scale_factor =
static_cast<double>(quantization_table->quantval[i]) / std_table[i];
quality_entries_sum += (scale_factor > 1.0 ?
(0.5/scale_factor) : ((2.0 - scale_factor)/2.0));
}
}
return quality_entries_sum;
}
} // namespace
namespace pagespeed {
namespace image_compression {
using net_instaweb::MessageHandler;
JpegUtils::JpegUtils() {
}
int JpegUtils::GetImageQualityFromImage(const void* image_data,
size_t image_length,
MessageHandler* handler) {
JpegReader reader(handler);
jpeg_decompress_struct* jpeg_decompress = reader.decompress_struct();
// libjpeg's error handling mechanism requires that longjmp be used
// to get control after an error.
jmp_buf env;
if (setjmp(env)) {
// This code is run only when libjpeg hit an error, and called
// longjmp(env).
return -1;
}
// Need to install env so that it will be longjmp()ed to on error.
jpeg_decompress->client_data = static_cast<void *>(&env);
reader.PrepareForRead(image_data, image_length);
// Read jpeg data into the decompression struct.
jpeg_read_header(jpeg_decompress, TRUE);
// The standard tables are taken from JPEG spec section K.1.
static const unsigned int std_luminance_quant_tbl[DCTSIZE2] = {
16, 11, 10, 16, 24, 40, 51, 61,
12, 12, 14, 19, 26, 58, 60, 55,
14, 13, 16, 24, 40, 57, 69, 56,
14, 17, 22, 29, 51, 87, 80, 62,
18, 22, 37, 56, 68, 109, 103, 77,
24, 35, 55, 64, 81, 104, 113, 92,
49, 64, 78, 87, 103, 121, 120, 101,
72, 92, 95, 98, 112, 100, 103, 99
};
static const unsigned int std_chrominance_quant_tbl[DCTSIZE2] = {
17, 18, 24, 47, 99, 99, 99, 99,
18, 21, 26, 66, 99, 99, 99, 99,
24, 26, 56, 99, 99, 99, 99, 99,
47, 66, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99,
99, 99, 99, 99, 99, 99, 99, 99
};
double quality_entries_sum = 0;
double quality_entries_count = 0;
if (jpeg_decompress->quant_tbl_ptrs[0] != NULL) {
quality_entries_sum += ComputeQualityEntriesSum(
jpeg_decompress->quant_tbl_ptrs[0], std_luminance_quant_tbl);
quality_entries_count += DCTSIZE2;
}
if (jpeg_decompress->quant_tbl_ptrs[1] != NULL) {
quality_entries_sum += ComputeQualityEntriesSum(
jpeg_decompress->quant_tbl_ptrs[1], std_chrominance_quant_tbl);
quality_entries_count += DCTSIZE2;
}
if (quality_entries_count > 0) {
// This computed quality is in the form of a fraction, so multiplying with
// 100 and rounding off to nearest integer.
double quality = quality_entries_sum * 100 / quality_entries_count;
return static_cast<int>(quality + 0.5);
}
return -1;
}
} // namespace image_compression
} // namespace pagespeed