Clean up image subsampling code, make it less iOS-specific
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / BitmapImageCG.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006 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 "BitmapImage.h"
28
29 #if USE(CG)
30
31 #include "FloatConversion.h"
32 #include "GeometryUtilities.h"
33 #include "GraphicsContextCG.h"
34 #include "ImageObserver.h"
35 #include "SubimageCacheWithTimer.h"
36 #include <wtf/RetainPtr.h>
37
38 #if USE(APPKIT)
39 #include <ApplicationServices/ApplicationServices.h>
40 #endif
41
42 #if PLATFORM(IOS)
43 #include <CoreGraphics/CGContextPrivate.h>
44 #endif
45
46 #if PLATFORM(COCOA)
47 #include "WebCoreSystemInterface.h"
48 #endif
49
50 #if PLATFORM(WIN)
51 #include <WebKitSystemInterface/WebKitSystemInterface.h>
52 #endif
53
54 namespace WebCore {
55
56 bool FrameData::clear(bool clearMetadata)
57 {
58     if (clearMetadata)
59         m_haveMetadata = false;
60
61     m_orientation = DefaultImageOrientation;
62     m_subsamplingLevel = 0;
63
64     if (m_frame) {
65 #if CACHE_SUBIMAGES
66         subimageCache().clearImage(m_frame);
67 #endif
68         CGImageRelease(m_frame);
69         m_frame = 0;
70         return true;
71     }
72     return false;
73 }
74
75 BitmapImage::BitmapImage(CGImageRef cgImage, ImageObserver* observer)
76     : Image(observer)
77     , m_minimumSubsamplingLevel(0)
78     , m_imageOrientation(OriginTopLeft)
79     , m_shouldRespectImageOrientation(false)
80     , m_currentFrame(0)
81     , m_repetitionCount(cAnimationNone)
82     , m_repetitionCountStatus(Unknown)
83     , m_repetitionsComplete(0)
84     , m_decodedSize(0)
85     , m_decodedPropertiesSize(0)
86     , m_frameCount(1)
87     , m_isSolidColor(false)
88     , m_checkedForSolidColor(false)
89     , m_animationFinished(true)
90     , m_allDataReceived(true)
91     , m_haveSize(true)
92     , m_sizeAvailable(true)
93     , m_haveFrameCount(true)
94 {
95     CGFloat width = CGImageGetWidth(cgImage);
96     CGFloat height = CGImageGetHeight(cgImage);
97     m_decodedSize = width * height * 4;
98     m_size = IntSize(width, height);
99
100     // Since we don't have a decoder, we can't figure out the image orientation.
101     // Set m_sizeRespectingOrientation to be the same as m_size so it's not 0x0.
102     m_sizeRespectingOrientation = m_size;
103
104     m_frames.grow(1);
105     m_frames[0].m_frame = CGImageRetain(cgImage);
106     m_frames[0].m_hasAlpha = true;
107     m_frames[0].m_haveMetadata = true;
108
109     checkForSolidColor();
110 }
111
112 void BitmapImage::determineMinimumSubsamplingLevel() const
113 {
114     if (!m_allowSubsampling)
115         return;
116
117     if (!m_source.allowSubsamplingOfFrameAtIndex(0))
118         return;
119
120     // Values chosen to be appropriate for iOS.
121     const int cMaximumImageAreaBeforeSubsampling = 5 * 1024 * 1024;
122     const SubsamplingLevel maxSubsamplingLevel = 3;
123
124     SubsamplingLevel currentLevel = 0;
125     for ( ; currentLevel <= maxSubsamplingLevel; ++currentLevel) {
126         IntSize frameSize = m_source.frameSizeAtIndex(0, currentLevel);
127         if (frameSize.area() < cMaximumImageAreaBeforeSubsampling)
128             break;
129     }
130
131     m_minimumSubsamplingLevel = currentLevel;
132 }
133
134 void BitmapImage::checkForSolidColor()
135 {
136     m_checkedForSolidColor = true;
137     m_isSolidColor = false;
138
139     if (frameCount() > 1)
140         return;
141
142     if (!haveFrameAtIndex(0)) {
143         IntSize size = m_source.frameSizeAtIndex(0, 0);
144         if (size.width() != 1 || size.height() != 1)
145             return;
146
147         if (!ensureFrameIsCached(0))
148             return;
149     }
150
151     CGImageRef image = nullptr;
152     if (m_frames.size())
153         image = m_frames[0].m_frame;
154
155     if (!image)
156         return;
157
158     // Currently we only check for solid color in the important special case of a 1x1 image.
159     if (CGImageGetWidth(image) == 1 && CGImageGetHeight(image) == 1) {
160         unsigned char pixel[4]; // RGBA
161         RetainPtr<CGContextRef> bitmapContext = adoptCF(CGBitmapContextCreate(pixel, 1, 1, 8, sizeof(pixel), deviceRGBColorSpaceRef(),
162             kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big));
163         if (!bitmapContext)
164             return;
165         GraphicsContext(bitmapContext.get()).setCompositeOperation(CompositeCopy);
166         CGRect destinationRect = CGRectMake(0, 0, 1, 1);
167         CGContextDrawImage(bitmapContext.get(), destinationRect, image);
168         if (!pixel[3])
169             m_solidColor = Color(0, 0, 0, 0);
170         else
171             m_solidColor = Color(pixel[0] * 255 / pixel[3], pixel[1] * 255 / pixel[3], pixel[2] * 255 / pixel[3], pixel[3]);
172
173         m_isSolidColor = true;
174     }
175 }
176
177 CGImageRef BitmapImage::getCGImageRef()
178 {
179     return frameAtIndex(0);
180 }
181
182 CGImageRef BitmapImage::getFirstCGImageRefOfSize(const IntSize& size)
183 {
184     size_t count = frameCount();
185     for (size_t i = 0; i < count; ++i) {
186         CGImageRef cgImage = frameAtIndex(i);
187         if (cgImage && IntSize(CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)) == size)
188             return cgImage;
189     }
190
191     // Fallback to the default CGImageRef if we can't find the right size
192     return getCGImageRef();
193 }
194
195 RetainPtr<CFArrayRef> BitmapImage::getCGImageArray()
196 {
197     size_t count = frameCount();
198     if (!count)
199         return 0;
200     
201     CFMutableArrayRef array = CFArrayCreateMutable(NULL, count, &kCFTypeArrayCallBacks);
202     for (size_t i = 0; i < count; ++i) {
203         if (CGImageRef currFrame = frameAtIndex(i))
204             CFArrayAppendValue(array, currFrame);
205     }
206     return adoptCF(array);
207 }
208
209 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, ColorSpace styleColorSpace, CompositeOperator compositeOp, BlendMode blendMode, ImageOrientationDescription description)
210 {
211 #if PLATFORM(IOS)
212     startAnimation(DoNotCatchUp);
213 #else
214     startAnimation();
215 #endif
216
217     RetainPtr<CGImageRef> image;
218     // Never use subsampled images for drawing into PDF contexts.
219     if (wkCGContextIsPDFContext(ctxt->platformContext()))
220         image = adoptCF(copyUnscaledFrameAtIndex(m_currentFrame));
221     else {
222         CGRect transformedDestinationRect = CGRectApplyAffineTransform(destRect, CGContextGetCTM(ctxt->platformContext()));
223         float subsamplingScale = std::min<float>(1, std::max(transformedDestinationRect.size.width / srcRect.width(), transformedDestinationRect.size.height / srcRect.height()));
224
225         image = frameAtIndex(m_currentFrame, subsamplingScale);
226     }
227
228     if (!image) // If it's too early we won't have an image yet.
229         return;
230     
231     if (mayFillWithSolidColor()) {
232         fillWithSolidColor(ctxt, destRect, solidColor(), styleColorSpace, compositeOp);
233         return;
234     }
235
236     // Subsampling may have given us an image that is smaller than size().
237     IntSize imageSize(CGImageGetWidth(image.get()), CGImageGetHeight(image.get()));
238     
239     // srcRect is in the coordinates of the unsubsampled image, so we have to map it to the subsampled image.
240     FloatRect scaledSrcRect = srcRect;
241     if (imageSize != m_size) {
242         FloatRect originalImageBounds(FloatPoint(), m_size);
243         FloatRect subsampledImageBounds(FloatPoint(), imageSize);
244         scaledSrcRect = mapRect(srcRect, originalImageBounds, subsampledImageBounds);
245     }
246     
247     ImageOrientation orientation;
248     if (description.respectImageOrientation() == RespectImageOrientation)
249         orientation = frameOrientationAtIndex(m_currentFrame);
250
251     ctxt->drawNativeImage(image.get(), imageSize, styleColorSpace, destRect, scaledSrcRect, compositeOp, blendMode, orientation);
252
253     if (imageObserver())
254         imageObserver()->didDraw(this);
255 }
256
257 PassNativeImagePtr BitmapImage::copyUnscaledFrameAtIndex(size_t index)
258 {
259     if (index >= frameCount())
260         return nullptr;
261
262     if (index >= m_frames.size() || !m_frames[index].m_frame)
263         cacheFrame(index, 0);
264
265     if (!m_frames[index].m_subsamplingLevel)
266         return CGImageRetain(m_frames[index].m_frame);
267
268     return m_source.createFrameAtIndex(index);
269 }
270
271 }
272
273 #endif // USE(CG)