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()) |
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) |
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. |
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 = std::make_unique<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 | } |
404 | |