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.
getSourceDataFormat(unsigned int componentsPerPixel,AlphaFormat alphaFormat,bool is16BitFormat,bool bigEndian)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 
getImageData(Image * image,GC3Denum format,GC3Denum type,bool premultiplyAlpha,bool ignoreGammaAndColorProfile,Vector<uint8_t> & outputVector)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 
paintToCanvas(const unsigned char * imagePixels,int imageWidth,int imageHeight,int canvasWidth,int canvasHeight,CGContextRef context)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