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