1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "ImageDecoder.h"
28
29#if USE(CG)
30
31#include "CoreGraphicsSPI.h"
32#include "ImageOrientation.h"
33#include "IntPoint.h"
34#include "IntSize.h"
35#include "MIMETypeRegistry.h"
36#include "SharedBuffer.h"
37#include <wtf/NeverDestroyed.h>
38
39#if !PLATFORM(IOS)
40#include <ApplicationServices/ApplicationServices.h>
41#else
42#include <ImageIO/ImageIO.h>
43#include <wtf/RetainPtr.h>
44#endif
45
46#if USE(APPLE_INTERNAL_SDK)
47#import <ImageIO/CGImageSourcePrivate.h>
48#else
49const CFStringRef kCGImageSourceSubsampleFactor = CFSTR("kCGImageSourceSubsampleFactor");
50const CFStringRef kCGImageSourceShouldCacheImmediately = CFSTR("kCGImageSourceShouldCacheImmediately");
51#endif
52
53namespace WebCore {
54
55const CFStringRef WebCoreCGImagePropertyAPNGUnclampedDelayTime = CFSTR("UnclampedDelayTime");
56const CFStringRef WebCoreCGImagePropertyAPNGDelayTime = CFSTR("DelayTime");
57const CFStringRef WebCoreCGImagePropertyAPNGLoopCount = CFSTR("LoopCount");
58
59const CFStringRef kCGImageSourceShouldPreferRGB32 = CFSTR("kCGImageSourceShouldPreferRGB32");
60const CFStringRef kCGImageSourceSkipMetadata = CFSTR("kCGImageSourceSkipMetadata");
61
62static RetainPtr<CFDictionaryRef> createImageSourceOptions(SubsamplingLevel subsamplingLevel, DecodeMode decodeMode)
63{
64 const CFIndex basicOptions = 3;
65 const void* keys[basicOptions + 2] = { kCGImageSourceShouldCache, kCGImageSourceShouldPreferRGB32, kCGImageSourceSkipMetadata };
66 const void* values[basicOptions + 2] = { kCFBooleanTrue, kCFBooleanTrue, kCFBooleanTrue };
67 CFIndex numOptions = basicOptions;
68
69 if (subsamplingLevel > 0) {
70 short constrainedSubsamplingLevel = std::min<short>(3, std::max<short>(0, subsamplingLevel));
71 int subsampleInt = 1 << constrainedSubsamplingLevel; // [0..3] => [1, 2, 4, 8]
72 RetainPtr<CFNumberRef> subsampleNumber = adoptCF(CFNumberCreate(nullptr, kCFNumberIntType, &subsampleInt));
73
74 keys[numOptions] = kCGImageSourceSubsampleFactor;
75 values[numOptions++] = kCGImageSourceSubsampleFactor;
76 }
77
78 if (decodeMode == DecodeMode::Immediate) {
79 keys[numOptions] = kCGImageSourceShouldCacheImmediately;
80 values[numOptions++] = kCFBooleanTrue;
81 }
82
83 return adoptCF(CFDictionaryCreate(nullptr, keys, values, numOptions, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
84}
85
86static RetainPtr<CFDictionaryRef> imageSourceOptions(SubsamplingLevel subsamplingLevel = 0, DecodeMode decodeMode = DecodeMode::Lazy)
87{
88 if (subsamplingLevel || decodeMode == DecodeMode::Immediate)
89 return createImageSourceOptions(subsamplingLevel, decodeMode);
90
91 static NeverDestroyed<RetainPtr<CFDictionaryRef>> options = createImageSourceOptions(0, DecodeMode::Lazy);
92 return options;
93}
94
95static ImageOrientation orientationFromProperties(CFDictionaryRef imageProperties)
96{
97 ASSERT(imageProperties);
98 CFNumberRef orientationProperty = (CFNumberRef)CFDictionaryGetValue(imageProperties, kCGImagePropertyOrientation);
99 if (!orientationProperty)
100 return DefaultImageOrientation;
101
102 int exifValue;
103 CFNumberGetValue(orientationProperty, kCFNumberIntType, &exifValue);
104 return ImageOrientation::fromEXIFValue(exifValue);
105}
106
107#if !PLATFORM(COCOA)
108size_t sharedBufferGetBytesAtPosition(void* info, void* buffer, off_t position, size_t count)
109{
110 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
111 size_t sourceSize = sharedBuffer->size();
112 if (position >= sourceSize)
113 return 0;
114
115 const char* source = sharedBuffer->data() + position;
116 size_t amount = std::min<size_t>(count, sourceSize - position);
117 memcpy(buffer, source, amount);
118 return amount;
119}
120
121void sharedBufferRelease(void* info)
122{
123 SharedBuffer* sharedBuffer = static_cast<SharedBuffer*>(info);
124 sharedBuffer->deref();
125}
126#endif
127
128ImageDecoder::ImageDecoder()
129{
130 m_nativeDecoder = CGImageSourceCreateIncremental(0);
131}
132
133SubsamplingLevel ImageDecoder::subsamplingLevelForScale(float scale, SubsamplingLevel maximumSubsamplingLevel)
134{
135 // There are four subsampling levels: 0 = 1x, 1 = 0.5x, 2 = 0.25x, 3 = 0.125x.
136 float clampedScale = std::max<float>(0.125, std::min<float>(1, scale));
137 SubsamplingLevel result = ceilf(log2f(1 / clampedScale));
138 ASSERT(result >=0 && result <= 3);
139 return std::min(result, maximumSubsamplingLevel);
140}
141
142size_t ImageDecoder::bytesDecodedToDetermineProperties()
143{
144 // Measured by tracing malloc/calloc calls on Mac OS 10.6.6, x86_64.
145 // A non-zero value ensures cached images with no decoded frames still enter
146 // the live decoded resources list when the CGImageSource decodes image
147 // properties, allowing the cache to prune the partially decoded image.
148 // This value is likely to be inaccurate on other platforms, but the overall
149 // behavior is unchanged.
150 return 13088;
151}
152
153String ImageDecoder::filenameExtension() const
154{
155 CFStringRef imageSourceType = CGImageSourceGetType(m_nativeDecoder);
156 return WebCore::preferredExtensionForImageSourceType(imageSourceType);
157}
158
159bool ImageDecoder::isSizeAvailable() const
160{
161 // Ragnaros yells: TOO SOON! You have awakened me TOO SOON, Executus!
162 if (CGImageSourceGetStatus(m_nativeDecoder) < kCGImageStatusIncomplete)
163 return false;
164
165 RetainPtr<CFDictionaryRef> image0Properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, 0, imageSourceOptions().get()));
166 if (!image0Properties)
167 return false;
168
169 return CFDictionaryContainsKey(image0Properties.get(), kCGImagePropertyPixelWidth)
170 && CFDictionaryContainsKey(image0Properties.get(), kCGImagePropertyPixelHeight);
171}
172
173IntSize ImageDecoder::size() const
174{
175 if (m_size.isEmpty())
176 m_size = frameSizeAtIndex(0, 0);
177 return m_size;
178}
179
180size_t ImageDecoder::frameCount() const
181{
182 return CGImageSourceGetCount(m_nativeDecoder);
183}
184
185int ImageDecoder::repetitionCount() const
186{
187 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyProperties(m_nativeDecoder, imageSourceOptions().get()));
188 if (!properties)
189 return cAnimationLoopOnce;
190
191 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
192 if (gifProperties) {
193 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFLoopCount);
194
195 // No property means loop once.
196 if (!num)
197 return cAnimationLoopOnce;
198
199 int loopCount;
200 CFNumberGetValue(num, kCFNumberIntType, &loopCount);
201
202 // A property with value 0 means loop forever.
203 return loopCount ? loopCount : cAnimationLoopInfinite;
204 }
205
206 CFDictionaryRef pngProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPNGDictionary);
207 if (pngProperties) {
208 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGLoopCount);
209 if (!num)
210 return cAnimationLoopOnce;
211
212 int loopCount;
213 CFNumberGetValue(num, kCFNumberIntType, &loopCount);
214 return loopCount ? loopCount : cAnimationLoopInfinite;
215 }
216
217 // Turns out we're not an animated image after all, so we don't animate.
218 return cAnimationNone;
219}
220
221bool ImageDecoder::getHotSpot(IntPoint& hotSpot) const
222{
223 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, 0, imageSourceOptions().get()));
224 if (!properties)
225 return false;
226
227 int x = -1, y = -1;
228 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotX"));
229 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &x))
230 return false;
231
232 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), CFSTR("hotspotY"));
233 if (!num || !CFNumberGetValue(num, kCFNumberIntType, &y))
234 return false;
235
236 if (x < 0 || y < 0)
237 return false;
238
239 hotSpot = IntPoint(x, y);
240 return true;
241}
242
243IntSize ImageDecoder::frameSizeAtIndex(size_t index, SubsamplingLevel subsamplingLevel) const
244{
245 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, index, imageSourceOptions(subsamplingLevel).get()));
246
247 if (!properties)
248 return { };
249
250 int width = 0;
251 int height = 0;
252 CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelWidth);
253 if (num)
254 CFNumberGetValue(num, kCFNumberIntType, &width);
255
256 num = (CFNumberRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPixelHeight);
257 if (num)
258 CFNumberGetValue(num, kCFNumberIntType, &height);
259
260 return IntSize(width, height);
261}
262
263bool ImageDecoder::frameIsCompleteAtIndex(size_t index) const
264{
265 ASSERT(frameCount());
266 return CGImageSourceGetStatusAtIndex(m_nativeDecoder, index) == kCGImageStatusComplete;
267}
268
269ImageOrientation ImageDecoder::orientationAtIndex(size_t index) const
270{
271 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, index, imageSourceOptions().get()));
272 if (!properties)
273 return DefaultImageOrientation;
274
275 return orientationFromProperties(properties.get());
276}
277
278float ImageDecoder::frameDurationAtIndex(size_t index) const
279{
280 float duration = 0;
281 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, index, imageSourceOptions().get()));
282 if (properties) {
283 CFDictionaryRef gifProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyGIFDictionary);
284 if (gifProperties) {
285 if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFUnclampedDelayTime)) {
286 // Use the unclamped frame delay if it exists.
287 CFNumberGetValue(num, kCFNumberFloatType, &duration);
288 } else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(gifProperties, kCGImagePropertyGIFDelayTime)) {
289 // Fall back to the clamped frame delay if the unclamped frame delay does not exist.
290 CFNumberGetValue(num, kCFNumberFloatType, &duration);
291 }
292 }
293
294 CFDictionaryRef pngProperties = (CFDictionaryRef)CFDictionaryGetValue(properties.get(), kCGImagePropertyPNGDictionary);
295 if (pngProperties) {
296 if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGUnclampedDelayTime))
297 CFNumberGetValue(num, kCFNumberFloatType, &duration);
298 else if (CFNumberRef num = (CFNumberRef)CFDictionaryGetValue(pngProperties, WebCoreCGImagePropertyAPNGDelayTime))
299 CFNumberGetValue(num, kCFNumberFloatType, &duration);
300 }
301 }
302
303 // Many annoying ads specify a 0 duration to make an image flash as quickly as possible.
304 // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify
305 // a duration of <= 10 ms. See <rdar://problem/7689300> and <http://webkit.org/b/36082>
306 // for more information.
307 if (duration < 0.011f)
308 return 0.1f;
309 return duration;
310}
311
312bool ImageDecoder::allowSubsamplingOfFrameAtIndex(size_t) const
313{
314 RetainPtr<CFDictionaryRef> properties = adoptCF(CGImageSourceCopyPropertiesAtIndex(m_nativeDecoder, 0, imageSourceOptions().get()));
315 if (!properties)
316 return false;
317
318 CFDictionaryRef jfifProperties = static_cast<CFDictionaryRef>(CFDictionaryGetValue(properties.get(), kCGImagePropertyJFIFDictionary));
319 if (jfifProperties) {
320 CFBooleanRef isProgCFBool = static_cast<CFBooleanRef>(CFDictionaryGetValue(jfifProperties, kCGImagePropertyJFIFIsProgressive));
321 if (isProgCFBool) {
322 bool isProgressive = CFBooleanGetValue(isProgCFBool);
323 // Workaround for <rdar://problem/5184655> - Hang rendering very large progressive JPEG. Decoding progressive
324 // images hangs for a very long time right now. Until this is fixed, don't sub-sample progressive images. This
325 // will cause them to fail our large image check and they won't be decoded.
326 // FIXME: Remove once underlying issue is fixed (<rdar://problem/5191418>)
327 return !isProgressive;
328 }
329 }
330
331 return true;
332}
333
334bool ImageDecoder::frameHasAlphaAtIndex(size_t index) const
335{
336 if (!frameIsCompleteAtIndex(index))
337 return true;
338
339 CFStringRef imageType = CGImageSourceGetType(m_nativeDecoder);
340
341 // Return false if there is no image type or the image type is JPEG, because
342 // JPEG does not support alpha transparency.
343 if (!imageType || CFEqual(imageType, CFSTR("public.jpeg")))
344 return false;
345
346 // FIXME: Could return false for other non-transparent image formats.
347 // FIXME: Could maybe return false for a GIF Frame if we have enough info in the GIF properties dictionary
348 // to determine whether or not a transparent color was defined.
349 return true;
350}
351
352unsigned ImageDecoder::frameBytesAtIndex(size_t index, SubsamplingLevel subsamplingLevel) const
353{
354 IntSize frameSize = frameSizeAtIndex(index, subsamplingLevel);
355 return frameSize.width() * frameSize.height() * 4;
356}
357
358PassNativeImagePtr ImageDecoder::createFrameImageAtIndex(size_t index, SubsamplingLevel subsamplingLevel, DecodeMode decodeMode) const
359{
360 RetainPtr<CGImageRef> image = adoptCF(CGImageSourceCreateImageAtIndex(m_nativeDecoder, index, imageSourceOptions(subsamplingLevel, decodeMode).get()));
361
362#if PLATFORM(IOS)
363 // <rdar://problem/7371198> - CoreGraphics changed the default caching behaviour in iOS 4.0 to kCGImageCachingTransient
364 // which caused a performance regression for us since the images had to be resampled/recreated every time we called
365 // CGContextDrawImage. We now tell CG to cache the drawn images. See also <rdar://problem/14366755> -
366 // CoreGraphics needs to un-deprecate kCGImageCachingTemporary since it's still not the default.
367#if COMPILER(CLANG)
368#pragma clang diagnostic push
369#pragma clang diagnostic ignored "-Wdeprecated-declarations"
370#endif
371 CGImageSetCachingFlags(image.get(), kCGImageCachingTemporary);
372#if COMPILER(CLANG)
373#pragma clang diagnostic pop
374#endif
375#endif // PLATFORM(IOS)
376
377 CFStringRef imageUTI = CGImageSourceGetType(m_nativeDecoder);
378 static const CFStringRef xbmUTI = CFSTR("public.xbitmap-image");
379
380 if (!imageUTI)
381 return image;
382
383 if (!CFEqual(imageUTI, xbmUTI))
384 return image;
385
386 // If it is an xbm image, mask out all the white areas to render them transparent.
387 const CGFloat maskingColors[6] = {255, 255, 255, 255, 255, 255};
388 RetainPtr<CGImageRef> maskedImage = adoptCF(CGImageCreateWithMaskingColors(image.get(), maskingColors));
389 return maskedImage ? maskedImage : image;
390}
391
392void ImageDecoder::setData(SharedBuffer* data, bool allDataReceived)
393{
394#if PLATFORM(COCOA)
395 // On Mac the NSData inside the SharedBuffer can be secretly appended to without the SharedBuffer's knowledge.
396 // We use SharedBuffer's ability to wrap itself inside CFData to get around this, ensuring that ImageIO is
397 // really looking at the SharedBuffer.
398 CGImageSourceUpdateData(m_nativeDecoder, data->createCFData().get(), allDataReceived);
399#else
400 // Create a CGDataProvider to wrap the SharedBuffer.
401 data->ref();
402 // We use the GetBytesAtPosition callback rather than the GetBytePointer one because SharedBuffer
403 // does not provide a way to lock down the byte pointer and guarantee that it won't move, which
404 // is a requirement for using the GetBytePointer callback.
405 CGDataProviderDirectCallbacks providerCallbacks = { 0, 0, 0, sharedBufferGetBytesAtPosition, sharedBufferRelease };
406 RetainPtr<CGDataProviderRef> dataProvider = adoptCF(CGDataProviderCreateDirect(data, data->size(), &providerCallbacks));
407 CGImageSourceUpdateDataProvider(m_nativeDecoder, dataProvider.get(), allDataReceived);
408#endif
409}
410
411}
412
413#endif // USE(CG)