2 * Copyright (C) 2004, 2005, 2006 Apple Computer, Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
30 #import "FoundationExtras.h"
31 #import "GraphicsContext.h"
32 #import "PDFDocumentImage.h"
33 #import "PlatformString.h"
34 #import "WebCoreFrameBridge.h"
35 #import "WebCoreSystemInterface.h"
39 void FrameData::clear()
49 // ================================================
51 // ================================================
53 void Image::initNativeData()
61 void Image::destroyNativeData()
66 void Image::invalidateNativeData()
68 if (m_frames.size() != 1)
82 Image* Image::loadResource(const char *name)
84 NSBundle *bundle = [NSBundle bundleForClass:[WebCoreFrameBridge class]];
85 NSString *imagePath = [bundle pathForResource:[NSString stringWithUTF8String:name] ofType:@"tiff"];
86 NSData *namedImageData = [NSData dataWithContentsOfFile:imagePath];
88 Image* image = new Image;
89 image->setNativeData((CFDataRef)namedImageData, true);
95 bool Image::supportsType(const String& type)
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];
104 void Image::checkForSolidColor()
106 if (frameCount() > 1)
107 m_isSolidColor = false;
109 CGImageRef image = frameAtIndex(0);
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);
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;
130 CFDataRef Image::getTIFFRepresentation()
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);
142 for (unsigned i = 0; i < numFrames; ++i ) {
143 CGImageRef cgImage = frameAtIndex(i);
145 CFRelease(destination);
148 CGImageDestinationAddImage(destination, cgImage, 0);
150 CGImageDestinationFinalize(destination);
151 CFRelease(destination);
157 NSImage* Image::getNSImage()
162 CFDataRef data = getTIFFRepresentation();
166 m_nsImage = HardRetainWithNSRelease([[NSImage alloc] initWithData:(NSData*)data]);
170 CGImageRef Image::getCGImageRef()
172 return frameAtIndex(0);
175 void Image::draw(GraphicsContext* ctxt, const FloatRect& dstRect, const FloatRect& srcRect, CompositeOperator compositeOp)
179 m_PDFDoc->draw(ctxt, srcRect, dstRect, compositeOp);
183 if (!m_source.initialized())
186 CGRect fr = ctxt->roundToDevicePixels(srcRect);
187 CGRect ir = ctxt->roundToDevicePixels(dstRect);
189 CGImageRef image = frameAtIndex(m_currentFrame);
190 if (!image) // If it's too early we won't have an image yet.
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);
201 CGContextRef context = ctxt->platformContext();
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
209 CGSize adjustedSize = selfSize;
210 if (curHeight < selfSize.height) {
211 adjustedSize.height *= curHeight / selfSize.height;
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
218 return; // clipped out entirely
219 ir.size.height *= (frHeight / fr.size.height); // scale ir proportionally to fr
220 fr.size.height = frHeight;
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);
230 // Translated to origin, now draw at 0,0.
231 ir.origin.x = ir.origin.y = 0;
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;
245 image = CGImageCreateWithImageInRect(image, fr);
247 CGContextDrawImage(context, ir, image);
250 } else // Draw the whole image.
251 CGContextDrawImage(context, ir, image);
259 static void drawPattern(void* info, CGContextRef context)
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);
269 static const CGPatternCallbacks patternCallbacks = { 0, drawPattern, NULL };
271 void Image::drawTiled(GraphicsContext* ctxt, const FloatRect& destRect, const FloatPoint& srcPoint,
272 const FloatSize& tileSize, CompositeOperator op)
274 CGImageRef image = frameAtIndex(m_currentFrame);
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);
286 CGPoint scaledPoint = srcPoint;
287 CGSize intrinsicTileSize = size();
288 CGSize scaledTileSize = intrinsicTileSize;
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
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;
303 // Check and see if a single draw of the image can cover the entire area we are supposed to tile.
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;
312 // If the single image draw covers the whole area, then just draw once.
313 if (NSContainsRect(oneTileRect, destRect)) {
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;
320 draw(ctxt, destRect, fromRect, op);
324 CGPatternRef pattern = CGPatternCreate(this, CGRectMake(0, 0, intrinsicTileSize.width, intrinsicTileSize.height),
325 patternTransform, intrinsicTileSize.width, intrinsicTileSize.height,
326 kCGPatternTilingConstantSpacing, true, &patternCallbacks);
329 CGContextRef context = ctxt->platformContext();
333 wkSetPatternPhaseInUserSpace(context, CGPointMake(oneTileRect.origin.x, oneTileRect.origin.y));
335 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
336 CGContextSetFillColorSpace(context, patternSpace);
337 CGColorSpaceRelease(patternSpace);
339 CGFloat patternAlpha = 1;
340 CGContextSetFillPattern(context, pattern, &patternAlpha);
342 ctxt->setCompositeOperation(op);
344 CGContextFillRect(context, destRect);
348 CGPatternRelease(pattern);
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)
358 CGImageRef image = frameAtIndex(m_currentFrame);
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);
372 CGSize tileSize = srcRect.size();
376 // Now scale the slice in the appropriate direction using an affine transform that we will pass into
378 float scaleX = 1.0f, scaleY = 1.0f;
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;
385 if (hRule == Image::RepeatTile)
387 if (vRule == Image::RepeatTile)
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;
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;
404 CGAffineTransform patternTransform = CGAffineTransformMakeScale(scaleX, scaleY);
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);
411 CGContextRef context = ctxt->platformContext();
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;
422 wkSetPatternPhaseInUserSpace(context, CGPointMake(ir.origin.x - hPhase, ir.origin.y - vPhase));
424 CGColorSpaceRef patternSpace = CGColorSpaceCreatePattern(NULL);
425 CGContextSetFillColorSpace(context, patternSpace);
426 CGColorSpaceRelease(patternSpace);
428 CGFloat patternAlpha = 1;
429 CGContextSetFillPattern(context, pattern, &patternAlpha);
431 ctxt->setCompositeOperation(op);
433 CGContextFillRect(context, ir);
435 CGPatternRelease(pattern);