diff options
author | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-02 22:35:42 +0000 |
---|---|---|
committer | brettw@google.com <brettw@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2008-12-02 22:35:42 +0000 |
commit | 004a96a015b5dfcadaad4ea1b192c97c415b75de (patch) | |
tree | cb32d3809e4366c07e837416a106a3083d2690a5 /skia | |
parent | f8ed412acb7bcab9855d0ebd9d29f7e38a3a8f92 (diff) | |
download | chromium_src-004a96a015b5dfcadaad4ea1b192c97c415b75de.zip chromium_src-004a96a015b5dfcadaad4ea1b192c97c415b75de.tar.gz chromium_src-004a96a015b5dfcadaad4ea1b192c97c415b75de.tar.bz2 |
Move convolver, image_operations, and skia_utils from base/gfx to skia/ext.
This changes the namespace in those files from "gfx" to "skia".
I split skia_utils into two parts, the Windows specific part is now in a separate file called skia_utils_win.
There were several obsolete includes of these headers which I removed. I also removed img_resize_perftest which isn't used and has bitrotted.
Review URL: http://codereview.chromium.org/12842
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@6248 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia')
-rw-r--r-- | skia/SConscript | 6 | ||||
-rwxr-xr-x | skia/ext/bitmap_platform_device_mac.cc | 2 | ||||
-rw-r--r-- | skia/ext/convolver.cc | 336 | ||||
-rw-r--r-- | skia/ext/convolver.h | 137 | ||||
-rw-r--r-- | skia/ext/convolver_unittest.cc | 128 | ||||
-rw-r--r-- | skia/ext/image_operations.cc | 366 | ||||
-rw-r--r-- | skia/ext/image_operations.h | 63 | ||||
-rw-r--r-- | skia/ext/image_operations_unittest.cc | 149 | ||||
-rwxr-xr-x | skia/ext/platform_device_mac.cc | 2 | ||||
-rw-r--r-- | skia/ext/platform_device_win.cc | 5 | ||||
-rw-r--r-- | skia/ext/skia_utils.cc | 26 | ||||
-rw-r--r-- | skia/ext/skia_utils.h | 23 | ||||
-rw-r--r-- | skia/ext/skia_utils_mac.cc | 83 | ||||
-rw-r--r-- | skia/ext/skia_utils_mac.h | 51 | ||||
-rw-r--r-- | skia/ext/skia_utils_win.cc | 59 | ||||
-rw-r--r-- | skia/ext/skia_utils_win.h | 56 | ||||
-rw-r--r-- | skia/ext/vector_device.cc | 6 | ||||
-rw-r--r-- | skia/skia.vcproj | 32 |
18 files changed, 1525 insertions, 5 deletions
diff --git a/skia/SConscript b/skia/SConscript index 00b8e1b..fbafc92 100644 --- a/skia/SConscript +++ b/skia/SConscript @@ -152,6 +152,10 @@ input_files = [ 'sgl/SkUtils.cpp', 'sgl/SkWriter32.cpp', 'sgl/SkXfermode.cpp', + + 'ext/convolver.cc', + 'ext/image_operations.cc', + 'ext/skia_utils.cc', ] if env['PLATFORM'] == 'posix': @@ -176,11 +180,13 @@ if env['PLATFORM'] == 'darwin': input_files.append('ext/bitmap_platform_device_mac.cc') input_files.append('ext/platform_canvas_mac.cc') input_files.append('ext/platform_device_mac.cc') + input_files.append('ext/skia_utils_mac.cc') if env['PLATFORM'] == 'win32': input_files.append('ext/bitmap_platform_device_win.cc') input_files.append('ext/platform_canvas_win.cc') input_files.append('ext/platform_device_win.cc') + input_files.append('ext/skia_utils_win.cc') input_files.append('ext/vector_canvas.cc') input_files.append('ext/vector_device.cc') diff --git a/skia/ext/bitmap_platform_device_mac.cc b/skia/ext/bitmap_platform_device_mac.cc index d4458d7..52ce586 100755 --- a/skia/ext/bitmap_platform_device_mac.cc +++ b/skia/ext/bitmap_platform_device_mac.cc @@ -10,8 +10,8 @@ #include "SkRegion.h" #include "SkUtils.h" -#include "base/gfx/skia_utils_mac.h" #include "base/logging.h" +#include "skia/ext/skia_utils_mac.h" namespace gfx { diff --git a/skia/ext/convolver.cc b/skia/ext/convolver.cc new file mode 100644 index 0000000..c854019 --- /dev/null +++ b/skia/ext/convolver.cc @@ -0,0 +1,336 @@ +// 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 "skia/ext/convolver.h" + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace skia { + +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 skia + diff --git a/skia/ext/convolver.h b/skia/ext/convolver.h new file mode 100644 index 0000000..62c1e30 --- /dev/null +++ b/skia/ext/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 SKIA_EXT_CONVOLVER_H_ +#define SKIA_EXT_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 skia { + +// 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 skia + +#endif // SKIA_EXT_CONVOLVER_H_ + diff --git a/skia/ext/convolver_unittest.cc b/skia/ext/convolver_unittest.cc new file mode 100644 index 0000000..c0ae734 --- /dev/null +++ b/skia/ext/convolver_unittest.cc @@ -0,0 +1,128 @@ +// 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 "skia/ext/convolver.h" + +#include "testing/gtest/include/gtest/gtest.h" + +namespace skia { + +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 skia + diff --git a/skia/ext/image_operations.cc b/skia/ext/image_operations.cc new file mode 100644 index 0000000..9eab6e5 --- /dev/null +++ b/skia/ext/image_operations.cc @@ -0,0 +1,366 @@ +// 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 "skia/ext/image_operations.h" + +#include "base/gfx/rect.h" +#include "base/gfx/size.h" +#include "base/logging.h" +#include "base/stack_container.h" +#include "skia/ext/convolver.h" +#include "SkBitmap.h" + +// TODO(brettw) this should be removed when the base/gfx dependencies are +// removed. +using namespace gfx; + +namespace skia { + +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(2, 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 skia + diff --git a/skia/ext/image_operations.h b/skia/ext/image_operations.h new file mode 100644 index 0000000..60a8da7c --- /dev/null +++ b/skia/ext/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 SKIA_EXT_IMAGE_OPERATIONS_H_ +#define SKIA_EXT_IMAGE_OPERATIONS_H_ + +#include "base/basictypes.h" +#include "base/gfx/rect.h" + +class SkBitmap; + +namespace skia { + +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 gfx::Size& dest_size, + const gfx::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 gfx::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 skia + +#endif // SKIA_EXT_IMAGE_OPERATIONS_H_ + diff --git a/skia/ext/image_operations_unittest.cc b/skia/ext/image_operations_unittest.cc new file mode 100644 index 0000000..7a52d1f --- /dev/null +++ b/skia/ext/image_operations_unittest.cc @@ -0,0 +1,149 @@ +// 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 "skia/ext/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 = skia::ImageOperations::Resize( + src, skia::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 = skia::ImageOperations::Resize( + src, skia::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 = skia::ImageOperations::Resize( + src, skia::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 = skia::ImageOperations::Resize( + src, skia::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/skia/ext/platform_device_mac.cc b/skia/ext/platform_device_mac.cc index 9539e0f..3cc6fb4 100755 --- a/skia/ext/platform_device_mac.cc +++ b/skia/ext/platform_device_mac.cc @@ -5,7 +5,7 @@ #include "skia/ext/bitmap_platform_device_mac.h" #include "base/logging.h" -#include "base/gfx/skia_utils_mac.h" +#include "skia/ext/skia_utils_mac.h" #include "SkMatrix.h" #include "SkPath.h" #include "SkUtils.h" diff --git a/skia/ext/platform_device_win.cc b/skia/ext/platform_device_win.cc index 15201c0..1e8301d 100644 --- a/skia/ext/platform_device_win.cc +++ b/skia/ext/platform_device_win.cc @@ -5,11 +5,14 @@ #include "skia/ext/platform_device_win.h" #include "base/logging.h" -#include "base/gfx/skia_utils.h" #include "SkMatrix.h" #include "SkPath.h" #include "SkRegion.h" #include "SkUtils.h" +#include "skia/ext/skia_utils_win.h" + +// TODO(brettw) remove this when this file is converted to namespace skia. +using namespace skia; namespace gfx { diff --git a/skia/ext/skia_utils.cc b/skia/ext/skia_utils.cc new file mode 100644 index 0000000..651670e --- /dev/null +++ b/skia/ext/skia_utils.cc @@ -0,0 +1,26 @@ +// 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 "skia/ext/skia_utils_win.h" + +#include "SkRect.h" +#include "SkGradientShader.h" + +namespace skia { + +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); +} + +} // namespace skia + diff --git a/skia/ext/skia_utils.h b/skia/ext/skia_utils.h new file mode 100644 index 0000000..acfab28 --- /dev/null +++ b/skia/ext/skia_utils.h @@ -0,0 +1,23 @@ +// 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. + +// This file contains cross-platform Skia helper routines. + +#ifndef SKIA_EXT_SKIA_UTILS_H_ +#define SKIA_EXT_SKIA_UTILS_H_ + +#include "SkShader.h" + +namespace skia { + +// Creates a vertical gradient shader. The caller owns the shader. +SkShader* CreateGradientShader(int start_point, + int end_point, + SkColor start_color, + SkColor end_color); + +} // namespace skia + +#endif // SKIA_EXT_SKIA_UTILS_H_ + diff --git a/skia/ext/skia_utils_mac.cc b/skia/ext/skia_utils_mac.cc new file mode 100644 index 0000000..a7610aa --- /dev/null +++ b/skia/ext/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 "skia/ext/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/skia/ext/skia_utils_mac.h b/skia/ext/skia_utils_mac.h new file mode 100644 index 0000000..a99905f --- /dev/null +++ b/skia/ext/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 + diff --git a/skia/ext/skia_utils_win.cc b/skia/ext/skia_utils_win.cc new file mode 100644 index 0000000..f695a63 --- /dev/null +++ b/skia/ext/skia_utils_win.cc @@ -0,0 +1,59 @@ +// 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 <windows.h> + +#include "skia/ext/skia_utils_win.h" + +#include "SkRect.h" +#include "SkGradientShader.h" + +namespace { + +// Compiler assert from base/logging.h +template <bool> +struct CompileAssert { +}; +#undef COMPILE_ASSERT +#define COMPILE_ASSERT(expr, msg) \ + typedef CompileAssert<(bool(expr))> msg[bool(expr) ? 1 : -1] + +COMPILE_ASSERT(SK_OFFSETOF(RECT, left) == SK_OFFSETOF(SkIRect, fLeft), o1); +COMPILE_ASSERT(SK_OFFSETOF(RECT, top) == SK_OFFSETOF(SkIRect, fTop), o2); +COMPILE_ASSERT(SK_OFFSETOF(RECT, right) == SK_OFFSETOF(SkIRect, fRight), o3); +COMPILE_ASSERT(SK_OFFSETOF(RECT, bottom) == SK_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 skia { + +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; +} + +SkColor COLORREFToSkColor(COLORREF color) { + return SkColorSetRGB(GetRValue(color), GetGValue(color), GetBValue(color)); +} + +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 is needed, (SkColorGetX(color) * 255 / a) + // will have to be added in the conversion. + return RGB(SkColorGetR(color), SkColorGetG(color), SkColorGetB(color)); +} + +} // namespace skia + diff --git a/skia/ext/skia_utils_win.h b/skia/ext/skia_utils_win.h new file mode 100644 index 0000000..297608a --- /dev/null +++ b/skia/ext/skia_utils_win.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 SKIA_EXT_SKIA_UTILS_WIN_H_ +#define SKIA_EXT_SKIA_UTILS_WIN_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 skia { + +// 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 skia + +#endif // SKIA_EXT_SKIA_UTILS_WIN_H_ + diff --git a/skia/ext/vector_device.cc b/skia/ext/vector_device.cc index a587e0c..2dc0c30 100644 --- a/skia/ext/vector_device.cc +++ b/skia/ext/vector_device.cc @@ -5,12 +5,14 @@ #include "skia/ext/vector_device.h" #include "base/gfx/gdi_util.h" -#include "base/gfx/skia_utils.h" #include "base/logging.h" #include "base/scoped_handle.h" - +#include "skia/ext/skia_utils_win.h" #include "SkUtils.h" +// TOOD(brettw) remove this when this file is changed over to namespace skia. +using namespace skia; + namespace gfx { VectorDevice* VectorDevice::create(HDC dc, int width, int height) { diff --git a/skia/skia.vcproj b/skia/skia.vcproj index 5e06d73..65453c1 100644 --- a/skia/skia.vcproj +++ b/skia/skia.vcproj @@ -1321,6 +1321,22 @@ > </File> <File + RelativePath=".\ext\convolver.cc" + > + </File> + <File + RelativePath=".\ext\convolver.h" + > + </File> + <File + RelativePath=".\ext\image_operations.cc" + > + </File> + <File + RelativePath=".\ext\image_operations.h" + > + </File> + <File RelativePath=".\ext\platform_canvas.h" > </File> @@ -1345,6 +1361,22 @@ > </File> <File + RelativePath=".\ext\skia_utils.cc" + > + </File> + <File + RelativePath=".\ext\skia_utils.h" + > + </File> + <File + RelativePath=".\ext\skia_utils_win.cc" + > + </File> + <File + RelativePath=".\ext\skia_utils_win.h" + > + </File> + <File RelativePath=".\ext\vector_canvas.cc" > </File> |