| /* |
| * Copyright 2013 Google Inc. |
| * |
| * 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. |
| */ |
| |
| // Author: Huibao Lin |
| |
| #include "pagespeed/kernel/image/image_resizer.h" |
| |
| #include <math.h> |
| |
| #include "pagespeed/kernel/base/message_handler.h" |
| #include "pagespeed/kernel/base/string.h" |
| #include "pagespeed/kernel/image/scanline_utils.h" |
| |
| namespace pagespeed { |
| |
| namespace { |
| |
| using net_instaweb::MessageHandler; |
| |
| // Table for storing the resizing coefficients. |
| // |
| // Both the horizontal resizer and vertical resizer have their own resizing |
| // tables, but they are used in a similar way. The following example is for |
| // the horizontal resizer. The same example can be used for the vertical |
| // resizer, if "column" is replaced by "row". |
| // |
| // Each entry specifies an output column. The output columns are computed |
| // by weighting the column at first_index with first_weight, the column at |
| // last_index with last_weight, and the columns in between with 1. |
| // The output column is then normalized by the total weights. |
| // |
| // Range of first_weight and last_weight are (0, 1] and [0, 1], respectively. |
| // Note that first_weight cannot be 0 while last_weight can. |
| // |
| // The input image is uniquely divided into the entries as follows: |
| // if entry[i].last_weight is not 0 nor 1 then |
| // entry[i+1].first_index = entry[i].last_index |
| // entry[i+1].first_weight = 1 - entry[i].last_weight |
| // otherwise |
| // entry[i+1].first_index = entry[i].last_index + 1 |
| // entry[i+1].first_weight = 1 (note that resize ratio >= 1) |
| // |
| // There are some differences between the tables. For the horizontal resizer, |
| // the indices refer to the left border of the image and its unit is color |
| // component. For the vertical resizer, the indices refer to the top border |
| // of the buffer (which is smaller than the image) and its unit is row. |
| struct ResizeTableEntry { |
| int first_index; |
| int last_index; |
| float first_weight; |
| float last_weight; |
| }; |
| |
| // Round to the nearest integer. |
| inline double Round(double val) { |
| return lrintf(val); |
| } |
| |
| // Check if the value is very close to the specific integer. |
| // This function will be used to assist IsApproximatelyZero() |
| // and IsApproximatelyInteger(), which will be used to optimize |
| // interpolation coefficients for the "area" method. |
| // |
| // The "area" method basically divides the input image into grids. |
| // Each grid corresponds to an output pixel and the average value |
| // of the input pixels within the grid determines the value for the |
| // output pixel. When the grid does not align with the border of |
| // input pixels, some input pixels will be involved to compute |
| // multiple (2) output pixels. When the difference between the grid |
| // and the border of input pixel is small, we can ignore the difference. |
| // Therefore we can save computation because one input pixel will only |
| // be used to compute one output pixel. The numerical results shall |
| // not have a noticeable difference because we quantize the output to integers |
| // of 0...255. |
| inline bool IsCloseToDouble(double val, double int_val) { |
| // Threshold for determining whether a double-precision value is close enough |
| // to an integer. A larger threshold increases the chance for the value to |
| // be approximated by an integer and consequently reduces computation, but it |
| // may also reduce accuracy. The value of 1.0E-10 was empirically chosen. |
| const double kThreshold = 1.0E-10; |
| double difference = fabs(val - int_val); |
| bool is_integer = difference <= kThreshold; |
| return is_integer; |
| } |
| |
| inline bool IsApproximatelyInteger(double val) { |
| return IsCloseToDouble(val, Round(val)); |
| } |
| |
| // Compute the interpolation coefficents for the "area" method. |
| // Reference for the "area" resizing method: |
| // http://opencv.willowgarage.com/documentation/cpp/ |
| // geometric_image_transformations.html |
| // |
| // The inputs, in_size and out_size, are 1-D sizes specified in pixels. |
| ResizeTableEntry* CreateTableForAreaMethod(int in_size, |
| int out_size, |
| double ratio, |
| MessageHandler* handler) { |
| if (in_size <= 0 || out_size <= 0 || ratio <= 0) { |
| PS_LOG_DFATAL(handler, "The inputs must be positive values."); |
| return NULL; |
| } |
| ResizeTableEntry* table = new ResizeTableEntry[out_size]; |
| if (table == NULL) { |
| PS_LOG_DFATAL(handler, "Failed to allocate memory."); |
| return NULL; |
| } |
| |
| double end_pos = 0; |
| for (int i = 0; i < out_size; ++i) { |
| double start_pos = end_pos; |
| double start_pos_floor = floor(start_pos); |
| table[i].first_index = static_cast<int>(start_pos_floor); |
| table[i].first_weight = |
| static_cast<float>(1.0 + start_pos_floor - start_pos); |
| |
| end_pos = (i + 1) * ratio; |
| if (IsApproximatelyInteger(end_pos)) { |
| end_pos = Round(end_pos); |
| table[i].last_index = static_cast<int>(end_pos) - 1; |
| } else { |
| table[i].last_index = static_cast<int>(end_pos); |
| } |
| |
| // If the current dimension is set to have the same resizing ratio as the |
| // other dimension, 'last_index' may be greater than in_size. This is |
| // because out_size was computed as Round(in_size / ratio), so |
| // last_index == out_size * ratio == Round(in_size / ratio) * ratio |
| // might be greater than in_size by (0.5 * ratio), where ratio >= 1. |
| if (table[i].last_index >= in_size) { |
| table[i].last_index = in_size - 1; |
| } |
| |
| if (table[i].first_index < table[i].last_index) { |
| table[i].last_weight = static_cast<float>(end_pos - table[i].last_index); |
| } else { |
| table[i].last_weight = static_cast<float>(ratio - table[i].first_weight); |
| } |
| |
| if (i > 0 && table[i - 1].first_index >= table[i].first_index) { |
| LOG(DFATAL) << "Significant rounding error has been accumulated."; |
| return NULL; |
| } |
| } |
| |
| return table; |
| } |
| |
| // Compute the output size and resizing ratios. Either output_width or |
| // output_height, or both, must be positive values. |
| void ComputeResizedSizeRatio(int input_width, |
| int input_height, |
| int output_width, |
| int output_height, |
| int* width, |
| int* height, |
| double* ratio_x, |
| double* ratio_y, |
| MessageHandler* handler) { |
| double original_width = input_width; |
| double original_height = input_height; |
| double resized_width = output_width; |
| double resized_height = output_height; |
| |
| if (resized_width > 0.0 && resized_height > 0.0) { |
| *ratio_x = original_width / resized_width; |
| *ratio_y = original_height / resized_height; |
| } else if (resized_width > 0.0) { |
| *ratio_x = original_width / resized_width; |
| *ratio_y = *ratio_x; |
| resized_height = Round(original_height / *ratio_y); |
| } else if (resized_height > 0.0) { |
| *ratio_x = original_height / resized_height; |
| *ratio_y = *ratio_x; |
| resized_width = Round(original_width / *ratio_x); |
| } else { |
| PS_LOG_DFATAL(handler, |
| "Either output_width or output_height, or both must be " \ |
| "positive."); |
| *ratio_x = 0; |
| *ratio_y = 0; |
| } |
| |
| *width = static_cast<int>(resized_width); |
| *height = static_cast<int>(resized_height); |
| } |
| |
| // ResizeRowAreaGray, ResizeRowAreaRGB, and ResizeRowAreaRGBA resize a |
| // scanline of pixels of different formats. They process every pixel in the |
| // image and do the most expensive computation for resizing an image. To |
| // minimize conditional jumps and take advantage of cache prediction, so as to |
| // improve speed, these methods are implemented as independent function without |
| // reusing code. |
| void ResizeRowAreaGray(const ResizeTableEntry* table, int pixels_per_row, |
| const uint8_t* in_data, float* out_data) { |
| for (int out_idx = 0; out_idx < pixels_per_row; ++out_idx) { |
| const ResizeTableEntry& table_entry = table[out_idx]; |
| |
| // Accumulate the first input pixel. |
| float acc1 = in_data[table_entry.first_index] * table_entry.first_weight; |
| |
| // Accumulate the intermediate input pixels which contribute 100% to the |
| // current output pixel. |
| for (int in_idx = table_entry.first_index + 1; |
| in_idx < table_entry.last_index; |
| ++in_idx) { |
| acc1 += in_data[in_idx]; |
| } |
| |
| // Accumulate the last input pixel. |
| acc1 += in_data[table_entry.last_index] * table_entry.last_weight; |
| out_data[out_idx] = acc1; |
| } |
| } |
| |
| void ResizeRowAreaRGB(const ResizeTableEntry* table, int pixels_per_row, |
| const uint8_t* in_data, float* out_data) { |
| int out_idx = 0; |
| for (int x = 0; x < pixels_per_row; ++x) { |
| const ResizeTableEntry& table_entry = table[x]; |
| |
| // Accumulate the first input pixel. |
| int in_idx = table_entry.first_index; |
| float weight = table_entry.first_weight; |
| float acc1 = in_data[in_idx] * weight; |
| float acc2 = in_data[in_idx + 1] * weight; |
| float acc3 = in_data[in_idx + 2] * weight; |
| |
| // Accumulate the intermediate input pixels which contribute 100% to the |
| // current output pixel. |
| for (in_idx += 3; in_idx < table_entry.last_index; in_idx += 3) { |
| acc1 += in_data[in_idx]; |
| acc2 += in_data[in_idx + 1]; |
| acc3 += in_data[in_idx + 2]; |
| } |
| |
| // Accumulate the last input pixel. |
| weight = table_entry.last_weight; |
| // In table, last_index may equal first_index, so we need to reset |
| // in_idx to last_index. |
| in_idx = table_entry.last_index; |
| acc1 += in_data[in_idx] * weight; |
| out_data[out_idx] = acc1; |
| acc2 += in_data[in_idx + 1] * weight; |
| out_data[out_idx + 1] = acc2; |
| acc3 += in_data[in_idx + 2] * weight; |
| out_data[out_idx + 2] = acc3; |
| |
| out_idx += 3; |
| } |
| } |
| |
| void ResizeRowAreaRGBA(const ResizeTableEntry* table, int pixels_per_row, |
| const uint8_t* in_data, float* out_data) { |
| int out_idx = 0; |
| for (int x = 0; x < pixels_per_row; ++x) { |
| const ResizeTableEntry& table_entry = table[x]; |
| |
| // Accumulate the first input pixel. |
| int in_idx = table_entry.first_index; |
| float weight = table_entry.first_weight; |
| float acc1 = in_data[in_idx] * weight; |
| float acc2 = in_data[in_idx + 1] * weight; |
| float acc3 = in_data[in_idx + 2] * weight; |
| float acc4 = in_data[in_idx + 3] * weight; |
| |
| // Accumulate the intermediate input pixels which contribute 100% to the |
| // current output pixel. |
| for (in_idx += 4; in_idx < table_entry.last_index; in_idx += 4) { |
| acc1 += in_data[in_idx]; |
| acc2 += in_data[in_idx + 1]; |
| acc3 += in_data[in_idx + 2]; |
| acc4 += in_data[in_idx + 3]; |
| } |
| |
| // Accumulate the last input pixel. |
| weight = table_entry.last_weight; |
| // In table, last_index may equal first_index, so we need to reset |
| // in_idx to last_index. |
| in_idx = table_entry.last_index; |
| acc1 += in_data[in_idx] * weight; |
| out_data[out_idx] = acc1; |
| acc2 += in_data[in_idx + 1] * weight; |
| out_data[out_idx + 1] = acc2; |
| acc3 += in_data[in_idx + 2] * weight; |
| out_data[out_idx + 2] = acc3; |
| acc4 += in_data[in_idx + 3] * weight; |
| out_data[out_idx + 3] = acc4; |
| |
| out_idx += 4; |
| } |
| } |
| |
| } // namespace |
| |
| namespace image_compression { |
| // Resizing an image includes operations in orthogonal directions: resizing |
| // horizontally and vertically. These operations are independent. So |
| // ScanlineResizer delegates its work to two classes, ResizeRow and ResizeCol, |
| // which resize horizontally and vertically, respectively. |
| // |
| // To compute an output scanline, multiple input scanlines may be required. |
| // The following code shows an example. |
| // |
| // resizer_y_->InitializeResize(); |
| // while (resizer_y_->NeedMoreScanlines()) { |
| // ... |
| // const void* buffer = resizer_x_->Resize(input_scanline); |
| // *out_scanline = resizer_y_->Resize(buffer); |
| // } |
| |
| // Base class for the horizontal resizer. If the object is not initialized, |
| // or if the object is initialized with 'output_buffer' set to 'NULL', |
| // Resize() will simply return 'in_data'. This class does not own |
| // 'output_buffer' nor the buffer which it returns. |
| class ResizeRow { |
| public: |
| virtual ~ResizeRow() {} |
| |
| virtual bool Initialize(int in_size, int out_size, double ratio, |
| float* output_buffer, MessageHandler* handler) = 0; |
| |
| // In order to process pixels stored in any data type, the base class, |
| // ScanlineReaderInterface, uses "void*" for the pixel buffer. Consequently, |
| // ScanlineResizer, which is derived from ScanlineReaderInterface, uses |
| // "void*". |
| // |
| // The implementation in this file, i.e., ResizeRow, ResizeCol, and the |
| // classes derived from them, only support pixels stored in "uint8_t" type. |
| // |
| // Therefore all internal methods and properties use "uint8_t*", while those |
| // that connect directly to the interface of ScanlineResizer use "void*". |
| virtual const void* Resize(const uint8_t* in_data) = 0; |
| }; |
| |
| // Base class for the vertical resizer. If the object is initialized with |
| // 'output_buffer' set to 'NULL', and resizing ratio set to '1', |
| // Resize() will simply return 'in_data_ptr'. This class does not own |
| // 'output_buffer' nor the buffer which it returns. |
| class ResizeCol { |
| public: |
| virtual ~ResizeCol() {} |
| virtual bool Initialize(int in_size, |
| int out_size, |
| double ratio_x, |
| double ratio_y, |
| int elements_per_output_row, |
| uint8_t* output_buffer, |
| MessageHandler* handler) = 0; |
| virtual const uint8_t* Resize(const void* in_data_ptr) = 0; |
| virtual void InitializeResize() {} |
| virtual bool NeedMoreScanlines() const = 0; |
| virtual int out_row() const = 0; |
| }; |
| |
| // Base class for the horizontal resizer using the "area" method. |
| class ResizeRowArea : public ResizeRow { |
| public: |
| explicit ResizeRowArea(int num_channels) |
| : num_channels_(num_channels), output_buffer_(NULL) {} |
| |
| virtual bool Initialize(int in_size, int out_size, double ratio, |
| float* output_buffer, MessageHandler* handler); |
| virtual const void* Resize(const uint8_t* in_data); |
| |
| protected: |
| const int num_channels_; |
| int pixels_per_row_; |
| float* output_buffer_; // Not owned |
| net_instaweb::scoped_array<ResizeTableEntry> table_; |
| }; |
| |
| bool ResizeRowArea::Initialize(int in_size, |
| int out_size, double ratio, float* output_buffer, MessageHandler* handler) { |
| if (num_channels_ != 1 && num_channels_ != 3 && num_channels_ != 4) { |
| return false; |
| } |
| table_.reset(CreateTableForAreaMethod(in_size, out_size, ratio, handler)); |
| if (table_ == NULL) { |
| return false; |
| } |
| |
| // Modify the indices so they are based on bytes instead of pixels. |
| for (int i = 0; i < out_size; ++i) { |
| table_[i].first_index *= num_channels_; |
| table_[i].last_index *= num_channels_; |
| } |
| pixels_per_row_ = out_size; |
| output_buffer_ = output_buffer; |
| return true; |
| } |
| |
| const void* ResizeRowArea::Resize(const uint8_t* in_data) { |
| if (output_buffer_ == NULL) { |
| return in_data; |
| } |
| |
| switch (num_channels_) { |
| case 1: // GRAY_8 |
| ResizeRowAreaGray(table_.get(), pixels_per_row_, in_data, output_buffer_); |
| break; |
| case 3: // RGB_888 |
| ResizeRowAreaRGB(table_.get(), pixels_per_row_, in_data, output_buffer_); |
| break; |
| case 4: // RGBA_8888 |
| ResizeRowAreaRGBA(table_.get(), pixels_per_row_, in_data, output_buffer_); |
| break; |
| } |
| |
| return output_buffer_; |
| } |
| |
| // Vertical resizer for all pixel formats using the "area" method. |
| template<class BufferType> |
| class ResizeColArea : public ResizeCol { |
| public: |
| ResizeColArea() : output_buffer_(NULL) {} |
| |
| virtual bool Initialize(int in_size, |
| int out_size, |
| double ratio_x, |
| double ratio_y, |
| int elements_per_output_row, |
| uint8_t* output_buffer, |
| MessageHandler* handler); |
| |
| virtual const uint8_t* Resize(const void* in_data_ptr); |
| |
| virtual int out_row() const { |
| return out_row_; |
| } |
| |
| void InitializeResize() { |
| need_more_scanlines_ = true; |
| } |
| |
| bool NeedMoreScanlines() const { |
| return need_more_scanlines_; |
| } |
| |
| private: |
| void AppendFirstRow(const BufferType* in_data, float weight); |
| void AppendMiddleRow(const BufferType* in_data); |
| void AppendLastRow(const BufferType* in_data, float weight); |
| void ComputeOutput(const float* in_data, uint8_t* out_data); |
| |
| net_instaweb::scoped_array<ResizeTableEntry> table_; |
| net_instaweb::scoped_array<float> buffer_; |
| uint8_t* output_buffer_; // Not owned |
| int elements_per_row_; |
| // elements_per_row_4_ is the largest multiple of 4 which is smaller than |
| // elements_per_row_. |
| int elements_per_row_4_; |
| int in_row_; |
| int out_row_; |
| int num_out_rows_; |
| bool need_more_scanlines_; |
| float inv_grid_area_; |
| float half_grid_area_; |
| bool only_scale_outputs_; |
| }; |
| |
| template<class BufferType> |
| bool ResizeColArea<BufferType>::Initialize( |
| int in_size, |
| int out_size, |
| double ratio_x, |
| double ratio_y, |
| int elements_per_output_row, |
| uint8_t* output_buffer, |
| MessageHandler* handler) { |
| table_.reset(CreateTableForAreaMethod(in_size, out_size, ratio_y, handler)); |
| if (table_ == NULL) { |
| return false; |
| } |
| |
| only_scale_outputs_ = (ratio_y == 1.0); |
| if (!only_scale_outputs_) { |
| buffer_.reset(new float[elements_per_output_row]); |
| if (buffer_ == NULL) { |
| return false; |
| } |
| } |
| output_buffer_ = output_buffer; |
| |
| float grid_area = static_cast<float>(ratio_x * ratio_y); |
| inv_grid_area_ = 1.0f / grid_area; |
| half_grid_area_ = 0.5f * grid_area; |
| in_row_ = 0; |
| out_row_ = 0; |
| num_out_rows_ = out_size; |
| need_more_scanlines_ = true; |
| elements_per_row_ = elements_per_output_row; |
| // elements_per_row_4_ is the largest multiplier of 4 which is smaller than |
| // elements_per_row_. |
| elements_per_row_4_ = (elements_per_output_row & ~3); |
| return true; |
| } |
| |
| // To speed up computation, loop unrolling is used in AppendFirstRow() |
| // AppendMiddleRow(), AppendLastRow(), and ComputeOutput(). |
| template<class BufferType> |
| void ResizeColArea<BufferType>::AppendFirstRow( |
| const BufferType* in_data, float weight) { |
| int index = 0; |
| for (; index < elements_per_row_4_; index += 4) { |
| buffer_[index] = weight * in_data[index]; |
| buffer_[index + 1] = weight * in_data[index + 1]; |
| buffer_[index + 2] = weight * in_data[index + 2]; |
| buffer_[index + 3] = weight * in_data[index + 3]; |
| } |
| for (; index < elements_per_row_; ++index) { |
| buffer_[index] = weight * in_data[index]; |
| } |
| } |
| |
| template<class BufferType> |
| void ResizeColArea<BufferType>::AppendMiddleRow( |
| const BufferType* in_data) { |
| int index = 0; |
| for (; index < elements_per_row_4_; index += 4) { |
| buffer_[index] += in_data[index]; |
| buffer_[index + 1] += in_data[index + 1]; |
| buffer_[index + 2] += in_data[index + 2]; |
| buffer_[index + 3] += in_data[index + 3]; |
| } |
| for (; index < elements_per_row_; ++index) { |
| buffer_[index] += in_data[index]; |
| } |
| } |
| |
| template<class BufferType> |
| void ResizeColArea<BufferType>::AppendLastRow( |
| const BufferType* in_data, float weight) { |
| int index = 0; |
| for (; index < elements_per_row_4_; index += 4) { |
| buffer_[index] += weight * in_data[index]; |
| buffer_[index + 1] += weight * in_data[index + 1]; |
| buffer_[index + 2] += weight * in_data[index + 2]; |
| buffer_[index + 3] += weight * in_data[index + 3]; |
| } |
| for (; index < elements_per_row_; ++index) { |
| buffer_[index] += weight * in_data[index]; |
| } |
| } |
| |
| template<class BufferType> |
| void ResizeColArea<BufferType>::ComputeOutput(const float* in_data, |
| uint8_t* out_data) { |
| int index = 0; |
| // Make local copies of the data in order to speed up computation. |
| const float half_grid_area = half_grid_area_; |
| const float inv_grid_area = inv_grid_area_; |
| for (; index < elements_per_row_4_; index += 4) { |
| out_data[index] = static_cast<uint8_t>(( |
| in_data[index] + half_grid_area) * inv_grid_area); |
| out_data[index + 1] = static_cast<uint8_t>(( |
| in_data[index + 1] + half_grid_area) * inv_grid_area); |
| out_data[index + 2] = static_cast<uint8_t>(( |
| in_data[index + 2] + half_grid_area) * inv_grid_area); |
| out_data[index + 3] = static_cast<uint8_t>(( |
| in_data[index + 3] + half_grid_area) * inv_grid_area); |
| } |
| for (; index < elements_per_row_; ++index) { |
| out_data[index] = static_cast<uint8_t>(( |
| in_data[index] + half_grid_area) * inv_grid_area); |
| } |
| } |
| |
| // Resize the image vertically and output a row. |
| template<class BufferType> |
| const uint8_t* ResizeColArea<BufferType>::Resize(const void* in_data_ptr) { |
| if (only_scale_outputs_) { |
| need_more_scanlines_ = false; |
| ++in_row_; |
| ++out_row_; |
| |
| if (output_buffer_ == NULL) { |
| return static_cast<const uint8_t*>(in_data_ptr); |
| } else { |
| const float* in_data = static_cast<const float*>(in_data_ptr); |
| ComputeOutput(in_data, output_buffer_); |
| return output_buffer_; |
| } |
| } |
| |
| const BufferType* in_data = reinterpret_cast<const BufferType*>(in_data_ptr); |
| const ResizeTableEntry& table_entry = table_[out_row_]; |
| need_more_scanlines_ = (in_row_ < table_entry.last_index); |
| |
| if (in_row_ == table_entry.first_index) { |
| AppendFirstRow(in_data, table_entry.first_weight); |
| } else if (in_row_ < table_entry.last_index) { |
| AppendMiddleRow(in_data); |
| } else { |
| float weight = table_entry.last_weight; |
| if (weight > 0) { |
| AppendLastRow(in_data, weight); |
| } |
| } |
| |
| // If we have enough input scanlines, we can compute the output scanline. |
| if (!need_more_scanlines_) { |
| ComputeOutput(buffer_.get(), output_buffer_); |
| |
| // If 'last_weight' is not 0 or 1, the current input scanline shall |
| // be used for computing the next output scanline too. |
| ++out_row_; |
| if (out_row_ < num_out_rows_) { |
| float weight = table_entry.last_weight; |
| if (weight > 0 && weight < 1) { |
| weight = table_[out_row_].first_weight; |
| AppendFirstRow(in_data, weight); |
| } |
| } |
| } |
| ++in_row_; |
| return output_buffer_; |
| } |
| |
| // Instantiate the resizers. It is based on the pixel format as well as the |
| // resizing ratios. |
| template<class BufferType> |
| bool InstantiateResizers(pagespeed::image_compression::PixelFormat pixel_format, |
| net_instaweb::scoped_ptr<ResizeRow>* resizer_x, |
| net_instaweb::scoped_ptr<ResizeCol>* resizer_y, |
| MessageHandler* handler) { |
| const int num_channels = GetNumChannelsFromPixelFormat(pixel_format, handler); |
| resizer_x->reset(new ResizeRowArea(num_channels)); |
| resizer_y->reset(new ResizeColArea<BufferType>()); |
| return (resizer_x->get() != NULL && resizer_y->get() != NULL); |
| } |
| |
| ScanlineResizer::ScanlineResizer(MessageHandler* handler) |
| : reader_(NULL), |
| width_(0), |
| height_(0), |
| elements_per_row_(0), |
| bytes_per_buffer_row_(0), |
| message_handler_(handler) { |
| } |
| |
| ScanlineResizer::~ScanlineResizer() { |
| } |
| |
| // Reset the scanline reader to its initial state. |
| bool ScanlineResizer::Reset() { |
| reader_ = NULL; |
| width_ = 0; |
| height_ = 0; |
| elements_per_row_ = 0; |
| bytes_per_buffer_row_ = 0; |
| return true; |
| } |
| |
| bool ScanlineResizer::HasMoreScanLines() { |
| return (resizer_y_->out_row() < height_); |
| } |
| |
| ScanlineStatus ScanlineResizer::InitializeWithStatus( |
| const void* /* image_buffer */, |
| size_t /* buffer_length */) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_RESIZER, |
| "unexpected call to InitializeWithStatus()"); |
| } |
| |
| // Reads the next available scanline. |
| ScanlineStatus ScanlineResizer::ReadNextScanlineWithStatus( |
| void** out_scanline_bytes) { |
| if (reader_ == NULL || !HasMoreScanLines()) { |
| return PS_LOGGED_STATUS(PS_LOG_DFATAL, message_handler_, |
| SCANLINE_STATUS_INVOCATION_ERROR, |
| SCANLINE_RESIZER, |
| "null reader or no more scanlines"); |
| } |
| |
| // Fetch scanlines from the reader until we have enough input rows for |
| // computing an output row. |
| resizer_y_->InitializeResize(); |
| while (resizer_y_->NeedMoreScanlines()) { |
| if (!reader_->HasMoreScanLines()) { |
| return PS_LOGGED_STATUS(PS_LOG_INFO, message_handler_, |
| SCANLINE_STATUS_INTERNAL_ERROR, |
| SCANLINE_RESIZER, |
| "HasMoreScanLines()"); |
| } |
| void* in_scanline_bytes = NULL; |
| ScanlineStatus status = reader_->ReadNextScanlineWithStatus( |
| &in_scanline_bytes); |
| if (!status.Success()) { |
| Reset(); |
| return status; |
| } |
| |
| // Resize the input scanline horizontally and put the results in buffer_. |
| const void* buffer = resizer_x_->Resize( |
| static_cast<uint8_t*>(in_scanline_bytes)); |
| *out_scanline_bytes = const_cast<uint8_t*>(resizer_y_->Resize(buffer)); |
| } |
| |
| return ScanlineStatus(SCANLINE_STATUS_SUCCESS); |
| } |
| |
| // Initialize the resizer. For computational efficiency, we try to use |
| // integer for internal computation and buffer if it is possible. In particular, |
| // - If both ratio_x and ratio_y are integers, use integer for all computation; |
| // - If ratio_x is an integer but ratio_y is not, use integer for the |
| // horizontal resizer and floating point for the vertical resizer; |
| // - Otherwise, use floating point for all computation. |
| bool ScanlineResizer::Initialize(ScanlineReaderInterface* reader, |
| size_t request_width, |
| size_t request_height) { |
| if (reader == NULL || |
| reader->GetImageWidth() == 0 || |
| reader->GetImageHeight() == 0) { |
| PS_LOG_DFATAL(message_handler_, "The input image cannot be empty."); |
| return false; |
| } |
| |
| if (request_width == kPreserveAspectRatio && |
| request_height == kPreserveAspectRatio) { |
| PS_LOG_DFATAL(message_handler_, \ |
| "Output width and height cannot be kPreserveAspectRatio " \ |
| "at the same time."); |
| return false; |
| } |
| |
| const int input_width = static_cast<int>(reader->GetImageWidth()); |
| const int input_height = static_cast<int>(reader->GetImageHeight()); |
| |
| // TODO(huibao): Truncate the requested image size if it is larger than the |
| // input in 'image_rewrite_filter.cc'. Report an error and return 'false' |
| // if it is larger than the input in this method. |
| |
| // If the request size for either dimension is greater than that of the input, |
| // it will be truncated. In other words, the image will not be enlarged. |
| if (static_cast<int>(request_width) > input_width || |
| static_cast<int>(request_height) > input_height) { |
| PS_DLOG_INFO(message_handler_, \ |
| "The requested output size will be truncated because it is " \ |
| "larger than the input."); |
| } |
| |
| const int output_width = |
| std::min(static_cast<int>(request_width), input_width); |
| const int output_height = |
| std::min(static_cast<int>(request_height), input_height); |
| |
| int resized_width, resized_height; |
| double ratio_x, ratio_y; |
| |
| ComputeResizedSizeRatio(input_width, |
| input_height, |
| output_width, |
| output_height, |
| &resized_width, |
| &resized_height, |
| &ratio_x, |
| &ratio_y, |
| message_handler_); |
| |
| reader_ = reader; |
| height_ = resized_height; |
| width_ = resized_width; |
| const PixelFormat pixel_format = reader->GetPixelFormat(); |
| elements_per_row_ = resized_width * |
| pagespeed::image_compression::GetNumChannelsFromPixelFormat( |
| pixel_format, message_handler_); |
| |
| // Ratios | X Resizer | X Buff | Y Input | Y Resizer | Y Buff |
| // x != 1 && y != 1 | Resize | Valid | float | Resize & Scale | Valid |
| // x != 1 && y == 1 | Resize | Valid | float | Scale Only | Valid |
| // x == 1 && y != 1 | Shortcut | NULL | uint8 | Resize & Scale | Valid |
| // x == 1 && y == 1 | Shortcut | NULL | uint8 | Shortcut | NULL |
| |
| const bool need_resize_x = (ratio_x != 1.0); |
| const bool need_resize_y = (ratio_y != 1.0); |
| float* resizer_x_buffer = NULL; |
| uint8_t* resizer_y_buffer = NULL; |
| if (need_resize_x) { |
| InstantiateResizers<float>(pixel_format, &resizer_x_, &resizer_y_, |
| message_handler_); |
| buffer_.reset(new float[elements_per_row_]); |
| resizer_x_buffer = buffer_.get(); |
| output_.reset(new uint8_t[elements_per_row_]); |
| resizer_y_buffer = output_.get(); |
| if (resizer_x_buffer == NULL || resizer_y_buffer == NULL) { |
| return false; |
| } |
| } else { |
| InstantiateResizers<uint8_t>(pixel_format, &resizer_x_, &resizer_y_, |
| message_handler_); |
| if (need_resize_y) { |
| output_.reset(new uint8_t[elements_per_row_]); |
| resizer_y_buffer = output_.get(); |
| if (resizer_y_buffer == NULL) { |
| return false; |
| } |
| } |
| } |
| |
| if (!resizer_x_->Initialize(input_width, resized_width, ratio_x, |
| resizer_x_buffer, message_handler_)) { |
| return false; |
| } |
| if (!resizer_y_->Initialize(input_height, resized_height, ratio_x, ratio_y, |
| elements_per_row_, resizer_y_buffer, |
| message_handler_)) { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| } // namespace image_compression |
| |
| } // namespace pagespeed |