LayoutTests:
[WebKit-https.git] / WebCore / platform / mac / ImageMac.mm
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 #import "config.h"
27 #import "Image.h"
28
29 #import "FloatRect.h"
30 #import "FoundationExtras.h"
31 #import "GraphicsContext.h"
32 #import "PDFDocumentImage.h"
33 #import "PlatformString.h"
34 #import "WebCoreFrameBridge.h"
35 #import "WebCoreSystemInterface.h"
36
37 namespace WebCore {
38
39 void FrameData::clear()
40 {
41     if (m_frame) {
42         CFRelease(m_frame);
43         m_frame = 0;
44         m_duration = 0.;
45         m_hasAlpha = true;
46     }
47 }
48
49 // ================================================
50 // Image Class
51 // ================================================
52
53 void Image::initNativeData()
54 {
55     m_nsImage = 0;
56     m_tiffRep = 0;
57     m_isPDF = false;
58     m_PDFDoc = 0;
59 }
60
61 void Image::destroyNativeData()
62 {
63     delete m_PDFDoc;
64 }
65
66 void Image::invalidateNativeData()
67 {
68     if (m_frames.size() != 1)
69         return;
70
71     if (m_nsImage) {
72         CFRelease(m_nsImage);
73         m_nsImage = 0;
74     }
75
76     if (m_tiffRep) {
77         CFRelease(m_tiffRep);
78         m_tiffRep = 0;
79     }
80 }
81
82 Image* Image::loadResource(const char *name)
83 {
84     NSBundle *bundle = [NSBundle bundleForClass:[WebCoreFrameBridge class]];
85     NSString *imagePath = [bundle pathForResource:[NSString stringWithUTF8String:name] ofType:@"tiff"];
86     NSData *namedImageData = [NSData dataWithContentsOfFile:imagePath];
87     if (namedImageData) {
88         Image* image = new Image;
89         image->setNativeData((CFDataRef)namedImageData, true);
90         return image;
91     }
92     return 0;
93 }
94
95 bool Image::supportsType(const String& type)
96 {
97     // FIXME: Would be better if this was looking in a set rather than an NSArray.
98     // FIXME: Would be better not to convert to an NSString just to check if a type is supported.
99     return [[WebCoreFrameBridge supportedImageResourceMIMETypes] containsObject:type];
100 }
101
102 // Drawing Routines
103
104 void Image::checkForSolidColor()
105 {
106     if (frameCount() > 1)
107         m_isSolidColor = false;
108     else {
109         CGImageRef image = frameAtIndex(0);
110         
111         // Currently we only check for solid color in the important special case of a 1x1 image.
112         if (image && CGImageGetWidth(image) == 1 && CGImageGetHeight(image) == 1) {
113             CGFloat pixel[4]; // RGBA
114             CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
115             CGContextRef bmap = CGBitmapContextCreate(&pixel, 1, 1, 8*sizeof(float), sizeof(pixel), space,
116                 kCGImageAlphaPremultipliedLast | kCGBitmapFloatComponents | kCGBitmapByteOrder32Host);
117             if (bmap) {
118                 GraphicsContext(bmap).setCompositeOperation(CompositeCopy);
119                 CGRect dst = { {0, 0}, {1, 1} };
120                 CGContextDrawImage(bmap, dst, image);
121                 m_solidColor = Color(int(pixel[0] * 255), int(pixel[1] * 255), int(pixel[2] * 255), int(pixel[3] * 255));
122                 m_isSolidColor = true;
123                 CFRelease(bmap);
124             } 
125             CFRelease(space);
126         }
127     }
128 }
129
130 CFDataRef Image::getTIFFRepresentation()
131 {
132     if (m_tiffRep)
133         return m_tiffRep;
134     
135     unsigned numFrames = frameCount();
136     CFMutableDataRef data = CFDataCreateMutable(0, 0);
137     // FIXME:  Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
138     CGImageDestinationRef destination = CGImageDestinationCreateWithData(data, CFSTR("public.tiff"), numFrames, 0);
139     if (!destination)
140         return 0;
141
142     for (unsigned i = 0; i < numFrames; ++i ) {
143         CGImageRef cgImage = frameAtIndex(i);
144         if (!cgImage) {
145             CFRelease(destination);
146             return 0;    
147         }
148         CGImageDestinationAddImage(destination, cgImage, 0);
149     }
150     CGImageDestinationFinalize(destination);
151     CFRelease(destination);
152
153     m_tiffRep = data;
154     return m_tiffRep;
155 }
156
157 NSImage* Image::getNSImage()
158 {
159     if (m_nsImage)
160         return m_nsImage;
161
162     CFDataRef data = getTIFFRepresentation();
163     if (!data)
164         return 0;
165     
166     m_nsImage = HardRetainWithNSRelease([[NSImage alloc] initWithData:(NSData*)data]);
167     return m_nsImage;
168 }
169
170 CGImageRef Image::getCGImageRef()
171 {
172     return frameAtIndex(0);
173 }
174
175 void Image::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
176 {
177     if (m_isPDF) {
178         if (m_PDFDoc)
179             m_PDFDoc->draw(ctxt, srcRect, dstRect, compositeOp);
180         return;
181     } 
182     
183     if (!m_source.initialized())
184         return;
185     
186     CGRect fr = ctxt->roundToDevicePixels(srcRect);
187     CGRect ir = ctxt->roundToDevicePixels(dstRect);
188
189     CGImageRef image = frameAtIndex(m_currentFrame);
190     if (!image) // If it's too early we won't have an image yet.
191         return;
192
193     if (m_isSolidColor && m_currentFrame == 0) {
194         if (m_solidColor.alpha() > 0) {
195             ctxt->save();
196             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && compositeOp == CompositeSourceOver ? CompositeCopy : compositeOp);
197             ctxt->fillRect(ir, m_solidColor);
198             ctxt->restore();
199         }
200         return;
201     }
202
203     CGContextRef context = ctxt->platformContext();
204     ctxt->save();
205
206     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
207     // that is currently decoded.  This could be less that the actual height.
208     CGSize selfSize = size();                          // full image size, in pixels
209     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
210     
211     CGSize adjustedSize = selfSize;
212     if (curHeight < selfSize.height) {
213         adjustedSize.height *= curHeight / selfSize.height;
214
215         // Is the amount of available bands less than what we need to draw?  If so,
216         // we may have to clip 'fr' if it goes outside the available bounds.
217         if (CGRectGetMaxY(fr) > adjustedSize.height) {
218             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
219             if (frHeight <= 0)
220                 return;                                             // clipped out entirely
221             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
222             fr.size.height = frHeight;
223         }
224     }
225
226     // Flip the coords.
227     ctxt->setCompositeOperation(compositeOp);
228     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y);
229     CGContextScaleCTM(context, 1, -1);
230     CGContextTranslateCTM(context, 0, -ir.size.height);
231     
232     // Translated to origin, now draw at 0,0.
233     ir.origin.x = ir.origin.y = 0;
234     
235     // If we're drawing a sub portion of the image then create
236     // a image for the sub portion and draw that.
237     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
238     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
239         // Convert ft to image pixel coords:
240         float xscale = adjustedSize.width / selfSize.width;
241         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
242         fr.origin.x /= xscale;
243         fr.origin.y /= yscale;
244         fr.size.width /= xscale;
245         fr.size.height /= yscale;
246         
247         image = CGImageCreateWithImageInRect(image, fr);
248         if (image) {
249             CGContextDrawImage(context, ir, image);
250             CFRelease(image);
251         }
252     } else // Draw the whole image.
253         CGContextDrawImage(context, ir, image);
254
255     ctxt->restore();
256     
257     startAnimation();
258
259 }
260
261 static void drawPattern(void* info, CGContextRef context)
262 {
263     Image* data = (Image*)info;
264     CGImageRef image = data->frameAtIndex(data->currentFrame());
265     float w = CGImageGetWidth(image);
266     float h = CGImageGetHeight(image);
267     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect
268         (0, data->size().height() - h, w, h)), image);    
269 }
270
271 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
272
273 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint,
274                       const FloatSize& tileSize, CompositeOperator op)
275 {    
276     CGImageRef image = frameAtIndex(m_currentFrame);
277     if (!image)
278         return;
279
280     if (m_isSolidColor && m_currentFrame == 0) {
281         if (m_solidColor.alpha() > 0) {
282             ctxt->save();
283             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
284             ctxt->fillRect(destRect, m_solidColor);
285             ctxt->restore();
286         }
287         return;
288     }
289
290     CGPoint scaledPoint = srcPoint;
291     CGSize intrinsicTileSize = size();
292     CGSize scaledTileSize = intrinsicTileSize;
293
294     // If tileSize is not equal to the intrinsic size of the image, set patternTransform
295     // to the appropriate scalar matrix, scale the source point, and set the size of the
296     // scaled tile. 
297     float scaleX = 1.0;
298     float scaleY = 1.0;
299     CGAffineTransform patternTransform = CGAffineTransformIdentity;
300     if (tileSize.width() != intrinsicTileSize.width || tileSize.height() != intrinsicTileSize.height) {
301         scaleX = tileSize.width() / intrinsicTileSize.width;
302         scaleY = tileSize.height() / intrinsicTileSize.height;
303         patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
304         scaledTileSize = tileSize;
305     }
306
307     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
308     NSRect oneTileRect;
309     oneTileRect.origin.x = destRect.x() + fmodf(fmodf(-scaledPoint.x, scaledTileSize.width) - 
310                             scaledTileSize.width, scaledTileSize.width);
311     oneTileRect.origin.y = destRect.y() + fmodf(fmodf(-scaledPoint.y, scaledTileSize.height) - 
312                             scaledTileSize.height, scaledTileSize.height);
313     oneTileRect.size.width = scaledTileSize.width;
314     oneTileRect.size.height = scaledTileSize.height;
315
316     // If the single image draw covers the whole area, then just draw once.
317     if (NSContainsRect(oneTileRect, destRect)) {
318         CGRect fromRect;
319         fromRect.origin.x = (destRect.x() - oneTileRect.origin.x) / scaleX;
320         fromRect.origin.y = (destRect.y() - oneTileRect.origin.y) / scaleY;
321         fromRect.size.width = destRect.width() / scaleX;
322         fromRect.size.height = destRect.height() / scaleY;
323
324         draw(ctxt, destRect, fromRect, op);
325         return;
326     }
327
328     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(0, 0, intrinsicTileSize.width, intrinsicTileSize.height),
329                                            patternTransform, intrinsicTileSize.width, intrinsicTileSize.height, 
330                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
331     
332     if (pattern) {
333         CGContextRef context = ctxt->platformContext();
334
335         ctxt->save();
336
337         wkSetPatternPhaseInUserSpace(context, CGPointMake(oneTileRect.origin.x, oneTileRect.origin.y));
338
339         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
340         CGContextSetFillColorSpace(context, patternSpace);
341         CGColorSpaceRelease(patternSpace);
342
343         CGFloat patternAlpha = 1;
344         CGContextSetFillPattern(context, pattern, &patternAlpha);
345
346         ctxt->setCompositeOperation(op);
347
348         CGContextFillRect(context, destRect);
349
350         ctxt->restore();
351
352         CGPatternRelease(pattern);
353     }
354     
355     startAnimation();
356 }
357
358 // FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things.
359 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, TileRule hRule,
360                       TileRule vRule, CompositeOperator op)
361 {    
362     CGImageRef image = frameAtIndex(m_currentFrame);
363     if (!image)
364         return;
365
366     if (m_isSolidColor && m_currentFrame == 0) {
367         if (m_solidColor.alpha() > 0) {
368             ctxt->save();
369             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
370             ctxt->fillRect(dstRect, m_solidColor);
371             ctxt->restore();
372         }
373         return;
374     }
375
376     ctxt->save();
377
378     CGSize tileSize = srcRect.size();
379     CGRect ir = dstRect;
380     CGRect fr = srcRect;
381
382     // Now scale the slice in the appropriate direction using an affine transform that we will pass into
383     // the pattern.
384     float scaleX = 1.0f, scaleY = 1.0f;
385
386     if (hRule == Image::StretchTile)
387         scaleX = ir.size.width / fr.size.width;
388     if (vRule == Image::StretchTile)
389         scaleY = ir.size.height / fr.size.height;
390     
391     if (hRule == Image::RepeatTile)
392         scaleX = scaleY;
393     if (vRule == Image::RepeatTile)
394         scaleY = scaleX;
395         
396     if (hRule == Image::RoundTile) {
397         // Complicated math ensues.
398         float imageWidth = fr.size.width * scaleY;
399         float newWidth = ir.size.width / ceilf(ir.size.width / imageWidth);
400         scaleX = newWidth / fr.size.width;
401     }
402     
403     if (vRule == Image::RoundTile) {
404         // More complicated math ensues.
405         float imageHeight = fr.size.height * scaleX;
406         float newHeight = ir.size.height / ceilf(ir.size.height / imageHeight);
407         scaleY = newHeight / fr.size.height;
408     }
409     
410     CGAffineTransform patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
411
412     // Possible optimization:  We may want to cache the CGPatternRef    
413     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(fr.origin.x, fr.origin.y, tileSize.width, tileSize.height),
414                                            patternTransform, tileSize.width, tileSize.height, 
415                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
416     if (pattern) {
417         CGContextRef context = ctxt->platformContext();
418     
419         // We want to construct the phase such that the pattern is centered (when stretch is not
420         // set for a particular rule).
421         float hPhase = scaleX * fr.origin.x;
422         float vPhase = scaleY * (tileSize.height - fr.origin.y);
423         if (hRule == Image::RepeatTile)
424             hPhase -= fmodf(ir.size.width, scaleX * tileSize.width) / 2.0f;
425         if (vRule == Image::RepeatTile)
426             vPhase -= fmodf(ir.size.height, scaleY * tileSize.height) / 2.0f;
427         
428         wkSetPatternPhaseInUserSpace(context, CGPointMake(ir.origin.x - hPhase, ir.origin.y - vPhase));
429
430         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
431         CGContextSetFillColorSpace(context, patternSpace);
432         CGColorSpaceRelease(patternSpace);
433
434         CGFloat patternAlpha = 1;
435         CGContextSetFillPattern(context, pattern, &patternAlpha);
436
437         ctxt->setCompositeOperation(op);
438         
439         CGContextFillRect(context, ir);
440
441         CGPatternRelease(pattern);
442     }
443
444     ctxt->restore();
445
446     startAnimation();
447 }
448
449 }