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
49namespace WebCore {
50
51Image::Image(ImageObserver* observer)
52 : m_imageObserver(observer)
53{
54}
55
56Image::~Image() = default;
57
58Image& Image::nullImage()
59{
60 ASSERT(isMainThread());
61 static Image& nullImage = BitmapImage::create().leakRef();
62 return nullImage;
63}
64
65RefPtr<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
83bool Image::supportsType(const String& type)
84{
85 return MIMETypeRegistry::isSupportedImageMIMEType(type);
86}
87
88bool 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
95bool 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
103EncodedDataStatus 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
114URL Image::sourceURL() const
115{
116 return imageObserver() ? imageObserver()->sourceUrl() : URL();
117}
118
119String Image::mimeType() const
120{
121 return imageObserver() ? imageObserver()->mimeType() : emptyString();
122}
123
124long long Image::expectedContentLength() const
125{
126 return imageObserver() ? imageObserver()->expectedContentLength() : 0;
127}
128
129void 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
140void 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())
144 return;
145
146 ctxt.drawPattern(*this, destRect, tileRect, patternTransform, phase, spacing, op, blendMode);
147
148 if (imageObserver())
149 imageObserver()->didDraw(*this);
150}
151
152ImageDrawResult Image::drawTiled(GraphicsContext& ctxt, const FloatRect& destRect, const FloatPoint& srcPoint, const FloatSize& scaledTileSize, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode, DecodingMode decodingMode)
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, ImageOrientationDescription());
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, ImageOrientationDescription());
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, ImageOrientationDescription());
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, ImageOrientationDescription());
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.
259ImageDrawResult 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
338void 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
349void Image::startAnimationAsynchronously()
350{
351 if (!m_animationStartTimer)
352 m_animationStartTimer = std::make_unique<Timer>(*this, &Image::startAnimation);
353 if (m_animationStartTimer->isActive())
354 return;
355 m_animationStartTimer->startOneShot(0_s);
356}
357
358void 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
369TextStream& 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
392void BitmapImage::invalidatePlatformData()
393{
394}
395
396Ref<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}
404