c081c663e16aa65d47427086bf8578058a8c766d
[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->setCompositeOperation(!m_solidColor.hasAlpha() && compositeOp == CompositeSourceOver ? CompositeCopy : compositeOp);
196             ctxt->fillRect(ir, m_solidColor);
197         }
198         return;
199     }
200
201     CGContextRef context = ctxt->platformContext();
202     ctxt->save();
203
204     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
205     // that is currently decoded.  This could be less that the actual height.
206     CGSize selfSize = size();                          // full image size, in pixels
207     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
208     
209     CGSize adjustedSize = selfSize;
210     if (curHeight < selfSize.height) {
211         adjustedSize.height *= curHeight / selfSize.height;
212
213         // Is the amount of available bands less than what we need to draw?  If so,
214         // we may have to clip 'fr' if it goes outside the available bounds.
215         if (CGRectGetMaxY(fr) > adjustedSize.height) {
216             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
217             if (frHeight <= 0)
218                 return;                                             // clipped out entirely
219             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
220             fr.size.height = frHeight;
221         }
222     }
223
224     // Flip the coords.
225     ctxt->setCompositeOperation(compositeOp);
226     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y);
227     CGContextScaleCTM(context, 1, -1);
228     CGContextTranslateCTM(context, 0, -ir.size.height);
229     
230     // Translated to origin, now draw at 0,0.
231     ir.origin.x = ir.origin.y = 0;
232     
233     // If we're drawing a sub portion of the image then create
234     // a image for the sub portion and draw that.
235     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
236     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
237         // Convert ft to image pixel coords:
238         float xscale = adjustedSize.width / selfSize.width;
239         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
240         fr.origin.x /= xscale;
241         fr.origin.y /= yscale;
242         fr.size.width /= xscale;
243         fr.size.height /= yscale;
244         
245         image = CGImageCreateWithImageInRect(image, fr);
246         if (image) {
247             CGContextDrawImage(context, ir, image);
248             CFRelease(image);
249         }
250     } else // Draw the whole image.
251         CGContextDrawImage(context, ir, image);
252
253     ctxt->restore();
254     
255     startAnimation();
256
257 }
258
259 static void drawPattern(void* info, CGContextRef context)
260 {
261     Image* data = (Image*)info;
262     CGImageRef image = data->frameAtIndex(data->currentFrame());
263     float w = CGImageGetWidth(image);
264     float h = CGImageGetHeight(image);
265     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect
266         (0, data->size().height() - h, w, h)), image);    
267 }
268
269 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
270
271 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint,
272                       const FloatSize& tileSize, CompositeOperator op)
273 {    
274     CGImageRef image = frameAtIndex(m_currentFrame);
275     if (!image)
276         return;
277
278     if (m_isSolidColor && m_currentFrame == 0) {
279         if (m_solidColor.alpha() > 0) {
280             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
281             ctxt->fillRect(destRect, m_solidColor);
282         }
283         return;
284     }
285
286     CGPoint scaledPoint = srcPoint;
287     CGSize intrinsicTileSize = size();
288     CGSize scaledTileSize = intrinsicTileSize;
289
290     // If tileSize is not equal to the intrinsic size of the image, set patternTransform
291     // to the appropriate scalar matrix, scale the source point, and set the size of the
292     // scaled tile. 
293     float scaleX = 1.0;
294     float scaleY = 1.0;
295     CGAffineTransform patternTransform = CGAffineTransformIdentity;
296     if (tileSize.width() != intrinsicTileSize.width || tileSize.height() != intrinsicTileSize.height) {
297         scaleX = tileSize.width() / intrinsicTileSize.width;
298         scaleY = tileSize.height() / intrinsicTileSize.height;
299         patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
300         scaledTileSize = tileSize;
301     }
302
303     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
304     NSRect oneTileRect;
305     oneTileRect.origin.x = destRect.x() + fmodf(fmodf(-scaledPoint.x, scaledTileSize.width) - 
306                             scaledTileSize.width, scaledTileSize.width);
307     oneTileRect.origin.y = destRect.y() + fmodf(fmodf(-scaledPoint.y, scaledTileSize.height) - 
308                             scaledTileSize.height, scaledTileSize.height);
309     oneTileRect.size.width = scaledTileSize.width;
310     oneTileRect.size.height = scaledTileSize.height;
311
312     // If the single image draw covers the whole area, then just draw once.
313     if (NSContainsRect(oneTileRect, destRect)) {
314         CGRect fromRect;
315         fromRect.origin.x = (destRect.x() - oneTileRect.origin.x) / scaleX;
316         fromRect.origin.y = (destRect.y() - oneTileRect.origin.y) / scaleY;
317         fromRect.size.width = destRect.width() / scaleX;
318         fromRect.size.height = destRect.height() / scaleY;
319
320         draw(ctxt, destRect, fromRect, op);
321         return;
322     }
323
324     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(0, 0, intrinsicTileSize.width, intrinsicTileSize.height),
325                                            patternTransform, intrinsicTileSize.width, intrinsicTileSize.height, 
326                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
327     
328     if (pattern) {
329         CGContextRef context = ctxt->platformContext();
330
331         ctxt->save();
332
333         wkSetPatternPhaseInUserSpace(context, CGPointMake(oneTileRect.origin.x, oneTileRect.origin.y));
334
335         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
336         CGContextSetFillColorSpace(context, patternSpace);
337         CGColorSpaceRelease(patternSpace);
338
339         CGFloat patternAlpha = 1;
340         CGContextSetFillPattern(context, pattern, &patternAlpha);
341
342         ctxt->setCompositeOperation(op);
343
344         CGContextFillRect(context, destRect);
345
346         ctxt->restore();
347
348         CGPatternRelease(pattern);
349     }
350     
351     startAnimation();
352 }
353
354 // FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things.
355 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, TileRule hRule,
356                       TileRule vRule, CompositeOperator op)
357 {    
358     CGImageRef image = frameAtIndex(m_currentFrame);
359     if (!image)
360         return;
361
362     if (m_isSolidColor && m_currentFrame == 0) {
363         if (m_solidColor.alpha() > 0) {
364             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
365             ctxt->fillRect(dstRect, m_solidColor);
366         }
367         return;
368     }
369
370     ctxt->save();
371
372     CGSize tileSize = srcRect.size();
373     CGRect ir = dstRect;
374     CGRect fr = srcRect;
375
376     // Now scale the slice in the appropriate direction using an affine transform that we will pass into
377     // the pattern.
378     float scaleX = 1.0f, scaleY = 1.0f;
379
380     if (hRule == Image::StretchTile)
381         scaleX = ir.size.width / fr.size.width;
382     if (vRule == Image::StretchTile)
383         scaleY = ir.size.height / fr.size.height;
384     
385     if (hRule == Image::RepeatTile)
386         scaleX = scaleY;
387     if (vRule == Image::RepeatTile)
388         scaleY = scaleX;
389         
390     if (hRule == Image::RoundTile) {
391         // Complicated math ensues.
392         float imageWidth = fr.size.width * scaleY;
393         float newWidth = ir.size.width / ceilf(ir.size.width / imageWidth);
394         scaleX = newWidth / fr.size.width;
395     }
396     
397     if (vRule == Image::RoundTile) {
398         // More complicated math ensues.
399         float imageHeight = fr.size.height * scaleX;
400         float newHeight = ir.size.height / ceilf(ir.size.height / imageHeight);
401         scaleY = newHeight / fr.size.height;
402     }
403     
404     CGAffineTransform patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
405
406     // Possible optimization:  We may want to cache the CGPatternRef    
407     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(fr.origin.x, fr.origin.y, tileSize.width, tileSize.height),
408                                            patternTransform, tileSize.width, tileSize.height, 
409                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
410     if (pattern) {
411         CGContextRef context = ctxt->platformContext();
412     
413         // We want to construct the phase such that the pattern is centered (when stretch is not
414         // set for a particular rule).
415         float hPhase = scaleX * fr.origin.x;
416         float vPhase = scaleY * (tileSize.height - fr.origin.y);
417         if (hRule == Image::RepeatTile)
418             hPhase -= fmodf(ir.size.width, scaleX * tileSize.width) / 2.0f;
419         if (vRule == Image::RepeatTile)
420             vPhase -= fmodf(ir.size.height, scaleY * tileSize.height) / 2.0f;
421         
422         wkSetPatternPhaseInUserSpace(context, CGPointMake(ir.origin.x - hPhase, ir.origin.y - vPhase));
423
424         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
425         CGContextSetFillColorSpace(context, patternSpace);
426         CGColorSpaceRelease(patternSpace);
427
428         CGFloat patternAlpha = 1;
429         CGContextSetFillPattern(context, pattern, &patternAlpha);
430
431         ctxt->setCompositeOperation(op);
432         
433         CGContextFillRect(context, ir);
434
435         CGPatternRelease(pattern);
436     }
437
438     ctxt->restore();
439
440     startAnimation();
441 }
442
443 }