blob: 499405154e3e9a1f880c80ccb387e0e33e7201cb [file] [log] [blame]
/*
* 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