*/
#pragma once
-#include <memory>
+#include "math.h"
+#include <array>
+#include <cassert>
#include <functional>
+#include <memory>
+#include <stdlib.h>
+#include <vector>
/*
* 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 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.
+ * 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 Plane {
+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;
+
/**
- * Provides access to several rows of planar data at a time, copied into an
- * intermediate buffer with pixel data packed in contiguous rows which can be
- * passed to libjpeg.
+ * Transforms the input coordinates. Coordinates outside the cropped region
+ * are clamped to valid values.
*/
- class RowIterator {
- public:
- RowIterator(const Plane* plane);
-
- /**
- * Retrieves the y-th row, copying it into a buffer with as much padding
- * as is necessary for use with libjpeg.
- */
- unsigned char* operator()(int y);
-
- private:
- const Plane* plane_;
-
- // Stores a ring-buffer of cache-aligned buffers for storing contiguous
- // pixel data for rows of the image to be sent to libjpeg.
- std::unique_ptr<unsigned char[]> buffer_;
- // The cache-aligned start index of buffer_
- unsigned char* alignedBuffer_;
- // The total number of rows in the ring-buffer
- int bufRowCount_;
- // The current ring-buffer row being used
- int bufCurRow_;
- // The number of bytes between consecutive rows in the buffer
- int bufRowStride_;
-
- // The number of bytes of padding-pixels which must be appended to each row
- // to reach the multiple of 16-bytes required by libjpeg.
- int rowPadding_;
- };
-
- Plane(int imgWidth, int imgHeight, int planeWidth, int planeHeight,
- unsigned char* data, int pixelStride, int rowStride);
-
- int imgWidth() const { return imgWidth_; }
- int imgHeight() const { return imgHeight_; }
+ void Map(int x, int y, int* x_out, int* y_out) const;
private:
- // The dimensions of the entire image
- int imgWidth_;
- int imgHeight_;
+ 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 planeWidth_;
- int planeHeight_;
+ int width;
+ int height;
// A pointer to raw pixel data
- unsigned char* data_;
- // The difference in address between the start of consecutive rows
- int rowStride_;
+ const unsigned char* data;
// The difference in address between consecutive pixels in the same row
- int pixelStride_;
+ 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 <unsigned int ROWS>
+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<unsigned char*, ROWS> 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<unsigned char> buf_;
};
/**
* out the specified number of bytes from outBuf. Returns the number of bytes
* written, or -1 in case of an error.
*/
-int compress(const Plane& yPlane, const Plane& cbPlane, const Plane& crPlane,
- unsigned char* outBuf, size_t outBufCapacity,
+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<void(size_t)> 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 <unsigned int ROWS>
+jpegutil::RowIterator<ROWS>::RowIterator(Plane plane, Transform transform,
+ int row_length)
+ : plane_(plane), transform_(transform) {
+ padded_row_length_ = row_length;
+ buf_ = std::vector<unsigned char>(row_length * ROWS);
+}
+
+template <unsigned int ROWS>
+const std::array<unsigned char*, ROWS> jpegutil::RowIterator<ROWS>::LoadAt(
+ int y_base) {
+ std::array<unsigned char*, ROWS> 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;
}