summaryrefslogtreecommitdiffstats
path: root/base/gfx
diff options
context:
space:
mode:
authorbrettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-12-03 01:11:58 +0000
committerbrettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98>2008-12-03 01:11:58 +0000
commitae615162135445eb0b94365b1a1cfa511e7f4be4 (patch)
treea32c9ad049aa25b3c40038713d546c3521d3d20e /base/gfx
parent693f8e4af78b0bb38cb56cd913c8774c75b43067 (diff)
downloadchromium_src-ae615162135445eb0b94365b1a1cfa511e7f4be4.zip
chromium_src-ae615162135445eb0b94365b1a1cfa511e7f4be4.tar.gz
chromium_src-ae615162135445eb0b94365b1a1cfa511e7f4be4.tar.bz2
Revert my skia file moves because of layout test failures.
Review URL: http://codereview.chromium.org/12892 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6266 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base/gfx')
-rw-r--r--base/gfx/base_gfx.scons4
-rw-r--r--base/gfx/convolver.cc335
-rw-r--r--base/gfx/convolver.h137
-rw-r--r--base/gfx/convolver_unittest.cc127
-rw-r--r--base/gfx/image_operations.cc362
-rw-r--r--base/gfx/image_operations.h63
-rw-r--r--base/gfx/image_operations_unittest.cc148
-rw-r--r--base/gfx/img_resize_perftest.cc70
-rw-r--r--base/gfx/native_theme.cc12
-rw-r--r--base/gfx/skia_utils.cc75
-rw-r--r--base/gfx/skia_utils.h56
-rw-r--r--base/gfx/skia_utils_mac.cc83
-rw-r--r--base/gfx/skia_utils_mac.h51
13 files changed, 1517 insertions, 6 deletions
diff --git a/base/gfx/base_gfx.scons b/base/gfx/base_gfx.scons
index a69206b..7173340 100644
--- a/base/gfx/base_gfx.scons
+++ b/base/gfx/base_gfx.scons
@@ -25,13 +25,16 @@ if env['PLATFORM'] == 'win32':
)
input_files = [
+ 'convolver.cc',
'gdi_util.cc',
+ 'image_operations.cc',
'native_theme.cc',
'png_decoder.cc',
'png_encoder.cc',
'point.cc',
'rect.cc',
'size.cc',
+ 'skia_utils.cc',
]
if env['PLATFORM'] in ('posix', 'darwin'):
@@ -40,6 +43,7 @@ if env['PLATFORM'] in ('posix', 'darwin'):
to_be_ported_files = [
'gdi_util.cc',
'native_theme.cc',
+ 'skia_utils.cc',
]
for remove in to_be_ported_files:
input_files.remove(remove)
diff --git a/base/gfx/convolver.cc b/base/gfx/convolver.cc
new file mode 100644
index 0000000..fd3503f
--- /dev/null
+++ b/base/gfx/convolver.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+
+#include "base/basictypes.h"
+#include "base/gfx/convolver.h"
+#include "base/logging.h"
+
+namespace gfx {
+
+namespace {
+
+// Converts the argument to an 8-bit unsigned value by clamping to the range
+// 0-255.
+inline uint8 ClampTo8(int32 a) {
+ if (static_cast<uint32>(a) < 256)
+ return a; // Avoid the extra check in the common case.
+ if (a < 0)
+ return 0;
+ return 255;
+}
+
+// Stores a list of rows in a circular buffer. The usage is you write into it
+// by calling AdvanceRow. It will keep track of which row in the buffer it
+// should use next, and the total number of rows added.
+class CircularRowBuffer {
+ public:
+ // The number of pixels in each row is given in |source_row_pixel_width|.
+ // The maximum number of rows needed in the buffer is |max_y_filter_size|
+ // (we only need to store enough rows for the biggest filter).
+ //
+ // We use the |first_input_row| to compute the coordinates of all of the
+ // following rows returned by Advance().
+ CircularRowBuffer(int dest_row_pixel_width, int max_y_filter_size,
+ int first_input_row)
+ : row_byte_width_(dest_row_pixel_width * 4),
+ num_rows_(max_y_filter_size),
+ next_row_(0),
+ next_row_coordinate_(first_input_row) {
+ buffer_.resize(row_byte_width_ * max_y_filter_size);
+ row_addresses_.resize(num_rows_);
+ }
+
+ // Moves to the next row in the buffer, returning a pointer to the beginning
+ // of it.
+ uint8* AdvanceRow() {
+ uint8* row = &buffer_[next_row_ * row_byte_width_];
+ next_row_coordinate_++;
+
+ // Set the pointer to the next row to use, wrapping around if necessary.
+ next_row_++;
+ if (next_row_ == num_rows_)
+ next_row_ = 0;
+ return row;
+ }
+
+ // Returns a pointer to an "unrolled" array of rows. These rows will start
+ // at the y coordinate placed into |*first_row_index| and will continue in
+ // order for the maximum number of rows in this circular buffer.
+ //
+ // The |first_row_index_| may be negative. This means the circular buffer
+ // starts before the top of the image (it hasn't been filled yet).
+ uint8* const* GetRowAddresses(int* first_row_index) {
+ // Example for a 4-element circular buffer holding coords 6-9.
+ // Row 0 Coord 8
+ // Row 1 Coord 9
+ // Row 2 Coord 6 <- next_row_ = 2, next_row_coordinate_ = 10.
+ // Row 3 Coord 7
+ //
+ // The "next" row is also the first (lowest) coordinate. This computation
+ // may yield a negative value, but that's OK, the math will work out
+ // since the user of this buffer will compute the offset relative
+ // to the first_row_index and the negative rows will never be used.
+ *first_row_index = next_row_coordinate_ - num_rows_;
+
+ int cur_row = next_row_;
+ for (int i = 0; i < num_rows_; i++) {
+ row_addresses_[i] = &buffer_[cur_row * row_byte_width_];
+
+ // Advance to the next row, wrapping if necessary.
+ cur_row++;
+ if (cur_row == num_rows_)
+ cur_row = 0;
+ }
+ return &row_addresses_[0];
+ }
+
+ private:
+ // The buffer storing the rows. They are packed, each one row_byte_width_.
+ std::vector<uint8> buffer_;
+
+ // Number of bytes per row in the |buffer_|.
+ int row_byte_width_;
+
+ // The number of rows available in the buffer.
+ int num_rows_;
+
+ // The next row index we should write into. This wraps around as the
+ // circular buffer is used.
+ int next_row_;
+
+ // The y coordinate of the |next_row_|. This is incremented each time a
+ // new row is appended and does not wrap.
+ int next_row_coordinate_;
+
+ // Buffer used by GetRowAddresses().
+ std::vector<uint8*> row_addresses_;
+};
+
+// Convolves horizontally along a single row. The row data is given in
+// |src_data| and continues for the num_values() of the filter.
+template<bool has_alpha>
+void ConvolveHorizontally(const uint8* src_data,
+ const ConvolusionFilter1D& filter,
+ unsigned char* out_row) {
+ // Loop over each pixel on this row in the output image.
+ int num_values = filter.num_values();
+ for (int out_x = 0; out_x < num_values; out_x++) {
+ // Get the filter that determines the current output pixel.
+ int filter_offset, filter_length;
+ const int16* filter_values =
+ filter.FilterForValue(out_x, &filter_offset, &filter_length);
+
+ // Compute the first pixel in this row that the filter affects. It will
+ // touch |filter_length| pixels (4 bytes each) after this.
+ const uint8* row_to_filter = &src_data[filter_offset * 4];
+
+ // Apply the filter to the row to get the destination pixel in |accum|.
+ int32 accum[4] = {0};
+ for (int filter_x = 0; filter_x < filter_length; filter_x++) {
+ int16 cur_filter = filter_values[filter_x];
+ accum[0] += cur_filter * row_to_filter[filter_x * 4 + 0];
+ accum[1] += cur_filter * row_to_filter[filter_x * 4 + 1];
+ accum[2] += cur_filter * row_to_filter[filter_x * 4 + 2];
+ if (has_alpha)
+ accum[3] += cur_filter * row_to_filter[filter_x * 4 + 3];
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of fractional part.
+ accum[0] >>= ConvolusionFilter1D::kShiftBits;
+ accum[1] >>= ConvolusionFilter1D::kShiftBits;
+ accum[2] >>= ConvolusionFilter1D::kShiftBits;
+ if (has_alpha)
+ accum[3] >>= ConvolusionFilter1D::kShiftBits;
+
+ // Store the new pixel.
+ out_row[out_x * 4 + 0] = ClampTo8(accum[0]);
+ out_row[out_x * 4 + 1] = ClampTo8(accum[1]);
+ out_row[out_x * 4 + 2] = ClampTo8(accum[2]);
+ if (has_alpha)
+ out_row[out_x * 4 + 3] = ClampTo8(accum[3]);
+ }
+}
+
+// Does vertical convolusion to produce one output row. The filter values and
+// length are given in the first two parameters. These are applied to each
+// of the rows pointed to in the |source_data_rows| array, with each row
+// being |pixel_width| wide.
+//
+// The output must have room for |pixel_width * 4| bytes.
+template<bool has_alpha>
+void ConvolveVertically(const int16* filter_values,
+ int filter_length,
+ uint8* const* source_data_rows,
+ int pixel_width,
+ uint8* out_row) {
+ // We go through each column in the output and do a vertical convolusion,
+ // generating one output pixel each time.
+ for (int out_x = 0; out_x < pixel_width; out_x++) {
+ // Compute the number of bytes over in each row that the current column
+ // we're convolving starts at. The pixel will cover the next 4 bytes.
+ int byte_offset = out_x * 4;
+
+ // Apply the filter to one column of pixels.
+ int32 accum[4] = {0};
+ for (int filter_y = 0; filter_y < filter_length; filter_y++) {
+ int16 cur_filter = filter_values[filter_y];
+ accum[0] += cur_filter * source_data_rows[filter_y][byte_offset + 0];
+ accum[1] += cur_filter * source_data_rows[filter_y][byte_offset + 1];
+ accum[2] += cur_filter * source_data_rows[filter_y][byte_offset + 2];
+ if (has_alpha)
+ accum[3] += cur_filter * source_data_rows[filter_y][byte_offset + 3];
+ }
+
+ // Bring this value back in range. All of the filter scaling factors
+ // are in fixed point with kShiftBits bits of precision.
+ accum[0] >>= ConvolusionFilter1D::kShiftBits;
+ accum[1] >>= ConvolusionFilter1D::kShiftBits;
+ accum[2] >>= ConvolusionFilter1D::kShiftBits;
+ if (has_alpha)
+ accum[3] >>= ConvolusionFilter1D::kShiftBits;
+
+ // Store the new pixel.
+ out_row[byte_offset + 0] = ClampTo8(accum[0]);
+ out_row[byte_offset + 1] = ClampTo8(accum[1]);
+ out_row[byte_offset + 2] = ClampTo8(accum[2]);
+ if (has_alpha) {
+ uint8 alpha = ClampTo8(accum[3]);
+
+ // Make sure the alpha channel doesn't come out larger than any of the
+ // color channels. We use premultipled alpha channels, so this should
+ // never happen, but rounding errors will cause this from time to time.
+ // These "impossible" colors will cause overflows (and hence random pixel
+ // values) when the resulting bitmap is drawn to the screen.
+ //
+ // We only need to do this when generating the final output row (here).
+ int max_color_channel = std::max(out_row[byte_offset + 0],
+ std::max(out_row[byte_offset + 1], out_row[byte_offset + 2]));
+ if (alpha < max_color_channel)
+ out_row[byte_offset + 3] = max_color_channel;
+ else
+ out_row[byte_offset + 3] = alpha;
+ } else {
+ // No alpha channel, the image is opaque.
+ out_row[byte_offset + 3] = 0xff;
+ }
+ }
+}
+
+} // namespace
+
+// ConvolusionFilter1D ---------------------------------------------------------
+
+void ConvolusionFilter1D::AddFilter(int filter_offset,
+ const float* filter_values,
+ int filter_length) {
+ FilterInstance instance;
+ instance.data_location = static_cast<int>(filter_values_.size());
+ instance.offset = filter_offset;
+ instance.length = filter_length;
+ filters_.push_back(instance);
+
+ DCHECK(filter_length > 0);
+ for (int i = 0; i < filter_length; i++)
+ filter_values_.push_back(FloatToFixed(filter_values[i]));
+
+ max_filter_ = std::max(max_filter_, filter_length);
+}
+
+void ConvolusionFilter1D::AddFilter(int filter_offset,
+ const int16* filter_values,
+ int filter_length) {
+ FilterInstance instance;
+ instance.data_location = static_cast<int>(filter_values_.size());
+ instance.offset = filter_offset;
+ instance.length = filter_length;
+ filters_.push_back(instance);
+
+ DCHECK(filter_length > 0);
+ for (int i = 0; i < filter_length; i++)
+ filter_values_.push_back(filter_values[i]);
+
+ max_filter_ = std::max(max_filter_, filter_length);
+}
+
+// BGRAConvolve2D -------------------------------------------------------------
+
+void BGRAConvolve2D(const uint8* source_data,
+ int source_byte_row_stride,
+ bool source_has_alpha,
+ const ConvolusionFilter1D& filter_x,
+ const ConvolusionFilter1D& filter_y,
+ uint8* output) {
+ int max_y_filter_size = filter_y.max_filter();
+
+ // The next row in the input that we will generate a horizontally
+ // convolved row for. If the filter doesn't start at the beginning of the
+ // image (this is the case when we are only resizing a subset), then we
+ // don't want to generate any output rows before that. Compute the starting
+ // row for convolusion as the first pixel for the first vertical filter.
+ int filter_offset, filter_length;
+ const int16* filter_values =
+ filter_y.FilterForValue(0, &filter_offset, &filter_length);
+ int next_x_row = filter_offset;
+
+ // We loop over each row in the input doing a horizontal convolusion. This
+ // will result in a horizontally convolved image. We write the results into
+ // a circular buffer of convolved rows and do vertical convolusion as rows
+ // are available. This prevents us from having to store the entire
+ // intermediate image and helps cache coherency.
+ CircularRowBuffer row_buffer(filter_x.num_values(), max_y_filter_size,
+ filter_offset);
+
+ // Loop over every possible output row, processing just enough horizontal
+ // convolusions to run each subsequent vertical convolusion.
+ int output_row_byte_width = filter_x.num_values() * 4;
+ int num_output_rows = filter_y.num_values();
+ for (int out_y = 0; out_y < num_output_rows; out_y++) {
+ filter_values = filter_y.FilterForValue(out_y,
+ &filter_offset, &filter_length);
+
+ // Generate output rows until we have enough to run the current filter.
+ while (next_x_row < filter_offset + filter_length) {
+ if (source_has_alpha) {
+ ConvolveHorizontally<true>(
+ &source_data[next_x_row * source_byte_row_stride],
+ filter_x, row_buffer.AdvanceRow());
+ } else {
+ ConvolveHorizontally<false>(
+ &source_data[next_x_row * source_byte_row_stride],
+ filter_x, row_buffer.AdvanceRow());
+ }
+ next_x_row++;
+ }
+
+ // Compute where in the output image this row of final data will go.
+ uint8* cur_output_row = &output[out_y * output_row_byte_width];
+
+ // Get the list of rows that the circular buffer has, in order.
+ int first_row_in_circular_buffer;
+ uint8* const* rows_to_convolve =
+ row_buffer.GetRowAddresses(&first_row_in_circular_buffer);
+
+ // Now compute the start of the subset of those rows that the filter
+ // needs.
+ uint8* const* first_row_for_filter =
+ &rows_to_convolve[filter_offset - first_row_in_circular_buffer];
+
+ if (source_has_alpha) {
+ ConvolveVertically<true>(filter_values, filter_length,
+ first_row_for_filter,
+ filter_x.num_values(), cur_output_row);
+ } else {
+ ConvolveVertically<false>(filter_values, filter_length,
+ first_row_for_filter,
+ filter_x.num_values(), cur_output_row);
+ }
+ }
+}
+
+} // namespace gfx
+
diff --git a/base/gfx/convolver.h b/base/gfx/convolver.h
new file mode 100644
index 0000000..12c9228
--- /dev/null
+++ b/base/gfx/convolver.h
@@ -0,0 +1,137 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_GFX_CONVOLVER_H__
+#define BASE_GFX_CONVOLVER_H__
+
+#include <vector>
+
+#include "base/basictypes.h"
+
+// avoid confusion with Mac OS X's math library (Carbon)
+#if defined(OS_MACOSX)
+#undef FloatToFixed
+#endif
+
+namespace gfx {
+
+// Represents a filter in one dimension. Each output pixel has one entry in this
+// object for the filter values contributing to it. You build up the filter
+// list by calling AddFilter for each output pixel (in order).
+//
+// We do 2-dimensional convolusion by first convolving each row by one
+// ConvolusionFilter1D, then convolving each column by another one.
+//
+// Entries are stored in fixed point, shifted left by kShiftBits.
+class ConvolusionFilter1D {
+ public:
+ // The number of bits that fixed point values are shifted by.
+ enum { kShiftBits = 14 };
+
+ ConvolusionFilter1D() : max_filter_(0) {
+ }
+
+ // Convert between floating point and our fixed point representation.
+ static inline int16 FloatToFixed(float f) {
+ return static_cast<int16>(f * (1 << kShiftBits));
+ }
+ static inline unsigned char FixedToChar(int16 x) {
+ return static_cast<unsigned char>(x >> kShiftBits);
+ }
+
+ // Returns the maximum pixel span of a filter.
+ int max_filter() const { return max_filter_; }
+
+ // Returns the number of filters in this filter. This is the dimension of the
+ // output image.
+ int num_values() const { return static_cast<int>(filters_.size()); }
+
+ // Appends the given list of scaling values for generating a given output
+ // pixel. |filter_offset| is the distance from the edge of the image to where
+ // the scaling factors start. The scaling factors apply to the source pixels
+ // starting from this position, and going for the next |filter_length| pixels.
+ //
+ // You will probably want to make sure your input is normalized (that is,
+ // all entries in |filter_values| sub to one) to prevent affecting the overall
+ // brighness of the image.
+ //
+ // The filter_length must be > 0.
+ //
+ // This version will automatically convert your input to fixed point.
+ void AddFilter(int filter_offset,
+ const float* filter_values,
+ int filter_length);
+
+ // Same as the above version, but the input is already fixed point.
+ void AddFilter(int filter_offset,
+ const int16* filter_values,
+ int filter_length);
+
+ // Retrieves a filter for the given |value_offset|, a position in the output
+ // image in the direction we're convolving. The offset and length of the
+ // filter values are put into the corresponding out arguments (see AddFilter
+ // above for what these mean), and a pointer to the first scaling factor is
+ // returned. There will be |filter_length| values in this array.
+ inline const int16* FilterForValue(int value_offset,
+ int* filter_offset,
+ int* filter_length) const {
+ const FilterInstance& filter = filters_[value_offset];
+ *filter_offset = filter.offset;
+ *filter_length = filter.length;
+ return &filter_values_[filter.data_location];
+ }
+
+ private:
+ struct FilterInstance {
+ // Offset within filter_values for this instance of the filter.
+ int data_location;
+
+ // Distance from the left of the filter to the center. IN PIXELS
+ int offset;
+
+ // Number of values in this filter instance.
+ int length;
+ };
+
+ // Stores the information for each filter added to this class.
+ std::vector<FilterInstance> filters_;
+
+ // We store all the filter values in this flat list, indexed by
+ // |FilterInstance.data_location| to avoid the mallocs required for storing
+ // each one separately.
+ std::vector<int16> filter_values_;
+
+ // The maximum size of any filter we've added.
+ int max_filter_;
+};
+
+// Does a two-dimensional convolusion on the given source image.
+//
+// It is assumed the source pixel offsets referenced in the input filters
+// reference only valid pixels, so the source image size is not required. Each
+// row of the source image starts |source_byte_row_stride| after the previous
+// one (this allows you to have rows with some padding at the end).
+//
+// The result will be put into the given output buffer. The destination image
+// size will be xfilter.num_values() * yfilter.num_values() pixels. It will be
+// in rows of exactly xfilter.num_values() * 4 bytes.
+//
+// |source_has_alpha| is a hint that allows us to avoid doing computations on
+// the alpha channel if the image is opaque. If you don't know, set this to
+// true and it will work properly, but setting this to false will be a few
+// percent faster if you know the image is opaque.
+//
+// The layout in memory is assumed to be 4-bytes per pixel in B-G-R-A order
+// (this is ARGB when loaded into 32-bit words on a little-endian machine).
+void BGRAConvolve2D(const uint8* source_data,
+ int source_byte_row_stride,
+ bool source_has_alpha,
+ const ConvolusionFilter1D& xfilter,
+ const ConvolusionFilter1D& yfilter,
+ uint8* output);
+
+} // namespace gfx
+
+#endif // BASE_GFX_CONVOLVER_H__
+
diff --git a/base/gfx/convolver_unittest.cc b/base/gfx/convolver_unittest.cc
new file mode 100644
index 0000000..0a30a6b
--- /dev/null
+++ b/base/gfx/convolver_unittest.cc
@@ -0,0 +1,127 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <string.h>
+#include <time.h>
+#include <vector>
+
+#include "base/gfx/convolver.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace gfx {
+
+namespace {
+
+// Fills the given filter with impulse functions for the range 0->num_entries.
+ void FillImpulseFilter(int num_entries, ConvolusionFilter1D* filter) {
+ float one = 1.0f;
+ for (int i = 0; i < num_entries; i++)
+ filter->AddFilter(i, &one, 1);
+}
+
+// Filters the given input with the impulse function, and verifies that it
+// does not change.
+void TestImpulseConvolusion(const unsigned char* data, int width, int height) {
+ int byte_count = width * height * 4;
+
+ ConvolusionFilter1D filter_x;
+ FillImpulseFilter(width, &filter_x);
+
+ ConvolusionFilter1D filter_y;
+ FillImpulseFilter(height, &filter_y);
+
+ std::vector<unsigned char> output;
+ output.resize(byte_count);
+ BGRAConvolve2D(data, width * 4, true, filter_x, filter_y, &output[0]);
+
+ // Output should exactly match input.
+ EXPECT_EQ(0, memcmp(data, &output[0], byte_count));
+}
+
+// Fills the destination filter with a box filter averaging every two pixels
+// to produce the output.
+void FillBoxFilter(int size, ConvolusionFilter1D* filter) {
+ const float box[2] = { 0.5, 0.5 };
+ for (int i = 0; i < size; i++)
+ filter->AddFilter(i * 2, box, 2);
+}
+
+} // namespace
+
+// Tests that each pixel, when set and run through the impulse filter, does
+// not change.
+TEST(Convolver, Impulse) {
+ // We pick an "odd" size that is not likely to fit on any boundaries so that
+ // we can see if all the widths and paddings are handled properly.
+ int width = 15;
+ int height = 31;
+ int byte_count = width * height * 4;
+ std::vector<unsigned char> input;
+ input.resize(byte_count);
+
+ unsigned char* input_ptr = &input[0];
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < width; x++) {
+ for (int channel = 0; channel < 3; channel++) {
+ memset(input_ptr, 0, byte_count);
+ input_ptr[(y * width + x) * 4 + channel] = 0xff;
+ // Always set the alpha channel or it will attempt to "fix" it for us.
+ input_ptr[(y * width + x) * 4 + 3] = 0xff;
+ TestImpulseConvolusion(input_ptr, width, height);
+ }
+ }
+ }
+}
+
+// Tests that using a box filter to halve an image results in every square of 4
+// pixels in the original get averaged to a pixel in the output.
+TEST(Convolver, Halve) {
+ static const int kSize = 16;
+
+ int src_width = kSize;
+ int src_height = kSize;
+ int src_row_stride = src_width * 4;
+ int src_byte_count = src_row_stride * src_height;
+ std::vector<unsigned char> input;
+ input.resize(src_byte_count);
+
+ int dest_width = src_width / 2;
+ int dest_height = src_height / 2;
+ int dest_byte_count = dest_width * dest_height * 4;
+ std::vector<unsigned char> output;
+ output.resize(dest_byte_count);
+
+ // First fill the array with a bunch of random data.
+ srand(static_cast<unsigned>(time(NULL)));
+ for (int i = 0; i < src_byte_count; i++)
+ input[i] = rand() * 255 / RAND_MAX;
+
+ // Compute the filters.
+ ConvolusionFilter1D filter_x, filter_y;
+ FillBoxFilter(dest_width, &filter_x);
+ FillBoxFilter(dest_height, &filter_y);
+
+ // Do the convolusion.
+ BGRAConvolve2D(&input[0], src_width, true, filter_x, filter_y, &output[0]);
+
+ // Compute the expected results and check, allowing for a small difference
+ // to account for rounding errors.
+ for (int y = 0; y < dest_height; y++) {
+ for (int x = 0; x < dest_width; x++) {
+ for (int channel = 0; channel < 4; channel++) {
+ int src_offset = (y * 2 * src_row_stride + x * 2 * 4) + channel;
+ int value = input[src_offset] + // Top left source pixel.
+ input[src_offset + 4] + // Top right source pixel.
+ input[src_offset + src_row_stride] + // Lower left.
+ input[src_offset + src_row_stride + 4]; // Lower right.
+ value /= 4; // Average.
+ int difference = value - output[(y * dest_width + x) * 4 + channel];
+ EXPECT_TRUE(difference >= -1 || difference <= 1);
+ }
+ }
+ }
+}
+
+} // namespace gfx
+
diff --git a/base/gfx/image_operations.cc b/base/gfx/image_operations.cc
new file mode 100644
index 0000000..a60d19e
--- /dev/null
+++ b/base/gfx/image_operations.cc
@@ -0,0 +1,362 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+#define _USE_MATH_DEFINES
+#include <cmath>
+#include <limits>
+#include <vector>
+
+#include "base/gfx/image_operations.h"
+
+#include "base/gfx/convolver.h"
+#include "base/gfx/rect.h"
+#include "base/gfx/size.h"
+#include "base/logging.h"
+#include "base/stack_container.h"
+#include "SkBitmap.h"
+
+namespace gfx {
+
+namespace {
+
+// Returns the ceiling/floor as an integer.
+inline int CeilInt(float val) {
+ return static_cast<int>(ceil(val));
+}
+inline int FloorInt(float val) {
+ return static_cast<int>(floor(val));
+}
+
+// Filter function computation -------------------------------------------------
+
+// Evaluates the box filter, which goes from -0.5 to +0.5.
+float EvalBox(float x) {
+ return (x >= -0.5f && x < 0.5f) ? 1.0f : 0.0f;
+}
+
+// Evaluates the Lanczos filter of the given filter size window for the given
+// position.
+//
+// |filter_size| is the width of the filter (the "window"), outside of which
+// the value of the function is 0. Inside of the window, the value is the
+// normalized sinc function:
+// lanczos(x) = sinc(x) * sinc(x / filter_size);
+// where
+// sinc(x) = sin(pi*x) / (pi*x);
+float EvalLanczos(int filter_size, float x) {
+ if (x <= -filter_size || x >= filter_size)
+ return 0.0f; // Outside of the window.
+ if (x > -std::numeric_limits<float>::epsilon() &&
+ x < std::numeric_limits<float>::epsilon())
+ return 1.0f; // Special case the discontinuity at the origin.
+ float xpi = x * static_cast<float>(M_PI);
+ return (sin(xpi) / xpi) * // sinc(x)
+ sin(xpi / filter_size) / (xpi / filter_size); // sinc(x/filter_size)
+}
+
+// ResizeFilter ----------------------------------------------------------------
+
+// Encapsulates computation and storage of the filters required for one complete
+// resize operation.
+class ResizeFilter {
+ public:
+ ResizeFilter(ImageOperations::ResizeMethod method,
+ const Size& src_full_size,
+ const Size& dest_size,
+ const Rect& dest_subset);
+
+ // Returns the bounds in the input bitmap of data that is used in the output.
+ // The filter offsets are within this rectangle.
+ const Rect& src_depend() { return src_depend_; }
+
+ // Returns the filled filter values.
+ const ConvolusionFilter1D& x_filter() { return x_filter_; }
+ const ConvolusionFilter1D& y_filter() { return y_filter_; }
+
+ private:
+ // Returns the number of pixels that the filer spans, in filter space (the
+ // destination image).
+ float GetFilterSupport(float scale) {
+ switch (method_) {
+ case ImageOperations::RESIZE_BOX:
+ // The box filter just scales with the image scaling.
+ return 0.5f; // Only want one side of the filter = /2.
+ case ImageOperations::RESIZE_LANCZOS3:
+ // The lanczos filter takes as much space in the source image in
+ // each direction as the size of the window = 3 for Lanczos3.
+ return 3.0f;
+ default:
+ NOTREACHED();
+ return 1.0f;
+ }
+ }
+
+ // Computes one set of filters either horizontally or vertically. The caller
+ // will specify the "min" and "max" rather than the bottom/top and
+ // right/bottom so that the same code can be re-used in each dimension.
+ //
+ // |src_depend_lo| and |src_depend_size| gives the range for the source
+ // depend rectangle (horizontally or vertically at the caller's discretion
+ // -- see above for what this means).
+ //
+ // Likewise, the range of destination values to compute and the scale factor
+ // for the transform is also specified.
+ void ComputeFilters(int src_size,
+ int dest_subset_lo, int dest_subset_size,
+ float scale, float src_support,
+ ConvolusionFilter1D* output);
+
+ // Computes the filter value given the coordinate in filter space.
+ inline float ComputeFilter(float pos) {
+ switch (method_) {
+ case ImageOperations::RESIZE_BOX:
+ return EvalBox(pos);
+ case ImageOperations::RESIZE_LANCZOS3:
+ return EvalLanczos(3, pos);
+ default:
+ NOTREACHED();
+ return 0;
+ }
+ }
+
+ ImageOperations::ResizeMethod method_;
+
+ // Subset of source the filters will touch.
+ Rect src_depend_;
+
+ // Size of the filter support on one side only in the destination space.
+ // See GetFilterSupport.
+ float x_filter_support_;
+ float y_filter_support_;
+
+ // Subset of scaled destination bitmap to compute.
+ Rect out_bounds_;
+
+ ConvolusionFilter1D x_filter_;
+ ConvolusionFilter1D y_filter_;
+
+ DISALLOW_EVIL_CONSTRUCTORS(ResizeFilter);
+};
+
+ResizeFilter::ResizeFilter(ImageOperations::ResizeMethod method,
+ const Size& src_full_size,
+ const Size& dest_size,
+ const Rect& dest_subset)
+ : method_(method),
+ out_bounds_(dest_subset) {
+ float scale_x = static_cast<float>(dest_size.width()) /
+ static_cast<float>(src_full_size.width());
+ float scale_y = static_cast<float>(dest_size.height()) /
+ static_cast<float>(src_full_size.height());
+
+ x_filter_support_ = GetFilterSupport(scale_x);
+ y_filter_support_ = GetFilterSupport(scale_y);
+
+ gfx::Rect src_full(0, 0, src_full_size.width(), src_full_size.height());
+ gfx::Rect dest_full(0, 0,
+ static_cast<int>(src_full_size.width() * scale_x + 0.5),
+ static_cast<int>(src_full_size.height() * scale_y + 0.5));
+
+ // Support of the filter in source space.
+ float src_x_support = x_filter_support_ / scale_x;
+ float src_y_support = y_filter_support_ / scale_y;
+
+ ComputeFilters(src_full_size.width(), dest_subset.x(), dest_subset.width(),
+ scale_x, src_x_support, &x_filter_);
+ ComputeFilters(src_full_size.height(), dest_subset.y(), dest_subset.height(),
+ scale_y, src_y_support, &y_filter_);
+}
+
+void ResizeFilter::ComputeFilters(int src_size,
+ int dest_subset_lo, int dest_subset_size,
+ float scale, float src_support,
+ ConvolusionFilter1D* output) {
+ int dest_subset_hi = dest_subset_lo + dest_subset_size; // [lo, hi)
+
+ // When we're doing a magnification, the scale will be larger than one. This
+ // means the destination pixels are much smaller than the source pixels, and
+ // that the range covered by the filter won't necessarily cover any source
+ // pixel boundaries. Therefore, we use these clamped values (max of 1) for
+ // some computations.
+ float clamped_scale = std::min(1.0f, scale);
+
+ // Speed up the divisions below by turning them into multiplies.
+ float inv_scale = 1.0f / scale;
+
+ StackVector<float, 64> filter_values;
+ StackVector<int16, 64> fixed_filter_values;
+
+ // Loop over all pixels in the output range. We will generate one set of
+ // filter values for each one. Those values will tell us how to blend the
+ // source pixels to compute the destination pixel.
+ for (int dest_subset_i = dest_subset_lo; dest_subset_i < dest_subset_hi;
+ dest_subset_i++) {
+ // Reset the arrays. We don't declare them inside so they can re-use the
+ // same malloc-ed buffer.
+ filter_values->clear();
+ fixed_filter_values->clear();
+
+ // This is the pixel in the source directly under the pixel in the dest.
+ float src_pixel = dest_subset_i * inv_scale;
+
+ // Compute the (inclusive) range of source pixels the filter covers.
+ int src_begin = std::max(0, FloorInt(src_pixel - src_support));
+ int src_end = std::min(src_size - 1, CeilInt(src_pixel + src_support));
+
+ // Compute the unnormalized filter value at each location of the source
+ // it covers.
+ float filter_sum = 0.0f; // Sub of the filter values for normalizing.
+ for (int cur_filter_pixel = src_begin; cur_filter_pixel <= src_end;
+ cur_filter_pixel++) {
+ // Distance from the center of the filter, this is the filter coordinate
+ // in source space.
+ float src_filter_pos = cur_filter_pixel - src_pixel;
+
+ // Since the filter really exists in dest space, map it there.
+ float dest_filter_pos = src_filter_pos * clamped_scale;
+
+ // Compute the filter value at that location.
+ float filter_value = ComputeFilter(dest_filter_pos);
+ filter_values->push_back(filter_value);
+
+ filter_sum += filter_value;
+ }
+ DCHECK(!filter_values->empty()) << "We should always get a filter!";
+
+ // The filter must be normalized so that we don't affect the brightness of
+ // the image. Convert to normalized fixed point.
+ int16 fixed_sum = 0;
+ for (size_t i = 0; i < filter_values->size(); i++) {
+ int16 cur_fixed = output->FloatToFixed(filter_values[i] / filter_sum);
+ fixed_sum += cur_fixed;
+ fixed_filter_values->push_back(cur_fixed);
+ }
+
+ // The conversion to fixed point will leave some rounding errors, which
+ // we add back in to avoid affecting the brightness of the image. We
+ // arbitrarily add this to the center of the filter array (this won't always
+ // be the center of the filter function since it could get clipped on the
+ // edges, but it doesn't matter enough to worry about that case).
+ int16 leftovers = output->FloatToFixed(1.0f) - fixed_sum;
+ fixed_filter_values[fixed_filter_values->size() / 2] += leftovers;
+
+ // Now it's ready to go.
+ output->AddFilter(src_begin, &fixed_filter_values[0],
+ static_cast<int>(fixed_filter_values->size()));
+ }
+}
+
+} // namespace
+
+// Resize ----------------------------------------------------------------------
+
+// static
+SkBitmap ImageOperations::Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size,
+ const Rect& dest_subset) {
+ DCHECK(Rect(dest_size.width(), dest_size.height()).Contains(dest_subset)) <<
+ "The supplied subset does not fall within the destination image.";
+
+ // If the size of source or destination is 0, i.e. 0x0, 0xN or Nx0, just
+ // return empty
+ if (source.width() < 1 || source.height() < 1 ||
+ dest_size.width() < 1 || dest_size.height() < 1)
+ return SkBitmap();
+
+ SkAutoLockPixels locker(source);
+
+ ResizeFilter filter(method, Size(source.width(), source.height()),
+ dest_size, dest_subset);
+
+ // Get a source bitmap encompassing this touched area. We construct the
+ // offsets and row strides such that it looks like a new bitmap, while
+ // referring to the old data.
+ const uint8* source_subset =
+ reinterpret_cast<const uint8*>(source.getPixels());
+
+ // Convolve into the result.
+ SkBitmap result;
+ result.setConfig(SkBitmap::kARGB_8888_Config,
+ dest_subset.width(), dest_subset.height());
+ result.allocPixels();
+ BGRAConvolve2D(source_subset, static_cast<int>(source.rowBytes()),
+ !source.isOpaque(), filter.x_filter(), filter.y_filter(),
+ static_cast<unsigned char*>(result.getPixels()));
+
+ // Preserve the "opaque" flag for use as an optimization later.
+ result.setIsOpaque(source.isOpaque());
+
+ return result;
+}
+
+// static
+SkBitmap ImageOperations::Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size) {
+ Rect dest_subset(0, 0, dest_size.width(), dest_size.height());
+ return Resize(source, method, dest_size, dest_subset);
+}
+
+// static
+SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first,
+ const SkBitmap& second,
+ double alpha) {
+ DCHECK(alpha <= 1 && alpha >= 0);
+ DCHECK(first.width() == second.width());
+ DCHECK(first.height() == second.height());
+ DCHECK(first.bytesPerPixel() == second.bytesPerPixel());
+ DCHECK(first.config() == SkBitmap::kARGB_8888_Config);
+
+ // Optimize for case where we won't need to blend anything.
+ static const double alpha_min = 1.0 / 255;
+ static const double alpha_max = 254.0 / 255;
+ if (alpha < alpha_min) {
+ return first;
+ } else if (alpha > alpha_max) {
+ return second;
+ }
+
+ SkAutoLockPixels lock_first(first);
+ SkAutoLockPixels lock_second(second);
+
+ SkBitmap blended;
+ blended.setConfig(SkBitmap::kARGB_8888_Config, first.width(),
+ first.height(), 0);
+ blended.allocPixels();
+ blended.eraseARGB(0, 0, 0, 0);
+
+ double first_alpha = 1 - alpha;
+
+ for (int y = 0; y < first.height(); y++) {
+ uint32* first_row = first.getAddr32(0, y);
+ uint32* second_row = second.getAddr32(0, y);
+ uint32* dst_row = blended.getAddr32(0, y);
+
+ for (int x = 0; x < first.width(); x++) {
+ uint32 first_pixel = first_row[x];
+ uint32 second_pixel = second_row[x];
+
+ int a = static_cast<int>(
+ SkColorGetA(first_pixel) * first_alpha +
+ SkColorGetA(second_pixel) * alpha);
+ int r = static_cast<int>(
+ SkColorGetR(first_pixel) * first_alpha +
+ SkColorGetR(second_pixel) * alpha);
+ int g = static_cast<int>(
+ SkColorGetG(first_pixel) * first_alpha +
+ SkColorGetG(second_pixel) * alpha);
+ int b = static_cast<int>(
+ SkColorGetB(first_pixel) * first_alpha +
+ SkColorGetB(second_pixel) * alpha);
+
+ dst_row[x] = SkColorSetARGB(a, r, g, b);
+ }
+ }
+
+ return blended;
+}
+
+} // namespace gfx
+
diff --git a/base/gfx/image_operations.h b/base/gfx/image_operations.h
new file mode 100644
index 0000000..826e651
--- /dev/null
+++ b/base/gfx/image_operations.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_GFX_IMAGE_OPERATIONS_H__
+#define BASE_GFX_IMAGE_OPERATIONS_H__
+
+#include "base/basictypes.h"
+#include "base/gfx/rect.h"
+
+class SkBitmap;
+
+namespace gfx {
+
+class ImageOperations {
+ public:
+ enum ResizeMethod {
+ // Box filter. This is a weighted average of all of the pixels touching
+ // the destination pixel. For enlargement, this is nearest neighbor.
+ //
+ // You probably don't want this, it is here for testing since it is easy to
+ // compute. Use RESIZE_LANCZOS3 instead.
+ RESIZE_BOX,
+
+ // 3-cycle Lanczos filter. This is tall in the middle, goes negative on
+ // each side, then oscillates 2 more times. It gives nice sharp edges.
+ RESIZE_LANCZOS3,
+ };
+
+ // Resizes the given source bitmap using the specified resize method, so that
+ // the entire image is (dest_size) big. The dest_subset is the rectangle in
+ // this destination image that should actually be returned.
+ //
+ // The output image will be (dest_subset.width(), dest_subset.height()). This
+ // will save work if you do not need the entire bitmap.
+ //
+ // The destination subset must be smaller than the destination image.
+ static SkBitmap Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size,
+ const Rect& dest_subset);
+
+ // Alternate version for resizing and returning the entire bitmap rather than
+ // a subset.
+ static SkBitmap Resize(const SkBitmap& source,
+ ResizeMethod method,
+ const Size& dest_size);
+
+
+ // Create a bitmap that is a blend of two others. The alpha argument
+ // specifies the opacity of the second bitmap. The provided bitmaps must
+ // use have the kARGB_8888_Config config and be of equal dimensions.
+ static SkBitmap CreateBlendedBitmap(const SkBitmap& first,
+ const SkBitmap& second,
+ double alpha);
+ private:
+ ImageOperations(); // Class for scoping only.
+};
+
+} // namespace gfx
+
+#endif // BASE_GFX_IMAGE_OPERATIONS_H__
+
diff --git a/base/gfx/image_operations_unittest.cc b/base/gfx/image_operations_unittest.cc
new file mode 100644
index 0000000..a15e648
--- /dev/null
+++ b/base/gfx/image_operations_unittest.cc
@@ -0,0 +1,148 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdlib.h>
+
+#include "base/gfx/image_operations.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "SkBitmap.h"
+
+namespace {
+
+// Computes the average pixel value for the given range, inclusive.
+uint32_t AveragePixel(const SkBitmap& bmp,
+ int x_min, int x_max,
+ int y_min, int y_max) {
+ float accum[4] = {0, 0, 0, 0};
+ int count = 0;
+ for (int y = y_min; y <= y_max; y++) {
+ for (int x = x_min; x <= x_max; x++) {
+ uint32_t cur = *bmp.getAddr32(x, y);
+ accum[0] += SkColorGetB(cur);
+ accum[1] += SkColorGetG(cur);
+ accum[2] += SkColorGetR(cur);
+ accum[3] += SkColorGetA(cur);
+ count++;
+ }
+ }
+
+ return SkColorSetARGB(static_cast<unsigned char>(accum[3] / count),
+ static_cast<unsigned char>(accum[2] / count),
+ static_cast<unsigned char>(accum[1] / count),
+ static_cast<unsigned char>(accum[0] / count));
+}
+
+// Returns true if each channel of the given two colors are "close." This is
+// used for comparing colors where rounding errors may cause off-by-one.
+bool ColorsClose(uint32_t a, uint32_t b) {
+ return abs(static_cast<int>(SkColorGetB(a) - SkColorGetB(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetG(a) - SkColorGetG(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetR(a) - SkColorGetR(b))) < 2 &&
+ abs(static_cast<int>(SkColorGetA(a) - SkColorGetA(b))) < 2;
+}
+
+void FillDataToBitmap(int w, int h, SkBitmap* bmp) {
+ bmp->setConfig(SkBitmap::kARGB_8888_Config, w, h);
+ bmp->allocPixels();
+
+ unsigned char* src_data =
+ reinterpret_cast<unsigned char*>(bmp->getAddr32(0, 0));
+ for (int i = 0; i < w * h; i++) {
+ src_data[i * 4 + 0] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 1] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 2] = static_cast<unsigned char>(i % 255);
+ src_data[i * 4 + 3] = static_cast<unsigned char>(i % 255);
+ }
+}
+
+} // namespace
+
+// Makes the bitmap 50% the size as the original using a box filter. This is
+// an easy operation that we can check the results for manually.
+TEST(ImageOperations, Halve) {
+ // Make our source bitmap.
+ int src_w = 30, src_h = 38;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a halving of the full bitmap.
+ SkBitmap actual_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2));
+ ASSERT_EQ(src_w / 2, actual_results.width());
+ ASSERT_EQ(src_h / 2, actual_results.height());
+
+ // Compute the expected values & compare.
+ SkAutoLockPixels lock(actual_results);
+ for (int y = 0; y < actual_results.height(); y++) {
+ for (int x = 0; x < actual_results.width(); x++) {
+ int first_x = std::max(0, x * 2 - 1);
+ int last_x = std::min(src_w - 1, x * 2);
+
+ int first_y = std::max(0, y * 2 - 1);
+ int last_y = std::min(src_h - 1, y * 2);
+
+ uint32_t expected_color = AveragePixel(src,
+ first_x, last_x, first_y, last_y);
+ EXPECT_TRUE(ColorsClose(expected_color, *actual_results.getAddr32(x, y)));
+ }
+ }
+}
+
+TEST(ImageOperations, HalveSubset) {
+ // Make our source bitmap.
+ int src_w = 16, src_h = 34;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a halving of the full bitmap.
+ SkBitmap full_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX, gfx::Size(src_w / 2, src_h / 2));
+ ASSERT_EQ(src_w / 2, full_results.width());
+ ASSERT_EQ(src_h / 2, full_results.height());
+
+ // Now do a halving of a a subset, recall the destination subset is in the
+ // destination coordinate system (max = half of the original image size).
+ gfx::Rect subset_rect(2, 3, 3, 6);
+ SkBitmap subset_results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_BOX,
+ gfx::Size(src_w / 2, src_h / 2), subset_rect);
+ ASSERT_EQ(subset_rect.width(), subset_results.width());
+ ASSERT_EQ(subset_rect.height(), subset_results.height());
+
+ // The computed subset and the corresponding subset of the original image
+ // should be the same.
+ SkAutoLockPixels full_lock(full_results);
+ SkAutoLockPixels subset_lock(subset_results);
+ for (int y = 0; y < subset_rect.height(); y++) {
+ for (int x = 0; x < subset_rect.width(); x++) {
+ ASSERT_EQ(
+ *full_results.getAddr32(x + subset_rect.x(), y + subset_rect.y()),
+ *subset_results.getAddr32(x, y));
+ }
+ }
+}
+
+// Resamples an iamge to the same image, it should give almost the same result.
+TEST(ImageOperations, ResampleToSame) {
+ // Make our source bitmap.
+ int src_w = 16, src_h = 34;
+ SkBitmap src;
+ FillDataToBitmap(src_w, src_h, &src);
+
+ // Do a resize of the full bitmap to the same size. The lanczos filter is good
+ // enough that we should get exactly the same image for output.
+ SkBitmap results = gfx::ImageOperations::Resize(
+ src, gfx::ImageOperations::RESIZE_LANCZOS3, gfx::Size(src_w, src_h));
+ ASSERT_EQ(src_w, results.width());
+ ASSERT_EQ(src_h, results.height());
+
+ SkAutoLockPixels src_lock(src);
+ SkAutoLockPixels results_lock(results);
+ for (int y = 0; y < src_h; y++) {
+ for (int x = 0; x < src_w; x++) {
+ EXPECT_EQ(*src.getAddr32(x, y), *results.getAddr32(x, y));
+ }
+ }
+}
+
diff --git a/base/gfx/img_resize_perftest.cc b/base/gfx/img_resize_perftest.cc
new file mode 100644
index 0000000..6a4b070
--- /dev/null
+++ b/base/gfx/img_resize_perftest.cc
@@ -0,0 +1,70 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <stdlib.h>
+#include <time.h>
+
+#include "base/perftimer.h"
+#include "base/gfx/convolver.h"
+#include "base/gfx/image_operations.h"
+#include "base/gfx/image_resizer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+void FillRandomData(char* dest, int byte_count) {
+ srand(static_cast<unsigned>(time(NULL)));
+ for (int i = 0; i < byte_count; i++)
+ dest[i] = rand() * 255 / RAND_MAX;
+}
+
+} // namespace
+
+// Old code gives [1521, 1519]ms for this, 4000x4000 -> 2100x2100 lanczos8
+
+TEST(ImageResizePerf, BigFilter) {
+ static const int kSrcWidth = 4000;
+ static const int kSrcHeight = 4000;
+ static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4;
+
+ SkBitmap src_bmp;
+ src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight);
+ src_bmp.allocPixels();
+ FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)),
+ kSrcByteSize);
+
+ // Make the dest size > 1/2 so the 50% optimization doesn't kick in.
+ static const int kDestWidth = 1400;
+ static const int kDestHeight = 1400;
+
+ PerfTimeLogger resize_timer("resize");
+ gfx::ImageResizer resizer(gfx::ImageResizer::LANCZOS3);
+ SkBitmap dest = resizer.Resize(src_bmp, kDestWidth, kDestHeight);
+}
+
+// The original image filter we were using took 523ms for this test, while this
+// one takes 857ms.
+// TODO(brettw) make this at least 64% faster.
+TEST(ImageOperationPerf, BigFilter) {
+ static const int kSrcWidth = 4000;
+ static const int kSrcHeight = 4000;
+ static const int kSrcByteSize = kSrcWidth * kSrcHeight * 4;
+
+ SkBitmap src_bmp;
+ src_bmp.setConfig(SkBitmap::kARGB_8888_Config, kSrcWidth, kSrcHeight);
+ src_bmp.allocPixels();
+ src_bmp.setIsOpaque(true);
+ FillRandomData(reinterpret_cast<char*>(src_bmp.getAddr32(0, 0)),
+ kSrcByteSize);
+
+ // Make the dest size > 1/2 so the 50% optimization doesn't kick in.
+ static const int kDestWidth = 1400;
+ static const int kDestHeight = 1400;
+
+ PerfTimeLogger resize_timer("resize");
+ SkBitmap dest = gfx::ImageOperations::Resize(src_bmp,
+ gfx::ImageOperations::RESIZE_LANCZOS3, (float)kDestWidth / (float)kSrcWidth,
+ (float)kDestHeight / (float)kSrcHeight);
+}
+
diff --git a/base/gfx/native_theme.cc b/base/gfx/native_theme.cc
index 8c7914d..71289d8 100644
--- a/base/gfx/native_theme.cc
+++ b/base/gfx/native_theme.cc
@@ -10,11 +10,11 @@
#include <vssym32.h>
#include "base/gfx/gdi_util.h"
+#include "base/gfx/skia_utils.h"
#include "base/gfx/rect.h"
#include "base/logging.h"
#include "base/scoped_handle.h"
#include "skia/ext/platform_canvas.h"
-#include "skia/ext/skia_utils_win.h"
#include "skia/include/SkShader.h"
namespace gfx {
@@ -213,8 +213,8 @@ HRESULT NativeTheme::PaintScrollbarTrack(HDC hdc,
} else {
// Create a 2x2 checkerboard pattern using the 3D face and highlight
// colors.
- SkColor face = skia::COLORREFToSkColor(color3DFace);
- SkColor highlight = skia::COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT));
+ SkColor face = COLORREFToSkColor(color3DFace);
+ SkColor highlight = COLORREFToSkColor(GetSysColor(COLOR_3DHILIGHT));
SkColor buffer[] = { face, highlight, highlight, face };
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, 2, 2);
@@ -232,7 +232,7 @@ HRESULT NativeTheme::PaintScrollbarTrack(HDC hdc,
shader->setLocalMatrix(matrix);
SkPaint paint;
paint.setShader(shader)->unref();
- canvas->drawIRect(skia::RECTToSkIRect(*target_rect), paint);
+ canvas->drawIRect(RECTToSkIRect(*target_rect), paint);
}
if (classic_state & DFCS_PUSHED)
InvertRect(hdc, target_rect);
@@ -466,7 +466,7 @@ HRESULT NativeTheme::GetThemeColor(ThemeName theme,
COLORREF color_ref;
if (get_theme_color_(handle, part_id, state_id, prop_id, &color_ref) ==
S_OK) {
- *color = skia::COLORREFToSkColor(color_ref);
+ *color = gfx::COLORREFToSkColor(color_ref);
return S_OK;
}
}
@@ -480,7 +480,7 @@ SkColor NativeTheme::GetThemeColorWithDefault(ThemeName theme,
int default_sys_color) const {
SkColor color;
if (GetThemeColor(theme, part_id, state_id, prop_id, &color) != S_OK)
- color = skia::COLORREFToSkColor(GetSysColor(default_sys_color));
+ color = gfx::COLORREFToSkColor(GetSysColor(default_sys_color));
return color;
}
diff --git a/base/gfx/skia_utils.cc b/base/gfx/skia_utils.cc
new file mode 100644
index 0000000..7b70056
--- /dev/null
+++ b/base/gfx/skia_utils.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/gfx/skia_utils.h"
+
+#include "base/logging.h"
+#include "SkRect.h"
+#include "SkGradientShader.h"
+
+namespace {
+
+COMPILE_ASSERT(offsetof(RECT, left) == offsetof(SkIRect, fLeft), o1);
+COMPILE_ASSERT(offsetof(RECT, top) == offsetof(SkIRect, fTop), o2);
+COMPILE_ASSERT(offsetof(RECT, right) == offsetof(SkIRect, fRight), o3);
+COMPILE_ASSERT(offsetof(RECT, bottom) == offsetof(SkIRect, fBottom), o4);
+COMPILE_ASSERT(sizeof(RECT().left) == sizeof(SkIRect().fLeft), o5);
+COMPILE_ASSERT(sizeof(RECT().top) == sizeof(SkIRect().fTop), o6);
+COMPILE_ASSERT(sizeof(RECT().right) == sizeof(SkIRect().fRight), o7);
+COMPILE_ASSERT(sizeof(RECT().bottom) == sizeof(SkIRect().fBottom), o8);
+COMPILE_ASSERT(sizeof(RECT) == sizeof(SkIRect), o9);
+
+} // namespace
+
+namespace gfx {
+
+POINT SkPointToPOINT(const SkPoint& point) {
+ POINT win_point = { SkScalarRound(point.fX), SkScalarRound(point.fY) };
+ return win_point;
+}
+
+SkRect RECTToSkRect(const RECT& rect) {
+ SkRect sk_rect = { SkIntToScalar(rect.left), SkIntToScalar(rect.top),
+ SkIntToScalar(rect.right), SkIntToScalar(rect.bottom) };
+ return sk_rect;
+}
+
+SkShader* CreateGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color) {
+ SkColor grad_colors[2] = { start_color, end_color};
+ SkPoint grad_points[2];
+ grad_points[0].set(SkIntToScalar(0), SkIntToScalar(start_point));
+ grad_points[1].set(SkIntToScalar(0), SkIntToScalar(end_point));
+
+ return SkGradientShader::CreateLinear(
+ grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode);
+}
+
+
+SkColor COLORREFToSkColor(COLORREF color) {
+#ifndef _MSC_VER
+ return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color));
+#else
+ // ARGB = 0xFF000000 | ((0BGR -> RGB0) >> 8)
+ return 0xFF000000u | (_byteswap_ulong(color) >> 8);
+#endif
+}
+
+COLORREF SkColorToCOLORREF(SkColor color) {
+ // Currently, Alpha is always 255 or the color is 0 so there is no need to
+ // demultiply the channels. If this DCHECK() is ever hit, the full
+ // (SkColorGetX(color) * 255 / a) will have to be added in the conversion.
+ DCHECK((0xFF == SkColorGetA(color)) || (0 == color));
+#ifndef _MSC_VER
+ return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color));
+#else
+ // 0BGR = ((ARGB -> BGRA) >> 8)
+ return (_byteswap_ulong(color) >> 8);
+#endif
+}
+
+} // namespace gfx
+
diff --git a/base/gfx/skia_utils.h b/base/gfx/skia_utils.h
new file mode 100644
index 0000000..8509072
--- /dev/null
+++ b/base/gfx/skia_utils.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#ifndef BASE_GFX_SKIA_UTILS_H__
+#define BASE_GFX_SKIA_UTILS_H__
+
+#include "SkColor.h"
+#include "SkShader.h"
+
+struct SkIRect;
+struct SkPoint;
+struct SkRect;
+typedef unsigned long DWORD;
+typedef DWORD COLORREF;
+typedef struct tagPOINT POINT;
+typedef struct tagRECT RECT;
+
+namespace gfx {
+
+// Converts a Skia point to a Windows POINT.
+POINT SkPointToPOINT(const SkPoint& point);
+
+// Converts a Windows RECT to a Skia rect.
+SkRect RECTToSkRect(const RECT& rect);
+
+// Converts a Windows RECT to a Skia rect.
+// Both use same in-memory format. Verified by COMPILE_ASSERT() in
+// skia_utils.cc.
+inline const SkIRect& RECTToSkIRect(const RECT& rect) {
+ return reinterpret_cast<const SkIRect&>(rect);
+}
+
+// Converts a Skia rect to a Windows RECT.
+// Both use same in-memory format. Verified by COMPILE_ASSERT() in
+// skia_utils.cc.
+inline const RECT& SkIRectToRECT(const SkIRect& rect) {
+ return reinterpret_cast<const RECT&>(rect);
+}
+
+// Creates a vertical gradient shader. The caller owns the shader.
+SkShader* CreateGradientShader(int start_point,
+ int end_point,
+ SkColor start_color,
+ SkColor end_color);
+
+// Converts COLORREFs (0BGR) to the ARGB layout Skia expects.
+SkColor COLORREFToSkColor(COLORREF color);
+
+// Converts ARGB to COLORREFs (0BGR).
+COLORREF SkColorToCOLORREF(SkColor color);
+
+} // namespace gfx
+
+#endif
+
diff --git a/base/gfx/skia_utils_mac.cc b/base/gfx/skia_utils_mac.cc
new file mode 100644
index 0000000..b5962c3
--- /dev/null
+++ b/base/gfx/skia_utils_mac.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "base/gfx/skia_utils_mac.h"
+
+#include "base/logging.h"
+#include "SkMatrix.h"
+#include "SkRect.h"
+
+namespace gfx {
+
+CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix) {
+ // CGAffineTransforms don't support perspective transforms, so make sure
+ // we don't get those.
+ DCHECK(matrix[SkMatrix::kMPersp0] == 0.0f);
+ DCHECK(matrix[SkMatrix::kMPersp1] == 0.0f);
+ DCHECK(matrix[SkMatrix::kMPersp2] == 1.0f);
+
+ return CGAffineTransformMake(matrix[SkMatrix::kMScaleX],
+ matrix[SkMatrix::kMSkewY],
+ matrix[SkMatrix::kMSkewX],
+ matrix[SkMatrix::kMScaleY],
+ matrix[SkMatrix::kMTransX],
+ matrix[SkMatrix::kMTransY]);
+}
+
+SkIRect CGRectToSkIRect(const CGRect& rect) {
+ SkIRect sk_rect = {
+ SkScalarRound(rect.origin.x),
+ SkScalarRound(rect.origin.y),
+ SkScalarRound(rect.origin.x + rect.size.width),
+ SkScalarRound(rect.origin.y + rect.size.height)
+ };
+ return sk_rect;
+}
+
+SkRect CGRectToSkRect(const CGRect& rect) {
+ SkRect sk_rect = {
+ rect.origin.x,
+ rect.origin.y,
+ rect.origin.x + rect.size.width,
+ rect.origin.y + rect.size.height,
+ };
+ return sk_rect;
+}
+
+CGRect SkIRectToCGRect(const SkIRect& rect) {
+ CGRect cg_rect = {
+ { rect.fLeft, rect.fTop },
+ { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop }
+ };
+ return cg_rect;
+}
+
+CGRect SkRectToCGRect(const SkRect& rect) {
+ CGRect cg_rect = {
+ { rect.fLeft, rect.fTop },
+ { rect.fRight - rect.fLeft, rect.fBottom - rect.fTop }
+ };
+ return cg_rect;
+}
+
+// Converts CGColorRef to the ARGB layout Skia expects.
+SkColor CGColorRefToSkColor(CGColorRef color) {
+ DCHECK(CGColorGetNumberOfComponents(color) == 4);
+ const CGFloat *components = CGColorGetComponents(color);
+ return SkColorSetARGB(SkScalarRound(255.0 * components[3]), // alpha
+ SkScalarRound(255.0 * components[0]), // red
+ SkScalarRound(255.0 * components[1]), // green
+ SkScalarRound(255.0 * components[2])); // blue
+}
+
+// Converts ARGB to CGColorRef.
+CGColorRef SkColorToCGColorRef(SkColor color) {
+ return CGColorCreateGenericRGB(SkColorGetR(color) / 255.0,
+ SkColorGetG(color) / 255.0,
+ SkColorGetB(color) / 255.0,
+ SkColorGetA(color) / 255.0);
+}
+
+} // namespace gfx
+
diff --git a/base/gfx/skia_utils_mac.h b/base/gfx/skia_utils_mac.h
new file mode 100644
index 0000000..a99905f
--- /dev/null
+++ b/base/gfx/skia_utils_mac.h
@@ -0,0 +1,51 @@
+// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+
+#ifndef BASE_GFX_SKIA_UTILS_MAC_H__
+#define BASE_GFX_SKIA_UTILS_MAC_H__
+
+#include "SkColor.h"
+#include <CoreGraphics/CGColor.h>
+
+struct SkMatrix;
+struct SkIRect;
+struct SkPoint;
+struct SkRect;
+
+namespace gfx {
+
+// Converts a Skia point to a CoreGraphics CGPoint.
+// Both use same in-memory format.
+inline const CGPoint& SkPointToCGPoint(const SkPoint& point) {
+ return reinterpret_cast<const CGPoint&>(point);
+}
+
+// Converts a CoreGraphics point to a Skia CGPoint.
+// Both use same in-memory format.
+inline const SkPoint& CGPointToSkPoint(const CGPoint& point) {
+ return reinterpret_cast<const SkPoint&>(point);
+}
+
+// Matrix converters.
+CGAffineTransform SkMatrixToCGAffineTransform(const SkMatrix& matrix);
+
+// Rectangle converters.
+SkRect CGRectToSkRect(const CGRect& rect);
+SkIRect CGRectToSkIRect(const CGRect& rect);
+
+// Converts a Skia rect to a CoreGraphics CGRect.
+CGRect SkIRectToCGRect(const SkIRect& rect);
+CGRect SkRectToCGRect(const SkRect& rect);
+
+// Converts CGColorRef to the ARGB layout Skia expects.
+SkColor CGColorRefToSkColor(CGColorRef color);
+
+// Converts ARGB to CGColorRef.
+CGColorRef SkColorToCGColorRef(SkColor color);
+
+} // namespace gfx
+
+#endif
+