Fix by Mitz Pettel, reviewed by Darin.
[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                 if (pixel[3] == 0)
122                     m_solidColor = Color(0, 0, 0, 0);
123                 else
124                     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));
125                 m_isSolidColor = true;
126                 CFRelease(bmap);
127             } 
128             CFRelease(space);
129         }
130     }
131 }
132
133 CFDataRef Image::getTIFFRepresentation()
134 {
135     if (m_tiffRep)
136         return m_tiffRep;
137     
138     unsigned numFrames = frameCount();
139     CFMutableDataRef data = CFDataCreateMutable(0, 0);
140     // FIXME:  Use type kCGImageTypeIdentifierTIFF constant once is becomes available in the API
141     CGImageDestinationRef destination = CGImageDestinationCreateWithData(data, CFSTR("public.tiff"), numFrames, 0);
142     if (!destination)
143         return 0;
144
145     for (unsigned i = 0; i < numFrames; ++i ) {
146         CGImageRef cgImage = frameAtIndex(i);
147         if (!cgImage) {
148             CFRelease(destination);
149             return 0;    
150         }
151         CGImageDestinationAddImage(destination, cgImage, 0);
152     }
153     CGImageDestinationFinalize(destination);
154     CFRelease(destination);
155
156     m_tiffRep = data;
157     return m_tiffRep;
158 }
159
160 NSImage* Image::getNSImage()
161 {
162     if (m_nsImage)
163         return m_nsImage;
164
165     CFDataRef data = getTIFFRepresentation();
166     if (!data)
167         return 0;
168     
169     m_nsImage = HardRetainWithNSRelease([[NSImage alloc] initWithData:(NSData*)data]);
170     return m_nsImage;
171 }
172
173 CGImageRef Image::getCGImageRef()
174 {
175     return frameAtIndex(0);
176 }
177
178 void Image::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
179 {
180     if (m_isPDF) {
181         if (m_PDFDoc)
182             m_PDFDoc->draw(ctxt, srcRect, dstRect, compositeOp);
183         return;
184     } 
185     
186     if (!m_source.initialized())
187         return;
188     
189     CGRect fr = ctxt->roundToDevicePixels(srcRect);
190     CGRect ir = ctxt->roundToDevicePixels(dstRect);
191
192     CGImageRef image = frameAtIndex(m_currentFrame);
193     if (!image) // If it's too early we won't have an image yet.
194         return;
195
196     if (m_isSolidColor && m_currentFrame == 0) {
197         if (m_solidColor.alpha() > 0) {
198             ctxt->save();
199             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && compositeOp == CompositeSourceOver ? CompositeCopy : compositeOp);
200             ctxt->fillRect(ir, m_solidColor);
201             ctxt->restore();
202         }
203         return;
204     }
205
206     CGContextRef context = ctxt->platformContext();
207     ctxt->save();
208
209     // Get the height (in adjusted, i.e. scaled, coords) of the portion of the image
210     // that is currently decoded.  This could be less that the actual height.
211     CGSize selfSize = size();                          // full image size, in pixels
212     float curHeight = CGImageGetHeight(image);         // height of loaded portion, in pixels
213     
214     CGSize adjustedSize = selfSize;
215     if (curHeight < selfSize.height) {
216         adjustedSize.height *= curHeight / selfSize.height;
217
218         // Is the amount of available bands less than what we need to draw?  If so,
219         // we may have to clip 'fr' if it goes outside the available bounds.
220         if (CGRectGetMaxY(fr) > adjustedSize.height) {
221             float frHeight = adjustedSize.height - fr.origin.y; // clip fr to available bounds
222             if (frHeight <= 0)
223                 return;                                             // clipped out entirely
224             ir.size.height *= (frHeight / fr.size.height);    // scale ir proportionally to fr
225             fr.size.height = frHeight;
226         }
227     }
228
229     // Flip the coords.
230     ctxt->setCompositeOperation(compositeOp);
231     CGContextTranslateCTM(context, ir.origin.x, ir.origin.y);
232     CGContextScaleCTM(context, 1, -1);
233     CGContextTranslateCTM(context, 0, -ir.size.height);
234     
235     // Translated to origin, now draw at 0,0.
236     ir.origin.x = ir.origin.y = 0;
237     
238     // If we're drawing a sub portion of the image then create
239     // a image for the sub portion and draw that.
240     // Test using example site at http://www.meyerweb.com/eric/css/edge/complexspiral/demo.html
241     if (fr.size.width != adjustedSize.width || fr.size.height != adjustedSize.height) {
242         // Convert ft to image pixel coords:
243         float xscale = adjustedSize.width / selfSize.width;
244         float yscale = adjustedSize.height / curHeight;     // yes, curHeight, not selfSize.height!
245         fr.origin.x /= xscale;
246         fr.origin.y /= yscale;
247         fr.size.width /= xscale;
248         fr.size.height /= yscale;
249         
250         image = CGImageCreateWithImageInRect(image, fr);
251         if (image) {
252             CGContextDrawImage(context, ir, image);
253             CFRelease(image);
254         }
255     } else // Draw the whole image.
256         CGContextDrawImage(context, ir, image);
257
258     ctxt->restore();
259     
260     startAnimation();
261
262 }
263
264 static void drawPattern(void* info, CGContextRef context)
265 {
266     Image* data = (Image*)info;
267     CGImageRef image = data->frameAtIndex(data->currentFrame());
268     float w = CGImageGetWidth(image);
269     float h = CGImageGetHeight(image);
270     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect
271         (0, data->size().height() - h, w, h)), image);    
272 }
273
274 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
275
276 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint,
277                       const FloatSize& tileSize, CompositeOperator op)
278 {    
279     CGImageRef image = frameAtIndex(m_currentFrame);
280     if (!image)
281         return;
282
283     if (m_isSolidColor && m_currentFrame == 0) {
284         if (m_solidColor.alpha() > 0) {
285             ctxt->save();
286             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
287             ctxt->fillRect(destRect, m_solidColor);
288             ctxt->restore();
289         }
290         return;
291     }
292
293     CGPoint scaledPoint = srcPoint;
294     CGSize intrinsicTileSize = size();
295     CGSize scaledTileSize = intrinsicTileSize;
296
297     // If tileSize is not equal to the intrinsic size of the image, set patternTransform
298     // to the appropriate scalar matrix, scale the source point, and set the size of the
299     // scaled tile. 
300     float scaleX = 1.0;
301     float scaleY = 1.0;
302     CGAffineTransform patternTransform = CGAffineTransformIdentity;
303     if (tileSize.width() != intrinsicTileSize.width || tileSize.height() != intrinsicTileSize.height) {
304         scaleX = tileSize.width() / intrinsicTileSize.width;
305         scaleY = tileSize.height() / intrinsicTileSize.height;
306         patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
307         scaledTileSize = tileSize;
308     }
309
310     // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
311     NSRect oneTileRect;
312     oneTileRect.origin.x = destRect.x() + fmodf(fmodf(-scaledPoint.x, scaledTileSize.width) - 
313                             scaledTileSize.width, scaledTileSize.width);
314     oneTileRect.origin.y = destRect.y() + fmodf(fmodf(-scaledPoint.y, scaledTileSize.height) - 
315                             scaledTileSize.height, scaledTileSize.height);
316     oneTileRect.size.width = scaledTileSize.width;
317     oneTileRect.size.height = scaledTileSize.height;
318
319     // If the single image draw covers the whole area, then just draw once.
320     if (NSContainsRect(oneTileRect, destRect)) {
321         CGRect fromRect;
322         fromRect.origin.x = (destRect.x() - oneTileRect.origin.x) / scaleX;
323         fromRect.origin.y = (destRect.y() - oneTileRect.origin.y) / scaleY;
324         fromRect.size.width = destRect.width() / scaleX;
325         fromRect.size.height = destRect.height() / scaleY;
326
327         draw(ctxt, destRect, fromRect, op);
328         return;
329     }
330
331     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(0, 0, intrinsicTileSize.width, intrinsicTileSize.height),
332                                            patternTransform, intrinsicTileSize.width, intrinsicTileSize.height, 
333                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
334     
335     if (pattern) {
336         CGContextRef context = ctxt->platformContext();
337
338         ctxt->save();
339
340         wkSetPatternPhaseInUserSpace(context, CGPointMake(oneTileRect.origin.x, oneTileRect.origin.y));
341
342         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
343         CGContextSetFillColorSpace(context, patternSpace);
344         CGColorSpaceRelease(patternSpace);
345
346         CGFloat patternAlpha = 1;
347         CGContextSetFillPattern(context, pattern, &patternAlpha);
348
349         ctxt->setCompositeOperation(op);
350
351         CGContextFillRect(context, destRect);
352
353         ctxt->restore();
354
355         CGPatternRelease(pattern);
356     }
357     
358     startAnimation();
359 }
360
361 // FIXME: Merge with the other drawTiled eventually, since we need a combination of both for some things.
362 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, TileRule hRule,
363                       TileRule vRule, CompositeOperator op)
364 {    
365     CGImageRef image = frameAtIndex(m_currentFrame);
366     if (!image)
367         return;
368
369     if (m_isSolidColor && m_currentFrame == 0) {
370         if (m_solidColor.alpha() > 0) {
371             ctxt->save();
372             ctxt->setCompositeOperation(!m_solidColor.hasAlpha() && op == CompositeSourceOver ? CompositeCopy : op);
373             ctxt->fillRect(dstRect, m_solidColor);
374             ctxt->restore();
375         }
376         return;
377     }
378
379     ctxt->save();
380
381     CGSize tileSize = srcRect.size();
382     CGRect ir = dstRect;
383     CGRect fr = srcRect;
384
385     // Now scale the slice in the appropriate direction using an affine transform that we will pass into
386     // the pattern.
387     float scaleX = 1.0f, scaleY = 1.0f;
388
389     if (hRule == Image::StretchTile)
390         scaleX = ir.size.width / fr.size.width;
391     if (vRule == Image::StretchTile)
392         scaleY = ir.size.height / fr.size.height;
393     
394     if (hRule == Image::RepeatTile)
395         scaleX = scaleY;
396     if (vRule == Image::RepeatTile)
397         scaleY = scaleX;
398         
399     if (hRule == Image::RoundTile) {
400         // Complicated math ensues.
401         float imageWidth = fr.size.width * scaleY;
402         float newWidth = ir.size.width / ceilf(ir.size.width / imageWidth);
403         scaleX = newWidth / fr.size.width;
404     }
405     
406     if (vRule == Image::RoundTile) {
407         // More complicated math ensues.
408         float imageHeight = fr.size.height * scaleX;
409         float newHeight = ir.size.height / ceilf(ir.size.height / imageHeight);
410         scaleY = newHeight / fr.size.height;
411     }
412     
413     CGAffineTransform patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
414
415     // Possible optimization:  We may want to cache the CGPatternRef    
416     CGPatternRef pattern = CGPatternCreate(this, CGRectMake(fr.origin.x, fr.origin.y, tileSize.width, tileSize.height),
417                                            patternTransform, tileSize.width, tileSize.height, 
418                                            kCGPatternTilingConstantSpacing, true, &patternCallbacks);
419     if (pattern) {
420         CGContextRef context = ctxt->platformContext();
421     
422         // We want to construct the phase such that the pattern is centered (when stretch is not
423         // set for a particular rule).
424         float hPhase = scaleX * fr.origin.x;
425         float vPhase = scaleY * (tileSize.height - fr.origin.y);
426         if (hRule == Image::RepeatTile)
427             hPhase -= fmodf(ir.size.width, scaleX * tileSize.width) / 2.0f;
428         if (vRule == Image::RepeatTile)
429             vPhase -= fmodf(ir.size.height, scaleY * tileSize.height) / 2.0f;
430         
431         wkSetPatternPhaseInUserSpace(context, CGPointMake(ir.origin.x - hPhase, ir.origin.y - vPhase));
432
433         CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
434         CGContextSetFillColorSpace(context, patternSpace);
435         CGColorSpaceRelease(patternSpace);
436
437         CGFloat patternAlpha = 1;
438         CGContextSetFillPattern(context, pattern, &patternAlpha);
439
440         ctxt->setCompositeOperation(op);
441         
442         CGContextFillRect(context, ir);
443
444         CGPatternRelease(pattern);
445     }
446
447     ctxt->restore();
448
449     startAnimation();
450 }
451
452 }