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