Home | History | Annotate | Download | only in cg
      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