/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */


#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

