b6bda3014a01dff365c489e78978132e358cb8dd
[WebKit.git] / Source / WebCore / platform / graphics / Image.cpp
1 /*
2  * Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
3  * Copyright (C) 2004, 2005, 2006 Apple Inc.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
25  */
26
27 #include "config.h"
28 #include "Image.h"
29
30 #include "AffineTransform.h"
31 #include "BitmapImage.h"
32 #include "GraphicsContext.h"
33 #include "ImageObserver.h"
34 #include "Length.h"
35 #include "MIMETypeRegistry.h"
36 #include "SVGImage.h"
37 #include "SharedBuffer.h"
38 #include <math.h>
39 #include <wtf/MainThread.h>
40 #include <wtf/StdLibExtras.h>
41 #include <wtf/URL.h>
42 #include <wtf/text/TextStream.h>
43
44 #if USE(CG)
45 #include "PDFDocumentImage.h"
46 #include <CoreFoundation/CoreFoundation.h>
47 #endif
48
49 namespace WebCore {
50
51 Image::Image(ImageObserver* observer)
52     : m_imageObserver(observer)
53 {
54 }
55
56 Image::~Image() = default;
57
58 Image& Image::nullImage()
59 {
60     ASSERT(isMainThread());
61     static Image& nullImage = BitmapImage::create().leakRef();
62     return nullImage;
63 }
64
65 RefPtr<Image> Image::create(ImageObserver& observer)
66 {
67     auto mimeType = observer.mimeType();
68     if (mimeType == "image/svg+xml")
69         return SVGImage::create(observer);
70
71     auto url = observer.sourceUrl();
72     if (isPDFResource(mimeType, url) || isPostScriptResource(mimeType, url)) {
73 #if USE(CG) && !USE(WEBKIT_IMAGE_DECODERS)
74         return PDFDocumentImage::create(&observer);
75 #else
76         return nullptr;
77 #endif
78     }
79
80     return BitmapImage::create(&observer);
81 }
82
83 bool Image::supportsType(const String& type)
84 {
85     return MIMETypeRegistry::isSupportedImageMIMEType(type);
86
87
88 bool Image::isPDFResource(const String& mimeType, const URL& url)
89 {
90     if (mimeType.isEmpty())
91         return url.path().endsWithIgnoringASCIICase(".pdf");
92     return MIMETypeRegistry::isPDFMIMEType(mimeType);
93 }
94
95 bool Image::isPostScriptResource(const String& mimeType, const URL& url)
96 {
97     if (mimeType.isEmpty())
98         return url.path().endsWithIgnoringASCIICase(".ps");
99     return MIMETypeRegistry::isPostScriptMIMEType(mimeType);
100 }
101
102
103 EncodedDataStatus Image::setData(RefPtr<SharedBuffer>&& data, bool allDataReceived)
104 {
105     m_encodedImageData = WTFMove(data);
106
107     // Don't do anything; it is an empty image.
108     if (!m_encodedImageData.get() || !m_encodedImageData->size())
109         return EncodedDataStatus::Complete;
110
111     return dataChanged(allDataReceived);
112 }
113
114 URL Image::sourceURL() const
115 {
116     return imageObserver() ? imageObserver()->sourceUrl() : URL();
117 }
118
119 String Image::mimeType() const
120 {
121     return imageObserver() ? imageObserver()->mimeType() : emptyString();
122 }
123
124 long long Image::expectedContentLength() const
125 {
126     return imageObserver() ? imageObserver()->expectedContentLength() : 0;
127 }
128
129 void Image::fillWithSolidColor(GraphicsContext& ctxt, const FloatRect& dstRect, const Color& color, CompositeOperator op)
130 {
131     if (!color.isVisible())
132         return;
133     
134     CompositeOperator previousOperator = ctxt.compositeOperation();
135     ctxt.setCompositeOperation(color.isOpaque() && op == CompositeSourceOver ? CompositeCopy : op);
136     ctxt.fillRect(dstRect, color);
137     ctxt.setCompositeOperation(previousOperator);
138 }
139
140 void Image::drawPattern(GraphicsContext& ctxt, const FloatRect& destRect, const FloatRect& tileRect, const AffineTransform& patternTransform,
141     const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode)
142 {
143     if (!nativeImageForCurrentFrame(&ctxt))
144         return;
145
146     ctxt.drawPattern(*this, destRect, tileRect, patternTransform, phase, spacing, op, blendMode);
147
148     if (imageObserver())
149         imageObserver()->didDraw(*this);
150 }
151
152 ImageDrawResult Image::drawTiled(GraphicsContext& ctxt, const FloatRect& destRect, const FloatPoint& srcPoint, const FloatSize& scaledTileSize, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode, DecodingMode decodingMode, ImageOrientation orientation)
153 {
154     Color color = singlePixelSolidColor();
155     if (color.isValid()) {
156         fillWithSolidColor(ctxt, destRect, color, op);
157         return ImageDrawResult::DidDraw;
158     }
159
160     ASSERT(!isBitmapImage() || notSolidColor());
161
162 #if PLATFORM(IOS_FAMILY)
163     FloatSize intrinsicTileSize = originalSize();
164 #else
165     FloatSize intrinsicTileSize = size();
166 #endif
167     if (hasRelativeWidth())
168         intrinsicTileSize.setWidth(scaledTileSize.width());
169     if (hasRelativeHeight())
170         intrinsicTileSize.setHeight(scaledTileSize.height());
171
172     FloatSize scale(scaledTileSize / intrinsicTileSize);
173
174     FloatRect oneTileRect;
175     FloatSize actualTileSize = scaledTileSize + spacing;
176     oneTileRect.setX(destRect.x() + fmodf(fmodf(-srcPoint.x(), actualTileSize.width()) - actualTileSize.width(), actualTileSize.width()));
177     oneTileRect.setY(destRect.y() + fmodf(fmodf(-srcPoint.y(), actualTileSize.height()) - actualTileSize.height(), actualTileSize.height()));
178     oneTileRect.setSize(scaledTileSize);
179     
180     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
181     if (oneTileRect.contains(destRect) && !ctxt.drawLuminanceMask()) {
182         FloatRect visibleSrcRect;
183         visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width());
184         visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height());
185         visibleSrcRect.setWidth(destRect.width() / scale.width());
186         visibleSrcRect.setHeight(destRect.height() / scale.height());
187         return draw(ctxt, destRect, visibleSrcRect, op, blendMode, decodingMode, orientation);
188     }
189
190 #if PLATFORM(IOS_FAMILY)
191     // When using accelerated drawing on iOS, it's faster to stretch an image than to tile it.
192     if (ctxt.isAcceleratedContext()) {
193         if (size().width() == 1 && intersection(oneTileRect, destRect).height() == destRect.height()) {
194             FloatRect visibleSrcRect;
195             visibleSrcRect.setX(0);
196             visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height());
197             visibleSrcRect.setWidth(1);
198             visibleSrcRect.setHeight(destRect.height() / scale.height());
199             return draw(ctxt, destRect, visibleSrcRect, op, BlendMode::Normal, decodingMode, orientation);
200         }
201         if (size().height() == 1 && intersection(oneTileRect, destRect).width() == destRect.width()) {
202             FloatRect visibleSrcRect;
203             visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width());
204             visibleSrcRect.setY(0);
205             visibleSrcRect.setWidth(destRect.width() / scale.width());
206             visibleSrcRect.setHeight(1);
207             return draw(ctxt, destRect, visibleSrcRect, op, BlendMode::Normal, decodingMode, orientation);
208         }
209     }
210 #endif
211
212     // Patterned images and gradients can use lots of memory for caching when the
213     // tile size is large (<rdar://problem/4691859>, <rdar://problem/6239505>).
214     // Memory consumption depends on the transformed tile size which can get
215     // larger than the original tile if user zooms in enough.
216 #if PLATFORM(IOS_FAMILY)
217     const float maxPatternTilePixels = 512 * 512;
218 #else
219     const float maxPatternTilePixels = 2048 * 2048;
220 #endif
221     FloatRect transformedTileSize = ctxt.getCTM().mapRect(FloatRect(FloatPoint(), scaledTileSize));
222     float transformedTileSizePixels = transformedTileSize.width() * transformedTileSize.height();
223     FloatRect currentTileRect = oneTileRect;
224     if (transformedTileSizePixels > maxPatternTilePixels) {
225         GraphicsContextStateSaver stateSaver(ctxt);
226         ctxt.clip(destRect);
227
228         currentTileRect.shiftYEdgeTo(destRect.y());
229         float toY = currentTileRect.y();
230         ImageDrawResult result = ImageDrawResult::DidNothing;
231         while (toY < destRect.maxY()) {
232             currentTileRect.shiftXEdgeTo(destRect.x());
233             float toX = currentTileRect.x();
234             while (toX < destRect.maxX()) {
235                 FloatRect toRect(toX, toY, currentTileRect.width(), currentTileRect.height());
236                 FloatRect fromRect(toFloatPoint(currentTileRect.location() - oneTileRect.location()), currentTileRect.size());
237                 fromRect.scale(1 / scale.width(), 1 / scale.height());
238
239                 result = draw(ctxt, toRect, fromRect, op, BlendMode::Normal, decodingMode, orientation);
240                 if (result == ImageDrawResult::DidRequestDecoding)
241                     return result;
242                 toX += currentTileRect.width();
243                 currentTileRect.shiftXEdgeTo(oneTileRect.x());
244             }
245             toY += currentTileRect.height();
246             currentTileRect.shiftYEdgeTo(oneTileRect.y());
247         }
248         return result;
249     }
250
251     AffineTransform patternTransform = AffineTransform().scaleNonUniform(scale.width(), scale.height());
252     FloatRect tileRect(FloatPoint(), intrinsicTileSize);
253     drawPattern(ctxt, destRect, tileRect, patternTransform, oneTileRect.location(), spacing, op, blendMode);
254     startAnimation();
255     return ImageDrawResult::DidDraw;
256 }
257
258 // FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things.
259 ImageDrawResult Image::drawTiled(GraphicsContext& ctxt, const FloatRect& dstRect, const FloatRect& srcRect, const FloatSize& tileScaleFactor, TileRule hRule, TileRule vRule, CompositeOperator op)
260 {    
261     Color color = singlePixelSolidColor();
262     if (color.isValid()) {
263         fillWithSolidColor(ctxt, dstRect, color, op);
264         return ImageDrawResult::DidDraw;
265     }
266     
267     FloatSize tileScale = tileScaleFactor;
268     FloatSize spacing;
269     
270     // FIXME: These rules follow CSS border-image rules, but they should not be down here in Image.
271     bool centerOnGapHorizonally = false;
272     bool centerOnGapVertically = false;
273     switch (hRule) {
274     case RoundTile: {
275         int numItems = std::max<int>(floorf(dstRect.width() / srcRect.width()), 1);
276         tileScale.setWidth(dstRect.width() / (srcRect.width() * numItems));
277         break;
278     }
279     case SpaceTile: {
280         int numItems = floorf(dstRect.width() / srcRect.width());
281         if (!numItems)
282             return ImageDrawResult::DidNothing;
283         spacing.setWidth((dstRect.width() - srcRect.width() * numItems) / (numItems + 1));
284         tileScale.setWidth(1);
285         centerOnGapHorizonally = !(numItems & 1);
286         break;
287     }
288     case StretchTile:
289     case RepeatTile:
290         break;
291     }
292
293     switch (vRule) {
294     case RoundTile: {
295         int numItems = std::max<int>(floorf(dstRect.height() / srcRect.height()), 1);
296         tileScale.setHeight(dstRect.height() / (srcRect.height() * numItems));
297         break;
298         }
299     case SpaceTile: {
300         int numItems = floorf(dstRect.height() / srcRect.height());
301         if (!numItems)
302             return ImageDrawResult::DidNothing;
303         spacing.setHeight((dstRect.height() - srcRect.height() * numItems) / (numItems + 1));
304         tileScale.setHeight(1);
305         centerOnGapVertically = !(numItems & 1);
306         break;
307     }
308     case StretchTile:
309     case RepeatTile:
310         break;
311     }
312
313     AffineTransform patternTransform = AffineTransform().scaleNonUniform(tileScale.width(), tileScale.height());
314
315     // We want to construct the phase such that the pattern is centered (when stretch is not
316     // set for a particular rule).
317     float hPhase = tileScale.width() * srcRect.x();
318     float vPhase = tileScale.height() * srcRect.y();
319     float scaledTileWidth = tileScale.width() * srcRect.width();
320     float scaledTileHeight = tileScale.height() * srcRect.height();
321
322     if (centerOnGapHorizonally)
323         hPhase -= spacing.width();
324     else if (hRule == Image::RepeatTile || hRule == Image::SpaceTile)
325         hPhase -= (dstRect.width() - scaledTileWidth) / 2;
326
327     if (centerOnGapVertically)
328         vPhase -= spacing.height();
329     else if (vRule == Image::RepeatTile || vRule == Image::SpaceTile)
330         vPhase -= (dstRect.height() - scaledTileHeight) / 2;
331
332     FloatPoint patternPhase(dstRect.x() - hPhase, dstRect.y() - vPhase);
333     drawPattern(ctxt, dstRect, srcRect, patternTransform, patternPhase, spacing, op);
334     startAnimation();
335     return ImageDrawResult::DidDraw;
336 }
337
338 void Image::computeIntrinsicDimensions(Length& intrinsicWidth, Length& intrinsicHeight, FloatSize& intrinsicRatio)
339 {
340 #if PLATFORM(IOS_FAMILY)
341     intrinsicRatio = originalSize();
342 #else
343     intrinsicRatio = size();
344 #endif
345     intrinsicWidth = Length(intrinsicRatio.width(), Fixed);
346     intrinsicHeight = Length(intrinsicRatio.height(), Fixed);
347 }
348
349 void Image::startAnimationAsynchronously()
350 {
351     if (!m_animationStartTimer)
352         m_animationStartTimer = makeUnique<Timer>(*this, &Image::startAnimation);
353     if (m_animationStartTimer->isActive())
354         return;
355     m_animationStartTimer->startOneShot(0_s);
356 }
357
358 void Image::dump(TextStream& ts) const
359 {
360     if (isAnimated())
361         ts.dumpProperty("animated", isAnimated());
362
363     if (isNull())
364         ts.dumpProperty("is-null-image", true);
365
366     ts.dumpProperty("size", size());
367 }
368
369 TextStream& operator<<(TextStream& ts, const Image& image)
370 {
371     TextStream::GroupScope scope(ts);
372     
373     if (image.isBitmapImage())
374         ts << "bitmap image";
375     else if (image.isCrossfadeGeneratedImage())
376         ts << "crossfade image";
377     else if (image.isNamedImageGeneratedImage())
378         ts << "named image";
379     else if (image.isGradientImage())
380         ts << "gradient image";
381     else if (image.isSVGImage())
382         ts << "svg image";
383     else if (image.isPDFDocumentImage())
384         ts << "pdf image";
385
386     image.dump(ts);
387     return ts;
388 }
389
390 #if !PLATFORM(COCOA) && !PLATFORM(GTK) && !PLATFORM(WIN)
391
392 void BitmapImage::invalidatePlatformData()
393 {
394 }
395
396 Ref<Image> Image::loadPlatformResource(const char* resource)
397 {
398     WTFLogAlways("WARNING: trying to load platform resource '%s'", resource);
399     return BitmapImage::create();
400 }
401
402 #endif // !PLATFORM(COCOA) && !PLATFORM(GTK) && !PLATFORM(WIN)
403 }