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