Home | History | Annotate | Download | only in cg
      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 #include "config.h"
     27 #include "BitmapImage.h"
     28 
     29 #if PLATFORM(CG)
     30 
     31 #include "AffineTransform.h"
     32 #include "FloatConversion.h"
     33 #include "FloatRect.h"
     34 #include "GraphicsContext.h"
     35 #include "GraphicsContextPlatformPrivateCG.h"
     36 #include "ImageObserver.h"
     37 #include "PDFDocumentImage.h"
     38 #include "PlatformString.h"
     39 #include <ApplicationServices/ApplicationServices.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         static CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
    116         RetainPtr<CGContextRef> bmap(AdoptCF, CGBitmapContextCreate(pixel, 1, 1, 8, sizeof(pixel), space,
    117             kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big));
    118         if (!bmap)
    119             return;
    120         GraphicsContext(bmap.get()).setCompositeOperation(CompositeCopy);
    121         CGRect dst = { {0, 0}, {1, 1} };
    122         CGContextDrawImage(bmap.get(), dst, image);
    123         if (pixel[3] == 0)
    124             m_solidColor = Color(0, 0, 0, 0);
    125         else
    126             m_solidColor = Color(pixel[0] * 255 / pixel[3], pixel[1] * 255 / pixel[3], pixel[2] * 255 / pixel[3], pixel[3]);
    127         m_isSolidColor = true;
    128     }
    129 }
    130 
    131 static RetainPtr<CGImageRef> imageWithColorSpace(CGImageRef originalImage, ColorSpace colorSpace)
    132 {
    133     CGColorSpaceRef originalColorSpace = CGImageGetColorSpace(originalImage);
    134 
    135     // If the image already has a (non-device) color space, we don't want to
    136     // override it, so return.
    137     if (!originalColorSpace || !CFEqual(originalColorSpace, deviceRGBColorSpaceRef()))
    138         return originalImage;
    139 
    140     switch (colorSpace) {
    141     case DeviceColorSpace:
    142         return originalImage;
    143     case sRGBColorSpace:
    144         return RetainPtr<CGImageRef>(AdoptCF, CGImageCreateCopyWithColorSpace(originalImage,
    145             sRGBColorSpaceRef()));
    146     }
    147 
    148     ASSERT_NOT_REACHED();
    149     return originalImage;
    150 }
    151 
    152 CGImageRef BitmapImage::getCGImageRef()
    153 {
    154     return frameAtIndex(0);
    155 }
    156 
    157 void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, ColorSpace styleColorSpace, CompositeOperator compositeOp)
    158 {
    159     startAnimation();
    160 
    161     RetainPtr<CGImageRef> image = frameAtIndex(m_currentFrame);
    162     if (!image) // If it's too early we won't have an image yet.
    163         return;
    164 
    165     if (mayFillWithSolidColor()) {
    166         fillWithSolidColor(ctxt, destRect, solidColor(), styleColorSpace, compositeOp);
    167         return;
    168     }
    169 
    170     float currHeight = CGImageGetHeight(image.get());
    171     if (currHeight <= srcRect.y())
    172         return;
    173 
    174     CGContextRef context = ctxt->platformContext();
    175     ctxt->save();
    176 
    177     bool shouldUseSubimage = false;
    178 
    179     // If the source rect is a subportion of the image, then we compute an inflated destination rect that will hold the entire image
    180     // and then set a clip to the portion that we want to display.
    181     FloatRect adjustedDestRect = destRect;
    182     FloatSize selfSize = currentFrameSize();
    183     if (srcRect.size() != selfSize) {
    184         CGInterpolationQuality interpolationQuality = CGContextGetInterpolationQuality(context);
    185         // When the image is scaled using high-quality interpolation, we create a temporary CGImage
    186         // containing only the portion we want to display. We need to do this because high-quality
    187         // interpolation smoothes sharp edges, causing pixels from outside the source rect to bleed
    188         // into the destination rect. See <rdar://problem/6112909>.
    189         shouldUseSubimage = (interpolationQuality == kCGInterpolationHigh || interpolationQuality == kCGInterpolationDefault) && srcRect.size() != destRect.size();
    190         float xScale = srcRect.width() / destRect.width();
    191         float yScale = srcRect.height() / destRect.height();
    192         if (shouldUseSubimage) {
    193             FloatRect subimageRect = srcRect;
    194             float leftPadding = srcRect.x() - floorf(srcRect.x());
    195             float topPadding = srcRect.y() - floorf(srcRect.y());
    196 
    197             subimageRect.move(-leftPadding, -topPadding);
    198             adjustedDestRect.move(-leftPadding / xScale, -topPadding / yScale);
    199 
    200             subimageRect.setWidth(ceilf(subimageRect.width() + leftPadding));
    201             adjustedDestRect.setWidth(subimageRect.width() / xScale);
    202 
    203             subimageRect.setHeight(ceilf(subimageRect.height() + topPadding));
    204             adjustedDestRect.setHeight(subimageRect.height() / yScale);
    205 
    206             image.adoptCF(CGImageCreateWithImageInRect(image.get(), subimageRect));
    207             if (currHeight < srcRect.bottom()) {
    208                 ASSERT(CGImageGetHeight(image.get()) == currHeight - CGRectIntegral(srcRect).origin.y);
    209                 adjustedDestRect.setHeight(CGImageGetHeight(image.get()) / yScale);
    210             }
    211         } else {
    212             adjustedDestRect.setLocation(FloatPoint(destRect.x() - srcRect.x() / xScale, destRect.y() - srcRect.y() / yScale));
    213             adjustedDestRect.setSize(FloatSize(selfSize.width() / xScale, selfSize.height() / yScale));
    214         }
    215 
    216         CGContextClipToRect(context, destRect);
    217     }
    218 
    219     // If the image is only partially loaded, then shrink the destination rect that we're drawing into accordingly.
    220     if (!shouldUseSubimage && currHeight < selfSize.height())
    221         adjustedDestRect.setHeight(adjustedDestRect.height() * currHeight / selfSize.height());
    222 
    223     ctxt->setCompositeOperation(compositeOp);
    224 
    225     // Flip the coords.
    226     CGContextScaleCTM(context, 1, -1);
    227     adjustedDestRect.setY(-adjustedDestRect.bottom());
    228 
    229     // Adjust the color space.
    230     image = imageWithColorSpace(image.get(), styleColorSpace);
    231 
    232     // Draw the image.
    233     CGContextDrawImage(context, adjustedDestRect, image.get());
    234 
    235     ctxt->restore();
    236 
    237     if (imageObserver())
    238         imageObserver()->didDraw(this);
    239 }
    240 
    241 static void drawPatternCallback(void* info, CGContextRef context)
    242 {
    243     CGImageRef image = (CGImageRef)info;
    244     CGContextDrawImage(context, GraphicsContext(context).roundToDevicePixels(FloatRect(0, 0, CGImageGetWidth(image), CGImageGetHeight(image))), image);
    245 }
    246 
    247 void Image::drawPattern(GraphicsContext* ctxt, const FloatRect& tileRect, const AffineTransform& patternTransform,
    248                         const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
    249 {
    250     if (!nativeImageForCurrentFrame())
    251         return;
    252 
    253     ASSERT(patternTransform.isInvertible());
    254     if (!patternTransform.isInvertible())
    255         // Avoid a hang under CGContextDrawTiledImage on release builds.
    256         return;
    257 
    258     CGContextRef context = ctxt->platformContext();
    259     ctxt->save();
    260     CGContextClipToRect(context, destRect);
    261     ctxt->setCompositeOperation(op);
    262     CGContextTranslateCTM(context, destRect.x(), destRect.y() + destRect.height());
    263     CGContextScaleCTM(context, 1, -1);
    264 
    265     // Compute the scaled tile size.
    266     float scaledTileHeight = tileRect.height() * narrowPrecisionToFloat(patternTransform.d());
    267 
    268     // 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
    269     // the origin).
    270     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.
    271     float adjustedY = destRect.height() - (phase.y() - destRect.y() + tileRect.y() * narrowPrecisionToFloat(patternTransform.d()) + scaledTileHeight);
    272 
    273     CGImageRef tileImage = nativeImageForCurrentFrame();
    274     float h = CGImageGetHeight(tileImage);
    275 
    276     RetainPtr<CGImageRef> subImage;
    277     if (tileRect.size() == size())
    278         subImage = tileImage;
    279     else {
    280         // Copying a sub-image out of a partially-decoded image stops the decoding of the original image. It should never happen
    281         // because sub-images are only used for border-image, which only renders when the image is fully decoded.
    282         ASSERT(h == height());
    283         subImage.adoptCF(CGImageCreateWithImageInRect(tileImage, tileRect));
    284     }
    285 
    286     // Adjust the color space.
    287     subImage = imageWithColorSpace(subImage.get(), styleColorSpace);
    288 
    289 #ifndef BUILDING_ON_TIGER
    290     // 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
    291     // its buffer is the same size as the overall image.  Because a partially decoded CGImageRef with a smaller width or height than the
    292     // overall image buffer needs to tile with "gaps", we can't use the optimized tiling call in that case.
    293     // FIXME: Could create WebKitSystemInterface SPI for CGCreatePatternWithImage2 and probably make Tiger tile faster as well.
    294     // FIXME: We cannot use CGContextDrawTiledImage with scaled tiles on Leopard, because it suffers from rounding errors.  Snow Leopard is ok.
    295     float scaledTileWidth = tileRect.width() * narrowPrecisionToFloat(patternTransform.a());
    296     float w = CGImageGetWidth(tileImage);
    297 #ifdef BUILDING_ON_LEOPARD
    298     if (w == size().width() && h == size().height() && scaledTileWidth == tileRect.width() && scaledTileHeight == tileRect.height())
    299 #else
    300     if (w == size().width() && h == size().height())
    301 #endif
    302         CGContextDrawTiledImage(context, FloatRect(adjustedX, adjustedY, scaledTileWidth, scaledTileHeight), subImage.get());
    303     else {
    304 #endif
    305 
    306     // On Leopard, this code now only runs for partially decoded images whose buffers do not yet match the overall size of the image.
    307     // On Tiger this code runs all the time.  This code is suboptimal because the pattern does not reference the image directly, and the
    308     // pattern is destroyed before exiting the function.  This means any decoding the pattern does doesn't end up cached anywhere, so we
    309     // redecode every time we paint.
    310     static const CGPatternCallbacks patternCallbacks = { 0, drawPatternCallback, NULL };
    311     CGAffineTransform matrix = CGAffineTransformMake(narrowPrecisionToCGFloat(patternTransform.a()), 0, 0, narrowPrecisionToCGFloat(patternTransform.d()), adjustedX, adjustedY);
    312     matrix = CGAffineTransformConcat(matrix, CGContextGetCTM(context));
    313     // The top of a partially-decoded image is drawn at the bottom of the tile. Map it to the top.
    314     matrix = CGAffineTransformTranslate(matrix, 0, size().height() - h);
    315     RetainPtr<CGPatternRef> pattern(AdoptCF, CGPatternCreate(subImage.get(), CGRectMake(0, 0, tileRect.width(), tileRect.height()),
    316                                              matrix, tileRect.width(), tileRect.height(),
    317                                              kCGPatternTilingConstantSpacing, true, &patternCallbacks));
    318     if (!pattern) {
    319         ctxt->restore();
    320         return;
    321     }
    322 
    323     RetainPtr<CGColorSpaceRef> patternSpace(AdoptCF, CGColorSpaceCreatePattern(0));
    324 
    325     CGFloat alpha = 1;
    326     RetainPtr<CGColorRef> color(AdoptCF, CGColorCreateWithPattern(patternSpace.get(), pattern.get(), &alpha));
    327     CGContextSetFillColorSpace(context, patternSpace.get());
    328 
    329     // FIXME: Really want a public API for this.  It is just CGContextSetBaseCTM(context, CGAffineTransformIdentiy).
    330     wkSetPatternBaseCTM(context, CGAffineTransformIdentity);
    331     CGContextSetPatternPhase(context, CGSizeZero);
    332 
    333     CGContextSetFillColorWithColor(context, color.get());
    334     CGContextFillRect(context, CGContextGetClipBoundingBox(context));
    335 
    336 #ifndef BUILDING_ON_TIGER
    337     }
    338 #endif
    339 
    340     ctxt->restore();
    341 
    342     if (imageObserver())
    343         imageObserver()->didDraw(this);
    344 }
    345 
    346 
    347 }
    348 
    349 #endif // PLATFORM(CG)
    350