1 /* 2 * Copyright (C) 2004, 2005, 2006 Apple 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 #include "config.h" 27 #include "BitmapImage.h" 28 29 #if USE(CG) 30 31 #include "AffineTransform.h" 32 #include "FloatConversion.h" 33 #include "FloatRect.h" 34 #include "GraphicsContextCG.h" 35 #include "ImageObserver.h" 36 #include "PDFDocumentImage.h" 37 #include "PlatformString.h" 38 #include <ApplicationServices/ApplicationServices.h> 39 #include <wtf/RetainPtr.h> 40 41 #if PLATFORM(MAC) || PLATFORM(CHROMIUM) 42 #include "WebCoreSystemInterface.h" 43 #endif 44 45 #if PLATFORM(WIN) 46 #include <WebKitSystemInterface/WebKitSystemInterface.h> 47 #endif 48 49 namespace WebCore { 50 51 bool FrameData::clear(bool clearMetadata) 52 { 53 if (clearMetadata) 54 m_haveMetadata = false; 55 56 if (m_frame) { 57 CGImageRelease(m_frame); 58 m_frame = 0; 59 return true; 60 } 61 return false; 62 } 63 64 // ================================================ 65 // Image Class 66 // ================================================ 67 68 BitmapImage::BitmapImage(CGImageRef cgImage, ImageObserver* observer) 69 : Image(observer) 70 , m_currentFrame(0) 71 , m_frames(0) 72 , m_frameTimer(0) 73 , m_repetitionCount(cAnimationNone) 74 , m_repetitionCountStatus(Unknown) 75 , m_repetitionsComplete(0) 76 , m_isSolidColor(false) 77 , m_checkedForSolidColor(false) 78 , m_animationFinished(true) 79 , m_allDataReceived(true) 80 , m_haveSize(true) 81 , m_sizeAvailable(true) 82 , m_decodedSize(0) 83 , m_haveFrameCount(true) 84 , m_frameCount(1) 85 { 86 initPlatformData(); 87 88 CGFloat width = CGImageGetWidth(cgImage); 89 CGFloat height = CGImageGetHeight(cgImage); 90 m_decodedSize = width * height * 4; 91 m_size = IntSize(width, height); 92 93 m_frames.grow(1); 94 m_frames[0].m_frame = cgImage; 95 m_frames[0].m_hasAlpha = true; 96 m_frames[0].m_haveMetadata = true; 97 checkForSolidColor(); 98 } 99 100 // Drawing Routines 101 102 void BitmapImage::checkForSolidColor() 103 { 104 m_checkedForSolidColor = true; 105 if (frameCount() > 1) { 106 m_isSolidColor = false; 107 return; 108 } 109 110 CGImageRef image = frameAtIndex(0); 111 112 // Currently we only check for solid color in the important special case of a 1x1 image. 113 if (image && CGImageGetWidth(image) == 1 && CGImageGetHeight(image) == 1) { 114 unsigned char pixel[4]; // RGBA 115 RetainPtr<CGContextRef> bmap(AdoptCF, CGBitmapContextCreate(pixel, 1, 1, 8, sizeof(pixel), deviceRGBColorSpaceRef(), 116 kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big)); 117 if (!bmap) 118 return; 119 GraphicsContext(bmap.get()).setCompositeOperation(CompositeCopy); 120 CGRect dst = { {0, 0}, {1, 1} }; 121 CGContextDrawImage(bmap.get(), dst, image); 122 if (pixel[3] == 0) 123 m_solidColor = Color(0, 0, 0, 0); 124 else 125 m_solidColor = Color(pixel[0] * 255 / pixel[3], pixel[1] * 255 / pixel[3], pixel[2] * 255 / pixel[3], pixel[3]); 126 m_isSolidColor = true; 127 } 128 } 129 130 static RetainPtr<CGImageRef> imageWithColorSpace(CGImageRef originalImage, ColorSpace colorSpace) 131 { 132 CGColorSpaceRef originalColorSpace = CGImageGetColorSpace(originalImage); 133 134 // If the image already has a (non-device) color space, we don't want to 135 // override it, so return. 136 if (!originalColorSpace || !CFEqual(originalColorSpace, deviceRGBColorSpaceRef())) 137 return originalImage; 138 139 switch (colorSpace) { 140 case ColorSpaceDeviceRGB: 141 return originalImage; 142 case ColorSpaceSRGB: 143 return RetainPtr<CGImageRef>(AdoptCF, CGImageCreateCopyWithColorSpace(originalImage, sRGBColorSpaceRef())); 144 case ColorSpaceLinearRGB: 145 return RetainPtr<CGImageRef>(AdoptCF, CGImageCreateCopyWithColorSpace(originalImage, linearRGBColorSpaceRef())); 146 } 147 148 ASSERT_NOT_REACHED(); 149 return originalImage; 150 } 151 152 CGImageRef BitmapImage::getCGImageRef() 153 { 154 return frameAtIndex(0); 155 } 156 157 CGImageRef BitmapImage::getFirstCGImageRefOfSize(const IntSize& size) 158 { 159 size_t count = frameCount(); 160 for (size_t i = 0; i < count; ++i) { 161 CGImageRef cgImage = frameAtIndex(i); 162 if (IntSize(CGImageGetWidth(cgImage), CGImageGetHeight(cgImage)) == size) 163 return cgImage; 164 } 165 166 // Fallback to the default CGImageRef if we can't find the right size 167 return getCGImageRef(); 168 } 169 170 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, ColorSpace styleColorSpace, CompositeOperator compositeOp) 171 { 172 startAnimation(); 173 174 RetainPtr<CGImageRef> image = frameAtIndex(m_currentFrame); 175 if (!image) // If it's too early we won't have an image yet. 176 return; 177 178 if (mayFillWithSolidColor()) { 179 fillWithSolidColor(ctxt, destRect, solidColor(), styleColorSpace, compositeOp); 180 return; 181 } 182 183 float currHeight = CGImageGetHeight(image.get()); 184 if (currHeight <= srcRect.y()) 185 return; 186 187 CGContextRef context = ctxt->platformContext(); 188 ctxt->save(); 189 190 bool shouldUseSubimage = false; 191 192 // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image 193 // and then set a clip to the portion that we want to display. 194 FloatRect adjustedDestRect = destRect; 195 FloatSize selfSize = currentFrameSize(); 196 if (srcRect.size() != selfSize) { 197 CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context); 198 // When the image is scaled using high-quality interpolation, we create a temporary CGImage 199 // containing only the portion we want to display. We need to do this because high-quality 200 // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed 201 // into the destination rect. See <rdar://problem/6112909>. 202 shouldUseSubimage = (interpolationQuality != kCGInterpolationNone) && (srcRect.size() != destRect.size() || !ctxt->getCTM().isIdentityOrTranslationOrFlipped()); 203 float xScale = srcRect.width() / destRect.width(); 204 float yScale = srcRect.height() / destRect.height(); 205 if (shouldUseSubimage) { 206 FloatRect subimageRect = srcRect; 207 float leftPadding = srcRect.x() - floorf(srcRect.x()); 208 float topPadding = srcRect.y() - floorf(srcRect.y()); 209 210 subimageRect.move(-leftPadding, -topPadding); 211 adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale); 212 213 subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding)); 214 adjustedDestRect.setWidth(subimageRect.width() / xScale); 215 216 subimageRect.setHeight(ceilf(subimageRect.height() + topPadding)); 217 adjustedDestRect.setHeight(subimageRect.height() / yScale); 218 219 image.adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect)); 220 if (currHeight < srcRect.maxY()) { 221 ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y); 222 adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale); 223 } 224 } else { 225 adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale)); 226 adjustedDestRect.setSize(FloatSize(selfSize.width() / xScale, selfSize.height() / yScale)); 227 } 228 229 CGContextClipToRect(context, destRect); 230 } 231 232 // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly. 233 if (!shouldUseSubimage && currHeight < selfSize.height()) 234 adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / selfSize.height()); 235 236 ctxt->setCompositeOperation(compositeOp); 237 238 // Flip the coords. 239 CGContextScaleCTM(context, 1, -1); 240 adjustedDestRect.setY(-adjustedDestRect.maxY()); 241 242 // Adjust the color space. 243 image = imageWithColorSpace(image.get(), styleColorSpace); 244 245 // Draw the image. 246 CGContextDrawImage(context, adjustedDestRect, image.get()); 247 248 ctxt->restore(); 249 250 if (imageObserver()) 251 imageObserver()->didDraw(this); 252 } 253 254 static void drawPatternCallback(void* info, CGContextRef context) 255 { 256 CGImageRef image = (CGImageRef)info; 257 CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect(0, 0, CGImageGetWidth(image), CGImageGetHeight(image))), image); 258 } 259 260 void Image::drawPattern(GraphicsContext* ctxt, const FloatRect& tileRect, const AffineTransform& patternTransform, 261 const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect) 262 { 263 if (!nativeImageForCurrentFrame()) 264 return; 265 266 ASSERT(patternTransform.isInvertible()); 267 if (!patternTransform.isInvertible()) 268 // Avoid a hang under CGContextDrawTiledImage on release builds. 269 return; 270 271 #if !ASSERT_DISABLED 272 if (this->isBitmapImage()) 273 ASSERT(static_cast<BitmapImage*>(this)->notSolidColor()); 274 #endif 275 276 CGContextRef context = ctxt->platformContext(); 277 ctxt->save(); 278 CGContextClipToRect(context, destRect); 279 ctxt->setCompositeOperation(op); 280 CGContextTranslateCTM(context, destRect.x(), destRect.y() + destRect.height()); 281 CGContextScaleCTM(context, 1, -1); 282 283 // Compute the scaled tile size. 284 float scaledTileHeight = tileRect.height() * narrowPrecisionToFloat(patternTransform.d()); 285 286 // We have to adjust the phase to deal with the fact we're in Cartesian space now (with the bottom left corner of destRect being 287 // the origin). 288 float adjustedX = phase.x() - destRect.x() + tileRect.x() * narrowPrecisionToFloat(patternTransform.a()); // We translated the context so that destRect.x() is the origin, so subtract it out. 289 float adjustedY = destRect.height() - (phase.y() - destRect.y() + tileRect.y() * narrowPrecisionToFloat(patternTransform.d()) + scaledTileHeight); 290 291 CGImageRef tileImage = nativeImageForCurrentFrame(); 292 float h = CGImageGetHeight(tileImage); 293 294 RetainPtr<CGImageRef> subImage; 295 if (tileRect.size() == size()) 296 subImage = tileImage; 297 else { 298 // Copying a sub-image out of a partially-decoded image stops the decoding of the original image. It should never happen 299 // because sub-images are only used for border-image, which only renders when the image is fully decoded. 300 ASSERT(h == height()); 301 subImage.adoptCF(CGImageCreateWithImageInRect(tileImage, tileRect)); 302 } 303 304 // Adjust the color space. 305 subImage = imageWithColorSpace(subImage.get(), styleColorSpace); 306 307 #ifndef BUILDING_ON_TIGER 308 // Leopard has an optimized call for the tiling of image patterns, but we can only use it if the image has been decoded enough that 309 // its buffer is the same size as the overall image. Because a partially decoded CGImageRef with a smaller width or height than the 310 // overall image buffer needs to tile with "gaps", we can't use the optimized tiling call in that case. 311 // FIXME: Could create WebKitSystemInterface SPI for CGCreatePatternWithImage2 and probably make Tiger tile faster as well. 312 // FIXME: We cannot use CGContextDrawTiledImage with scaled tiles on Leopard, because it suffers from rounding errors. Snow Leopard is ok. 313 float scaledTileWidth = tileRect.width() * narrowPrecisionToFloat(patternTransform.a()); 314 float w = CGImageGetWidth(tileImage); 315 #ifdef BUILDING_ON_LEOPARD 316 if (w == size().width() && h == size().height() && scaledTileWidth == tileRect.width() && scaledTileHeight == tileRect.height()) 317 #else 318 if (w == size().width() && h == size().height()) 319 #endif 320 CGContextDrawTiledImage(context, FloatRect(adjustedX, adjustedY, scaledTileWidth, scaledTileHeight), subImage.get()); 321 else { 322 #endif 323 324 // On Leopard, this code now only runs for partially decoded images whose buffers do not yet match the overall size of the image. 325 // On Tiger this code runs all the time. This code is suboptimal because the pattern does not reference the image directly, and the 326 // pattern is destroyed before exiting the function. This means any decoding the pattern does doesn't end up cached anywhere, so we 327 // redecode every time we paint. 328 static const CGPatternCallbacks patternCallbacks = { 0, drawPatternCallback, NULL }; 329 CGAffineTransform matrix = CGAffineTransformMake(narrowPrecisionToCGFloat(patternTransform.a()), 0, 0, narrowPrecisionToCGFloat(patternTransform.d()), adjustedX, adjustedY); 330 matrix = CGAffineTransformConcat(matrix, CGContextGetCTM(context)); 331 // The top of a partially-decoded image is drawn at the bottom of the tile. Map it to the top. 332 matrix = CGAffineTransformTranslate(matrix, 0, size().height() - h); 333 RetainPtr<CGPatternRef> pattern(AdoptCF, CGPatternCreate(subImage.get(), CGRectMake(0, 0, tileRect.width(), tileRect.height()), 334 matrix, tileRect.width(), tileRect.height(), 335 kCGPatternTilingConstantSpacing, true, &patternCallbacks)); 336 if (!pattern) { 337 ctxt->restore(); 338 return; 339 } 340 341 RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0)); 342 343 CGFloat alpha = 1; 344 RetainPtr<CGColorRef> color(AdoptCF, CGColorCreateWithPattern(patternSpace.get(), pattern.get(), &alpha)); 345 CGContextSetFillColorSpace(context, patternSpace.get()); 346 347 // FIXME: Really want a public API for this. It is just CGContextSetBaseCTM(context, CGAffineTransformIdentiy). 348 wkSetPatternBaseCTM(context, CGAffineTransformIdentity); 349 CGContextSetPatternPhase(context, CGSizeZero); 350 351 CGContextSetFillColorWithColor(context, color.get()); 352 CGContextFillRect(context, CGContextGetClipBoundingBox(context)); 353 354 #ifndef BUILDING_ON_TIGER 355 } 356 #endif 357 358 ctxt->restore(); 359 360 if (imageObserver()) 361 imageObserver()->didDraw(this); 362 } 363 364 365 } 366 367 #endif // USE(CG) 368