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
44namespace WebCore {
45
46enum SourceDataFormatBase {
47    SourceFormatBaseR = 0,
48    SourceFormatBaseA,
49    SourceFormatBaseRA,
50    SourceFormatBaseAR,
51    SourceFormatBaseRGB,
52    SourceFormatBaseRGBA,
53    SourceFormatBaseARGB,
54    SourceFormatBaseNumFormats
55};
56
57enum AlphaFormat {
58    AlphaFormatNone = 0,
59    AlphaFormatFirst,
60    AlphaFormatLast,
61    AlphaFormatNumFormats
62};
63
64// This returns SourceFormatNumFormats if the combination of input parameters is unsupported.
65static 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
92bool 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
257void 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