diff options
author | glen@chromium.org <glen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-06 04:40:23 +0000 |
---|---|---|
committer | glen@chromium.org <glen@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-05-06 04:40:23 +0000 |
commit | d7dd9842e776eb4d512355d3b4eb1b2918cc2ea1 (patch) | |
tree | 34cce0405b64335470d5a7d7c077d06916d2f3a3 /skia/ext | |
parent | f8fc5885250b5cbe1026b13b23452d278b8c058e (diff) | |
download | chromium_src-d7dd9842e776eb4d512355d3b4eb1b2918cc2ea1.zip chromium_src-d7dd9842e776eb4d512355d3b4eb1b2918cc2ea1.tar.gz chromium_src-d7dd9842e776eb4d512355d3b4eb1b2918cc2ea1.tar.bz2 |
Redo of http://codereview.chromium.org/100097 to make work on Linux and Mac.
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@15380 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'skia/ext')
-rw-r--r-- | skia/ext/image_operations.cc | 211 | ||||
-rw-r--r-- | skia/ext/image_operations.h | 37 | ||||
-rw-r--r-- | skia/ext/image_operations_unittest.cc | 254 | ||||
-rw-r--r-- | skia/ext/skia_utils.cc | 100 | ||||
-rw-r--r-- | skia/ext/skia_utils.h | 6 | ||||
-rw-r--r-- | skia/ext/skia_utils_unittest.cc | 47 |
6 files changed, 650 insertions, 5 deletions
diff --git a/skia/ext/image_operations.cc b/skia/ext/image_operations.cc index 4a1438b..a6ccec4 100644 --- a/skia/ext/image_operations.cc +++ b/skia/ext/image_operations.cc @@ -15,6 +15,8 @@ #include "base/stack_container.h" #include "SkBitmap.h" #include "skia/ext/convolver.h" +#include "skia/include/SkColorPriv.h" +#include "skia/ext/skia_utils.h" namespace skia { @@ -263,7 +265,7 @@ SkBitmap ImageOperations::Resize(const SkBitmap& source, "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 + // return empty. if (source.width() < 1 || source.height() < 1 || dest_width < 1 || dest_height < 1) return SkBitmap(); @@ -315,11 +317,10 @@ SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first, // 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) { + if (alpha < alpha_min) return first; - } else if (alpha > alpha_max) { + else if (alpha > alpha_max) return second; - } SkAutoLockPixels lock_first(first); SkAutoLockPixels lock_second(second); @@ -361,5 +362,207 @@ SkBitmap ImageOperations::CreateBlendedBitmap(const SkBitmap& first, return blended; } +// static +SkBitmap ImageOperations::CreateMaskedBitmap(const SkBitmap& rgb, + const SkBitmap& alpha) { + DCHECK(rgb.width() == alpha.width()); + DCHECK(rgb.height() == alpha.height()); + DCHECK(rgb.bytesPerPixel() == alpha.bytesPerPixel()); + DCHECK(rgb.config() == SkBitmap::kARGB_8888_Config); + DCHECK(alpha.config() == SkBitmap::kARGB_8888_Config); + + SkBitmap masked; + masked.setConfig(SkBitmap::kARGB_8888_Config, rgb.width(), rgb.height(), 0); + masked.allocPixels(); + masked.eraseARGB(0, 0, 0, 0); + + SkAutoLockPixels lock_rgb(rgb); + SkAutoLockPixels lock_alpha(alpha); + SkAutoLockPixels lock_masked(masked); + + for (int y = 0; y < rgb.height(); y++) { + uint32* rgb_row = rgb.getAddr32(0, y); + uint32* alpha_row = alpha.getAddr32(0, y); + uint32* dst_row = masked.getAddr32(0, y); + + for (int x = 0; x < rgb.width(); x++) { + uint32 alpha_pixel = alpha_row[x]; + uint32 rgb_pixel = rgb_row[x]; + + int alpha = SkColorGetA(alpha_pixel); + dst_row[x] = SkColorSetARGB(alpha, + SkAlphaMul(SkColorGetR(rgb_pixel), alpha), + SkAlphaMul(SkColorGetG(rgb_pixel), alpha), + SkAlphaMul(SkColorGetB(rgb_pixel), alpha)); + } + } + + return masked; +} + +SkBitmap ImageOperations::CreateBlurredBitmap(const SkBitmap& bitmap, + int blur_amount ) { + DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config); + + // Blur factor (1 divided by how many pixels the blur takes place over). + double v = 1.0 / pow(static_cast<double>(blur_amount * 2 + 1), 2); + + SkBitmap blurred; + blurred.setConfig(SkBitmap::kARGB_8888_Config, bitmap.width(), + bitmap.height(), 0); + blurred.allocPixels(); + blurred.eraseARGB(0, 0, 0, 0); + + SkAutoLockPixels lock_bitmap(bitmap); + SkAutoLockPixels lock_blurred(blurred); + + // Loop through every pixel in the image. + for (int y = 0; y < bitmap.height(); y++) { // Skip top and bottom edges. + uint32* dst_row = blurred.getAddr32(0, y); + + for (int x = 0; x < bitmap.width(); x++) { // Skip left and right edges. + // Sums for this pixel. + double a = 0; + double r = 0; + double g = 0; + double b = 0; + + for (int ky = -blur_amount; ky <= blur_amount; ky++) { + for (int kx = -blur_amount; kx <= blur_amount; kx++) { + // Calculate the adjacent pixel for this kernel point. Blurs + // are wrapped. + int bx = (x + kx) % bitmap.width(); + while (bx < 0) + bx += bitmap.width(); + int by = (y + ky) % bitmap.height(); + while (by < 0) + by += bitmap.height(); + + uint32 src_pixel = bitmap.getAddr32(0, by)[bx]; + + a += v * static_cast<double>(SkColorGetA(src_pixel)); + r += v * static_cast<double>(SkColorGetR(src_pixel)); + g += v * static_cast<double>(SkColorGetG(src_pixel)); + b += v * static_cast<double>(SkColorGetB(src_pixel)); + } + } + + dst_row[x] = SkColorSetARGB( + static_cast<int>(a), + static_cast<int>(r), + static_cast<int>(g), + static_cast<int>(b)); + } + } + + return blurred; +} + +// static +SkBitmap ImageOperations::CreateHSLShiftedBitmap(const SkBitmap& bitmap, + float hsl_shift[3]) { + DCHECK(bitmap.empty() == false); + DCHECK(bitmap.config() == SkBitmap::kARGB_8888_Config); + + SkBitmap shifted; + shifted.setConfig(SkBitmap::kARGB_8888_Config, bitmap.width(), + bitmap.height(), 0); + shifted.allocPixels(); + shifted.eraseARGB(0, 0, 0, 0); + shifted.setIsOpaque(false); + + SkAutoLockPixels lock_bitmap(bitmap); + SkAutoLockPixels lock_shifted(shifted); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < bitmap.height(); y++) { + SkPMColor* pixels = bitmap.getAddr32(0, y); + SkPMColor* tinted_pixels = shifted.getAddr32(0, y); + + for (int x = 0; x < bitmap.width(); x++) { + // Convert the color of this pixel to HSL. + SkPMColor color = pixels[x]; + int alpha = SkColorGetA(color); + if (alpha != 255) { + // We have to normalize the colors as they're pre-multiplied. + double r = SkColorGetR(color) / static_cast<double>(alpha); + double g = SkColorGetG(color) / static_cast<double>(alpha); + double b = SkColorGetB(color) / static_cast<double>(alpha); + color = SkColorSetARGB(255, + static_cast<int>(r * 255.0), + static_cast<int>(g * 255.0), + static_cast<int>(b * 255.0)); + } + + float pixel_hsl[3]; + SkColorToHSL(color, pixel_hsl); + + // Replace the hue with the tint's hue. + if (hsl_shift[0] >= 0) + pixel_hsl[0] = hsl_shift[0]; + + // Change the saturation. + if (hsl_shift[1] >= 0) { + if (hsl_shift[1] <= 0.5) { + pixel_hsl[1] *= hsl_shift[1] * 2.0; + } else { + pixel_hsl[1] = pixel_hsl[1] + (1.0 - pixel_hsl[1]) * + ((hsl_shift[1] - 0.5) * 2.0); + } + } + + // Change the lightness. + if (hsl_shift[2] >= 0) { + if (hsl_shift[2] <= 0.5) { + pixel_hsl[2] *= hsl_shift[2] * 2.0; + } else { + pixel_hsl[2] = pixel_hsl[2] + (1.0 - pixel_hsl[2]) * + ((hsl_shift[2] - 0.5) * 2.0); + } + } + + // Convert back to RGB. + tinted_pixels[x] = HSLToSKColor(alpha, pixel_hsl); + } + } + + return shifted; +} + +// static +SkBitmap ImageOperations::CreateTiledBitmap(const SkBitmap& source, + int src_x, int src_y, + int dst_w, int dst_h) { + DCHECK(source.getConfig() == SkBitmap::kARGB_8888_Config); + + SkBitmap cropped; + cropped.setConfig(SkBitmap::kARGB_8888_Config, dst_w, dst_h, 0); + cropped.allocPixels(); + cropped.eraseARGB(0, 0, 0, 0); + + SkAutoLockPixels lock_source(source); + SkAutoLockPixels lock_cropped(cropped); + + // Loop through the pixels of the original bitmap. + for (int y = 0; y < dst_h; y++) { + int y_pix = (src_y + y) % source.height(); + while (y_pix < 0) + y_pix += source.height(); + + uint32* source_row = source.getAddr32(0, y_pix); + uint32* dst_row = cropped.getAddr32(0, y); + + for (int x = 0; x < dst_w; x++) { + int x_pix = (src_x + x) % source.width(); + while (x_pix < 0) + x_pix += source.width(); + + dst_row[x] = source_row[x_pix]; + } + } + + return cropped; +} + } // namespace skia diff --git a/skia/ext/image_operations.h b/skia/ext/image_operations.h index 5188dba..5c46b36 100644 --- a/skia/ext/image_operations.h +++ b/skia/ext/image_operations.h @@ -7,6 +7,7 @@ #include "base/basictypes.h" #include "base/gfx/rect.h" +#include "SkColor.h" class SkBitmap; @@ -46,13 +47,47 @@ class ImageOperations { ResizeMethod method, int dest_width, int dest_height); - // 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); + + // Create a bitmap that is the original bitmap masked out by the mask defined + // in the alpha bitmap. The images must use the kARGB_8888_Config config and + // be of equal dimensions. + static SkBitmap CreateMaskedBitmap(const SkBitmap& first, + const SkBitmap& alpha); + + // Blur a bitmap using an average-blur algorithm over the rectangle defined + // by |blur_amount|. The blur will wrap around image edges. + static SkBitmap CreateBlurredBitmap(const SkBitmap& bitmap, int blur_amount); + + // Shift a bitmap's HSL values. The shift values are in the range of 0-1, + // with the option to specify -1 for 'no change'. The shift values are + // defined as: + // hsl_shift[0] (hue): The absolute hue value for the image - 0 and 1 map + // to 0 and 360 on the hue color wheel (red). + // hsl_shift[1] (saturation): A saturation shift for the image, with the + // following key values: + // 0 = remove all color. + // 0.5 = leave unchanged. + // 1 = fully saturate the image. + // hsl_shift[2] (lightness): A lightness shift for the image, with the + // following key values: + // 0 = remove all lightness (make all pixels black). + // 0.5 = leave unchanged. + // 1 = full lightness (make all pixels white). + static SkBitmap CreateHSLShiftedBitmap(const SkBitmap& bitmap, + float hsl_shift[3]); + + // Create a bitmap that is cropped from another bitmap. This is special + // because it tiles the original bitmap, so your coordinates can extend + // outside the bounds of the original image. + static SkBitmap CreateTiledBitmap(const SkBitmap& bitmap, + int src_x, int src_y, + int dst_w, int dst_h); private: ImageOperations(); // Class for scoping only. }; diff --git a/skia/ext/image_operations_unittest.cc b/skia/ext/image_operations_unittest.cc index 27e3fcc..5ed69da 100644 --- a/skia/ext/image_operations_unittest.cc +++ b/skia/ext/image_operations_unittest.cc @@ -5,6 +5,7 @@ #include <stdlib.h> #include "skia/ext/image_operations.h" +#include "skia/include/SkColorPriv.h" #include "testing/gtest/include/gtest/gtest.h" #include "SkBitmap.h" @@ -145,3 +146,256 @@ TEST(ImageOperations, ResampleToSame) { } } } + +// Blend two bitmaps together at 50% alpha and verify that the result +// is the middle-blend of the two. +TEST(ImageOperations, CreateBlendedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src_a; + src_a.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src_a.allocPixels(); + + SkBitmap src_b; + src_b.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src_b.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src_a.getAddr32(x, y) = SkColorSetARGB(255, 0, i * 2 % 255, i % 255); + *src_b.getAddr32(x, y) = + SkColorSetARGB((255 - i) % 255, i % 255, i * 4 % 255, 0); + i++; + } + } + + // Shift to red. + SkBitmap blended = skia::ImageOperations::CreateBlendedBitmap( + src_a, src_b, 0.5); + SkAutoLockPixels srca_lock(src_a); + SkAutoLockPixels srcb_lock(src_b); + SkAutoLockPixels blended_lock(blended); + + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int i = y * src_w + x; + EXPECT_EQ((255 + ((255 - i) % 255)) / 2, + SkColorGetA(*blended.getAddr32(x, y))); + EXPECT_EQ(i % 255 / 2, + SkColorGetR(*blended.getAddr32(x, y))); + EXPECT_EQ(((i * 2) % 255 + (i * 4) % 255) / 2, + SkColorGetG(*blended.getAddr32(x, y))); + EXPECT_EQ(i % 255 / 2, + SkColorGetB(*blended.getAddr32(x, y))); + } + } +} + +// Test our masking functions. +TEST(ImageOperations, CreateMaskedBitmap) { + int src_w = 16, src_h = 16; + + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + // Generate alpha mask + SkBitmap alpha; + alpha.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + alpha.allocPixels(); + + unsigned char* src_data = + reinterpret_cast<unsigned char*>(alpha.getAddr32(0, 0)); + for (int i = 0; i < src_w * src_h; i++) { + src_data[i * 4] = SkColorSetARGB(i + 128 % 255, + i + 128 % 255, + i + 64 % 255, + i + 0 % 255); + } + + SkBitmap masked = skia::ImageOperations::CreateMaskedBitmap(src, alpha); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels masked_lock(masked); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + // Test that the alpha is equal. + SkColor src_pixel = *src.getAddr32(x, y); + SkColor alpha_pixel = *alpha.getAddr32(x, y); + SkColor masked_pixel = *masked.getAddr32(x, y); + + // Test that the alpha is equal. + int alpha = (alpha_pixel & 0xff000000) >> SK_A32_SHIFT; + EXPECT_EQ(alpha, (masked_pixel & 0xff000000) >> SK_A32_SHIFT); + + // Test that the colors are right - SkBitmaps have premultiplied alpha, + // so we can't just do a direct comparison. + EXPECT_EQ(SkColorGetR(masked_pixel), + SkAlphaMul(SkColorGetR(src_pixel), alpha)); + } + } +} + +// Testing blur without reimplementing the blur algorithm here is tough, +// so we just check to see if the pixels have moved in the direction we +// think they should move in (and also checking the wrapping behavior). +// This will allow us to tweak the blur algorithm to suit speed/visual +// needs without breaking the fundamentals. +TEST(ImageOperations, CreateBlurredBitmap) { + int src_w = 4, src_h = 4; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + int r = (y == 0) ? 255 : 0; // Make the top row red. + int g = (i % 2 == 0) ? 255 : 0; // Make green alternate in each pixel. + int b = (y == src_h - 1) ? 255 : 0; // Make the bottom row blue. + + *src.getAddr32(x, y) = SkColorSetARGB(255, r, g, b); + i++; + } + } + + // Perform a small blur (enough to shove the values in the direction we + // need - more would just be an unneccessary unit test slowdown). + SkBitmap blurred = skia::ImageOperations::CreateBlurredBitmap(src, 2); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels blurred_lock(blurred); + for (int y = 0, i = 0; y < src_w; y++) { + for (int x = 0; x < src_h; x++) { + SkColor src_pixel = *src.getAddr32(x, y); + SkColor blurred_pixel = *blurred.getAddr32(x, y); + if (y == 0) { + // We expect our red to have decreased, but our blue to have + // increased (from the wrapping from the bottom line). + EXPECT_TRUE(SkColorGetR(blurred_pixel) < SkColorGetR(src_pixel)); + EXPECT_TRUE(SkColorGetB(blurred_pixel) > SkColorGetB(src_pixel)); + } else if (y == src_h - 1) { + // Now for the opposite. + EXPECT_TRUE(SkColorGetB(blurred_pixel) < SkColorGetB(src_pixel)); + EXPECT_TRUE(SkColorGetR(blurred_pixel) > SkColorGetR(src_pixel)); + } + + // Expect the green channel to have moved towards the center (but + // not past it). + if (i % 2 == 0) { + EXPECT_LT(SkColorGetG(blurred_pixel), SkColorGetG(src_pixel)); + EXPECT_GE(SkColorGetG(blurred_pixel), static_cast<uint32>(128)); + } else { + EXPECT_GT(SkColorGetG(blurred_pixel), SkColorGetG(src_pixel)); + EXPECT_LE(SkColorGetG(blurred_pixel), static_cast<uint32>(128)); + } + + i++; + } + } +} + +// Make sure that when shifting a bitmap without any shift parameters, +// the end result is close enough to the original (rounding errors +// notwithstanding). +TEST(ImageOperations, CreateHSLShiftedBitmapToSame) { + int src_w = 4, src_h = 4; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkColorSetARGB(i + 128 % 255, + i + 128 % 255, i + 64 % 255, i + 0 % 255); + i++; + } + } + + float hsl[3] = { -1, -1, -1 }; + + SkBitmap shifted = skia::ImageOperations::CreateHSLShiftedBitmap(src, hsl); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels shifted_lock(shifted); + + for (int y = 0; y < src_w; y++) { + for (int x = 0; x < src_h; x++) { + SkColor src_pixel = *src.getAddr32(x, y); + SkColor shifted_pixel = *shifted.getAddr32(x, y); + EXPECT_TRUE(ColorsClose(src_pixel, shifted_pixel)); + } + } +} + +// Shift a blue bitmap to red. +TEST(ImageOperations, CreateHSLShiftedBitmapHueOnly) { + int src_w = 16, src_h = 16; + SkBitmap src; + src.setConfig(SkBitmap::kARGB_8888_Config, src_w, src_h); + src.allocPixels(); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + *src.getAddr32(x, y) = SkColorSetARGB(255, 0, 0, i % 255); + i++; + } + } + + // Shift to red. + float hsl[3] = { 0, -1, -1 }; + + SkBitmap shifted = skia::ImageOperations::CreateHSLShiftedBitmap(src, hsl); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels shifted_lock(shifted); + + for (int y = 0, i = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_TRUE(ColorsClose(*shifted.getAddr32(x, y), + SkColorSetARGB(255, i % 255, 0, 0))); + i++; + } + } +} + +// Test our cropping. +TEST(ImageOperations, CreateCroppedBitmap) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = skia::ImageOperations::CreateTiledBitmap(src, 4, 4, + 8, 8); + ASSERT_EQ(8, cropped.width()); + ASSERT_EQ(8, cropped.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels cropped_lock(cropped); + for (int y = 4; y < 12; y++) { + for (int x = 4; x < 12; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32(x - 4, y - 4)); + } + } +} + +// Test whether our cropping correctly wraps across image boundaries. +TEST(ImageOperations, CreateCroppedBitmapWrapping) { + int src_w = 16, src_h = 16; + SkBitmap src; + FillDataToBitmap(src_w, src_h, &src); + + SkBitmap cropped = skia::ImageOperations::CreateTiledBitmap( + src, src_w / 2, src_h / 2, src_w, src_h); + ASSERT_EQ(src_w, cropped.width()); + ASSERT_EQ(src_h, cropped.height()); + + SkAutoLockPixels src_lock(src); + SkAutoLockPixels cropped_lock(cropped); + for (int y = 0; y < src_h; y++) { + for (int x = 0; x < src_w; x++) { + EXPECT_EQ(*src.getAddr32(x, y), + *cropped.getAddr32((x + src_w / 2) % src_w, + (y + src_h / 2) % src_h)); + } + } +} + diff --git a/skia/ext/skia_utils.cc b/skia/ext/skia_utils.cc index 80386e6..1424957 100644 --- a/skia/ext/skia_utils.cc +++ b/skia/ext/skia_utils.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "skia/ext/skia_utils.h" +#include "skia/include/SkColorPriv.h" #include "SkGradientShader.h" @@ -21,5 +22,104 @@ SkShader* CreateGradientShader(int start_point, grad_points, grad_colors, NULL, 2, SkShader::kRepeat_TileMode); } +// Helper function for HSLToSKColor. +static inline double calcHue(double temp1, double temp2, double hueVal) { + if (hueVal < 0.0) + hueVal++; + else if (hueVal > 1.0) + hueVal--; + + if (hueVal * 6.0 < 1.0) + return temp1 + (temp2 - temp1) * hueVal * 6.0; + if (hueVal * 2.0 < 1.0) + return temp2; + if (hueVal * 3.0 < 2.0) + return temp1 + (temp2 - temp1) * (2.0 / 3.0 - hueVal) * 6.0; + + return temp1; +} + +SkPMColor HSLToSKColor(U8CPU alpha, float hsl[3]) { + double hue = SkScalarToDouble(hsl[0]); + double saturation = SkScalarToDouble(hsl[1]); + double lightness = SkScalarToDouble(hsl[2]); + double scaleFactor = 256.0; + + // If there's no color, we don't care about hue and can do everything based + // on brightness. + if (!saturation) { + U8CPU lightness; + + if (hsl[2] < 0) + lightness = 0; + else if (hsl[2] >= SK_Scalar1) + lightness = 255; + else + lightness = SkScalarToFixed(hsl[2]) >> 8; + + unsigned greyValue = SkAlphaMul(lightness, alpha); + return SkColorSetARGB(alpha, greyValue, greyValue, greyValue); + } + + double temp2 = (lightness < 0.5) ? + lightness * (1.0 + saturation) : + lightness + saturation - (lightness * saturation); + double temp1 = 2.0 * lightness - temp2; + + double rh = calcHue(temp1, temp2, hue + 1.0 / 3.0); + double gh = calcHue(temp1, temp2, hue); + double bh = calcHue(temp1, temp2, hue - 1.0 / 3.0); + + return SkColorSetARGB(alpha, + SkAlphaMul(static_cast<int>(rh * scaleFactor), alpha), + SkAlphaMul(static_cast<int>(gh * scaleFactor), alpha), + SkAlphaMul(static_cast<int>(bh * scaleFactor), alpha)); +} + +void SkColorToHSL(SkPMColor c, float hsl[3]) { + double r = SkColorGetR(c) / 255.0; + double g = SkColorGetG(c) / 255.0; + double b = SkColorGetB(c) / 255.0; + + double h, s, l; + + double vmax = r > g ? r : g; + vmax = vmax > b ? vmax : b; + double vmin = r < g ? r : g; + vmin = vmin < b ? vmin : b; + double delta = vmax - vmin; + + l = (vmax + vmin) / 2; + + if (delta == 0) { + h = 0; + s = 0; + } else { + if (l < 0.5) + s = delta / (vmax + vmin); + else + s = delta / (2 - vmax - vmin); + + double dr = (((vmax - r) / 6.0) + (delta / 2.0)) / delta; + double dg = (((vmax - g) / 6.0) + (delta / 2.0)) / delta; + double db = (((vmax - b) / 6.0) + (delta / 2.0)) / delta; + + if (r == vmax) + h = db - dg; + else if (g == vmax) + h = (1.0 / 3.0) + dr - db; + else if (b == vmax) + h = (2.0 / 3.0) + dg - dr; + + if (h < 0) h += 1; + if (h > 1) h -= 1; + } + + hsl[0] = h; + hsl[1] = s; + hsl[2] = l; +} + + } // namespace skia diff --git a/skia/ext/skia_utils.h b/skia/ext/skia_utils.h index 4ab40f3..98fa1b0 100644 --- a/skia/ext/skia_utils.h +++ b/skia/ext/skia_utils.h @@ -21,6 +21,12 @@ SkShader* CreateGradientShader(int start_point, SkColor start_color, SkColor end_color); +// Convert a premultiplied SkColor to a HSL value. +void SkColorToHSL(SkPMColor c, float hsl[3]); + +// Convert a HSL color to a premultiplied SkColor. +SkPMColor HSLToSKColor(U8CPU alpha, float hsl[3]); + } // namespace skia #endif // SKIA_EXT_SKIA_UTILS_H_ diff --git a/skia/ext/skia_utils_unittest.cc b/skia/ext/skia_utils_unittest.cc new file mode 100644 index 0000000..f7b00b1 --- /dev/null +++ b/skia/ext/skia_utils_unittest.cc @@ -0,0 +1,47 @@ +// 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/skia_utils.h" +#include "skia/include/SkColorPriv.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "SkBitmap.h" + +TEST(SkiaUtils, SkColorToHSLRed) { + SkColor red = SkColorSetARGB(255, 255, 0, 0); + SkScalar hsl[3]; + skia::SkColorToHSL(red, hsl); + EXPECT_EQ(hsl[0], 0); + EXPECT_EQ(hsl[1], 1); + EXPECT_EQ(hsl[2], 0.5); +} + +TEST(SkiaUtils, SkColorToHSLGrey) { + SkColor red = SkColorSetARGB(255, 128, 128, 128); + SkScalar hsl[3]; + skia::SkColorToHSL(red, hsl); + EXPECT_EQ(hsl[0], 0); + EXPECT_EQ(hsl[1], 0); + EXPECT_EQ(static_cast<int>(hsl[2] * 100), + static_cast<int>(0.5 * 100)); // Accurate to two decimal places. +} + +TEST(SkiaUtils, HSLToSkColorWithAlpha) { + // Premultiplied alpha - this is full red. + SkColor red = SkColorSetARGB(128, 128, 0, 0); + + SkScalar hsl[3] = { + SkDoubleToScalar(0), + SkDoubleToScalar(1), + SkDoubleToScalar(0.5), + }; + + SkColor result = skia::HSLToSKColor(128, hsl); + EXPECT_EQ(SkColorGetA(red), SkColorGetA(result)); + EXPECT_EQ(SkColorGetR(red), SkColorGetR(result)); + EXPECT_EQ(SkColorGetG(red), SkColorGetG(result)); + EXPECT_EQ(SkColorGetB(red), SkColorGetB(result)); +} + |