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