0769ad227a8b6b2aad7965fac21705b1d60fedff
[WebKit-https.git] / WebCore / platform / graphics / cg / ImageCG.cpp
1 /*
2  * Copyright (C) 2004, 2005, 2006 Apple Computer, 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 COMPUTER, 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 COMPUTER, 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 PLATFORM(CG)
30
31 #include "FloatRect.h"
32 #include "GraphicsContext.h"
33 #include "PDFDocumentImage.h"
34 #include "PlatformString.h"
35 #include <ApplicationServices/ApplicationServices.h>
36 #include "WebCoreSystemInterface.h"
37
38 namespace WebCore {
39
40 void FrameData::clear()
41 {
42     if (m_frame) {
43         CFRelease(m_frame);
44         m_frame = 0;
45         m_duration = 0.;
46         m_hasAlpha = true;
47     }
48 }
49
50 // ================================================
51 // Image Class
52 // ================================================
53
54 // Drawing Routines
55
56 void BitmapImage::checkForSolidColor()
57 {
58     if (frameCount() > 1)
59         m_isSolidColor = false;
60     else {
61         CGImageRef image = frameAtIndex(0);
62         
63         // Currently we only check for solid color in the important special case of a 1x1 image.
64         if (image && CGImageGetWidth(image) == 1 && CGImageGetHeight(image) == 1) {
65             CGFloat pixel[4]; // RGBA
66             CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
67             CGContextRef bmap = CGBitmapContextCreate(&pixel, 1, 1, 8*sizeof(float), sizeof(pixel), space,
68                 kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents | kCGBitmapByteOrder32Host);
69             if (bmap) {
70                 GraphicsContext(bmap).setCompositeOperation(CompositeCopy);
71                 CGRect dst = { {0, 0}, {1, 1} };
72                 CGContextDrawImage(bmap, dst, image);
73                 if (pixel[3] == 0)
74                     m_solidColor = Color(0, 0, 0, 0);
75                 else
76                     m_solidColor = Color(int(pixel[0] / pixel[3] * 255), int(pixel[1] / pixel[3] * 255), int(pixel[2] / pixel[3] * 255), int(pixel[3] * 255));
77                 m_isSolidColor = true;
78                 CFRelease(bmap);
79             } 
80             CFRelease(space);
81         }
82     }
83 }
84
85 CGImageRef BitmapImage::getCGImageRef()
86 {
87     return frameAtIndex(0);
88 }
89
90 void fillWithSolidColor(GraphicsContext* ctxt, const FloatRect& dstRect, const Color& color, CompositeOperator op)
91 {
92     if (color.alpha() <= 0)
93         return;
94     
95     ctxt->save();
96     ctxt->setCompositeOperation(!color.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
97     ctxt->fillRect(dstRect, color);
98     ctxt->restore();
99 }
100
101 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
102 {
103     if (!m_source.initialized())
104         return;
105     
106     CGRect fr = ctxt->roundToDevicePixels(srcRect);
107     CGRect ir = ctxt->roundToDevicePixels(dstRect);
108
109     CGImageRef image = frameAtIndex(m_currentFrame);
110     if (!image) // If it's too early we won't have an image yet.
111         return;
112     
113     if (mayFillWithSolidColor()) {
114         fillWithSolidColor(ctxt, ir, solidColor(), compositeOp);
115         return;
116     }
117
118     CGContextRef context = ctxt->platformContext();
119     ctxt->save();
120
121     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
122     // that is currently decoded.  This could be less that the actual height.
123     CGSize selfSize = size();                          // full image size, in pixels
124     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
125     
126     CGSize adjustedSize = selfSize;
127     if (curHeight < selfSize.height) {
128         adjustedSize.height *= curHeight / selfSize.height;
129
130         // Is the amount of available bands less than what we need to draw?  If so,
131         // we may have to clip 'fr' if it goes outside the available bounds.
132         if (CGRectGetMaxY(fr) > adjustedSize.height) {
133             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
134             if (frHeight <= 0)
135                 return;                                             // clipped out entirely
136             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
137             fr.size.height = frHeight;
138         }
139     }
140
141     // Flip the coords.
142     ctxt->setCompositeOperation(compositeOp);
143     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y);
144     CGContextScaleCTM(context, 1, -1);
145     CGContextTranslateCTM(context, 0, -ir.size.height);
146     
147     // Translated to origin, now draw at 0,0.
148     ir.origin.x = ir.origin.y = 0;
149     
150     // If we're drawing a sub portion of the image then create
151     // a image for the sub portion and draw that.
152     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
153     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
154         // Convert ft to image pixel coords:
155         float xscale = adjustedSize.width / selfSize.width;
156         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
157         fr.origin.x /= xscale;
158         fr.origin.y /= yscale;
159         fr.size.width /= xscale;
160         fr.size.height /= yscale;
161         
162         image = CGImageCreateWithImageInRect(image, fr);
163         if (image) {
164             CGContextDrawImage(context, ir, image);
165             CFRelease(image);
166         }
167     } else // Draw the whole image.
168         CGContextDrawImage(context, ir, image);
169
170     ctxt->restore();
171     
172     startAnimation();
173
174 }
175
176 static void drawPattern(void* info, CGContextRef context)
177 {
178     BitmapImage* data = (BitmapImage*)info;
179     CGImageRef image = data->nativeImageForCurrentFrame();
180     float w = CGImageGetWidth(image);
181     float h = CGImageGetHeight(image);
182     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect
183         (0, data->size().height() - h, w, h)), image);    
184 }
185
186 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
187
188
189 static inline void drawPattern(GraphicsContext* ctxt, CGPatternRef pattern, CGPoint phase, CompositeOperator op, const FloatRect& destRect)
190 {
191     CGContextRef context = ctxt->platformContext();
192     ctxt->save();
193     
194     // FIXME: Really want a public API for this.
195     wkSetPatternPhaseInUserSpace(context, phase);
196     
197     CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
198     CGContextSetFillColorSpace(context, patternSpace);
199     CGColorSpaceRelease(patternSpace);
200     
201     CGFloat patternAlpha = 1;
202     CGContextSetFillPattern(context, pattern, &patternAlpha);
203     
204     ctxt->setCompositeOperation(op);
205     
206     CGContextFillRect(context, destRect);
207     
208     ctxt->restore();
209 }
210
211 void BitmapImage::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint, const FloatSize& scaledTileSize, CompositeOperator op)
212 {    
213     CGImageRef image = frameAtIndex(m_currentFrame);
214     if (!image)
215         return;
216     
217     if (mayFillWithSolidColor()) {
218         fillWithSolidColor(ctxt, destRect, solidColor(), op);
219         return;
220     }
221
222     FloatSize intrinsicTileSize = size();
223     FloatSize scale(scaledTileSize.width() / intrinsicTileSize.width(),
224                     scaledTileSize.height() / intrinsicTileSize.height());
225     CGAffineTransform patternTransform = CGAffineTransformMakeScale(scale.width(), scale.height());
226
227     FloatRect oneTileRect;
228     oneTileRect.setX(destRect.x() + fmodf(fmodf(-srcPoint.x(), scaledTileSize.width()) - scaledTileSize.width(), scaledTileSize.width()));
229     oneTileRect.setY(destRect.y() + fmodf(fmodf(-srcPoint.y(), scaledTileSize.height()) - scaledTileSize.height(), scaledTileSize.height()));
230     oneTileRect.setSize(scaledTileSize);
231     
232     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.    
233     if (oneTileRect.contains(destRect)) {
234         FloatRect visibleSrcRect;
235         visibleSrcRect.setX((destRect.x() - oneTileRect.x()) / scale.width());
236         visibleSrcRect.setY((destRect.y() - oneTileRect.y()) / scale.height());
237         visibleSrcRect.setWidth(destRect.width() / scale.width());
238         visibleSrcRect.setHeight(destRect.height() / scale.height());
239         draw(ctxt, destRect, visibleSrcRect, op);
240         return;
241     }
242
243     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(0, 0, intrinsicTileSize.width(), intrinsicTileSize.height()),
244                                            patternTransform, intrinsicTileSize.width(), intrinsicTileSize.height(), 
245                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
246     
247     if (pattern) {
248         drawPattern(ctxt, pattern, oneTileRect.location(), op, destRect);
249         CGPatternRelease(pattern);
250     }
251     
252     startAnimation();
253 }
254
255 static inline FloatSize caculatePatternScale(const FloatRect& dstRect, const FloatRect& srcRect, Image::TileRule hRule, Image::TileRule vRule)
256 {
257     float scaleX = 1.0f, scaleY = 1.0f;
258     
259     if (hRule == Image::StretchTile)
260         scaleX = dstRect.width() / srcRect.width();
261     if (vRule == Image::StretchTile)
262         scaleY = dstRect.height() / srcRect.height();
263     
264     if (hRule == Image::RepeatTile)
265         scaleX = scaleY;
266     if (vRule == Image::RepeatTile)
267         scaleY = scaleX;
268     
269     return FloatSize(scaleX, scaleY);
270 }
271
272 // FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things.
273 void BitmapImage::drawTiled(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, TileRule hRule, TileRule vRule, CompositeOperator op)
274 {    
275     CGImageRef image = frameAtIndex(m_currentFrame);
276     if (!image)
277         return;
278
279     if (mayFillWithSolidColor()) {
280         fillWithSolidColor(ctxt, dstRect, solidColor(), op);
281         return;
282     }
283     
284     // Optimization: Cache the CGPatternRef
285     FloatSize scale = caculatePatternScale(dstRect, srcRect, hRule, vRule);
286     CGPatternRef pattern = CGPatternCreate(this, srcRect, CGAffineTransformMakeScale(scale.width(), scale.height()),
287                                            srcRect.width(), srcRect.height(),
288                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
289     if (pattern) {    
290         // We want to construct the phase such that the pattern is centered (when stretch is not
291         // set for a particular rule).
292         float hPhase = scale.width() * srcRect.x();
293         float vPhase = scale.height() * (srcRect.height() - srcRect.y());
294         if (hRule == Image::RepeatTile)
295             hPhase -= fmodf(dstRect.width(), scale.width() * srcRect.width()) / 2.0f;
296         if (vRule == Image::RepeatTile)
297             vPhase -= fmodf(dstRect.height(), scale.height() * srcRect.height()) / 2.0f;
298         
299         CGPoint patternPhase = CGPointMake(dstRect.x() - hPhase, dstRect.y() - vPhase);
300         drawPattern(ctxt, pattern, patternPhase, op, dstRect);
301         CGPatternRelease(pattern);
302     }
303
304     startAnimation();
305 }
306
307 }
308
309 #endif // PLATFORM(CG)