OLD | NEW |
1 | 1 |
2 /* | 2 /* |
3 * Copyright 2006 The Android Open Source Project | 3 * Copyright 2006 The Android Open Source Project |
4 * | 4 * |
5 * Use of this source code is governed by a BSD-style license that can be | 5 * Use of this source code is governed by a BSD-style license that can be |
6 * found in the LICENSE file. | 6 * found in the LICENSE file. |
7 */ | 7 */ |
8 | 8 |
9 | 9 |
10 #include "SkBlurMask.h" | 10 #include "SkBlurMask.h" |
11 #include "SkMath.h" | 11 #include "SkMath.h" |
12 #include "SkTemplates.h" | 12 #include "SkTemplates.h" |
13 #include "SkEndian.h" | 13 #include "SkEndian.h" |
14 | 14 |
15 const SkScalar SkBlurMask::kBlurRadiusFudgeFactor = SkFloatToScalar(.57735f); | 15 |
| 16 SkScalar SkBlurMask::ConvertRadiusToSigma(SkScalar radius) { |
| 17 // This constant approximates the scaling done in the software path's |
| 18 // "high quality" mode, in SkBlurMask::Blur() (1 / sqrt(3)). |
| 19 // IMHO, it actually should be 1: we blur "less" than we should do |
| 20 // according to the CSS and canvas specs, simply because Safari does the sam
e. |
| 21 // Firefox used to do the same too, until 4.0 where they fixed it. So at so
me |
| 22 // point we should probably get rid of these scaling constants and rebaselin
e |
| 23 // all the blur tests. |
| 24 static const SkScalar kBLUR_SIGMA_SCALE = SkFloatToScalar(0.57735f); |
| 25 |
| 26 return radius ? kBLUR_SIGMA_SCALE * radius + 0.5f : 0.0f; |
| 27 } |
16 | 28 |
17 #define UNROLL_SEPARABLE_LOOPS | 29 #define UNROLL_SEPARABLE_LOOPS |
18 | 30 |
19 /** | 31 /** |
20 * This function performs a box blur in X, of the given radius. If the | 32 * This function performs a box blur in X, of the given radius. If the |
21 * "transpose" parameter is true, it will transpose the pixels on write, | 33 * "transpose" parameter is true, it will transpose the pixels on write, |
22 * such that X and Y are swapped. Reads are always performed from contiguous | 34 * such that X and Y are swapped. Reads are always performed from contiguous |
23 * memory in X, for speed. The destination buffer (dst) must be at least | 35 * memory in X, for speed. The destination buffer (dst) must be at least |
24 * (width + leftRadius + rightRadius) * height bytes in size. | 36 * (width + leftRadius + rightRadius) * height bytes in size. |
25 * | 37 * |
(...skipping 440 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
466 | 478 |
467 // we use a local function to wrap the class static method to work around | 479 // we use a local function to wrap the class static method to work around |
468 // a bug in gcc98 | 480 // a bug in gcc98 |
469 void SkMask_FreeImage(uint8_t* image); | 481 void SkMask_FreeImage(uint8_t* image); |
470 void SkMask_FreeImage(uint8_t* image) { | 482 void SkMask_FreeImage(uint8_t* image) { |
471 SkMask::FreeImage(image); | 483 SkMask::FreeImage(image); |
472 } | 484 } |
473 | 485 |
474 bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, | 486 bool SkBlurMask::Blur(SkMask* dst, const SkMask& src, |
475 SkScalar radius, Style style, Quality quality, | 487 SkScalar radius, Style style, Quality quality, |
476 SkIPoint* margin) | 488 SkIPoint* margin) { |
477 { | 489 return SkBlurMask::BoxBlur(dst, src, |
| 490 SkBlurMask::ConvertRadiusToSigma(radius), |
| 491 style, quality, margin); |
| 492 } |
| 493 |
| 494 bool SkBlurMask::BoxBlur(SkMask* dst, const SkMask& src, |
| 495 SkScalar sigma, Style style, Quality quality, |
| 496 SkIPoint* margin) { |
478 | 497 |
479 if (src.fFormat != SkMask::kA8_Format) { | 498 if (src.fFormat != SkMask::kA8_Format) { |
480 return false; | 499 return false; |
481 } | 500 } |
482 | 501 |
483 // Force high quality off for small radii (performance) | 502 // Force high quality off for small radii (performance) |
484 if (radius < SkIntToScalar(3)) { | 503 if (sigma <= SkIntToScalar(2)) { |
485 quality = kLow_Quality; | 504 quality = kLow_Quality; |
486 } | 505 } |
487 | 506 |
| 507 SkScalar passRadius; |
| 508 if (kHigh_Quality == quality) { |
| 509 // For the high quality path the 3 pass box blur kernel width is |
| 510 // 6*rad+1 while the full Gaussian width is 6*sigma. |
| 511 passRadius = sigma - (1/6.0f); |
| 512 } else { |
| 513 // For the low quality path we only attempt to cover 3*sigma of the |
| 514 // Gaussian blur area (1.5*sigma on each side). The single pass box |
| 515 // blur's kernel size is 2*rad+1. |
| 516 passRadius = 1.5f*sigma - 0.5f; |
| 517 } |
| 518 |
488 // highQuality: use three box blur passes as a cheap way | 519 // highQuality: use three box blur passes as a cheap way |
489 // to approximate a Gaussian blur | 520 // to approximate a Gaussian blur |
490 int passCount = (kHigh_Quality == quality) ? 3 : 1; | 521 int passCount = (kHigh_Quality == quality) ? 3 : 1; |
491 SkScalar passRadius = (kHigh_Quality == quality) ? | |
492 SkScalarMul( radius, kBlurRadiusFudgeFactor): | |
493 radius; | |
494 | 522 |
495 int rx = SkScalarCeil(passRadius); | 523 int rx = SkScalarCeil(passRadius); |
496 int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255
); | 524 int outerWeight = 255 - SkScalarRound((SkIntToScalar(rx) - passRadius) * 255
); |
497 | 525 |
498 SkASSERT(rx >= 0); | 526 SkASSERT(rx >= 0); |
499 SkASSERT((unsigned)outerWeight <= 255); | 527 SkASSERT((unsigned)outerWeight <= 255); |
500 if (rx <= 0) { | 528 if (rx <= 0) { |
501 return false; | 529 return false; |
502 } | 530 } |
503 | 531 |
504 int ry = rx; // only do square blur for now | 532 int ry = rx; // only do square blur for now |
505 | 533 |
506 int padx = passCount * rx; | 534 int padx = passCount * rx; |
507 int pady = passCount * ry; | 535 int pady = passCount * ry; |
508 | 536 |
509 if (margin) { | 537 if (margin) { |
510 margin->set(padx, pady); | 538 margin->set(padx, pady); |
511 } | 539 } |
512 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady, | 540 dst->fBounds.set(src.fBounds.fLeft - padx, src.fBounds.fTop - pady, |
513 src.fBounds.fRight + padx, src.fBounds.fBottom + pady); | 541 src.fBounds.fRight + padx, src.fBounds.fBottom + pady); |
514 | 542 |
515 dst->fRowBytes = dst->fBounds.width(); | 543 dst->fRowBytes = dst->fBounds.width(); |
516 dst->fFormat = SkMask::kA8_Format; | 544 dst->fFormat = SkMask::kA8_Format; |
517 dst->fImage = NULL; | 545 dst->fImage = NULL; |
518 | 546 |
519 if (src.fImage) { | 547 if (src.fImage) { |
520 size_t dstSize = dst->computeImageSize(); | 548 size_t dstSize = dst->computeImageSize(); |
521 if (0 == dstSize) { | 549 if (0 == dstSize) { |
522 return false; // too big to allocate, abort | 550 return false; // too big to allocate, abort |
523 } | 551 } |
(...skipping 120 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
644 | 672 |
645 if ( x > 0.5f ) { | 673 if ( x > 0.5f ) { |
646 return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x); | 674 return 0.5625f - (x3 / 6.0f - 3.0f * x2 * 0.25f + 1.125f * x); |
647 } | 675 } |
648 if ( x > -0.5f ) { | 676 if ( x > -0.5f ) { |
649 return 0.5f - (0.75f * x - x3 / 3.0f); | 677 return 0.5f - (0.75f * x - x3 / 3.0f); |
650 } | 678 } |
651 return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x); | 679 return 0.4375f + (-x3 / 6.0f - 3.0f * x2 * 0.25f - 1.125f * x); |
652 } | 680 } |
653 | 681 |
654 // Compute the size of the array allocated for the profile. | |
655 | |
656 static int compute_profile_size(SkScalar radius) { | |
657 return SkScalarRoundToInt(radius * 3); | |
658 | |
659 } | |
660 | |
661 /* compute_profile allocates and fills in an array of floating | 682 /* compute_profile allocates and fills in an array of floating |
662 point values between 0 and 255 for the profile signature of | 683 point values between 0 and 255 for the profile signature of |
663 a blurred half-plane with the given blur radius. Since we're | 684 a blurred half-plane with the given blur radius. Since we're |
664 going to be doing screened multiplications (i.e., 1 - (1-x)(1-y)) | 685 going to be doing screened multiplications (i.e., 1 - (1-x)(1-y)) |
665 all the time, we actually fill in the profile pre-inverted | 686 all the time, we actually fill in the profile pre-inverted |
666 (already done 255-x). | 687 (already done 255-x). |
667 | 688 |
668 It's the responsibility of the caller to delete the | 689 It's the responsibility of the caller to delete the |
669 memory returned in profile_out. | 690 memory returned in profile_out. |
670 */ | 691 */ |
671 | 692 |
672 static void compute_profile(SkScalar radius, unsigned int **profile_out) { | 693 static void compute_profile(SkScalar sigma, unsigned int **profile_out) { |
673 int size = compute_profile_size(radius); | 694 int size = SkScalarCeilToInt(6*sigma); |
674 | 695 |
675 int center = size >> 1; | 696 int center = size >> 1; |
676 unsigned int *profile = SkNEW_ARRAY(unsigned int, size); | 697 unsigned int *profile = SkNEW_ARRAY(unsigned int, size); |
677 | 698 |
678 float invr = 1.f/radius; | 699 float invr = 1.f/(2*sigma); |
679 | 700 |
680 profile[0] = 255; | 701 profile[0] = 255; |
681 for (int x = 1 ; x < size ; ++x) { | 702 for (int x = 1 ; x < size ; ++x) { |
682 float scaled_x = (center - x - .5f) * invr; | 703 float scaled_x = (center - x - .5f) * invr; |
683 float gi = gaussianIntegral(scaled_x); | 704 float gi = gaussianIntegral(scaled_x); |
684 profile[x] = 255 - (uint8_t) (255.f * gi); | 705 profile[x] = 255 - (uint8_t) (255.f * gi); |
685 } | 706 } |
686 | 707 |
687 *profile_out = profile; | 708 *profile_out = profile; |
688 } | 709 } |
689 | 710 |
690 // TODO MAYBE: Maintain a profile cache to avoid recomputing this for | 711 // TODO MAYBE: Maintain a profile cache to avoid recomputing this for |
691 // commonly used radii. Consider baking some of the most common blur radii | 712 // commonly used radii. Consider baking some of the most common blur radii |
692 // directly in as static data? | 713 // directly in as static data? |
693 | 714 |
694 // Implementation adapted from Michael Herf's approach: | 715 // Implementation adapted from Michael Herf's approach: |
695 // http://stereopsis.com/shadowrect/ | 716 // http://stereopsis.com/shadowrect/ |
696 | 717 |
697 static inline unsigned int profile_lookup( unsigned int *profile, int loc, int b
lurred_width, int sharp_width ) { | 718 static inline unsigned int profile_lookup( unsigned int *profile, int loc, int b
lurred_width, int sharp_width ) { |
698 int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far
are we from the original edge? | 719 int dx = SkAbs32(((loc << 1) + 1) - blurred_width) - sharp_width; // how far
are we from the original edge? |
699 int ox = dx >> 1; | 720 int ox = dx >> 1; |
700 if (ox < 0) { | 721 if (ox < 0) { |
701 ox = 0; | 722 ox = 0; |
702 } | 723 } |
703 | 724 |
704 return profile[ox]; | 725 return profile[ox]; |
705 } | 726 } |
706 | 727 |
707 bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src, | 728 bool SkBlurMask::BlurRect(SkMask *dst, const SkRect &src, |
708 SkScalar provided_radius, Style style, | 729 SkScalar radius, Style style, |
709 SkIPoint *margin, SkMask::CreateMode createMode) { | 730 SkIPoint *margin, SkMask::CreateMode createMode) { |
710 int profile_size; | 731 return SkBlurMask::BlurRect(SkBlurMask::ConvertRadiusToSigma(radius), |
| 732 dst, src, |
| 733 style, margin, createMode); |
| 734 } |
711 | 735 |
712 float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudge
Factor)); | 736 bool SkBlurMask::BlurRect(SkScalar sigma, SkMask *dst, |
713 | 737 const SkRect &src, Style style, |
714 // adjust blur radius to match interpretation from boxfilter code | 738 SkIPoint *margin, SkMask::CreateMode createMode) { |
715 radius = (radius + .5f) * 2.f; | 739 int profile_size = SkScalarCeilToInt(6*sigma); |
716 | |
717 profile_size = compute_profile_size(radius); | |
718 | 740 |
719 int pad = profile_size/2; | 741 int pad = profile_size/2; |
720 if (margin) { | 742 if (margin) { |
721 margin->set( pad, pad ); | 743 margin->set( pad, pad ); |
722 } | 744 } |
723 | 745 |
724 dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad), | 746 dst->fBounds.set(SkScalarRoundToInt(src.fLeft - pad), |
725 SkScalarRoundToInt(src.fTop - pad), | 747 SkScalarRoundToInt(src.fTop - pad), |
726 SkScalarRoundToInt(src.fRight + pad), | 748 SkScalarRoundToInt(src.fRight + pad), |
727 SkScalarRoundToInt(src.fBottom + pad)); | 749 SkScalarRoundToInt(src.fBottom + pad)); |
(...skipping 10 matching lines...) Expand all Loading... |
738 dst->fBounds.set(SkScalarRoundToInt(src.fLeft), | 760 dst->fBounds.set(SkScalarRoundToInt(src.fLeft), |
739 SkScalarRoundToInt(src.fTop), | 761 SkScalarRoundToInt(src.fTop), |
740 SkScalarRoundToInt(src.fRight), | 762 SkScalarRoundToInt(src.fRight), |
741 SkScalarRoundToInt(src.fBottom)); // restore trimme
d bounds | 763 SkScalarRoundToInt(src.fBottom)); // restore trimme
d bounds |
742 dst->fRowBytes = sw; | 764 dst->fRowBytes = sw; |
743 } | 765 } |
744 return true; | 766 return true; |
745 } | 767 } |
746 unsigned int *profile = NULL; | 768 unsigned int *profile = NULL; |
747 | 769 |
748 compute_profile(radius, &profile); | 770 compute_profile(sigma, &profile); |
749 SkAutoTDeleteArray<unsigned int> ada(profile); | 771 SkAutoTDeleteArray<unsigned int> ada(profile); |
750 | 772 |
751 size_t dstSize = dst->computeImageSize(); | 773 size_t dstSize = dst->computeImageSize(); |
752 if (0 == dstSize) { | 774 if (0 == dstSize) { |
753 return false; // too big to allocate, abort | 775 return false; // too big to allocate, abort |
754 } | 776 } |
755 | 777 |
756 uint8_t* dp = SkMask::AllocImage(dstSize); | 778 uint8_t* dp = SkMask::AllocImage(dstSize); |
757 | 779 |
758 dst->fImage = dp; | 780 dst->fImage = dp; |
759 | 781 |
760 int dstHeight = dst->fBounds.height(); | 782 int dstHeight = dst->fBounds.height(); |
761 int dstWidth = dst->fBounds.width(); | 783 int dstWidth = dst->fBounds.width(); |
762 | 784 |
763 // nearest odd number less than the profile size represents the center | 785 // nearest odd number less than the profile size represents the center |
764 // of the (2x scaled) profile | 786 // of the (2x scaled) profile |
765 int center = ( profile_size & ~1 ) - 1; | 787 int center = ( profile_size & ~1 ) - 1; |
766 | 788 |
767 int w = sw - center; | 789 int w = sw - center; |
768 int h = sh - center; | 790 int h = sh - center; |
769 | 791 |
770 uint8_t *outptr = dp; | 792 uint8_t *outptr = dp; |
771 | 793 |
772 SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth); | 794 SkAutoTMalloc<uint8_t> horizontalScanline(dstWidth); |
773 | 795 |
774 for (int x = 0 ; x < dstWidth ; ++x) { | 796 for (int x = 0 ; x < dstWidth ; ++x) { |
775 if (profile_size <= sw) { | 797 if (profile_size <= sw) { |
776 horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w); | 798 horizontalScanline[x] = profile_lookup(profile, x, dstWidth, w); |
777 } else { | 799 } else { |
778 float span = float(sw)/radius; | 800 float span = float(sw)/(2*sigma); |
779 float giX = 1.5f - (x+.5f)/radius; | 801 float giX = 1.5f - (x+.5f)/(2*sigma); |
780 horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - ga
ussianIntegral(giX + span))); | 802 horizontalScanline[x] = (uint8_t) (255 * (gaussianIntegral(giX) - ga
ussianIntegral(giX + span))); |
781 } | 803 } |
782 } | 804 } |
783 | 805 |
784 for (int y = 0 ; y < dstHeight ; ++y) { | 806 for (int y = 0 ; y < dstHeight ; ++y) { |
785 unsigned int profile_y; | 807 unsigned int profile_y; |
786 if (profile_size <= sh) { | 808 if (profile_size <= sh) { |
787 profile_y = profile_lookup(profile, y, dstHeight, h); | 809 profile_y = profile_lookup(profile, y, dstHeight, h); |
788 } else { | 810 } else { |
789 float span = float(sh)/radius; | 811 float span = float(sh)/(2*sigma); |
790 float giY = 1.5f - (y+.5f)/radius; | 812 float giY = 1.5f - (y+.5f)/(2*sigma); |
791 profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegr
al(giY + span))); | 813 profile_y = (uint8_t) (255 * (gaussianIntegral(giY) - gaussianIntegr
al(giY + span))); |
792 } | 814 } |
793 | 815 |
794 for (int x = 0 ; x < dstWidth ; x++) { | 816 for (int x = 0 ; x < dstWidth ; x++) { |
795 unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profi
le_y); | 817 unsigned int maskval = SkMulDiv255Round(horizontalScanline[x], profi
le_y); |
796 *(outptr++) = maskval; | 818 *(outptr++) = maskval; |
797 } | 819 } |
798 } | 820 } |
799 | 821 |
800 if (style == kInner_Style) { | 822 if (style == kInner_Style) { |
(...skipping 26 matching lines...) Expand all Loading... |
827 uint8_t *dst_scanline = dp + y*dstWidth + pad; | 849 uint8_t *dst_scanline = dp + y*dstWidth + pad; |
828 memset(dst_scanline, 0xff, sw); | 850 memset(dst_scanline, 0xff, sw); |
829 } | 851 } |
830 } | 852 } |
831 // normal and solid styles are the same for analytic rect blurs, so don't | 853 // normal and solid styles are the same for analytic rect blurs, so don't |
832 // need to handle solid specially. | 854 // need to handle solid specially. |
833 | 855 |
834 return true; | 856 return true; |
835 } | 857 } |
836 | 858 |
| 859 bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar radius
, |
| 860 Style style, SkIPoint* margin) { |
| 861 return BlurGroundTruth(ConvertRadiusToSigma(radius), dst, src, style, margin
); |
| 862 } |
837 // The "simple" blur is a direct implementation of separable convolution with a
discrete | 863 // The "simple" blur is a direct implementation of separable convolution with a
discrete |
838 // gaussian kernel. It's "ground truth" in a sense; too slow to be used, but ve
ry | 864 // gaussian kernel. It's "ground truth" in a sense; too slow to be used, but ve
ry |
839 // useful for correctness comparisons. | 865 // useful for correctness comparisons. |
840 | 866 |
841 bool SkBlurMask::BlurGroundTruth(SkMask* dst, const SkMask& src, SkScalar provid
ed_radius, | 867 bool SkBlurMask::BlurGroundTruth(SkScalar sigma, SkMask* dst, const SkMask& src, |
842 Style style, SkIPoint* margin) { | 868 Style style, SkIPoint* margin) { |
843 | 869 |
844 if (src.fFormat != SkMask::kA8_Format) { | 870 if (src.fFormat != SkMask::kA8_Format) { |
845 return false; | 871 return false; |
846 } | 872 } |
847 | 873 |
848 float radius = SkScalarToFloat(SkScalarMul(provided_radius, kBlurRadiusFudge
Factor)); | 874 float variance = sigma * sigma; |
849 float stddev = SkScalarToFloat(radius) /2.0f; | |
850 float variance = stddev * stddev; | |
851 | 875 |
852 int windowSize = SkScalarCeil(stddev*4); | 876 int windowSize = SkScalarCeil(sigma*4); |
853 // round window size up to nearest odd number | 877 // round window size up to nearest odd number |
854 windowSize |= 1; | 878 windowSize |= 1; |
855 | 879 |
856 SkAutoTMalloc<float> gaussWindow(windowSize); | 880 SkAutoTMalloc<float> gaussWindow(windowSize); |
857 | 881 |
858 int halfWindow = windowSize >> 1; | 882 int halfWindow = windowSize >> 1; |
859 | 883 |
860 gaussWindow[halfWindow] = 1; | 884 gaussWindow[halfWindow] = 1; |
861 | 885 |
862 float windowSum = 1; | 886 float windowSum = 1; |
(...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
976 (void)autoCall.detach(); | 1000 (void)autoCall.detach(); |
977 } | 1001 } |
978 | 1002 |
979 if (style == kInner_Style) { | 1003 if (style == kInner_Style) { |
980 dst->fBounds = src.fBounds; // restore trimmed bounds | 1004 dst->fBounds = src.fBounds; // restore trimmed bounds |
981 dst->fRowBytes = src.fRowBytes; | 1005 dst->fRowBytes = src.fRowBytes; |
982 } | 1006 } |
983 | 1007 |
984 return true; | 1008 return true; |
985 } | 1009 } |
OLD | NEW |