C++程序  |  310行  |  9.13 KB

/*
 * Copyright (C) 2014 The Android Open Source Project
 *
 * 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.
 */
#include "jpegutil.h"
#include <memory.h>
#include <array>
#include <vector>
#include <cstring>
#include <cstdio>

#include <setjmp.h>

extern "C" {
#include "jpeglib.h"
}

using namespace std;

template <typename T>
void safeDelete(T& t) {
  if (t != nullptr) {
    delete t;
    t = nullptr;
  }
}

template <typename T>
void safeDeleteArray(T& t) {
  if (t != nullptr) {
    delete[] t;
    t = nullptr;
  }
}

jpegutil::Plane::RowIterator::RowIterator(const Plane* plane) : plane_(plane) {
  // We must be able to supply up to 8 * 2 lines at a time to libjpeg.
  // 8 = vertical size of blocks transformed with DCT.
  // 2 = scaling factor for Y vs UV planes.
  bufRowCount_ = 16;

  // Rows must be padded to the next multiple of 16
  // TODO OPTIMIZE Cb and Cr components only need to be padded to a multiple of
  // 8.
  rowPadding_ = (16 - (plane_->planeWidth_ % 16)) % 16;
  bufRowStride_ = plane_->planeWidth_ + rowPadding_;

  // Round up to the nearest multiple of 64 for cache alignment
  bufRowStride_ = (bufRowStride_ + 63) & ~63;

  // Allocate an extra 64 bytes to allow for cache alignment
  size_t bufSize = bufRowStride_ * bufRowCount_ + 64;

  // TODO OPTIMIZE if the underlying data has a pixel-stride of 1, and an image
  // width which is a multiple of 16, we can avoid this allocation and simply
  // return pointers into the underlying data in operator()(int) instead of
  // copying the data.
  buffer_ = unique_ptr<unsigned char[]>(new unsigned char[bufSize]);

  // Find the start of the 64-byte aligned buffer we allocated.
  size_t bufStart = reinterpret_cast<size_t>(&buffer_[0]);
  size_t alignedBufStart = (bufStart + 63) & ~63;
  alignedBuffer_ = reinterpret_cast<unsigned char*>(alignedBufStart);

  bufCurRow_ = 0;
}

unsigned char* jpegutil::Plane::RowIterator::operator()(int y) {
  unsigned char* bufCurRowPtr = alignedBuffer_ + bufRowStride_ * bufCurRow_;

  unsigned char* srcPtr = &plane_->data_[y * plane_->rowStride_];
  unsigned char* dstPtr = bufCurRowPtr;

  // Use memcpy when possible.
  if (plane_->pixelStride_ == 1) {
    memcpy(dstPtr, srcPtr, plane_->planeWidth_);
  } else {
    int pixelStride = plane_->pixelStride_;

    for (int i = 0; i < plane_->planeWidth_; i++) {
      *dstPtr = *srcPtr;

      srcPtr += pixelStride;
      dstPtr++;
    }
  }

  // Add padding to the right side by replicating the rightmost column of
  // (actual) image values into the padding bytes.
  memset(&bufCurRowPtr[plane_->planeWidth_],
         bufCurRowPtr[plane_->planeWidth_ - 1], rowPadding_);

  bufCurRow_++;
  // Wrap within ring buffer.
  bufCurRow_ %= bufRowCount_;

  return bufCurRowPtr;
}

jpegutil::Plane::Plane(int imgWidth, int imgHeight, int planeWidth,
                       int planeHeight, unsigned char* data, int pixelStride,
                       int rowStride)
    : imgWidth_(imgWidth),
      imgHeight_(imgHeight),
      planeWidth_(planeWidth),
      planeHeight_(planeHeight),
      data_(data),
      rowStride_(rowStride),
      pixelStride_(pixelStride) {}

int jpegutil::compress(const Plane& yPlane, const Plane& cbPlane,
                       const Plane& crPlane, unsigned char* outBuf,
                       size_t outBufCapacity, std::function<void(size_t)> flush,
                       int quality) {
  int imgWidth = yPlane.imgWidth();
  int imgHeight = yPlane.imgHeight();

  // libjpeg requires the use of setjmp/longjmp to recover from errors.  Since
  // this doesn't play well with RAII, we must use pointers and manually call
  // delete. See POSIX documentation for longjmp() for details on why the
  // volatile keyword is necessary.
  volatile jpeg_compress_struct cinfov;

  jpeg_compress_struct& cinfo =
      *const_cast<struct jpeg_compress_struct*>(&cinfov);

  JSAMPROW* volatile yArr = nullptr;
  JSAMPROW* volatile cbArr = nullptr;
  JSAMPROW* volatile crArr = nullptr;

  Plane::RowIterator* volatile yRowGenerator = nullptr;
  Plane::RowIterator* volatile cbRowGenerator = nullptr;
  Plane::RowIterator* volatile crRowGenerator = nullptr;

  JSAMPARRAY imgArr[3];

  // Error handling

  struct my_error_mgr {
    struct jpeg_error_mgr pub;
    jmp_buf setjmp_buffer;
  } err;

  cinfo.err = jpeg_std_error(&err.pub);

  // Default error_exit will call exit(), so override
  // to return control via setjmp/longjmp.
  err.pub.error_exit = [](j_common_ptr cinfo) {
    my_error_mgr* myerr = reinterpret_cast<my_error_mgr*>(cinfo->err);

    (*cinfo->err->output_message)(cinfo);

    // Return control to the setjmp point (see call to setjmp()).
    longjmp(myerr->setjmp_buffer, 1);
  };

  cinfo.err = (struct jpeg_error_mgr*)&err;

  // Set the setjmp point to return to in case of error.
  if (setjmp(err.setjmp_buffer)) {
    // If libjpeg hits an error, control will jump to this point (see call to
    // longjmp()).
    jpeg_destroy_compress(&cinfo);

    safeDeleteArray(yArr);
    safeDeleteArray(cbArr);
    safeDeleteArray(crArr);
    safeDelete(yRowGenerator);
    safeDelete(cbRowGenerator);
    safeDelete(crRowGenerator);

    return -1;
  }

  // Create jpeg compression context
  jpeg_create_compress(&cinfo);

  // Stores data needed by our c-style callbacks into libjpeg
  struct ClientData {
    unsigned char* outBuf;
    size_t outBufCapacity;
    std::function<void(size_t)> flush;
    int totalOutputBytes;
  } clientData{outBuf, outBufCapacity, flush, 0};

  cinfo.client_data = &clientData;

  // Initialize destination manager
  jpeg_destination_mgr dest;

  dest.init_destination = [](j_compress_ptr cinfo) {
    ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);

    cinfo->dest->next_output_byte = cdata.outBuf;
    cinfo->dest->free_in_buffer = cdata.outBufCapacity;
  };

  dest.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
    ClientData& cdata = *reinterpret_cast<ClientData*>(cinfo->client_data);

    size_t numBytesInBuffer = cdata.outBufCapacity;
    cdata.flush(numBytesInBuffer);
    cdata.totalOutputBytes += numBytesInBuffer;

    // Reset the buffer
    cinfo->dest->next_output_byte = cdata.outBuf;
    cinfo->dest->free_in_buffer = cdata.outBufCapacity;

    return true;
  };

  dest.term_destination = [](j_compress_ptr cinfo) {
    // do nothing to terminate the output buffer
  };

  cinfo.dest = &dest;

  // Set jpeg parameters
  cinfo.image_width = imgWidth;
  cinfo.image_height = imgHeight;
  cinfo.input_components = 3;

  // Set defaults based on the above values
  jpeg_set_defaults(&cinfo);

  jpeg_set_quality(&cinfo, quality, true);

  cinfo.dct_method = JDCT_IFAST;

  cinfo.raw_data_in = true;

  jpeg_set_colorspace(&cinfo, JCS_YCbCr);

  cinfo.comp_info[0].h_samp_factor = 2;
  cinfo.comp_info[0].v_samp_factor = 2;
  cinfo.comp_info[1].h_samp_factor = 1;
  cinfo.comp_info[1].v_samp_factor = 1;
  cinfo.comp_info[2].h_samp_factor = 1;
  cinfo.comp_info[2].v_samp_factor = 1;

  jpeg_start_compress(&cinfo, true);

  yArr = new JSAMPROW[cinfo.comp_info[0].v_samp_factor * DCTSIZE];
  cbArr = new JSAMPROW[cinfo.comp_info[1].v_samp_factor * DCTSIZE];
  crArr = new JSAMPROW[cinfo.comp_info[2].v_samp_factor * DCTSIZE];

  imgArr[0] = const_cast<JSAMPARRAY>(yArr);
  imgArr[1] = const_cast<JSAMPARRAY>(cbArr);
  imgArr[2] = const_cast<JSAMPARRAY>(crArr);

  yRowGenerator = new Plane::RowIterator(&yPlane);
  cbRowGenerator = new Plane::RowIterator(&cbPlane);
  crRowGenerator = new Plane::RowIterator(&crPlane);

  Plane::RowIterator& yRG = *const_cast<Plane::RowIterator*>(yRowGenerator);
  Plane::RowIterator& cbRG = *const_cast<Plane::RowIterator*>(cbRowGenerator);
  Plane::RowIterator& crRG = *const_cast<Plane::RowIterator*>(crRowGenerator);

  for (int y = 0; y < imgHeight; y += DCTSIZE * 2) {
    for (int row = 0; row < DCTSIZE * 2; row++) {
      yArr[row] = yRG(y + row);
    }

    for (int row = 0; row < DCTSIZE; row++) {
      // The y-index within the subsampled chroma planes to send to libjpeg.
      const int chY = y / 2 + row;

      if (chY < imgHeight / 2) {
        cbArr[row] = cbRG(chY);
        crArr[row] = crRG(chY);
      } else {
        // When we have run out of rows in the chroma planes to compress, send
        // the last row as padding.
        cbArr[row] = cbRG(imgHeight / 2 - 1);
        crArr[row] = crRG(imgHeight / 2 - 1);
      }
    }

    jpeg_write_raw_data(&cinfo, imgArr, DCTSIZE * 2);
  }

  jpeg_finish_compress(&cinfo);

  int numBytesInBuffer = cinfo.dest->next_output_byte - outBuf;

  flush(numBytesInBuffer);

  clientData.totalOutputBytes += numBytesInBuffer;

  safeDeleteArray(yArr);
  safeDeleteArray(cbArr);
  safeDeleteArray(crArr);
  safeDelete(yRowGenerator);
  safeDelete(cbRowGenerator);
  safeDelete(crRowGenerator);

  return clientData.totalOutputBytes;
}