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