Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | #pragma once |
| 17 | |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 18 | #include "math.h" |
| 19 | #include <array> |
| 20 | #include <cassert> |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 21 | #include <functional> |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 22 | #include <memory> |
| 23 | #include <stdlib.h> |
| 24 | #include <vector> |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 25 | |
| 26 | /* |
| 27 | * Provides a wrapper around libjpeg. |
| 28 | */ |
| 29 | namespace jpegutil { |
| 30 | |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 31 | class Transform; |
| 32 | class Plane; |
| 33 | |
| 34 | inline int sgn(int val) { return (0 < val) - (val < 0); } |
| 35 | |
| 36 | inline int min(int a, int b) { return a < b ? a : b; } |
| 37 | |
| 38 | inline int max(int a, int b) { return a > b ? a : b; } |
| 39 | |
| 40 | /** |
| 41 | * Represents a combined cropping and rotation transformation. |
| 42 | * |
| 43 | * The transformation maps the coordinates (orig_x, orig_y) and (one_x, one_y) |
| 44 | * in the input image to the origin and (output_width, output_height) |
| 45 | * respectively. |
| 46 | */ |
| 47 | class Transform { |
| 48 | public: |
| 49 | Transform(int orig_x, int orig_y, int one_x, int one_y); |
| 50 | |
| 51 | static Transform ForCropFollowedByRotation(int cropLeft, int cropTop, |
| 52 | int cropRight, int cropBottom, |
| 53 | int rot90); |
| 54 | |
| 55 | inline int output_width() const { return output_width_; } |
| 56 | |
| 57 | inline int output_height() const { return output_height_; } |
| 58 | |
| 59 | bool operator==(const Transform& other) const; |
| 60 | |
| 61 | /** |
| 62 | * Transforms the input coordinates. Coordinates outside the cropped region |
| 63 | * are clamped to valid values. |
| 64 | */ |
| 65 | void Map(int x, int y, int* x_out, int* y_out) const; |
| 66 | |
| 67 | private: |
| 68 | int output_width_; |
| 69 | int output_height_; |
| 70 | |
| 71 | // The coordinates of the point to map the origin to. |
| 72 | const int orig_x_, orig_y_; |
| 73 | // The coordinates of the point to map the point (output_width(), |
| 74 | // output_height()) to. |
| 75 | const int one_x_, one_y_; |
| 76 | |
| 77 | // A matrix for the rotational component. |
| 78 | int mat00_, mat01_; |
| 79 | int mat10_, mat11_; |
| 80 | }; |
| 81 | |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 82 | /** |
| 83 | * Represents a model for accessing pixel data for a single plane of an image. |
| 84 | * Note that the actual data is not owned by this class, and the underlying |
| 85 | * data does not need to be stored in separate planes. |
| 86 | */ |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 87 | struct Plane { |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 88 | // The dimensions of this plane of the image |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 89 | int width; |
| 90 | int height; |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 91 | |
| 92 | // A pointer to raw pixel data |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 93 | const unsigned char* data; |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 94 | // The difference in address between consecutive pixels in the same row |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 95 | int pixel_stride; |
| 96 | // The difference in address between the start of consecutive rows |
| 97 | int row_stride; |
| 98 | }; |
| 99 | |
| 100 | /** |
| 101 | * Provides an interface for simultaneously reading a certain number of rows of |
| 102 | * an image plane as contiguous arrays, suitable for use with libjpeg. |
| 103 | */ |
| 104 | template <unsigned int ROWS> |
| 105 | class RowIterator { |
| 106 | public: |
| 107 | /** |
| 108 | * Creates a new RowIterator which will crop and rotate with the given |
| 109 | * transform. |
| 110 | * |
| 111 | * @param plane the plane to iterate over |
| 112 | * @param transform the transformation to map output values into the |
| 113 | * coordinate space of the plane |
| 114 | * @param row_length the length of the rows returned via LoadAt(). If this is |
| 115 | * longer than the width of the output (after applying the transform), then |
| 116 | * the right-most value is repeated. |
| 117 | */ |
| 118 | inline RowIterator(Plane plane, Transform transform, int row_length); |
| 119 | |
| 120 | /** |
| 121 | * Returns an array of pointers into consecutive rows of contiguous image |
| 122 | * data starting at y. That is, samples within each row are contiguous. |
| 123 | * However, the individual arrays pointed-to may be separate. |
| 124 | * When the end of the image is reached, the last row of the image is |
| 125 | * repeated. |
| 126 | * The returned pointers are valid until the next call to LoadAt(). |
| 127 | */ |
| 128 | inline const std::array<unsigned char*, ROWS> LoadAt(int y_base); |
| 129 | |
| 130 | private: |
| 131 | Plane plane_; |
| 132 | Transform transform_; |
| 133 | // The length of a row, with padding to the next multiple of 64. |
| 134 | int padded_row_length_; |
| 135 | std::vector<unsigned char> buf_; |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 136 | }; |
| 137 | |
| 138 | /** |
| 139 | * Compresses an image from YUV 420p to JPEG. Output is buffered in outBuf until |
| 140 | * capacity is reached, at which point flush(size_t) is called to write |
| 141 | * out the specified number of bytes from outBuf. Returns the number of bytes |
| 142 | * written, or -1 in case of an error. |
| 143 | */ |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 144 | int Compress(int img_width, int img_height, RowIterator<16>& y_row_generator, |
| 145 | RowIterator<8>& cb_row_generator, RowIterator<8>& cr_row_generator, |
| 146 | unsigned char* out_buf, size_t out_buf_capacity, |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 147 | std::function<void(size_t)> flush, int quality); |
Puneet Lall | 7e744b1 | 2015-02-09 17:45:44 -0800 | [diff] [blame] | 148 | |
| 149 | /** |
| 150 | * Compresses an image from YUV 420p to JPEG. Output is written into outBuf. |
| 151 | * Returns the number of bytes written, or -1 in case of an error. |
| 152 | */ |
| 153 | int Compress( |
| 154 | /** Input image dimensions */ |
| 155 | int width, int height, |
| 156 | /** Y Plane */ |
| 157 | unsigned char* yBuf, int yPStride, int yRStride, |
| 158 | /** Cb Plane */ |
| 159 | unsigned char* cbBuf, int cbPStride, int cbRStride, |
| 160 | /** Cr Plane */ |
| 161 | unsigned char* crBuf, int crPStride, int crRStride, |
| 162 | /** Output */ |
| 163 | unsigned char* outBuf, size_t outBufCapacity, |
| 164 | /** Jpeg compression parameters */ |
| 165 | int quality, |
| 166 | /** Crop */ |
| 167 | int cropLeft, int cropTop, int cropRight, int cropBottom, |
| 168 | /** Rotation */ |
| 169 | int rot90); |
| 170 | } |
| 171 | |
| 172 | template <unsigned int ROWS> |
| 173 | jpegutil::RowIterator<ROWS>::RowIterator(Plane plane, Transform transform, |
| 174 | int row_length) |
| 175 | : plane_(plane), transform_(transform) { |
| 176 | padded_row_length_ = row_length; |
| 177 | buf_ = std::vector<unsigned char>(row_length * ROWS); |
| 178 | } |
| 179 | |
| 180 | template <unsigned int ROWS> |
| 181 | const std::array<unsigned char*, ROWS> jpegutil::RowIterator<ROWS>::LoadAt( |
| 182 | int y_base) { |
| 183 | std::array<unsigned char*, ROWS> buf_ptrs; |
| 184 | for (int i = 0; i < ROWS; i++) { |
| 185 | buf_ptrs[i] = &buf_[padded_row_length_ * i]; |
| 186 | } |
| 187 | |
| 188 | if (plane_.width == 0 || plane_.height == 0) { |
| 189 | return buf_ptrs; |
| 190 | } |
| 191 | |
| 192 | for (int i = 0; i < ROWS; i++) { |
| 193 | int y = i + y_base; |
| 194 | y = min(y, transform_.output_height() - 1); |
| 195 | |
| 196 | int output_width = padded_row_length_; |
| 197 | output_width = min(output_width, transform_.output_width()); |
| 198 | output_width = min(output_width, plane_.width); |
| 199 | |
| 200 | // Each row in the output image will be copied into buf_ by gathering pixels |
| 201 | // along an axis-aligned line in the plane. |
| 202 | // The line is defined by (startX, startY) -> (endX, endY), computed via the |
| 203 | // current Transform. |
| 204 | int startX; |
| 205 | int startY; |
| 206 | transform_.Map(0, y, &startX, &startY); |
| 207 | |
| 208 | int endX; |
| 209 | int endY; |
| 210 | transform_.Map(output_width - 1, y, &endX, &endY); |
| 211 | |
| 212 | // Clamp (startX, startY) and (endX, endY) to the valid bounds of the plane. |
| 213 | startX = min(startX, plane_.width - 1); |
| 214 | startY = min(startY, plane_.height - 1); |
| 215 | endX = min(endX, plane_.width - 1); |
| 216 | endY = min(endY, plane_.height - 1); |
| 217 | startX = max(startX, 0); |
| 218 | startY = max(startY, 0); |
| 219 | endX = max(endX, 0); |
| 220 | endY = max(endY, 0); |
| 221 | |
| 222 | // To reduce work inside the copy-loop, precompute the start, end, and |
| 223 | // stride relating the values to be gathered from plane_ into buf |
| 224 | // for this particular scan-line. |
| 225 | int dx = sgn(endX - startX); |
| 226 | int dy = sgn(endY - startY); |
| 227 | assert(dx == 0 || dy == 0); |
| 228 | // The index into plane_.data of (startX, startY) |
| 229 | int plane_start = startX * plane_.pixel_stride + startY * plane_.row_stride; |
| 230 | // The index into plane_.data of (endX, endY) |
| 231 | int plane_end = endX * plane_.pixel_stride + endY * plane_.row_stride; |
| 232 | // The stride, in terms of indices in plane_data, required to enumerate the |
| 233 | // samples between the start and end points. |
| 234 | int stride = dx * plane_.pixel_stride + dy * plane_.row_stride; |
| 235 | // In the degenerate-case of a 1x1 plane, startX and endX are equal, so |
| 236 | // stride would be 0, resulting in an infinite-loop. To avoid this case, |
| 237 | // use a stride of at-least 1. |
| 238 | if (stride == 0) { |
| 239 | stride = 1; |
| 240 | } |
| 241 | |
| 242 | int outX = 0; |
| 243 | for (int idx = plane_start; idx >= min(plane_start, plane_end) && |
| 244 | idx <= max(plane_start, plane_end); |
| 245 | idx += stride) { |
| 246 | buf_ptrs[i][outX] = plane_.data[idx]; |
| 247 | outX++; |
| 248 | } |
| 249 | |
| 250 | // Fill the remaining right-edge of the buffer by extending the last |
| 251 | // value. |
| 252 | unsigned char right_padding_value = buf_ptrs[i][outX - 1]; |
| 253 | // TODO OPTIMIZE Use memset instead. |
| 254 | for (; outX < padded_row_length_; outX++) { |
| 255 | buf_ptrs[i][outX] = right_padding_value; |
| 256 | } |
| 257 | } |
| 258 | |
| 259 | return buf_ptrs; |
Puneet Lall | 967b782 | 2014-08-07 17:05:38 -0700 | [diff] [blame] | 260 | } |