Reviewed by Dave Hyatt and Sam Weinig.
[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 "FloatConversion.h"
33 #include "FloatRect.h"
34 #include "GraphicsContext.h"
35 #include "ImageObserver.h"
36 #include "PDFDocumentImage.h"
37 #include "PlatformString.h"
38 #include <ApplicationServices/ApplicationServices.h>
39 #include "WebCoreSystemInterface.h"
40
41 #if PLATFORM(WIN)
42 #include <WebKitSystemInterface/WebKitSystemInterface.h>
43 #endif
44
45 namespace WebCore {
46
47 void FrameData::clear()
48 {
49     if (m_frame) {
50         CGImageRelease(m_frame);
51         m_frame = 0;
52         m_duration = 0.0f;
53         m_hasAlpha = true;
54     }
55 }
56
57 // ================================================
58 // Image Class
59 // ================================================
60
61 // Drawing Routines
62
63 void BitmapImage::checkForSolidColor()
64 {
65     if (frameCount() > 1)
66         m_isSolidColor = false;
67     else {
68         CGImageRef image = frameAtIndex(0);
69         
70         // Currently we only check for solid color in the important special case of a 1x1 image.
71         if (image && CGImageGetWidth(image) == 1 && CGImageGetHeight(image) == 1) {
72             unsigned char pixel[4]; // RGBA
73             CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
74             CGContextRef bmap = CGBitmapContextCreate(pixel, 1, 1, 8, sizeof(pixel), space,
75                 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
76             if (bmap) {
77                 GraphicsContext(bmap).setCompositeOperation(CompositeCopy);
78                 CGRect dst = { {0, 0}, {1, 1} };
79                 CGContextDrawImage(bmap, dst, image);
80                 if (pixel[3] == 0)
81                     m_solidColor = Color(0, 0, 0, 0);
82                 else
83                     m_solidColor = Color(pixel[0] * 255 / pixel[3], pixel[1] * 255 / pixel[3], pixel[2] * 255 / pixel[3], pixel[3]);
84                 m_isSolidColor = true;
85                 CFRelease(bmap);
86             } 
87             CFRelease(space);
88         }
89     }
90 }
91
92 CGImageRef BitmapImage::getCGImageRef()
93 {
94     return frameAtIndex(0);
95 }
96
97 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
98 {
99     if (!m_source.initialized())
100         return;
101     
102     CGRect fr = ctxt->roundToDevicePixels(srcRect);
103     CGRect ir = ctxt->roundToDevicePixels(dstRect);
104
105     CGImageRef image = frameAtIndex(m_currentFrame);
106     if (!image) // If it's too early we won't have an image yet.
107         return;
108     
109     if (mayFillWithSolidColor()) {
110         fillWithSolidColor(ctxt, ir, solidColor(), compositeOp);
111         return;
112     }
113
114     CGContextRef context = ctxt->platformContext();
115     ctxt->save();
116
117     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
118     // that is currently decoded.  This could be less that the actual height.
119     CGSize selfSize = size();                          // full image size, in pixels
120     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
121     
122     CGSize adjustedSize = selfSize;
123     if (curHeight < selfSize.height) {
124         adjustedSize.height *= curHeight / selfSize.height;
125
126         // Is the amount of available bands less than what we need to draw?  If so,
127         // we may have to clip 'fr' if it goes outside the available bounds.
128         if (CGRectGetMaxY(fr) > adjustedSize.height) {
129             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
130             if (frHeight <= 0)
131                 return;                                             // clipped out entirely
132             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
133             fr.size.height = frHeight;
134         }
135     }
136
137     // Flip the coords.
138     ctxt->setCompositeOperation(compositeOp);
139     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y);
140     CGContextScaleCTM(context, 1, -1);
141     CGContextTranslateCTM(context, 0, -ir.size.height);
142     
143     // Translated to origin, now draw at 0,0.
144     ir.origin.x = ir.origin.y = 0;
145     
146     // If we're drawing a sub portion of the image then create
147     // a image for the sub portion and draw that.
148     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
149     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
150         // Convert ft to image pixel coords:
151         float xscale = adjustedSize.width / selfSize.width;
152         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
153         fr.origin.x /= xscale;
154         fr.origin.y /= yscale;
155         fr.size.width /= xscale;
156         fr.size.height /= yscale;
157         
158         image = CGImageCreateWithImageInRect(image, fr);
159         if (image) {
160             CGContextDrawImage(context, ir, image);
161             CFRelease(image);
162         }
163     } else // Draw the whole image.
164         CGContextDrawImage(context, ir, image);
165         
166     ctxt->restore();
167     
168     startAnimation();
169
170     if (imageObserver())
171         imageObserver()->didDraw(this);
172 }
173
174 struct ImageInfo {
175     ImageInfo(const FloatPoint& point, Image* i)
176     : tilePoint(point)
177     , image(i)
178     {}
179     
180     FloatPoint tilePoint;
181     Image* image;
182 };
183
184 void Image::drawPatternCallback(void* info, CGContextRef context)
185 {
186     ImageInfo* data = (ImageInfo*)info;
187     CGImageRef image = data->image->nativeImageForCurrentFrame();
188     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect(data->tilePoint.x(), data->tilePoint.y(), CGImageGetWidth(image), CGImageGetHeight(image))), image);
189 }
190
191 void Image::drawPattern(GraphicsContext* ctxt, const FloatRect& tileRect, const AffineTransform& patternTransform,
192                         const FloatPoint& phase, CompositeOperator op, const FloatRect& destRect)
193 {
194     CGContextRef context = ctxt->platformContext();
195     ctxt->save();
196     CGContextClipToRect(context, destRect);
197     ctxt->setCompositeOperation(op);
198     CGContextTranslateCTM(context, destRect.x(), destRect.y());
199     CGContextScaleCTM(context, 1, -1);
200     CGContextTranslateCTM(context, 0, -destRect.height());
201     
202     // Compute the scaled tile size.
203     float scaledTileHeight = tileRect.height() * narrowPrecisionToFloat(patternTransform.d());
204     
205     // We have to adjust the phase to deal with the fact we're in Cartesian space now (with the bottom left corner of destRect being
206     // the origin).
207     float adjustedX = phase.x() - destRect.x() + tileRect.x() * narrowPrecisionToFloat(patternTransform.a()); // We translated the context so that destRect.x() is the origin, so subtract it out.
208     float adjustedY = destRect.height() - (phase.y() - destRect.y() + tileRect.y() * narrowPrecisionToFloat(patternTransform.d()) + scaledTileHeight);
209
210     CGImageRef tileImage = nativeImageForCurrentFrame();
211     float h = CGImageGetHeight(tileImage);
212     
213 #ifndef BUILDING_ON_TIGER
214     // Leopard has an optimized call for the tiling of image patterns, but we can only use it if the image has been decoded enough that
215     // its buffer is the same size as the overall image.  Because a partially decoded CGImageRef with a smaller width or height than the
216     // overall image buffer needs to tile with "gaps", we can't use the optimized tiling call in that case.  We also avoid this optimization
217     // when tiling portions of an image, since until we can actually cache the subimage we want to tile, this code won't be any faster.
218     // FIXME: Could create WebKitSystemInterface SPI for CGCreatePatternWithImage2 and probably make Tiger tile faster as well.
219     float scaledTileWidth = tileRect.width() * narrowPrecisionToFloat(patternTransform.a());
220     float w = CGImageGetWidth(tileImage);
221     if (w == size().width() && h == size().height() && tileRect.size() == size())
222         CGContextDrawTiledImage(context, FloatRect(adjustedX, adjustedY, scaledTileWidth, scaledTileHeight), tileImage);
223     else {
224 #endif
225
226     // On Leopard, this code now only runs for partially decoded images whose buffers do not yet match the overall size of the image or for
227     // tiling a portion of an image (i.e., a subimage like the ones used by CSS border-image).
228     // On Tiger this code runs all the time.  This code is suboptimal because the pattern does not reference the image directly, and the
229     // pattern is destroyed before exiting the function.  This means any decoding the pattern does doesn't end up cached anywhere, so we
230     // redecode every time we paint.
231     static const CGPatternCallbacks patternCallbacks = { 0, drawPatternCallback, NULL };
232     CGAffineTransform matrix = CGAffineTransformMake(narrowPrecisionToCGFloat(patternTransform.a()), 0, 0, narrowPrecisionToCGFloat(patternTransform.d()), adjustedX, adjustedY);
233     matrix = CGAffineTransformConcat(matrix, CGContextGetCTM(context));
234     
235     // If we're painting a subimage, store the offset to the image.
236     ImageInfo info(FloatPoint(-tileRect.x(), tileRect.y() + tileRect.height() - h), this);
237     CGPatternRef pattern = CGPatternCreate(&info, CGRectMake(0, 0, tileRect.width(), tileRect.height()),
238                                            matrix, tileRect.width(), tileRect.height(), 
239                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
240     if (pattern == NULL) {
241         ctxt->restore();
242         return;
243     }
244
245     CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
246     
247     CGFloat alpha = 1;
248     CGColorRef color = CGColorCreateWithPattern(patternSpace, pattern, &alpha);
249     CGContextSetFillColorSpace(context, patternSpace);
250     CGColorSpaceRelease(patternSpace);
251     CGPatternRelease(pattern);
252
253     // FIXME: Really want a public API for this.  It is just CGContextSetBaseCTM(context, CGAffineTransformIdentiy).
254     wkSetPatternBaseCTM(context, CGAffineTransformIdentity);
255     CGContextSetPatternPhase(context, CGSizeZero);
256
257     CGContextSetFillColorWithColor(context, color);
258     CGContextFillRect(context, CGContextGetClipBoundingBox(context));
259     
260     CGColorRelease(color);
261     
262     ctxt->restore();
263     
264 #ifndef BUILDING_ON_TIGER
265     }
266 #endif
267
268     if (imageObserver())
269         imageObserver()->didDraw(this);
270 }
271
272
273 }
274
275 #endif // PLATFORM(CG)