Home | History | Annotate | Download | only in cg
      1 /*
      2  * Copyright (C) 2010 Apple Inc. All rights reserved.
      3  * Copyright (C) 2010 Google Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  * 1. Redistributions of source code must retain the above copyright
      9  *    notice, this list of conditions and the following disclaimer.
     10  * 2. Redistributions in binary form must reproduce the above copyright
     11  *    notice, this list of conditions and the following disclaimer in the
     12  *    documentation and/or other materials provided with the distribution.
     13  *
     14  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
     15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
     16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
     17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
     18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
     19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
     21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
     22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     25  */
     26 
     27 #include "config.h"
     28 
     29 #if ENABLE(WEBGL)
     30 
     31 #include "GraphicsContext3D.h"
     32 
     33 #include "BitmapImage.h"
     34 #include "GraphicsContextCG.h"
     35 #include "Image.h"
     36 
     37 #include <CoreGraphics/CGBitmapContext.h>
     38 #include <CoreGraphics/CGContext.h>
     39 #include <CoreGraphics/CGDataProvider.h>
     40 #include <CoreGraphics/CGImage.h>
     41 
     42 #include <wtf/RetainPtr.h>
     43 
     44 namespace WebCore {
     45 
     46 enum SourceDataFormatBase {
     47     SourceFormatBaseR = 0,
     48     SourceFormatBaseA,
     49     SourceFormatBaseRA,
     50     SourceFormatBaseAR,
     51     SourceFormatBaseRGB,
     52     SourceFormatBaseRGBA,
     53     SourceFormatBaseARGB,
     54     SourceFormatBaseNumFormats
     55 };
     56 
     57 enum AlphaFormat {
     58     AlphaFormatNone = 0,
     59     AlphaFormatFirst,
     60     AlphaFormatLast,
     61     AlphaFormatNumFormats
     62 };
     63 
     64 // This returns SourceFormatNumFormats if the combination of input parameters is unsupported.
     65 static GraphicsContext3D::SourceDataFormat getSourceDataFormat(unsigned int componentsPerPixel, AlphaFormat alphaFormat, bool is16BitFormat, bool bigEndian)
     66 {
     67     const static SourceDataFormatBase formatTableBase[4][AlphaFormatNumFormats] = { // componentsPerPixel x AlphaFormat
     68         // AlphaFormatNone            AlphaFormatFirst            AlphaFormatLast
     69         { SourceFormatBaseR,          SourceFormatBaseA,          SourceFormatBaseA          }, // 1 componentsPerPixel
     70         { SourceFormatBaseNumFormats, SourceFormatBaseAR,         SourceFormatBaseRA         }, // 2 componentsPerPixel
     71         { SourceFormatBaseRGB,        SourceFormatBaseNumFormats, SourceFormatBaseNumFormats }, // 3 componentsPerPixel
     72         { SourceFormatBaseNumFormats, SourceFormatBaseARGB,       SourceFormatBaseRGBA        } // 4 componentsPerPixel
     73     };
     74     const static GraphicsContext3D::SourceDataFormat formatTable[SourceFormatBaseNumFormats][4] = { // SourceDataFormatBase x bitsPerComponent x endian
     75         // 8bits, little endian                 8bits, big endian                     16bits, little endian                        16bits, big endian
     76         { GraphicsContext3D::SourceFormatR8,    GraphicsContext3D::SourceFormatR8,    GraphicsContext3D::SourceFormatR16Little,    GraphicsContext3D::SourceFormatR16Big },
     77         { GraphicsContext3D::SourceFormatA8,    GraphicsContext3D::SourceFormatA8,    GraphicsContext3D::SourceFormatA16Little,    GraphicsContext3D::SourceFormatA16Big },
     78         { GraphicsContext3D::SourceFormatAR8,   GraphicsContext3D::SourceFormatRA8,   GraphicsContext3D::SourceFormatRA16Little,   GraphicsContext3D::SourceFormatRA16Big },
     79         { GraphicsContext3D::SourceFormatRA8,   GraphicsContext3D::SourceFormatAR8,   GraphicsContext3D::SourceFormatAR16Little,   GraphicsContext3D::SourceFormatAR16Big },
     80         { GraphicsContext3D::SourceFormatBGR8,  GraphicsContext3D::SourceFormatRGB8,  GraphicsContext3D::SourceFormatRGB16Little,  GraphicsContext3D::SourceFormatRGB16Big },
     81         { GraphicsContext3D::SourceFormatABGR8, GraphicsContext3D::SourceFormatRGBA8, GraphicsContext3D::SourceFormatRGBA16Little, GraphicsContext3D::SourceFormatRGBA16Big },
     82         { GraphicsContext3D::SourceFormatBGRA8, GraphicsContext3D::SourceFormatARGB8, GraphicsContext3D::SourceFormatARGB16Little, GraphicsContext3D::SourceFormatARGB16Big }
     83     };
     84 
     85     ASSERT(componentsPerPixel <= 4 && componentsPerPixel > 0);
     86     SourceDataFormatBase formatBase = formatTableBase[componentsPerPixel - 1][alphaFormat];
     87     if (formatBase == SourceFormatBaseNumFormats)
     88         return GraphicsContext3D::SourceFormatNumFormats;
     89     return formatTable[formatBase][(is16BitFormat ? 2 : 0) + (bigEndian ? 1 : 0)];
     90 }
     91 
     92 bool GraphicsContext3D::getImageData(Image* image,
     93                                      GC3Denum format,
     94                                      GC3Denum type,
     95                                      bool premultiplyAlpha,
     96                                      bool ignoreGammaAndColorProfile,
     97                                      Vector<uint8_t>& outputVector)
     98 {
     99     if (!image)
    100         return false;
    101     CGImageRef cgImage;
    102     RetainPtr<CGImageRef> decodedImage;
    103     bool hasAlpha = image->isBitmapImage() ? static_cast<BitmapImage*>(image)->frameHasAlphaAtIndex(0) : true;
    104     if ((ignoreGammaAndColorProfile || (hasAlpha && !premultiplyAlpha)) && image->data()) {
    105         ImageSource decoder(ImageSource::AlphaNotPremultiplied,
    106                             ignoreGammaAndColorProfile ? ImageSource::GammaAndColorProfileIgnored : ImageSource::GammaAndColorProfileApplied);
    107         decoder.setData(image->data(), true);
    108         if (!decoder.frameCount())
    109             return false;
    110         decodedImage.adoptCF(decoder.createFrameAtIndex(0));
    111         cgImage = decodedImage.get();
    112     } else
    113         cgImage = image->nativeImageForCurrentFrame();
    114     if (!cgImage)
    115         return false;
    116 
    117     size_t width = CGImageGetWidth(cgImage);
    118     size_t height = CGImageGetHeight(cgImage);
    119     if (!width || !height)
    120         return false;
    121 
    122     // See whether the image is using an indexed color space, and if
    123     // so, re-render it into an RGB color space. The image re-packing
    124     // code requires color data, not color table indices, for the
    125     // image data.
    126     CGColorSpaceRef colorSpace = CGImageGetColorSpace(cgImage);
    127     CGColorSpaceModel model = CGColorSpaceGetModel(colorSpace);
    128     if (model == kCGColorSpaceModelIndexed) {
    129         RetainPtr<CGContextRef> bitmapContext;
    130         // FIXME: we should probably manually convert the image by indexing into
    131         // the color table, which would allow us to avoid premultiplying the
    132         // alpha channel. Creation of a bitmap context with an alpha channel
    133         // doesn't seem to work unless it's premultiplied.
    134         bitmapContext.adoptCF(CGBitmapContextCreate(0, width, height, 8, width * 4,
    135                                                     deviceRGBColorSpaceRef(),
    136                                                     kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host));
    137         if (!bitmapContext)
    138             return false;
    139 
    140         CGContextSetBlendMode(bitmapContext.get(), kCGBlendModeCopy);
    141         CGContextSetInterpolationQuality(bitmapContext.get(), kCGInterpolationNone);
    142         CGContextDrawImage(bitmapContext.get(), CGRectMake(0, 0, width, height), cgImage);
    143 
    144         // Now discard the original CG image and replace it with a copy from the bitmap context.
    145         decodedImage.adoptCF(CGBitmapContextCreateImage(bitmapContext.get()));
    146         cgImage = decodedImage.get();
    147     }
    148 
    149     size_t bitsPerComponent = CGImageGetBitsPerComponent(cgImage);
    150     size_t bitsPerPixel = CGImageGetBitsPerPixel(cgImage);
    151     if (bitsPerComponent != 8 && bitsPerComponent != 16)
    152         return false;
    153     if (bitsPerPixel % bitsPerComponent)
    154         return false;
    155     size_t componentsPerPixel = bitsPerPixel / bitsPerComponent;
    156 
    157     CGBitmapInfo bitInfo = CGImageGetBitmapInfo(cgImage);
    158     bool bigEndianSource = false;
    159     // These could technically be combined into one large switch
    160     // statement, but we prefer not to so that we fail fast if we
    161     // encounter an unexpected image configuration.
    162     if (bitsPerComponent == 16) {
    163         switch (bitInfo & kCGBitmapByteOrderMask) {
    164         case kCGBitmapByteOrder16Big:
    165             bigEndianSource = true;
    166             break;
    167         case kCGBitmapByteOrder16Little:
    168             bigEndianSource = false;
    169             break;
    170         case kCGBitmapByteOrderDefault:
    171             // This is a bug in earlier version of cg where the default endian
    172             // is little whereas the decoded 16-bit png image data is actually
    173             // Big. Later version (10.6.4) no longer returns ByteOrderDefault.
    174             bigEndianSource = true;
    175             break;
    176         default:
    177             return false;
    178         }
    179     } else {
    180         switch (bitInfo & kCGBitmapByteOrderMask) {
    181         case kCGBitmapByteOrder32Big:
    182             bigEndianSource = true;
    183             break;
    184         case kCGBitmapByteOrder32Little:
    185             bigEndianSource = false;
    186             break;
    187         case kCGBitmapByteOrderDefault:
    188             // It appears that the default byte order is actually big
    189             // endian even on little endian architectures.
    190             bigEndianSource = true;
    191             break;
    192         default:
    193             return false;
    194         }
    195     }
    196 
    197     AlphaOp neededAlphaOp = AlphaDoNothing;
    198     AlphaFormat alphaFormat = AlphaFormatNone;
    199     switch (CGImageGetAlphaInfo(cgImage)) {
    200     case kCGImageAlphaPremultipliedFirst:
    201         if (!premultiplyAlpha)
    202             neededAlphaOp = AlphaDoUnmultiply;
    203         alphaFormat = AlphaFormatFirst;
    204         break;
    205     case kCGImageAlphaFirst:
    206         // This path is only accessible for MacOS earlier than 10.6.4.
    207         if (premultiplyAlpha)
    208             neededAlphaOp = AlphaDoPremultiply;
    209         alphaFormat = AlphaFormatFirst;
    210         break;
    211     case kCGImageAlphaNoneSkipFirst:
    212         // This path is only accessible for MacOS earlier than 10.6.4.
    213         alphaFormat = AlphaFormatFirst;
    214         break;
    215     case kCGImageAlphaPremultipliedLast:
    216         if (!premultiplyAlpha)
    217             neededAlphaOp = AlphaDoUnmultiply;
    218         alphaFormat = AlphaFormatLast;
    219         break;
    220     case kCGImageAlphaLast:
    221         if (premultiplyAlpha)
    222             neededAlphaOp = AlphaDoPremultiply;
    223         alphaFormat = AlphaFormatLast;
    224         break;
    225     case kCGImageAlphaNoneSkipLast:
    226         alphaFormat = AlphaFormatLast;
    227         break;
    228     case kCGImageAlphaNone:
    229         alphaFormat = AlphaFormatNone;
    230         break;
    231     default:
    232         return false;
    233     }
    234     SourceDataFormat srcDataFormat = getSourceDataFormat(componentsPerPixel, alphaFormat, bitsPerComponent == 16, bigEndianSource);
    235     if (srcDataFormat == SourceFormatNumFormats)
    236         return false;
    237 
    238     RetainPtr<CFDataRef> pixelData;
    239     pixelData.adoptCF(CGDataProviderCopyData(CGImageGetDataProvider(cgImage)));
    240     if (!pixelData)
    241         return false;
    242     const UInt8* rgba = CFDataGetBytePtr(pixelData.get());
    243     outputVector.resize(width * height * 4);
    244     unsigned int srcUnpackAlignment = 0;
    245     size_t bytesPerRow = CGImageGetBytesPerRow(cgImage);
    246     unsigned int padding = bytesPerRow - bitsPerPixel / 8 * width;
    247     if (padding) {
    248         srcUnpackAlignment = padding + 1;
    249         while (bytesPerRow % srcUnpackAlignment)
    250             ++srcUnpackAlignment;
    251     }
    252     bool rt = packPixels(rgba, srcDataFormat, width, height, srcUnpackAlignment,
    253                          format, type, neededAlphaOp, outputVector.data());
    254     return rt;
    255 }
    256 
    257 void GraphicsContext3D::paintToCanvas(const unsigned char* imagePixels, int imageWidth, int imageHeight, int canvasWidth, int canvasHeight, CGContextRef context)
    258 {
    259     if (!imagePixels || imageWidth <= 0 || imageHeight <= 0 || canvasWidth <= 0 || canvasHeight <= 0 || !context)
    260         return;
    261     int rowBytes = imageWidth * 4;
    262     RetainPtr<CGDataProviderRef> dataProvider(AdoptCF, CGDataProviderCreateWithData(0, imagePixels, rowBytes * imageHeight, 0));
    263     RetainPtr<CGImageRef> cgImage(AdoptCF, CGImageCreate(imageWidth, imageHeight, 8, 32, rowBytes, deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host,
    264         dataProvider.get(), 0, false, kCGRenderingIntentDefault));
    265     // CSS styling may cause the canvas's content to be resized on
    266     // the page. Go back to the Canvas to figure out the correct
    267     // width and height to draw.
    268     CGRect rect = CGRectMake(0, 0, canvasWidth, canvasHeight);
    269     // We want to completely overwrite the previous frame's
    270     // rendering results.
    271     CGContextSaveGState(context);
    272     CGContextSetBlendMode(context, kCGBlendModeCopy);
    273     CGContextSetInterpolationQuality(context, kCGInterpolationNone);
    274     CGContextDrawImage(context, rect, cgImage.get());
    275     CGContextRestoreGState(context);
    276 }
    277 
    278 } // namespace WebCore
    279 
    280 #endif // ENABLE(WEBGL)
    281