• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2008 Apple Inc. All rights reserved.
4  * Copyright (C) 2010 Torch Mobile (Beijing) Co. Ltd. All rights reserved.
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  * 1. Redistributions of source code must retain the above copyright
10  *    notice, this list of conditions and the following disclaimer.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  *
15  * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
16  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE COMPUTER, INC. OR
19  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  */
27 
28 #include "config.h"
29 #include "ImageBuffer.h"
30 
31 #include "Base64.h"
32 #include "BitmapImage.h"
33 #include "GraphicsContext.h"
34 #include "GraphicsContextCG.h"
35 #include "ImageData.h"
36 #include "MIMETypeRegistry.h"
37 #include <ApplicationServices/ApplicationServices.h>
38 #include <wtf/Assertions.h>
39 #include <wtf/text/StringConcatenate.h>
40 #include <wtf/OwnArrayPtr.h>
41 #include <wtf/RetainPtr.h>
42 #include <wtf/Threading.h>
43 #include <math.h>
44 
45 #if PLATFORM(MAC) || PLATFORM(CHROMIUM)
46 #include "WebCoreSystemInterface.h"
47 #endif
48 
49 using namespace std;
50 
51 namespace WebCore {
52 
53 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
54 static const int maxIOSurfaceDimension = 4096;
55 static const int minIOSurfaceArea = 50 * 100;
56 
createIOSurface(const IntSize & size)57 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
58 {
59     unsigned pixelFormat = 'BGRA';
60     unsigned bytesPerElement = 4;
61     int width = size.width();
62     int height = size.height();
63 
64     unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
65     if (!bytesPerRow)
66         return 0;
67 
68     unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
69     if (!allocSize)
70         return 0;
71 
72     const void *keys[6];
73     const void *values[6];
74     keys[0] = kIOSurfaceWidth;
75     values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
76     keys[1] = kIOSurfaceHeight;
77     values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
78     keys[2] = kIOSurfacePixelFormat;
79     values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
80     keys[3] = kIOSurfaceBytesPerElement;
81     values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
82     keys[4] = kIOSurfaceBytesPerRow;
83     values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
84     keys[5] = kIOSurfaceAllocSize;
85     values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
86 
87     RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
88     for (unsigned i = 0; i < 6; i++)
89         CFRelease(values[i]);
90 
91     return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
92 }
93 #endif
94 
releaseImageData(void *,const void * data,size_t)95 static void releaseImageData(void*, const void* data, size_t)
96 {
97     fastFree(const_cast<void*>(data));
98 }
99 
ImageBuffer(const IntSize & size,ColorSpace imageColorSpace,RenderingMode renderingMode,bool & success)100 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
101     : m_data(size)
102     , m_size(size)
103     , m_accelerateRendering(renderingMode == Accelerated)
104 {
105     success = false;  // Make early return mean failure.
106     if (size.width() < 0 || size.height() < 0)
107         return;
108 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
109     if (size.width() >= maxIOSurfaceDimension || size.height() >= maxIOSurfaceDimension || size.width() * size.height() < minIOSurfaceArea)
110         m_accelerateRendering = false;
111 #else
112     ASSERT(renderingMode == Unaccelerated);
113 #endif
114 
115     unsigned bytesPerRow = size.width();
116     if (bytesPerRow > 0x3FFFFFFF) // Protect against overflow
117         return;
118     bytesPerRow *= 4;
119     m_data.m_bytesPerRow = bytesPerRow;
120     size_t dataSize = size.height() * bytesPerRow;
121 
122     switch (imageColorSpace) {
123     case ColorSpaceDeviceRGB:
124         m_data.m_colorSpace = deviceRGBColorSpaceRef();
125         break;
126     case ColorSpaceSRGB:
127         m_data.m_colorSpace = sRGBColorSpaceRef();
128         break;
129     case ColorSpaceLinearRGB:
130         m_data.m_colorSpace = linearRGBColorSpaceRef();
131         break;
132     }
133 
134     RetainPtr<CGContextRef> cgContext;
135     if (!m_accelerateRendering) {
136         if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
137             return;
138         ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
139 
140         m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
141         cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
142         // Create a live image that wraps the data.
143         m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
144     } else {
145 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
146         m_data.m_surface = createIOSurface(size);
147         cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
148 #else
149         m_accelerateRendering = false; // Force to false on older platforms
150 #endif
151     }
152 
153     if (!cgContext)
154         return;
155 
156     m_context.set(new GraphicsContext(cgContext.get()));
157     m_context->scale(FloatSize(1, -1));
158     m_context->translate(0, -size.height());
159     success = true;
160 }
161 
~ImageBuffer()162 ImageBuffer::~ImageBuffer()
163 {
164 }
165 
dataSize() const166 size_t ImageBuffer::dataSize() const
167 {
168     return m_size.height() * m_data.m_bytesPerRow;
169 }
170 
context() const171 GraphicsContext* ImageBuffer::context() const
172 {
173     return m_context.get();
174 }
175 
drawsUsingCopy() const176 bool ImageBuffer::drawsUsingCopy() const
177 {
178     return false;
179 }
180 
copyImage() const181 PassRefPtr<Image> ImageBuffer::copyImage() const
182 {
183     // BitmapImage will release the passed in CGImage on destruction
184     CGImageRef ctxImage = 0;
185     if (!m_accelerateRendering)
186         ctxImage = CGBitmapContextCreateImage(context()->platformContext());
187 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
188     else
189         ctxImage = wkIOSurfaceContextCreateImage(context()->platformContext());
190 #endif
191     return BitmapImage::create(ctxImage);
192 }
193 
cgImage(const IntSize & size,const ImageBufferData & data)194 static CGImageRef cgImage(const IntSize& size, const ImageBufferData& data)
195 {
196     return CGImageCreate(size.width(), size.height(), 8, 32, data.m_bytesPerRow,
197                          data.m_colorSpace, data.m_bitmapInfo, data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
198 }
199 
draw(GraphicsContext * destContext,ColorSpace styleColorSpace,const FloatRect & destRect,const FloatRect & srcRect,CompositeOperator op,bool useLowQualityScale)200 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
201                        CompositeOperator op, bool useLowQualityScale)
202 {
203     if (!m_accelerateRendering) {
204         if (destContext == context()) {
205             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
206             RefPtr<Image> copy = copyImage();
207             destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, useLowQualityScale);
208         } else {
209             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
210             destContext->drawImage(imageForRendering.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
211         }
212     } else {
213         RefPtr<Image> copy = copyImage();
214         ColorSpace colorSpace = (destContext == context()) ? ColorSpaceDeviceRGB : styleColorSpace;
215         destContext->drawImage(copy.get(), colorSpace, destRect, srcRect, op, useLowQualityScale);
216     }
217 }
218 
drawPattern(GraphicsContext * destContext,const FloatRect & srcRect,const AffineTransform & patternTransform,const FloatPoint & phase,ColorSpace styleColorSpace,CompositeOperator op,const FloatRect & destRect)219 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
220                               const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
221 {
222     if (!m_accelerateRendering) {
223         if (destContext == context()) {
224             // We're drawing into our own buffer.  In order for this to work, we need to copy the source buffer first.
225             RefPtr<Image> copy = copyImage();
226             copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
227         } else {
228             RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
229             imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
230         }
231     } else {
232         RefPtr<Image> copy = copyImage();
233         copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
234     }
235 }
236 
clip(GraphicsContext * contextToClip,const FloatRect & rect) const237 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
238 {
239     CGContextRef platformContextToClip = contextToClip->platformContext();
240     RetainPtr<CGImageRef> image;
241     if (!m_accelerateRendering)
242         image.adoptCF(cgImage(m_size, m_data));
243 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
244     else
245         image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
246 #endif
247     CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
248     CGContextScaleCTM(platformContextToClip, 1, -1);
249     CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
250     CGContextScaleCTM(platformContextToClip, 1, -1);
251     CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
252 }
253 
getUnmultipliedImageData(const IntRect & rect) const254 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
255 {
256     if (m_accelerateRendering)
257         CGContextFlush(context()->platformContext());
258     return m_data.getData(rect, m_size, m_accelerateRendering, true);
259 }
260 
getPremultipliedImageData(const IntRect & rect) const261 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
262 {
263     if (m_accelerateRendering)
264         CGContextFlush(context()->platformContext());
265     return m_data.getData(rect, m_size, m_accelerateRendering, false);
266 }
267 
putUnmultipliedImageData(ByteArray * source,const IntSize & sourceSize,const IntRect & sourceRect,const IntPoint & destPoint)268 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
269 {
270     if (m_accelerateRendering)
271         CGContextFlush(context()->platformContext());
272     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, true);
273 }
274 
putPremultipliedImageData(ByteArray * source,const IntSize & sourceSize,const IntRect & sourceRect,const IntPoint & destPoint)275 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
276 {
277     if (m_accelerateRendering)
278         CGContextFlush(context()->platformContext());
279     m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, false);
280 }
281 
jpegUTI()282 static inline CFStringRef jpegUTI()
283 {
284 #if PLATFORM(WIN)
285     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
286 #endif
287     return kUTTypeJPEG;
288 }
289 
utiFromMIMEType(const String & mimeType)290 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
291 {
292 #if PLATFORM(MAC)
293     RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
294     return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
295 #else
296     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
297 
298     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
299     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
300     static const CFStringRef kUTTypePNG = CFSTR("public.png");
301     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
302 
303     if (equalIgnoringCase(mimeType, "image/png"))
304         return kUTTypePNG;
305     if (equalIgnoringCase(mimeType, "image/jpeg"))
306         return jpegUTI();
307     if (equalIgnoringCase(mimeType, "image/gif"))
308         return kUTTypeGIF;
309 
310     ASSERT_NOT_REACHED();
311     return kUTTypePNG;
312 #endif
313 }
314 
CGImageToDataURL(CGImageRef image,const String & mimeType,const double * quality)315 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
316 {
317     RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
318     if (!data)
319         return "data:,";
320 
321     RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
322     ASSERT(uti);
323 
324     RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
325     if (!destination)
326         return "data:,";
327 
328     RetainPtr<CFDictionaryRef> imageProperties = 0;
329     if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
330         // Apply the compression quality to the image destination.
331         RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
332         const void* key = kCGImageDestinationLossyCompressionQuality;
333         const void* value = compressionQuality.get();
334         imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
335     }
336 
337     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
338     CGImageDestinationFinalize(destination.get());
339 
340     Vector<char> out;
341     base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
342 
343     return makeString("data:", mimeType, ";base64,", out);
344 }
345 
toDataURL(const String & mimeType,const double * quality) const346 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
347 {
348     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
349 
350     RetainPtr<CGImageRef> image;
351     if (!m_accelerateRendering)
352         image.adoptCF(CGBitmapContextCreateImage(context()->platformContext()));
353 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
354     else
355         image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
356 #endif
357 
358     if (!image)
359         return "data:,";
360 
361     return CGImageToDataURL(image.get(), mimeType, quality);
362 }
363 
ImageDataToDataURL(const ImageData & source,const String & mimeType,const double * quality)364 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
365 {
366     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
367 
368     RetainPtr<CGImageRef> image;
369     RetainPtr<CGDataProviderRef> dataProvider;
370 
371     dataProvider.adoptCF(CGDataProviderCreateWithData(0, source.data()->data()->data(),
372                                                       4 * source.width() * source.height(), 0));
373 
374     if (!dataProvider)
375         return "data:,";
376 
377     image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
378                                 CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
379                                 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
380 
381 
382     if (!image)
383         return "data:,";
384 
385     return CGImageToDataURL(image.get(), mimeType, quality);
386 }
387 } // namespace WebCore
388