/* * 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. */ #pragma once #include "math.h" #include #include #include #include #include #include /* * Provides a wrapper around libjpeg. */ namespace jpegutil { class Transform; class Plane; inline int sgn(int val) { return (0 < val) - (val < 0); } inline int min(int a, int b) { return a < b ? a : b; } inline int max(int a, int b) { return a > b ? a : b; } /** * Represents a combined cropping and rotation transformation. * * The transformation maps the coordinates (orig_x, orig_y) and (one_x, one_y) * in the input image to the origin and (output_width, output_height) * respectively. */ class Transform { public: Transform(int orig_x, int orig_y, int one_x, int one_y); static Transform ForCropFollowedByRotation(int cropLeft, int cropTop, int cropRight, int cropBottom, int rot90); inline int output_width() const { return output_width_; } inline int output_height() const { return output_height_; } bool operator==(const Transform& other) const; /** * Transforms the input coordinates. Coordinates outside the cropped region * are clamped to valid values. */ void Map(int x, int y, int* x_out, int* y_out) const; private: int output_width_; int output_height_; // The coordinates of the point to map the origin to. const int orig_x_, orig_y_; // The coordinates of the point to map the point (output_width(), // output_height()) to. const int one_x_, one_y_; // A matrix for the rotational component. int mat00_, mat01_; int mat10_, mat11_; }; /** * Represents a model for accessing pixel data for a single plane of an image. * Note that the actual data is not owned by this class, and the underlying * data does not need to be stored in separate planes. */ struct Plane { // The dimensions of this plane of the image int width; int height; // A pointer to raw pixel data const unsigned char* data; // The difference in address between consecutive pixels in the same row int pixel_stride; // The difference in address between the start of consecutive rows int row_stride; }; /** * Provides an interface for simultaneously reading a certain number of rows of * an image plane as contiguous arrays, suitable for use with libjpeg. */ template class RowIterator { public: /** * Creates a new RowIterator which will crop and rotate with the given * transform. * * @param plane the plane to iterate over * @param transform the transformation to map output values into the * coordinate space of the plane * @param row_length the length of the rows returned via LoadAt(). If this is * longer than the width of the output (after applying the transform), then * the right-most value is repeated. */ inline RowIterator(Plane plane, Transform transform, int row_length); /** * Returns an array of pointers into consecutive rows of contiguous image * data starting at y. That is, samples within each row are contiguous. * However, the individual arrays pointed-to may be separate. * When the end of the image is reached, the last row of the image is * repeated. * The returned pointers are valid until the next call to LoadAt(). */ inline const std::array LoadAt(int y_base); private: Plane plane_; Transform transform_; // The length of a row, with padding to the next multiple of 64. int padded_row_length_; std::vector buf_; }; /** * Compresses an image from YUV 420p to JPEG. Output is buffered in outBuf until * capacity is reached, at which point flush(size_t) is called to write * out the specified number of bytes from outBuf. Returns the number of bytes * written, or -1 in case of an error. */ int Compress(int img_width, int img_height, RowIterator<16>& y_row_generator, RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator, unsigned char* out_buf, size_t out_buf_capacity, std::function flush, int quality); /** * Compresses an image from YUV 420p to JPEG. Output is written into outBuf. * Returns the number of bytes written, or -1 in case of an error. */ int Compress( /** Input image dimensions */ int width, int height, /** Y Plane */ unsigned char* yBuf, int yPStride, int yRStride, /** Cb Plane */ unsigned char* cbBuf, int cbPStride, int cbRStride, /** Cr Plane */ unsigned char* crBuf, int crPStride, int crRStride, /** Output */ unsigned char* outBuf, size_t outBufCapacity, /** Jpeg compression parameters */ int quality, /** Crop */ int cropLeft, int cropTop, int cropRight, int cropBottom, /** Rotation */ int rot90); } template jpegutil::RowIterator::RowIterator(Plane plane, Transform transform, int row_length) : plane_(plane), transform_(transform) { padded_row_length_ = row_length; buf_ = std::vector(row_length * ROWS); } template const std::array jpegutil::RowIterator::LoadAt( int y_base) { std::array buf_ptrs; for (int i = 0; i < ROWS; i++) { buf_ptrs[i] = &buf_[padded_row_length_ * i]; } if (plane_.width == 0 || plane_.height == 0) { return buf_ptrs; } for (int i = 0; i < ROWS; i++) { int y = i + y_base; y = min(y, transform_.output_height() - 1); int output_width = padded_row_length_; output_width = min(output_width, transform_.output_width()); output_width = min(output_width, plane_.width); // Each row in the output image will be copied into buf_ by gathering pixels // along an axis-aligned line in the plane. // The line is defined by (startX, startY) -> (endX, endY), computed via the // current Transform. int startX; int startY; transform_.Map(0, y, &startX, &startY); int endX; int endY; transform_.Map(output_width - 1, y, &endX, &endY); // Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane. startX = min(startX, plane_.width - 1); startY = min(startY, plane_.height - 1); endX = min(endX, plane_.width - 1); endY = min(endY, plane_.height - 1); startX = max(startX, 0); startY = max(startY, 0); endX = max(endX, 0); endY = max(endY, 0); // To reduce work inside the copy-loop, precompute the start, end, and // stride relating the values to be gathered from plane_ into buf // for this particular scan-line. int dx = sgn(endX - startX); int dy = sgn(endY - startY); assert(dx == 0 || dy == 0); // The index into plane_.data of (startX, startY) int plane_start = startX * plane_.pixel_stride + startY * plane_.row_stride; // The index into plane_.data of (endX, endY) int plane_end = endX * plane_.pixel_stride + endY * plane_.row_stride; // The stride, in terms of indices in plane_data, required to enumerate the // samples between the start and end points. int stride = dx * plane_.pixel_stride + dy * plane_.row_stride; // In the degenerate-case of a 1x1 plane, startX and endX are equal, so // stride would be 0, resulting in an infinite-loop. To avoid this case, // use a stride of at-least 1. if (stride == 0) { stride = 1; } int outX = 0; for (int idx = plane_start; idx >= min(plane_start, plane_end) && idx <= max(plane_start, plane_end); idx += stride) { buf_ptrs[i][outX] = plane_.data[idx]; outX++; } // Fill the remaining right-edge of the buffer by extending the last // value. unsigned char right_padding_value = buf_ptrs[i][outX - 1]; // TODO OPTIMIZE Use memset instead. for (; outX < padded_row_length_; outX++) { buf_ptrs[i][outX] = right_padding_value; } } return buf_ptrs; }