WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
[patch]
Patch
bug-95121-20120903113301.patch (text/plain), 44.78 KB, created by
Hin-Chung Lam
on 2012-09-03 11:33:17 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Hin-Chung Lam
Created:
2012-09-03 11:33:17 PDT
Size:
44.78 KB
patch
obsolete
>Subversion Revision: 125357 >diff --git a/Source/WebCore/ChangeLog b/Source/WebCore/ChangeLog >index 092930390648477958ded2b4ff971f1bdddec7d4..3d47f1eceaaa2c1cb9e6907bf083d08d991c18dc 100644 >--- a/Source/WebCore/ChangeLog >+++ b/Source/WebCore/ChangeLog >@@ -1,3 +1,62 @@ >+2012-09-03 Alpha Lam <hclam@chromium.org> >+ >+ [skia] Drawing a subrect of bitmap image is misaligned >+ https://bugs.webkit.org/show_bug.cgi?id=95121 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ When drawing a subrect of BitmapImage and the subrect is not aligned >+ to integer boundaries, skia round it to the closest enclosing integer >+ rectangle. This creates prominent rendering artifacts when an image >+ is used as background and its sub-regions are invalidated frequently. >+ >+ This patch fixes the problem by doing a fancy alignment and clipping >+ for both cases of RESAMPLE_AWSOME and RESAMPLE_LINEAR. >+ >+ A clip rect is applied to the canvas using original destionation >+ rectangle, while source and destination rectangles are enlarged >+ appropriarely to ensure source rectangle is aligned. >+ >+ This patch also fixes a closely related problem. In RESAMPLE_AWSOME >+ resampling mode the previous algorithm applies different approximated >+ scale factor for each scaled image fragment. This caused the stitched >+ image to look ugly. >+ >+ Scale factor is approximated using entire image size, this gives a >+ more accurate approximation because of greater denominator and a >+ consistent scale factor across all fragments. >+ >+ Tests: fast/images/paint-subrect-grid.html >+ fast/images/paint-subrect.html >+ fast/images/repaint-subrect-grid.html >+ >+ * platform/graphics/skia/ImageSkia.cpp: >+ (WebCore::computeResamplingMode): Use float for width and height. >+ (WebCore): >+ (WebCore::mapImageRect): >+ (WebCore::needsFancyAlignment): >+ (WebCore::computeBitmapDrawRects): >+ (WebCore::rectMulDiv): >+ (WebCore::extractScaledImageFragment): >+ (WebCore::drawResampledBitmap): New algorithm for drawing scaled image fragment. >+ (WebCore::paintSkBitmap): >+ (WebCore::Image::drawPattern): >+ (WebCore::BitmapImage::draw): Use SkRect instead of SkIRect. >+ (WebCore::BitmapImageSingleFrameSkia::draw): >+ * platform/graphics/skia/NativeImageSkia.cpp: >+ Changing the algorithm for extracting a scaled image fragment. >+ A fragment is now identified by (scaledImageSize, scaledImagSubset). >+ (WebCore::NativeImageSkia::decodedSize): >+ (WebCore::NativeImageSkia::hasResizedBitmap): >+ (WebCore::NativeImageSkia::resizedBitmap): >+ (WebCore::NativeImageSkia::shouldCacheResampling): >+ (WebCore::NativeImageSkia::CachedImageInfo::CachedImageInfo): >+ (WebCore::NativeImageSkia::CachedImageInfo::isEqual): >+ (WebCore::NativeImageSkia::CachedImageInfo::set): >+ * platform/graphics/skia/NativeImageSkia.h: >+ (NativeImageSkia): >+ (CachedImageInfo): >+ > 2012-08-11 Benjamin Poulain <bpoulain@apple.com> > > Do the DecimalNumber to String conversion on 8 bits >diff --git a/Source/WebCore/platform/graphics/skia/ImageSkia.cpp b/Source/WebCore/platform/graphics/skia/ImageSkia.cpp >index a0af3d64a1af66741bc2596830703113f16cac27..f3ab7da3103d8beec8a879944245cad6c02af1a8 100644 >--- a/Source/WebCore/platform/graphics/skia/ImageSkia.cpp >+++ b/Source/WebCore/platform/graphics/skia/ImageSkia.cpp >@@ -41,6 +41,7 @@ > #include "NativeImageSkia.h" > #include "PlatformContextSkia.h" > #include "PlatformString.h" >+#include "SkBitmap.h" > #include "SkPixelRef.h" > #include "SkRect.h" > #include "SkShader.h" >@@ -50,6 +51,9 @@ > #include "skia/ext/image_operations.h" > #include "skia/ext/platform_canvas.h" > >+#include <limits> >+#include <math.h> >+ > #if PLATFORM(CHROMIUM) > #include "TraceEvent.h" > #endif >@@ -70,11 +74,8 @@ enum ResamplingMode { > RESAMPLE_AWESOME, > }; > >-static ResamplingMode computeResamplingMode(const SkMatrix& matrix, const NativeImageSkia& bitmap, int srcWidth, int srcHeight, float destWidth, float destHeight) >+static ResamplingMode computeResamplingMode(const SkMatrix& matrix, const NativeImageSkia& bitmap, float srcWidth, float srcHeight, float destWidth, float destHeight) > { >- int destIWidth = static_cast<int>(destWidth); >- int destIHeight = static_cast<int>(destHeight); >- > // The percent change below which we will not resample. This usually means > // an off-by-one error on the web page, and just doing nearest neighbor > // sampling is usually good enough. >@@ -92,7 +93,10 @@ static ResamplingMode computeResamplingMode(const SkMatrix& matrix, const Native > // Figure out if we should resample this image. We try to prune out some > // common cases where resampling won't give us anything, since it is much > // slower than drawing stretched. >- if (srcWidth == destIWidth && srcHeight == destIHeight) { >+ float diffWidth = fabs(destWidth - srcWidth); >+ float diffHeight = fabs(destHeight - srcHeight); >+ if (diffWidth < std::numeric_limits<float>::epsilon() >+ && diffHeight < std::numeric_limits<float>::epsilon()) { > // We don't need to resample if the source and destination are the same. > return RESAMPLE_NONE; > } >@@ -113,16 +117,18 @@ static ResamplingMode computeResamplingMode(const SkMatrix& matrix, const Native > // This is trying to catch cases where somebody has created a border > // (which might be large) and then is stretching it to fill some part > // of the page. >- if (srcWidth == destWidth || srcHeight == destHeight) >+ if (diffWidth < std::numeric_limits<float>::epsilon() >+ || diffHeight < std::numeric_limits<float>::epsilon()) { > return RESAMPLE_NONE; >+ } > > // The image is growing a lot and in more than one direction. Resampling > // is slow and doesn't give us very much when growing a lot. > return RESAMPLE_LINEAR; > } > >- if ((fabs(destWidth - srcWidth) / srcWidth < kFractionalChangeThreshold) >- && (fabs(destHeight - srcHeight) / srcHeight < kFractionalChangeThreshold)) { >+ if ((diffWidth / srcWidth < kFractionalChangeThreshold) >+ && (diffHeight / srcHeight < kFractionalChangeThreshold)) { > // It is disappointingly common on the web for image sizes to be off by > // one or two pixels. We don't bother resampling if the size difference > // is a small fraction of the original size. >@@ -162,11 +168,149 @@ static ResamplingMode limitResamplingMode(PlatformContextSkia* platformContext, > return resampling; > } > >-// Draws the given bitmap to the given canvas. The subset of the source bitmap >-// identified by src_rect is drawn to the given destination rect. The bitmap >-// will be resampled to resample_width * resample_height (this is the size of >-// the whole image, not the subset). See shouldResampleBitmap for more. >+// |srcRect| is a rectangle transformed to |dstRect| (scale and translation). >+// i.e. dstRect = T(srcRect). Returns T(otherRect). >+// >+// |otherRect| is related to |srcRect| by for example clipping, enclosing IntRect, >+// etc. Return |otherRect| with the same transformation. >+// >+// The reason we do this manually is to preserve accuracy for IntRect. And this is >+// also handy compared to using a transformation matrix. This function is used in >+// places where we want to apply transformation to clip rect and enclosing IntRect.. >+template <class T> >+static FloatRect mapImageRect(const FloatRect& srcRect, const FloatRect& dstRect, const T& otherRect) >+{ >+ float scaleWidth = dstRect.width() / srcRect.width(); >+ float scaleHeight = dstRect.height() / srcRect.height(); >+ float diffX = otherRect.x() - srcRect.x(); >+ float diffY = otherRect.y() - srcRect.y(); >+ >+ FloatRect otherRectTransformed = dstRect; >+ otherRectTransformed.move(diffX * scaleWidth, diffY * scaleHeight); >+ otherRectTransformed.setWidth(otherRect.width() * scaleWidth); >+ otherRectTransformed.setHeight(otherRect.height() * scaleHeight); >+ return otherRectTransformed; >+} >+ >+// Determine if fancy alignment is necessary when drawing a sub-region of a >+// bitmap image. Compute the difference of boundaries between input >+// rectangle and a rounded version to see if it is beyond acceptable range. >+// See comments for computeBitmapDrawRects() for more details. >+static bool needsFancyAlignment(const FloatRect& rect) >+{ >+ // Value is 1.19209e-007. >+ const float epsilon = std::numeric_limits<float>::epsilon(); >+ IntRect roundedRect = roundedIntRect(rect); >+ >+ return fabs(rect.x() - roundedRect.x()) >= epsilon >+ || fabs(rect.y() - roundedRect.y()) >= epsilon >+ || fabs(rect.maxX() - roundedRect.maxX()) >= epsilon >+ || fabs(rect.maxY() - roundedRect.maxY()) >= epsilon; >+} >+ >+// FIXME: Remove this code when SkCanvas accepts SkRect as source rectangle. >+// See crbug.com/117597 for background. >+// >+// WebKit wants to draw a sub-rectangle (FloatRect) in a bitmap and scale it to >+// another FloatRect. However Skia only allows bitmap to be addressed by a >+// IntRect. This function computes the appropriate IntRect that encloses the >+// source rectangle and the corresponding enclosing destination rectangle, >+// while maintaining the scale factor. >+// >+// |srcRect| is the source rectangle in the bitmap. Return true if fancy >+// alignment is required. User of this function needs to clip to |dstRect|. >+// Return false if clipping is not needed. >+// >+// |dstRect| is the input rectangle that |srcRect| is scaled to. > // >+// |outSrcRect| and |outDstRect| are the corresponding output rectangles. >+// >+// ALGORITHM >+// >+// The objective is to (a) find an enclosing IntRect for the source rectangle >+// and (b) the corresponding FloatRect in destination space. >+// >+// These are the steps performed: >+// >+// 1. IntRect enclosingSrcRect = enclosingIntRect(srcRect) >+// >+// Compute the enclosing IntRect for |srcRect|. This ensures the bitmap >+// image is addressed with integer boundaries. >+// >+// 2. FloatRect enclosingDestRect = mapSrcToDest(enclosingSrcRect) >+// >+// Map the enclosing source rectangle to destination coordinate space. >+// >+// The output will be enclosingSrcRect and enclosingDestRect from the >+// algorithm above. >+static bool computeBitmapDrawRects(const IntSize& bitmapSize, const FloatRect& srcRect, const FloatRect& dstRect, >+ IntRect* outSrcRect, FloatRect* outDstRect) >+{ >+ if (!needsFancyAlignment(srcRect)) { >+ *outSrcRect = roundedIntRect(srcRect); >+ *outDstRect = dstRect; >+ return false; >+ } >+ >+ IntRect bitmapRect(IntPoint(0, 0), bitmapSize); >+ IntRect enclosingSrcRect = enclosingIntRect(srcRect); >+ enclosingSrcRect.intersect(bitmapRect); // Clip to bitmap rectangle. >+ FloatRect enclosingDstRect = mapImageRect(srcRect, dstRect, enclosingSrcRect); >+ *outSrcRect = enclosingSrcRect; >+ *outDstRect = enclosingDstRect; >+ return true; >+} >+ >+// Return a rectangle scaled by (mulX / denomX) and (mulY / denomY) in X and Y >+// directions respectively. >+static SkRect rectMulDiv(const SkRect& rect, int mulX, int denomX, int mulY, int denomY) >+{ >+ return SkRect::MakeXYWH(rect.x() * mulX / denomX, >+ rect.y() * mulY / denomY, >+ rect.width() * mulX / denomX, >+ rect.height() * mulY / denomY); >+} >+ >+// This function is used to scale an image and extract a scaled fragment. >+// >+// ALGORITHM >+// >+// Because the scaled image size has to be integers. We approximate the real >+// scale with the following formula (only X direction is shown): >+// >+// scaledImageWidth = round(scaleX * imageRect.width()) >+// approximateScaleX = scaledImageWidth / imageRect.width() >+// >+// With this method we maintain a constant scale factor among fragments in >+// the scaled image. This allows fragments to stitch together to form the >+// full scaled image. The downside is there will be a small difference >+// between |scaleX| and |approximateScaleX|. >+// >+// A scaled image fragment is identified by: >+// >+// - Scaled image size >+// - Scaled image fragment rectangle (IntRect) >+// >+// Scaled image size has been determined and the next step is to compute the >+// rectangle for the scaled image fragment which needs to be an IntRect. >+// >+// scaledSrcRect = srcRect * (approximateScaleX, approximateScaleY) >+// enclosingScaledSrcRect = enclosingIntRect(scaledSrcRect) >+// >+// Finally we extract the scaled image fragment using >+// (scaledImageSize, enclosingScaledSrcRect). >+// >+static SkBitmap extractScaledImageFragment(const NativeImageSkia& bitmap, const SkRect& srcRect, float scaleX, float scaleY, >+ SkRect* scaledSrcRect, SkIRect* enclosingScaledSrcRect) >+{ >+ SkISize imageSize = SkISize::Make(bitmap.bitmap().width(), bitmap.bitmap().height()); >+ SkISize scaledImageSize = SkISize::Make(clampToInteger(roundf(imageSize.width() * scaleX)), >+ clampToInteger(roundf(imageSize.height() * scaleY))); >+ *scaledSrcRect = rectMulDiv(srcRect, scaledImageSize.width(), imageSize.width(), scaledImageSize.height(), imageSize.height()); >+ *enclosingScaledSrcRect = enclosingIntRect(*scaledSrcRect); >+ return bitmap.resizedBitmap(scaledImageSize, *enclosingScaledSrcRect); >+} >+ > // This does a lot of computation to resample only the portion of the bitmap > // that will only be drawn. This is critical for performance since when we are > // scrolling, for example, we are only drawing a small strip of the image. >@@ -175,48 +319,54 @@ static ResamplingMode limitResamplingMode(PlatformContextSkia* platformContext, > // > // Note: this code is only used when the canvas transformation is limited to > // scaling or translation. >-static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkIRect& srcIRect, const SkRect& destRect) >+static void drawResampledBitmap(SkCanvas& canvas, SkPaint& paint, const NativeImageSkia& bitmap, const SkRect& srcRect, const SkRect& destRect) > { > #if PLATFORM(CHROMIUM) > TRACE_EVENT0("skia", "drawResampledBitmap"); > #endif >- // Apply forward transform to destRect to estimate required size of >- // re-sampled bitmap, and use only in calls required to resize, or that >- // check for the required size. >- SkRect destRectTransformed; >- canvas.getTotalMatrix().mapRect(&destRectTransformed, destRect); >- SkIRect destRectTransformedRounded; >- destRectTransformed.round(&destRectTransformedRounded); >- >- // Compute the visible portion of our rect. >+ // We want to scale |destRect| with transformation in the canvas to obtain >+ // the final scale. The final scale is a combination of scale transform >+ // in canvas and explicit scaling (srcRect and destRect). >+ SkRect screenRect; >+ canvas.getTotalMatrix().mapRect(&screenRect, destRect); >+ float realScaleX = screenRect.width() / srcRect.width(); >+ float realScaleY = screenRect.height() / srcRect.height(); >+ >+ // This part of code limits scaling only to visible portion in the >+ // destination rectangle. > SkRect destRectVisibleSubset; > ClipRectToCanvas(canvas, destRect, &destRectVisibleSubset); >+ > // ClipRectToCanvas often overshoots, resulting in a larger region than our > // original destRect. Intersecting gets us back inside. > if (!destRectVisibleSubset.intersect(destRect)) > return; // Nothing visible in destRect. > >- // Compute the transformed (screen space) portion of the visible portion for >- // use below. >- SkRect destRectVisibleSubsetTransformed; >- canvas.getTotalMatrix().mapRect(&destRectVisibleSubsetTransformed, destRectVisibleSubset); >- SkRect destBitmapSubsetTransformed = destRectVisibleSubsetTransformed; >- destBitmapSubsetTransformed.offset(-destRectTransformed.fLeft, >- -destRectTransformed.fTop); >- SkIRect destBitmapSubsetTransformedRounded; >- destBitmapSubsetTransformed.round(&destBitmapSubsetTransformedRounded); >- >- // Transforms above plus rounding may cause destBitmapSubsetTransformedRounded >- // to go outside the image, so need to clip to avoid problems. >- if (!destBitmapSubsetTransformedRounded.intersect( >- 0, 0, destRectTransformedRounded.width(), destRectTransformedRounded.height())) >- return; // Image is not visible. >- >- SkBitmap resampled = bitmap.resizedBitmap(srcIRect, >- destRectTransformedRounded.width(), >- destRectTransformedRounded.height(), >- destBitmapSubsetTransformedRounded); >- canvas.drawBitmapRect(resampled, 0, destRectVisibleSubset, &paint); >+ // Destination rectangle is shrinked, find the corresponding rect in source >+ // image. >+ SkRect srcRectVisibleSubset = mapImageRect(destRect, srcRect, destRectVisibleSubset); >+ >+ SkRect scaledSrcRect; >+ SkIRect enclosingScaledSrcRect; >+ SkBitmap scaledImageFragment = extractScaledImageFragment(bitmap, srcRectVisibleSubset, realScaleX, realScaleY, &scaledSrcRect, &enclosingScaledSrcRect); >+ >+ SkRect enclosingDestRect = mapImageRect(scaledSrcRect, destRectVisibleSubset, enclosingScaledSrcRect); >+ canvas.save(); >+ >+ // The reason we do clipping is because Skia doesn't support SkRect as >+ // source rect. See http://crbug.com/145540. >+ // When Skia supports then use this as the source rect to replace 0. >+ // >+ // scaledSrcRect.offset(-enclosingScaledSrcRect.x(), -enclosingScaledSrcRect.y()); >+ canvas.clipRect(destRectVisibleSubset); >+ >+ // Because the image fragment is generated with an approxmiated scaling >+ // factor. This draw will perform a close to 1 scaling, >+ // >+ // NOTE: If the difference in scale is so small that Skia doesn't produce >+ // a difference then we can just blit it directly to enhance performance. >+ canvas.drawBitmapRect(scaledImageFragment, 0, enclosingDestRect, &paint); >+ canvas.restore(); > } > > static bool hasNon90rotation(PlatformContextSkia* context) >@@ -224,7 +374,7 @@ static bool hasNon90rotation(PlatformContextSkia* context) > return !context->canvas()->getTotalMatrix().rectStaysRect(); > } > >-static void paintSkBitmap(PlatformContextSkia* platformContext, const NativeImageSkia& bitmap, const SkIRect& srcRect, const SkRect& destRect, const SkXfermode::Mode& compOp) >+static void paintSkBitmap(PlatformContextSkia* platformContext, const NativeImageSkia& bitmap, const SkRect& srcRect, const SkRect& destRect, const SkXfermode::Mode& compOp) > { > #if PLATFORM(CHROMIUM) > TRACE_EVENT0("skia", "paintSkBitmap"); >@@ -249,7 +399,8 @@ static void paintSkBitmap(PlatformContextSkia* platformContext, const NativeImag > if (!(canvas->getTotalMatrix().getType() & (SkMatrix::kAffine_Mask | SkMatrix::kPerspective_Mask))) > canvas->getTotalMatrix().mapRect(&destRectTarget, destRect); > >- resampling = computeResamplingMode(canvas->getTotalMatrix(), bitmap, srcRect.width(), srcRect.height(), >+ resampling = computeResamplingMode(canvas->getTotalMatrix(), bitmap, >+ SkScalarToFloat(srcRect.width()), SkScalarToFloat(srcRect.height()), > SkScalarToFloat(destRectTarget.width()), SkScalarToFloat(destRectTarget.height())); > } > >@@ -262,14 +413,35 @@ static void paintSkBitmap(PlatformContextSkia* platformContext, const NativeImag > resampling = limitResamplingMode(platformContext, resampling); > paint.setFilterBitmap(resampling == RESAMPLE_LINEAR); > if (resampling == RESAMPLE_AWESOME) >- drawResampledBitmap(*canvas, paint, bitmap, srcRect, destRect); >+ drawResampledBitmap(*canvas, paint, bitamp, srcRect, destRect); > else { > // No resampling necessary, we can just draw the bitmap. We want to > // filter it if we decided to do linear interpolation above, or if there > // is something interesting going on with the matrix (like a rotation). > // Note: for serialization, we will want to subset the bitmap first so > // we don't send extra pixels. >- canvas->drawBitmapRect(bitmap.bitmap(), &srcRect, destRect, &paint); >+ IntRect enclosingSrcRect; >+ FloatRect enclosingDestRect; >+ IntSize bitmapSize(bitmap.bitmap().width(), bitmap.bitmap().height()); >+ bool needsClipping = computeBitmapDrawRects(bitmapSize, srcRect, destRect, &enclosingSrcRect, &enclosingDestRect); >+ >+ if (enclosingSrcRect.isEmpty() || enclosingDestRect.isEmpty()) >+ return; >+ >+ // If destination is enlarged because source rectangle didn't align to >+ // integer boundaries then we draw a slightly larger rectangle and clip >+ // to the original destination rectangle. >+ // See http://crbug.com/145540. >+ if (needsClipping) { >+ platformContext->save(); >+ platformContext->canvas()->clipRect(destRect); >+ } >+ >+ SkIRect skiaEnclosingSrcRect = enclosingSrcRect; >+ canvas->drawBitmapRect(bitmap.bitmap(), &skiaEnclosingSrcRect, enclosingDestRect, &paint); >+ >+ if (needsClipping) >+ platformContext->restore(); > } > platformContext->didDrawRect(destRect, paint, &bitmap.bitmap()); > } >@@ -323,7 +495,6 @@ void Image::drawPattern(GraphicsContext* context, > if (!bitmap) > return; > >- SkIRect srcRect = enclosingIntRect(normSrcRect); > SkMatrix ctm = context->platformContext()->canvas()->getTotalMatrix(); > SkMatrix totalMatrix; > totalMatrix.setConcat(ctm, patternTransform); >@@ -342,7 +513,7 @@ void Image::drawPattern(GraphicsContext* context, > if (context->platformContext()->isAccelerated() || context->platformContext()->printing()) > resampling = RESAMPLE_LINEAR; > else >- resampling = computeResamplingMode(totalMatrix, *bitmap, srcRect.width(), srcRect.height(), destBitmapWidth, destBitmapHeight); >+ resampling = computeResamplingMode(totalMatrix, *bitmap, normSrcRect.width(), normSrcRect.height(), destBitmapWidth, destBitmapHeight); > resampling = limitResamplingMode(context->platformContext(), resampling); > > // Load the transform WebKit requested. >@@ -350,10 +521,19 @@ void Image::drawPattern(GraphicsContext* context, > > SkShader* shader; > if (resampling == RESAMPLE_AWESOME) { >+ shader = 0; >+ > // Do nice resampling. >- int width = static_cast<int>(destBitmapWidth); >- int height = static_cast<int>(destBitmapHeight); >- SkBitmap resampled = bitmap->resizedBitmap(srcRect, width, height); >+ float scaleX = destBitmapWidth / normSrcRect.width(); >+ float scaleY = destBitmapHeight / normSrcRect.height(); >+ SkRect scaledSrcRect; >+ SkIRect enclosingScaledSrcRect; >+ >+ // The image fragment generated here is not exactly what is >+ // requested. The scale factor used is approximated and image >+ // fragment is slighyly larger to align to integer >+ // boundaries. >+ SkBitmap resampled = extractScaledImageFragment(*bitmap, normSrcRect, scaleX, scaleY, &scaledSrcRect, &enclosingScaledSrcRect); > shader = SkShader::CreateBitmapShader(resampled, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); > > // Since we just resized the bitmap, we need to remove the scale >@@ -367,7 +547,7 @@ void Image::drawPattern(GraphicsContext* context, > } else { > // No need to do nice resampling. > SkBitmap srcSubset; >- bitmap->bitmap().extractSubset(&srcSubset, srcRect); >+ bitmap->bitmap().extractSubset(&srcSubset, enclosingIntRect(normSrcRect)); > shader = SkShader::CreateBitmapShader(srcSubset, SkShader::kRepeat_TileMode, SkShader::kRepeat_TileMode); > } > >@@ -444,7 +624,7 @@ void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dstRect, > > paintSkBitmap(ctxt->platformContext(), > *bm, >- enclosingIntRect(normSrcRect), >+ normSrcRect, > normDstRect, > WebCoreCompositeToSkiaComposite(compositeOp)); > >@@ -468,7 +648,7 @@ void BitmapImageSingleFrameSkia::draw(GraphicsContext* ctxt, > > paintSkBitmap(ctxt->platformContext(), > m_nativeImage, >- enclosingIntRect(normSrcRect), >+ normSrcRect, > normDstRect, > WebCoreCompositeToSkiaComposite(compositeOp)); > >diff --git a/Source/WebCore/platform/graphics/skia/NativeImageSkia.cpp b/Source/WebCore/platform/graphics/skia/NativeImageSkia.cpp >index 7e34c079d85667357b5aad8960c54ca6e56e2d9c..506e25503d4d288437a02c1e6019ec28ffb5486b 100644 >--- a/Source/WebCore/platform/graphics/skia/NativeImageSkia.cpp >+++ b/Source/WebCore/platform/graphics/skia/NativeImageSkia.cpp >@@ -61,65 +61,53 @@ NativeImageSkia::~NativeImageSkia() > > int NativeImageSkia::decodedSize() const > { >- return m_image.getSize() + m_resizedImage.getSize(); >+ return m_image.getSize() + m_resizedImageFragment.getSize(); > } > >-bool NativeImageSkia::hasResizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight) const >+bool NativeImageSkia::hasResizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const > { >- return m_cachedImageInfo.isEqual(srcSubset, destWidth, destHeight) && !m_resizedImage.empty(); >+ return m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset) && !m_resizedImageFragment.empty(); > } > >-SkBitmap NativeImageSkia::resizedBitmap(const SkIRect& srcSubset, >- int destWidth, >- int destHeight, >- const SkIRect& destVisibleSubset) const >+SkBitmap NativeImageSkia::resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const > { > #if PLATFORM(CHROMIUM) > TRACE_EVENT0("skia", "NativeImageSkia::resizedBitmap"); > #endif >- if (!hasResizedBitmap(srcSubset, destWidth, destHeight)) { >+ if (!hasResizedBitmap(scaledImageSize, scaledImageSubset)) { > bool shouldCache = isDataComplete() >- && shouldCacheResampling(srcSubset, destWidth, destHeight, destVisibleSubset); >- >- SkBitmap subset; >- m_image.extractSubset(&subset, srcSubset); >+ && shouldCacheResampling(scaledImageSize, scaledImageSubset); > if (!shouldCache) { > #if PLATFORM(CHROMIUM) > TRACE_EVENT0("skia", "nonCachedResize"); > #endif >- // Just resize the visible subset and return it. >- SkBitmap resizedImage = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destWidth, destHeight, destVisibleSubset); >+ SkBitmap resizedImage = skia::ImageOperations::Resize(m_image, skia::ImageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(), scaledImageSubset); > resizedImage.setImmutable(); > return resizedImage; > } else { > #if PLATFORM(CHROMIUM) > TRACE_EVENT0("skia", "cachedResize"); > #endif >- m_resizedImage = skia::ImageOperations::Resize(subset, skia::ImageOperations::RESIZE_LANCZOS3, destWidth, destHeight); >+ m_resizedImageFragment = skia::ImageOperations::Resize(m_image, skia::ImageOperations::RESIZE_LANCZOS3, scaledImageSize.width(), scaledImageSize.height(), scaledImageSubset); > } >- m_resizedImage.setImmutable(); >+ m_resizedImageFragment.setImmutable(); > } >- >- SkBitmap visibleBitmap; >- m_resizedImage.extractSubset(&visibleBitmap, destVisibleSubset); >- return visibleBitmap; >+ return m_resizedImageFragment; > } > >-bool NativeImageSkia::shouldCacheResampling(const SkIRect& srcSubset, >- int destWidth, >- int destHeight, >- const SkIRect& destVisibleSubset) const >+bool NativeImageSkia::shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const > { > // Check whether the requested dimensions match previous request. >- bool matchesPreviousRequest = m_cachedImageInfo.isEqual(srcSubset, destWidth, destHeight); >+ bool matchesPreviousRequest = m_cachedImageInfo.isEqual(scaledImageSize, scaledImageSubset); > if (matchesPreviousRequest) > ++m_resizeRequests; > else { >- m_cachedImageInfo.set(srcSubset, destWidth, destHeight); >+ m_cachedImageInfo.set(scaledImageSize, scaledImageSubset); > m_resizeRequests = 0; >- // Reset m_resizedImage now, because we don't distinguish between the >- // last requested resize info and m_resizedImage's resize info. >- m_resizedImage.reset(); >+ // Reset m_resizedImageFragment now, because we don't distinguish >+ // between the last requested resize info and m_resizedImageFragment's >+ // resize info. >+ m_resizedImageFragment.reset(); > } > > // We can not cache incomplete frames. This might be a good optimization in >@@ -131,13 +119,14 @@ bool NativeImageSkia::shouldCacheResampling(const SkIRect& srcSubset, > > // If the destination bitmap is excessively large, we'll never allow caching. > static const unsigned long long kLargeBitmapSize = 4096ULL * 4096ULL; >- if ((static_cast<unsigned long long>(destWidth) * static_cast<unsigned long long>(destHeight)) > kLargeBitmapSize) >+ unsigned long long fragmentSize = static_cast<unsigned long long>(scaledImageSubset.width()) * static_cast<unsigned long long>(scaledImageSubset.height()); >+ if (fragmentSize > kLargeBitmapSize) > return false; > > // If the destination bitmap is small, we'll always allow caching, since > // there is not very much penalty for computing it and it may come in handy. >- static const int kSmallBitmapSize = 4096; >- if (destWidth * destHeight <= kSmallBitmapSize) >+ static const unsigned int kSmallBitmapSize = 4096; >+ if (fragmentSize <= kSmallBitmapSize) > return true; > > // If "too many" requests have been made for this bitmap, we assume that >@@ -146,28 +135,27 @@ bool NativeImageSkia::shouldCacheResampling(const SkIRect& srcSubset, > if (m_resizeRequests >= kManyRequestThreshold) > return true; > >- // If more than 1/4 of the resized image is visible, it's worth caching. >- int destVisibleSize = destVisibleSubset.width() * destVisibleSubset.height(); >- return (destVisibleSize > (destWidth * destHeight) / 4); >+ unsigned long long fullSize = static_cast<unsigned long long>(scaledImageSize.width()) * static_cast<unsigned long long>(scaledImageSize.height()); >+ >+ // If more than 1/4 of the resized image is requested, it's worth caching. >+ return fragmentSize > fullSize / 4; > } > > NativeImageSkia::CachedImageInfo::CachedImageInfo() > { >- srcSubset.setEmpty(); >+ scaledImageSize.setEmpty(); >+ scaledImageSubset.setEmpty(); > } > >-bool NativeImageSkia::CachedImageInfo::isEqual(const SkIRect& otherSrcSubset, int width, int height) const >+bool NativeImageSkia::CachedImageInfo::isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const > { >- return srcSubset == otherSrcSubset >- && requestSize.width() == width >- && requestSize.height() == height; >+ return scaledImageSize == otherScaledImageSize && scaledImageSubset == otherScaledImageSubset; > } > >-void NativeImageSkia::CachedImageInfo::set(const SkIRect& otherSrcSubset, int width, int height) >+void NativeImageSkia::CachedImageInfo::set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) > { >- srcSubset = otherSrcSubset; >- requestSize.setWidth(width); >- requestSize.setHeight(height); >+ scaledImageSize = otherScaledImageSize; >+ scaledImageSubset = otherScaledImageSubset; > } > > } // namespace WebCore >diff --git a/Source/WebCore/platform/graphics/skia/NativeImageSkia.h b/Source/WebCore/platform/graphics/skia/NativeImageSkia.h >index 2a52e68ee4ef175519d183fddfd557a52dfd46a5..679cff0f9a9804bd694cb6493f92162943a7bd53 100644 >--- a/Source/WebCore/platform/graphics/skia/NativeImageSkia.h >+++ b/Source/WebCore/platform/graphics/skia/NativeImageSkia.h >@@ -33,7 +33,7 @@ > > #include "SkBitmap.h" > #include "SkRect.h" >-#include "IntSize.h" >+#include "SkSize.h" > > namespace WebCore { > >@@ -71,40 +71,40 @@ public: > > // We can keep a resized version of the bitmap cached on this object. > // This function will return true if there is a cached version of the >- // given image subset with the given dimensions and subsets. >- bool hasResizedBitmap(const SkIRect& srcSubset, int width, int height) const; >+ // given image subset with the given scale and subset. >+ bool hasResizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const; > > // This will return an existing resized image subset, or generate a new one >- // of the specified size and subsets and possibly cache it. >- // srcSubset is the subset of the image to resize in image space. >- SkBitmap resizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight) const >- { >- SkIRect destVisibleSubset = {0, 0, destWidth, destHeight}; >- return resizedBitmap(srcSubset, destWidth, destHeight, destVisibleSubset); >- } >- >- // Same as above, but returns a subset of the destination image (ie: the >- // visible subset). destVisibleSubset is the subset of the resized >- // (destWidth x destHeight) image. >- // In other words: >- // - crop image by srcSubset -> imageSubset. >- // - resize imageSubset to destWidth x destHeight -> destImage. >- // - return destImage cropped by destVisibleSubset. >- SkBitmap resizedBitmap(const SkIRect& srcSubset, int destWidth, int destHeight, const SkIRect& destVisibleSubset) const; >+ // of the specified size and subset and possibly cache it. >+ // >+ // scaledImageSize >+ // Dimensions of the scaled full image. >+ // >+ // scaledImageSubset >+ // Rectangle of the subset in the scaled image. >+ SkBitmap resizedBitmap(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const; > > private: > // CachedImageInfo is used to uniquely identify cached or requested image > // resizes. >+ // Image resize is identified by two things: >+ // >+ // 1. Scaled image size >+ // The dimensions of the scaled full image. >+ // >+ // 2. Subset of the scaled image >+ // Subset rectangle of the scaled image. > struct CachedImageInfo { >- IntSize requestSize; >- SkIRect srcSubset; >+ SkISize scaledImageSize; >+ SkIRect scaledImageSubset; > > CachedImageInfo(); > >- bool isEqual(const SkIRect& otherSrcSubset, int width, int height) const; >- void set(const SkIRect& otherSrcSubset, int width, int height); >+ bool isEqual(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset) const; >+ void set(const SkISize& otherScaledImageSize, const SkIRect& otherScaledImageSubset); > }; > >+ // TODO(hclam): write this comments. > // Returns true if the given resize operation should either resize the whole > // image and cache it, or resize just the part it needs and throw the result > // away. >@@ -118,17 +118,15 @@ private: > // better if we're going to be using it more than once (like a bitmap > // scrolling on and off the screen. Since we only cache when doing the > // entire thing, it's best to just do it up front. >- bool shouldCacheResampling(const SkIRect& srcSubset, >- int destWidth, >- int destHeight, >- const SkIRect& destSubset) const; >+ bool shouldCacheResampling(const SkISize& scaledImageSize, const SkIRect& scaledImageSubset) const; > > // The original image. > SkBitmap m_image; > float m_resolutionScale; > >- // The cached bitmap. This will be empty() if there is no cached image. >- mutable SkBitmap m_resizedImage; >+ // The cached bitmap fragment. This is a subset of the scaled version of >+ // |m_image|. empty() returns true if there is no cached image. >+ mutable SkBitmap m_resizedImageFragment; > > // References how many times that the image size has been requested for > // the last size. >@@ -141,12 +139,11 @@ private: > // resized image, we know that we should probably cache it, even if all of > // those requests individually are small and would not otherwise be cached. > // >- // We also track the source and destination subsets for caching partial >- // image resizes. >+ // We also track scaling information and destination subset for the scaled >+ // image. See comments for CachedImageInfo. > mutable CachedImageInfo m_cachedImageInfo; > mutable int m_resizeRequests; > }; > > } > #endif // NativeImageSkia_h >- >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index c0c2158b3cd12f468ca1606f8d2a720e88fc066a..25f1645eb720690103e04ec8bfaf7b80d617c238 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,41 @@ >+2012-08-27 Alpha Lam <hclam@chromium.org> >+ >+ [skia] Drawing a subrect of bitmap image is misaligned >+ https://bugs.webkit.org/show_bug.cgi?id=95121 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ 3 new tests added. >+ >+ - fast/images/paint-subrect.html >+ >+ This test enlarges a 2x1 image in a canvas for easy verification. >+ 2/3 of the image is used as source rectangle to exercise the code path >+ for partial pixel addressing. >+ >+ Pixel test result should have 1/3 blue on the left and 2/3 green on the >+ right with gradient according to interpolation settings. >+ >+ - fast/images/paint-subrect-grid.html >+ >+ Draws a 11x11 image into a canvas in 8 fragments with partial pixels. >+ Canvas should look just like a single image enlarged. >+ >+ - fast/images/repaint-subrect-grid.html >+ >+ Scale a 31x31 image to 55x55. A blue square moves across the scaled >+ image such that the image is partially invalidated. After the animation >+ the image should look the same. >+ >+ * fast/images/paint-subrect-expected.txt: Added. >+ * fast/images/paint-subrect-grid-expected.txt: Added. >+ * fast/images/paint-subrect-grid.html: Added. >+ * fast/images/paint-subrect.html: Added. >+ * fast/images/repaint-subrect-grid.html: Added. >+ * fast/images/resources/grid-large.png: Added. >+ * fast/images/resources/grid-small.png: Added. >+ * platform/chromium/fast/images/paint-subrect-expected.png: Added. >+ > 2012-08-11 Jon Lee <jonlee@apple.com> > > webarchive/ignore-noscript-if-scripting.html expected results should use .webarchive extension >diff --git a/LayoutTests/fast/images/paint-subrect-expected.txt b/LayoutTests/fast/images/paint-subrect-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc >--- /dev/null >+++ b/LayoutTests/fast/images/paint-subrect-expected.txt >@@ -0,0 +1 @@ >+ >diff --git a/LayoutTests/fast/images/paint-subrect-grid-expected.txt b/LayoutTests/fast/images/paint-subrect-grid-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..8b137891791fe96927ad78e64b0aad7bded08bdc >--- /dev/null >+++ b/LayoutTests/fast/images/paint-subrect-grid-expected.txt >@@ -0,0 +1 @@ >+ >diff --git a/LayoutTests/fast/images/paint-subrect-grid.html b/LayoutTests/fast/images/paint-subrect-grid.html >new file mode 100644 >index 0000000000000000000000000000000000000000..a0203af57a5dca05ca7c74b01d1ed4170fd7cb15 >--- /dev/null >+++ b/LayoutTests/fast/images/paint-subrect-grid.html >@@ -0,0 +1,43 @@ >+<canvas id="canvas" width="30" height="30"></canvas> >+<script> >+if (window.testRunner) >+ testRunner.dumpAsText(true); >+ >+var img = new Image(); >+// This is a 11x11 black and white grid. >+img.src = 'resources/grid-small.png'; >+ >+function drawSubRectScaled(ctx, img, x, y, w, h, scale) >+{ >+ ctx.drawImage(img, x, y, w, h, x * scale, y * scale, w * scale, h * scale); >+} >+ >+img.onload = function() >+{ >+ // Draw the image into canvas with 8 fragments like shown: >+ // >+ // | 3.66 x 3.66 | | 3.66 x 3.66 | | 3.66 x 3.66 | >+ // >+ // | 5.5 x 3.66 | | 5.5 x 3.66 | >+ // >+ // | 3.66 x 3.66 | | 3.66 x 3.66 | | 3.66 x 3.66 | >+ >+ var ctx = document.getElementById("canvas").getContext("2d"); >+ >+ var scale = 30.0 / 11.0; >+ >+ // Draw the top row. >+ drawSubRectScaled(ctx, img, 0, 0, 3.66, 3.66, scale); >+ drawSubRectScaled(ctx, img, 3.66, 0, 3.66, 3.66, scale); >+ drawSubRectScaled(ctx, img, 7.33, 0, 3.66, 3.66, scale); >+ >+ // Draw the middle row. >+ drawSubRectScaled(ctx, img, 0, 3.66, 5.5, 3.66, scale); >+ drawSubRectScaled(ctx, img, 5.5, 3.66, 5.5, 3.66, scale); >+ >+ // Draw the bottom row. >+ drawSubRectScaled(ctx, img, 0, 7.33, 3.66, 3.66, scale); >+ drawSubRectScaled(ctx, img, 3.66, 7.33, 3.66, 3.66, scale); >+ drawSubRectScaled(ctx, img, 7.33, 7.33, 3.66, 3.66, scale); >+} >+</script> >diff --git a/LayoutTests/fast/images/paint-subrect.html b/LayoutTests/fast/images/paint-subrect.html >new file mode 100644 >index 0000000000000000000000000000000000000000..566124b32a47b761c0b576f6535616bab30da047 >--- /dev/null >+++ b/LayoutTests/fast/images/paint-subrect.html >@@ -0,0 +1,21 @@ >+<canvas id="canvas" width="200" height="100"></canvas> >+<script> >+if (window.testRunner) >+ testRunner.dumpAsText(true); >+ >+var img = new Image(); >+// This is a 2x1 image, blue at (0, 0) and green at (1, 0). >+img.src = 'data:image/png;base64,' + >+'iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAAXNSR0IArs4c6QAAAAlwSFlzAAALEwAA' + >+'CxMBAJqcGAAAAAd0SU1FB9wIGxQKIbPpHtMAAAAPSURBVAjXY2Bg+M/wnwEABgEB/6KPEdkAAAAASUVO' + >+'RK5CYII='; >+ >+img.onload = function() >+{ >+ // Start from the middle of the blue pixel and stretch to the entire >+ // canvas. Canvas should have 1/3 blue on the left and 2/3 green on the >+ // right, with gradient if interpolation allows. >+ var ctx = document.getElementById("canvas").getContext("2d"); >+ ctx.drawImage(img, 0.5, 0, 1.5, 1, 0, 0, 200, 100); >+} >+</script> >diff --git a/LayoutTests/fast/images/repaint-subrect-grid.html b/LayoutTests/fast/images/repaint-subrect-grid.html >new file mode 100644 >index 0000000000000000000000000000000000000000..d65a4d95a2a167bc9d3baa83212a855355d132c0 >--- /dev/null >+++ b/LayoutTests/fast/images/repaint-subrect-grid.html >@@ -0,0 +1,55 @@ >+<html> >+<head> >+<script> >+if (window.testRunner) { >+ testRunner.testRepaint(); >+ testRunner.dumpAsText(true); >+} >+ >+var pos = [ >+ // First row. >+ [0, 0], >+ [0, 18.33], >+ [0, 36.67], >+ >+ // Second row. >+ [18.33, -9.17], >+ [18.33, 9.17], >+ [18.33, 27.5], >+ [18.33, 45.83], >+ >+ // Third row. >+ [36.67, 0], >+ [36.67, 18.33], >+ [36.67, 36.67], >+ >+ // Disappear. >+ [-100, -100], >+]; >+var currentPos = 0; >+ >+function runTest() >+{ >+ if (currentPos >= pos.length) >+ return; >+ >+ var mask = document.getElementById("mask"); >+ mask.style.top = pos[currentPos][0]; >+ mask.style.left = pos[currentPos][1]; >+ ++currentPos; >+ >+ if (window.testRunner) { >+ testRunner.displayInvalidatedRegion(); >+ runTest(); >+ } else { >+ setTimeout("runTest();", 1000); >+ } >+} >+</script> >+</head> >+<body onload="runTest();" >+ style="background: url(resources/grid-large.png) no-repeat; background-size: 55px 55px;"> >+<div id="mask" style="position: absolute; top: -100px; left: -100px; >+ background-color: blue; width: 18.33px; height: 18.33px;"></div> >+</body> >+</html> >diff --git a/LayoutTests/fast/images/resources/grid-large.png b/LayoutTests/fast/images/resources/grid-large.png >new file mode 100644 >index 0000000000000000000000000000000000000000..32046ed73b2952a0392a37351288f10d2f6e08f6 >GIT binary patch >literal 271 >zcmeAS@N?(olHy`uVBq!ia0vp^@*ph00wkZBGkpY7OiAAEF8}|FtTsFg<Z%{wL>2>S >z4={E+nQaFWEGuwK2hw1@3|bt_H-Q`*PZ!4!kK=F88;UY02pqo9R6o(ef-~`;v*ab! >z<oo;%Ou;X_6qdfNyuLi(>8YvO;p<{{7J(W6=Ko>Jwz0X`aF(+HXpU-$YeY#(Vo9o1 >za#1RfVlXl=G}1M&)HN^)F*33;Ft;)^)CH0T2L9z!9-wH*%}>cptHiCrPD>{SsDZ)L >L)z4*}Q$iB}*4|9Z > >literal 0 >HcmV?d00001 > >diff --git a/LayoutTests/fast/images/resources/grid-small.png b/LayoutTests/fast/images/resources/grid-small.png >new file mode 100644 >index 0000000000000000000000000000000000000000..8dd2a76ba7021726ded3263fcfe90df1e344a5cd >GIT binary patch >literal 252 >zcmeAS@N?(olHy`uVBq!ia0vp^+#oE#0wjY?livX;rX+87m;e7oRvVrL@;D1TB8!2v >z2N=7Z%(epwmK8Xr18J~chB*(XFakN6o-U3d5|@*I{Qv*Io~`Zw|NFv@#zqEz{-1YH >zNvPrv{nfxxbtUE33?2r{4JnLSd^?T-HK~@kMwFx^mZVxG7o`Fz1|tJQBV7YaT?4}q >wLvt%bLn~tgT?2C~0|R|y(J3eza`RI%(<*Umh|6260o1_Y>FVdQ&MBb@0Ii}$0RR91 > >literal 0 >HcmV?d00001 > >diff --git a/LayoutTests/platform/chromium/fast/images/paint-subrect-expected.png b/LayoutTests/platform/chromium/fast/images/paint-subrect-expected.png >new file mode 100644 >index 0000000000000000000000000000000000000000..1fca07f0c61c5a71ee011c9369d7ae6657f06ea2 >GIT binary patch >literal 2881 >zcmeAS@N?(olHy`uVBq!ia0y~yU{+vYV2a>i0*Z)=h^hlA%@Ws$lH`ok<m}?oTn0<C >zB(p>VbMr*Yq$Hzc3yU;U)1)Nx<dihC6w4%Y3lp|p^8yA2uFIY-jv*Cu-rh0fVlotP >zxY&E+^<V#$>Js`1Tn`UF`V=>_QSqs|>$Nj73=AcC>G2E<53*Rq7#w^O7#WUWGT!Yz >z7&ZIg<E^pZ?)22zZhv+9XZ7w^>(4yAZ_2RlJU4EAm?n*iU=82_3znR-XKt1=FzgV~ >zk6>UhY-D8+5FTZW2Eu4c7|jKvxnMLGjOK#TTrfy-!AAbbK$+DcvxUzC+r$i>u6{1- >HoD!M<O~%Wb > >literal 0 >HcmV?d00001 >
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 95121
:
160805
|
160842
|
161942
|
162104
|
162106
|
162338
|
162634
|
162639
|
163429
|
163696
|
164059
|
164062
|
164983
|
166942
|
166961
|
167140