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