[iOS] PDFDocumentImage should not create a cached image larger than 4M pixels
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Jun 2016 00:39:58 +0000 (00:39 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 3 Jun 2016 00:39:58 +0000 (00:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=157857

Patch by Said Abou-Hallawa <sabouhallawa@apple,com> on 2016-06-02
Reviewed by Darin Adler.

Source/WebCore:

On iOS, if the scaled size of the PDFDocumentImage cached image will exceed
the 4M pixels limit and the system physical memory is 1GB or less, do not
create it. PDFDocumentImage::draw() falls back peacefully if the cached image
couldn't be created for any reason. The PDF will be drawn directly to the
GraphicsContext without having to go through the cached image. This means
the whole PDF will be drawn multiple times; one time for each tile. I think
this is okay for zooming a large PDFDocumentImage on a low end device.

* html/canvas/CanvasRenderingContext2D.cpp:
(WebCore::CanvasRenderingContext2D::drawTextInternal): Call the static function
ImageBuffer::createCompatibleBuffer() without having to go through the GraphicsContext.

* platform/graphics/BitmapImage.cpp:
(WebCore::BitmapImage::drawPattern): Ditto.

* platform/graphics/FloatSize.h:
(WebCore::FloatSize::area): A helper function similar to IntSize::area().
(WebCore::operator*): Scale a FloatSize by another FloatSize and return the result.

* platform/graphics/GradientImage.cpp:
(WebCore::GradientImage::drawPattern): Call ImageBuffer::createCompatibleBuffer().

* platform/graphics/GraphicsContext.cpp:
(WebCore::GraphicsContext::scaleFactor):: Return the scaling part of the current CTM.
(WebCore::scalesMatch): Deleted.
(WebCore::GraphicsContext::createCompatibleBuffer): Deleted.
(WebCore::GraphicsContext::isCompatibleWithBuffer): Deleted.
* platform/graphics/GraphicsContext.h:
Move these image buffer functions to ImageBuffer.

* platform/graphics/ImageBuffer.cpp:
(WebCore::ImageBuffer::createCompatibleBuffer): Make createCompatibleBuffer
a static function of the ImageBuffer. There is no need to go through the
GraphicsContext just to get the GraphicsContext scaleFactor.

(WebCore::ImageBuffer::compatibleBufferSize):  Scale the size of the cachedImage
by the scaleFactor of the context.

(WebCore::ImageBuffer::isCompatibleWithContext): Returns true if the drawing
context and the ImageBuffer context have the same scaleFactor.

* platform/graphics/ImageBuffer.h:

* platform/graphics/NamedImageGeneratedImage.cpp:
(WebCore::NamedImageGeneratedImage::drawPattern): Call ImageBuffer::createCompatibleBuffer().

* platform/graphics/cg/PDFDocumentImage.cpp:
(WebCore::PDFDocumentImage::updateCachedImageIfNeeded): On iOS, if the
physical memory is less than 1GB, do not allocate more than 16MB for the
PDF cachedImage.

* rendering/RenderBoxModelObject.cpp:
(WebCore::RenderBoxModelObject::paintFillLayerExtended): Call ImageBuffer::createCompatibleBuffer().
* rendering/svg/SVGRenderingContext.cpp:
(WebCore::SVGRenderingContext::bufferForeground): Ditto.

Source/WTF:

* wtf/StdLibExtras.h: Add a constant value for GB (2^30).

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201629 268f45cc-cd09-0410-ab3c-d52691b4dbfc

15 files changed:
Source/WTF/ChangeLog
Source/WTF/wtf/StdLibExtras.h
Source/WebCore/ChangeLog
Source/WebCore/html/canvas/CanvasRenderingContext2D.cpp
Source/WebCore/platform/graphics/BitmapImage.cpp
Source/WebCore/platform/graphics/FloatSize.h
Source/WebCore/platform/graphics/GradientImage.cpp
Source/WebCore/platform/graphics/GraphicsContext.cpp
Source/WebCore/platform/graphics/GraphicsContext.h
Source/WebCore/platform/graphics/ImageBuffer.cpp
Source/WebCore/platform/graphics/ImageBuffer.h
Source/WebCore/platform/graphics/NamedImageGeneratedImage.cpp
Source/WebCore/platform/graphics/cg/PDFDocumentImage.cpp
Source/WebCore/rendering/RenderBoxModelObject.cpp
Source/WebCore/rendering/svg/SVGRenderingContext.cpp

index 8d540d0..aa86265 100644 (file)
@@ -1,3 +1,12 @@
+2016-06-02  Said Abou-Hallawa  <sabouhallawa@apple,com>
+
+        [iOS] PDFDocumentImage should not create a cached image larger than 4M pixels
+        https://bugs.webkit.org/show_bug.cgi?id=157857
+
+        Reviewed by Darin Adler.
+
+        * wtf/StdLibExtras.h: Add a constant value for GB (2^30).
+
 2016-06-02  Oliver Hunt  <oliver@apple.com>
 
         JS parser incorrectly handles invalid utf8 in error messages.
index 7725b1a..4457ba2 100644 (file)
@@ -121,6 +121,7 @@ enum CheckMoveParameterTag { CheckMoveParameter };
 
 static const size_t KB = 1024;
 static const size_t MB = 1024 * 1024;
+static const size_t GB = 1024 * 1024 * 1024;
 
 inline bool isPointerAligned(void* p)
 {
index 8aefc67..9a16a16 100644 (file)
@@ -1,3 +1,66 @@
+2016-06-02  Said Abou-Hallawa  <sabouhallawa@apple,com>
+
+        [iOS] PDFDocumentImage should not create a cached image larger than 4M pixels
+        https://bugs.webkit.org/show_bug.cgi?id=157857
+
+        Reviewed by Darin Adler.
+
+        On iOS, if the scaled size of the PDFDocumentImage cached image will exceed
+        the 4M pixels limit and the system physical memory is 1GB or less, do not
+        create it. PDFDocumentImage::draw() falls back peacefully if the cached image
+        couldn't be created for any reason. The PDF will be drawn directly to the
+        GraphicsContext without having to go through the cached image. This means
+        the whole PDF will be drawn multiple times; one time for each tile. I think
+        this is okay for zooming a large PDFDocumentImage on a low end device.
+        
+        * html/canvas/CanvasRenderingContext2D.cpp:
+        (WebCore::CanvasRenderingContext2D::drawTextInternal): Call the static function
+        ImageBuffer::createCompatibleBuffer() without having to go through the GraphicsContext.
+        
+        * platform/graphics/BitmapImage.cpp:
+        (WebCore::BitmapImage::drawPattern): Ditto.
+        
+        * platform/graphics/FloatSize.h:
+        (WebCore::FloatSize::area): A helper function similar to IntSize::area().
+        (WebCore::operator*): Scale a FloatSize by another FloatSize and return the result.
+        
+        * platform/graphics/GradientImage.cpp:
+        (WebCore::GradientImage::drawPattern): Call ImageBuffer::createCompatibleBuffer().
+        
+        * platform/graphics/GraphicsContext.cpp:
+        (WebCore::GraphicsContext::scaleFactor):: Return the scaling part of the current CTM.
+        (WebCore::scalesMatch): Deleted.
+        (WebCore::GraphicsContext::createCompatibleBuffer): Deleted.
+        (WebCore::GraphicsContext::isCompatibleWithBuffer): Deleted.
+        * platform/graphics/GraphicsContext.h:
+        Move these image buffer functions to ImageBuffer.
+        
+        * platform/graphics/ImageBuffer.cpp:
+        (WebCore::ImageBuffer::createCompatibleBuffer): Make createCompatibleBuffer
+        a static function of the ImageBuffer. There is no need to go through the
+        GraphicsContext just to get the GraphicsContext scaleFactor.
+        
+        (WebCore::ImageBuffer::compatibleBufferSize):  Scale the size of the cachedImage
+        by the scaleFactor of the context.
+        
+        (WebCore::ImageBuffer::isCompatibleWithContext): Returns true if the drawing
+        context and the ImageBuffer context have the same scaleFactor.
+        
+        * platform/graphics/ImageBuffer.h:
+        
+        * platform/graphics/NamedImageGeneratedImage.cpp:
+        (WebCore::NamedImageGeneratedImage::drawPattern): Call ImageBuffer::createCompatibleBuffer().
+        
+        * platform/graphics/cg/PDFDocumentImage.cpp:
+        (WebCore::PDFDocumentImage::updateCachedImageIfNeeded): On iOS, if the
+        physical memory is less than 1GB, do not allocate more than 16MB for the
+        PDF cachedImage.
+        
+        * rendering/RenderBoxModelObject.cpp:
+        (WebCore::RenderBoxModelObject::paintFillLayerExtended): Call ImageBuffer::createCompatibleBuffer().
+        * rendering/svg/SVGRenderingContext.cpp:
+        (WebCore::SVGRenderingContext::bufferForeground): Ditto.
+
 2016-06-02  Chris Dumez  <cdumez@apple.com>
 
         [WebIDL] 'undefined' should be an acceptable value for nullable parameters
index 906c8e6..b52c456 100644 (file)
@@ -2448,7 +2448,7 @@ void CanvasRenderingContext2D::drawTextInternal(const String& text, float x, flo
             fontProxy.drawBidiText(*c, textRun, location + offset, FontCascade::UseFallbackIfFontNotReady);
         }
 
-        std::unique_ptr<ImageBuffer> maskImage = c->createCompatibleBuffer(maskRect.size());
+        auto maskImage = ImageBuffer::createCompatibleBuffer(maskRect.size(), *c);
         if (!maskImage)
             return;
 
index 67ce62c..0676ce0 100644 (file)
@@ -606,7 +606,7 @@ void BitmapImage::drawPattern(GraphicsContext& ctxt, const FloatRect& tileRect,
         return;
     }
     if (!m_cachedImage) {
-        std::unique_ptr<ImageBuffer> buffer = ctxt.createCompatibleBuffer(expandedIntSize(tileRect.size()));
+        auto buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(tileRect.size()), ctxt);
         if (!buffer)
             return;
 
index 5213c9a..b512644 100644 (file)
@@ -103,6 +103,11 @@ public:
     {
         return m_width * m_width + m_height * m_height;
     }
+    
+    float area() const
+    {
+        return m_width * m_height;
+    }
 
     FloatSize transposedSize() const
     {
@@ -163,6 +168,11 @@ inline FloatSize operator*(float a, const FloatSize& b)
     return FloatSize(a * b.width(), a * b.height());
 }
 
+inline FloatSize operator*(const FloatSize& a, const FloatSize& b)
+{
+    return FloatSize(a.width() * b.width(), a.height() * b.height());
+}
+
 inline FloatSize operator/(const FloatSize& a, float b)
 {
     return FloatSize(a.width() / b, a.height() / b);
index 372691a..ad33c55 100644 (file)
@@ -74,8 +74,8 @@ void GradientImage::drawPattern(GraphicsContext& destContext, const FloatRect& s
 
     unsigned generatorHash = m_gradient->hash();
 
-    if (!m_cachedImageBuffer || m_cachedGeneratorHash != generatorHash || m_cachedAdjustedSize != adjustedSize || !destContext.isCompatibleWithBuffer(*m_cachedImageBuffer)) {
-        m_cachedImageBuffer = destContext.createCompatibleBuffer(adjustedSize, m_gradient->hasAlpha());
+    if (!m_cachedImageBuffer || m_cachedGeneratorHash != generatorHash || m_cachedAdjustedSize != adjustedSize || !m_cachedImageBuffer->isCompatibleWithContext(destContext)) {
+        m_cachedImageBuffer = ImageBuffer::createCompatibleBuffer(adjustedSize, destContext, m_gradient->hasAlpha());
         if (!m_cachedImageBuffer)
             return;
 
index b9085c3..af19122 100644 (file)
@@ -1012,36 +1012,6 @@ void GraphicsContext::adjustLineToPixelBoundaries(FloatPoint& p1, FloatPoint& p2
     }
 }
 
-static bool scalesMatch(AffineTransform a, AffineTransform b)
-{
-    return a.xScale() == b.xScale() && a.yScale() == b.yScale();
-}
-
-std::unique_ptr<ImageBuffer> GraphicsContext::createCompatibleBuffer(const FloatSize& size, bool hasAlpha) const
-{
-    // Make the buffer larger if the context's transform is scaling it so we need a higher
-    // resolution than one pixel per unit. Also set up a corresponding scale factor on the
-    // graphics context.
-
-    AffineTransform transform = getCTM(DefinitelyIncludeDeviceScale);
-    FloatSize scaledSize(static_cast<int>(ceil(size.width() * transform.xScale())), static_cast<int>(ceil(size.height() * transform.yScale())));
-
-    std::unique_ptr<ImageBuffer> buffer = ImageBuffer::createCompatibleBuffer(scaledSize, 1, ColorSpaceSRGB, *this, hasAlpha);
-    if (!buffer)
-        return nullptr;
-
-    buffer->context().scale(FloatSize(scaledSize.width() / size.width(), scaledSize.height() / size.height()));
-
-    return buffer;
-}
-
-bool GraphicsContext::isCompatibleWithBuffer(ImageBuffer& buffer) const
-{
-    GraphicsContext& bufferContext = buffer.context();
-
-    return scalesMatch(getCTM(), bufferContext.getCTM()) && isAcceleratedContext() == bufferContext.isAcceleratedContext();
-}
-
 #if !USE(CG)
 void GraphicsContext::platformApplyDeviceScaleFactor(float)
 {
@@ -1059,6 +1029,12 @@ void GraphicsContext::applyDeviceScaleFactor(float deviceScaleFactor)
 
     platformApplyDeviceScaleFactor(deviceScaleFactor);
 }
+    
+FloatSize GraphicsContext::scaleFactor() const
+{
+    AffineTransform transform = getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
+    return FloatSize(transform.xScale(), transform.yScale());
+}
 
 void GraphicsContext::fillEllipse(const FloatRect& ellipse)
 {
index 06aa8e8..d00353e 100644 (file)
@@ -476,15 +476,12 @@ public:
     void set3DTransform(const TransformationMatrix&);
     TransformationMatrix get3DTransform() const;
 #endif
-    // Create an image buffer compatible with this context, with suitable resolution
-    // for drawing into the buffer and then into this context.
-    std::unique_ptr<ImageBuffer> createCompatibleBuffer(const FloatSize&, bool hasAlpha = true) const;
-    bool isCompatibleWithBuffer(ImageBuffer&) const;
 
     // This function applies the device scale factor to the context, making the context capable of
     // acting as a base-level context for a HiDPI environment.
     WEBCORE_EXPORT void applyDeviceScaleFactor(float);
     void platformApplyDeviceScaleFactor(float);
+    FloatSize scaleFactor() const;
 
 #if OS(WINDOWS)
     HDC getWindowsContext(const IntRect&, bool supportAlphaBlend, bool mayCreateBitmap); // The passed in rect is used to create a bitmap for compositing inside transparency layers.
index 2ace73c..c38d481 100644 (file)
@@ -164,9 +164,37 @@ bool ImageBuffer::copyToPlatformTexture(GraphicsContext3D&, GC3Denum, Platform3D
 }
 #endif
 
+std::unique_ptr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const GraphicsContext& context, bool hasAlpha)
+{
+    if (size.isEmpty())
+        return nullptr;
+
+    FloatSize scaledSize = ImageBuffer::compatibleBufferSize(size, context);
+
+    auto buffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(scaledSize), 1, ColorSpaceSRGB, context, hasAlpha);
+    if (!buffer)
+        return nullptr;
+
+    // Set up a corresponding scale factor on the graphics context.
+    buffer->context().scale(context.scaleFactor());
+    return buffer;
+}
+
 std::unique_ptr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, float resolutionScale, ColorSpace colorSpace, const GraphicsContext& context, bool)
 {
     return create(size, context.renderingMode(), resolutionScale, colorSpace);
 }
 
+FloatSize ImageBuffer::compatibleBufferSize(const FloatSize& size, const GraphicsContext& context)
+{
+    // Enlarge the buffer size if the context's transform is scaling it so we need a higher
+    // resolution than one pixel per unit.
+    return size * context.scaleFactor();
+}
+
+bool ImageBuffer::isCompatibleWithContext(const GraphicsContext& context) const
+{
+    return areEssentiallyEqual(context.scaleFactor(), this->context().scaleFactor());
+}
+
 }
index 57ba635..6df7994 100644 (file)
@@ -79,8 +79,13 @@ public:
         return buffer;
     }
 
+    // Create an image buffer compatible with the context, with suitable resolution for drawing into the buffer and then into this context.
+    static std::unique_ptr<ImageBuffer> createCompatibleBuffer(const FloatSize&, const GraphicsContext&, bool hasAlpha = true);
     static std::unique_ptr<ImageBuffer> createCompatibleBuffer(const FloatSize&, float resolutionScale, ColorSpace, const GraphicsContext&, bool hasAlpha);
 
+    static FloatSize compatibleBufferSize(const FloatSize&, const GraphicsContext&);
+    bool isCompatibleWithContext(const GraphicsContext&) const;
+
     WEBCORE_EXPORT ~ImageBuffer();
 
     // The actual resolution of the backing store
index bd798c0..6810974 100644 (file)
@@ -64,7 +64,7 @@ void NamedImageGeneratedImage::draw(GraphicsContext& context, const FloatRect& d
 void NamedImageGeneratedImage::drawPattern(GraphicsContext& context, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator compositeOp, const FloatRect& dstRect, BlendMode blendMode)
 {
 #if USE(NEW_THEME)
-    std::unique_ptr<ImageBuffer> imageBuffer = context.createCompatibleBuffer(size(), true);
+    auto imageBuffer = ImageBuffer::createCompatibleBuffer(size(), context, true);
     if (!imageBuffer)
         return;
 
index ef76b86..30622fb 100644 (file)
@@ -43,7 +43,9 @@
 #include <CoreGraphics/CGContext.h>
 #include <CoreGraphics/CGPDFDocument.h>
 #include <wtf/MathExtras.h>
+#include <wtf/RAMSize.h>
 #include <wtf/RetainPtr.h>
+#include <wtf/StdLibExtras.h>
 
 #if !PLATFORM(COCOA)
 #include "ImageSourceCG.h"
@@ -143,6 +145,15 @@ static void transformContextForPainting(GraphicsContext& context, const FloatRec
 void PDFDocumentImage::updateCachedImageIfNeeded(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect)
 {
 #if PLATFORM(IOS)
+    // On iOS, if the physical memory is less than 1GB, do not allocate more than 16MB for the PDF cachedImage.
+    const size_t memoryThreshold = WTF::GB;
+    const size_t maxArea = 16 * WTF::MB / 4; // 16 MB maximum size, divided by a rough cost of 4 bytes per pixel of area.
+    
+    if (ramSize() <= memoryThreshold && ImageBuffer::compatibleBufferSize(dstRect.size(), context).area() >= maxArea) {
+        m_cachedImageBuffer = nullptr;
+        return;
+    }
+
     // On iOS, some clients use low-quality image interpolation always, which throws off this optimization,
     // as we never get the subsequent high-quality paint. Since live resize is rare on iOS, disable the optimization.
     // FIXME (136593): It's also possible to do the wrong thing here if CSS specifies low-quality interpolation via the "image-rendering"
@@ -156,26 +167,27 @@ void PDFDocumentImage::updateCachedImageIfNeeded(GraphicsContext& context, const
     bool repaintIfNecessary = interpolationQuality != InterpolationNone && interpolationQuality != InterpolationLow;
 #endif
 
-    if (!m_cachedImageBuffer || (!cacheParametersMatch(context, dstRect, srcRect) && repaintIfNecessary)) {
-        m_cachedImageBuffer = context.createCompatibleBuffer(FloatRect(enclosingIntRect(dstRect)).size());
-        if (!m_cachedImageBuffer)
-            return;
-        GraphicsContext& bufferContext = m_cachedImageBuffer->context();
+    if (m_cachedImageBuffer && (!repaintIfNecessary || cacheParametersMatch(context, dstRect, srcRect)))
+        return;
+    
+    m_cachedImageBuffer = ImageBuffer::createCompatibleBuffer(FloatRect(enclosingIntRect(dstRect)).size(), context);
+    if (!m_cachedImageBuffer)
+        return;
+    auto& bufferContext = m_cachedImageBuffer->context();
 
-        transformContextForPainting(bufferContext, dstRect, srcRect);
-        drawPDFPage(bufferContext);
+    transformContextForPainting(bufferContext, dstRect, srcRect);
+    drawPDFPage(bufferContext);
 
-        m_cachedTransform = context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
-        m_cachedDestinationSize = dstRect.size();
-        m_cachedSourceRect = srcRect;
+    m_cachedTransform = context.getCTM(GraphicsContext::DefinitelyIncludeDeviceScale);
+    m_cachedDestinationSize = dstRect.size();
+    m_cachedSourceRect = srcRect;
 
-        IntSize internalSize = m_cachedImageBuffer->internalSize();
-        size_t oldCachedBytes = m_cachedBytes;
-        m_cachedBytes = internalSize.width() * internalSize.height() * 4;
+    IntSize internalSize = m_cachedImageBuffer->internalSize();
+    size_t oldCachedBytes = m_cachedBytes;
+    m_cachedBytes = safeCast<size_t>(internalSize.width()) * internalSize.height() * 4;
 
-        if (imageObserver())
-            imageObserver()->decodedSizeChanged(this, safeCast<int>(m_cachedBytes) - safeCast<int>(oldCachedBytes));
-    }
+    if (imageObserver())
+        imageObserver()->decodedSizeChanged(this, safeCast<int>(m_cachedBytes) - safeCast<int>(oldCachedBytes));
 }
 
 void PDFDocumentImage::draw(GraphicsContext& context, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator op, BlendMode, ImageOrientationDescription)
index 98aa361..82649ce 100644 (file)
@@ -756,7 +756,7 @@ void RenderBoxModelObject::paintFillLayerExtended(const PaintInfo& paintInfo, co
         maskRect.intersect(snappedIntRect(paintInfo.rect));
 
         // Now create the mask.
-        maskImage = context.createCompatibleBuffer(maskRect.size());
+        maskImage = ImageBuffer::createCompatibleBuffer(maskRect.size(), context);
         if (!maskImage)
             return;
         paintMaskForTextFillBox(maskImage.get(), maskRect, box, scrolledPaintRect);
index 3c1dfb5..976df5a 100644 (file)
@@ -351,7 +351,7 @@ bool SVGRenderingContext::bufferForeground(std::unique_ptr<ImageBuffer>& imageBu
 
     // Create a new buffer and paint the foreground into it.
     if (!imageBuffer) {
-        if ((imageBuffer = m_paintInfo->context().createCompatibleBuffer(expandedIntSize(boundingBox.size()), true))) {
+        if ((imageBuffer = ImageBuffer::createCompatibleBuffer(expandedIntSize(boundingBox.size()), m_paintInfo->context(), true))) {
             GraphicsContext& bufferedRenderingContext = imageBuffer->context();
             bufferedRenderingContext.translate(-boundingBox.x(), -boundingBox.y());
             PaintInfo bufferedInfo(*m_paintInfo);