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 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
50 #include <IOSurface/IOSurface.h>
51 #endif
52
53 using namespace std;
54
55 namespace WebCore {
56
57 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
58 static const int maxIOSurfaceDimension = 4096;
59 static const int minIOSurfaceArea = 50 * 100;
60
createIOSurface(const IntSize & size)61 static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size)
62 {
63 unsigned pixelFormat = 'BGRA';
64 unsigned bytesPerElement = 4;
65 int width = size.width();
66 int height = size.height();
67
68 unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement);
69 if (!bytesPerRow)
70 return 0;
71
72 unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow);
73 if (!allocSize)
74 return 0;
75
76 const void *keys[6];
77 const void *values[6];
78 keys[0] = kIOSurfaceWidth;
79 values[0] = CFNumberCreate(0, kCFNumberIntType, &width);
80 keys[1] = kIOSurfaceHeight;
81 values[1] = CFNumberCreate(0, kCFNumberIntType, &height);
82 keys[2] = kIOSurfacePixelFormat;
83 values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat);
84 keys[3] = kIOSurfaceBytesPerElement;
85 values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement);
86 keys[4] = kIOSurfaceBytesPerRow;
87 values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow);
88 keys[5] = kIOSurfaceAllocSize;
89 values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize);
90
91 RetainPtr<CFDictionaryRef> dict(AdoptCF, CFDictionaryCreate(0, keys, values, 6, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
92 for (unsigned i = 0; i < 6; i++)
93 CFRelease(values[i]);
94
95 return RetainPtr<IOSurfaceRef>(AdoptCF, IOSurfaceCreate(dict.get()));
96 }
97 #endif
98
releaseImageData(void *,const void * data,size_t)99 static void releaseImageData(void*, const void* data, size_t)
100 {
101 fastFree(const_cast<void*>(data));
102 }
103
ImageBuffer(const IntSize & size,ColorSpace imageColorSpace,RenderingMode renderingMode,bool & success)104 ImageBuffer::ImageBuffer(const IntSize& size, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
105 : m_data(size)
106 , m_size(size)
107 , m_accelerateRendering(renderingMode == Accelerated)
108 {
109 success = false; // Make early return mean failure.
110 if (size.width() < 0 || size.height() < 0)
111 return;
112 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
113 if (size.width() >= maxIOSurfaceDimension || size.height() >= maxIOSurfaceDimension || size.width() * size.height() < minIOSurfaceArea)
114 m_accelerateRendering = false;
115 #else
116 ASSERT(renderingMode == Unaccelerated);
117 #endif
118
119 unsigned bytesPerRow = size.width();
120 if (bytesPerRow > 0x3FFFFFFF) // Protect against overflow
121 return;
122 bytesPerRow *= 4;
123 m_data.m_bytesPerRow = bytesPerRow;
124 size_t dataSize = size.height() * bytesPerRow;
125
126 switch (imageColorSpace) {
127 case ColorSpaceDeviceRGB:
128 m_data.m_colorSpace = deviceRGBColorSpaceRef();
129 break;
130 case ColorSpaceSRGB:
131 m_data.m_colorSpace = sRGBColorSpaceRef();
132 break;
133 case ColorSpaceLinearRGB:
134 m_data.m_colorSpace = linearRGBColorSpaceRef();
135 break;
136 }
137
138 RetainPtr<CGContextRef> cgContext;
139 if (!m_accelerateRendering) {
140 if (!tryFastCalloc(size.height(), bytesPerRow).getValue(m_data.m_data))
141 return;
142 ASSERT(!(reinterpret_cast<size_t>(m_data.m_data) & 2));
143
144 m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast;
145 cgContext.adoptCF(CGBitmapContextCreate(m_data.m_data, size.width(), size.height(), 8, bytesPerRow, m_data.m_colorSpace, m_data.m_bitmapInfo));
146 // Create a live image that wraps the data.
147 m_data.m_dataProvider.adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, dataSize, releaseImageData));
148 } else {
149 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
150 m_data.m_surface = createIOSurface(size);
151 cgContext.adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), size.width(), size.height(), m_data.m_colorSpace));
152 #else
153 m_accelerateRendering = false; // Force to false on older platforms
154 #endif
155 }
156
157 if (!cgContext)
158 return;
159
160 m_context= adoptPtr(new GraphicsContext(cgContext.get()));
161 m_context->scale(FloatSize(1, -1));
162 m_context->translate(0, -size.height());
163 success = true;
164 }
165
~ImageBuffer()166 ImageBuffer::~ImageBuffer()
167 {
168 }
169
dataSize() const170 size_t ImageBuffer::dataSize() const
171 {
172 return m_size.height() * m_data.m_bytesPerRow;
173 }
174
context() const175 GraphicsContext* ImageBuffer::context() const
176 {
177 return m_context.get();
178 }
179
drawsUsingCopy() const180 bool ImageBuffer::drawsUsingCopy() const
181 {
182 return false;
183 }
184
copyImage() const185 PassRefPtr<Image> ImageBuffer::copyImage() const
186 {
187 // BitmapImage will release the passed in CGImage on destruction
188 CGImageRef ctxImage = 0;
189 if (!m_accelerateRendering)
190 ctxImage = CGBitmapContextCreateImage(context()->platformContext());
191 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
192 else
193 ctxImage = wkIOSurfaceContextCreateImage(context()->platformContext());
194 #endif
195 return BitmapImage::create(ctxImage);
196 }
197
cgImage(const IntSize & size,const ImageBufferData & data)198 static CGImageRef cgImage(const IntSize& size, const ImageBufferData& data)
199 {
200 return CGImageCreate(size.width(), size.height(), 8, 32, data.m_bytesPerRow,
201 data.m_colorSpace, data.m_bitmapInfo, data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault);
202 }
203
draw(GraphicsContext * destContext,ColorSpace styleColorSpace,const FloatRect & destRect,const FloatRect & srcRect,CompositeOperator op,bool useLowQualityScale)204 void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect,
205 CompositeOperator op, bool useLowQualityScale)
206 {
207 if (!m_accelerateRendering) {
208 if (destContext == context()) {
209 // We're drawing into our own buffer. In order for this to work, we need to copy the source buffer first.
210 RefPtr<Image> copy = copyImage();
211 destContext->drawImage(copy.get(), ColorSpaceDeviceRGB, destRect, srcRect, op, useLowQualityScale);
212 } else {
213 RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
214 destContext->drawImage(imageForRendering.get(), styleColorSpace, destRect, srcRect, op, useLowQualityScale);
215 }
216 } else {
217 RefPtr<Image> copy = copyImage();
218 ColorSpace colorSpace = (destContext == context()) ? ColorSpaceDeviceRGB : styleColorSpace;
219 destContext->drawImage(copy.get(), colorSpace, destRect, srcRect, op, useLowQualityScale);
220 }
221 }
222
drawPattern(GraphicsContext * destContext,const FloatRect & srcRect,const AffineTransform & patternTransform,const FloatPoint & phase,ColorSpace styleColorSpace,CompositeOperator op,const FloatRect & destRect)223 void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform,
224 const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect)
225 {
226 if (!m_accelerateRendering) {
227 if (destContext == context()) {
228 // We're drawing into our own buffer. In order for this to work, we need to copy the source buffer first.
229 RefPtr<Image> copy = copyImage();
230 copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
231 } else {
232 RefPtr<Image> imageForRendering = BitmapImage::create(cgImage(m_size, m_data));
233 imageForRendering->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
234 }
235 } else {
236 RefPtr<Image> copy = copyImage();
237 copy->drawPattern(destContext, srcRect, patternTransform, phase, styleColorSpace, op, destRect);
238 }
239 }
240
clip(GraphicsContext * contextToClip,const FloatRect & rect) const241 void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const
242 {
243 CGContextRef platformContextToClip = contextToClip->platformContext();
244 RetainPtr<CGImageRef> image;
245 if (!m_accelerateRendering)
246 image.adoptCF(cgImage(m_size, m_data));
247 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
248 else
249 image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
250 #endif
251 CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + rect.height());
252 CGContextScaleCTM(platformContextToClip, 1, -1);
253 CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), rect.size()), image.get());
254 CGContextScaleCTM(platformContextToClip, 1, -1);
255 CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height());
256 }
257
getUnmultipliedImageData(const IntRect & rect) const258 PassRefPtr<ByteArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect) const
259 {
260 if (m_accelerateRendering)
261 CGContextFlush(context()->platformContext());
262 return m_data.getData(rect, m_size, m_accelerateRendering, true);
263 }
264
getPremultipliedImageData(const IntRect & rect) const265 PassRefPtr<ByteArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect) const
266 {
267 if (m_accelerateRendering)
268 CGContextFlush(context()->platformContext());
269 return m_data.getData(rect, m_size, m_accelerateRendering, false);
270 }
271
putUnmultipliedImageData(ByteArray * source,const IntSize & sourceSize,const IntRect & sourceRect,const IntPoint & destPoint)272 void ImageBuffer::putUnmultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
273 {
274 if (m_accelerateRendering)
275 CGContextFlush(context()->platformContext());
276 m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, true);
277 }
278
putPremultipliedImageData(ByteArray * source,const IntSize & sourceSize,const IntRect & sourceRect,const IntPoint & destPoint)279 void ImageBuffer::putPremultipliedImageData(ByteArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint)
280 {
281 if (m_accelerateRendering)
282 CGContextFlush(context()->platformContext());
283 m_data.putData(source, sourceSize, sourceRect, destPoint, m_size, m_accelerateRendering, false);
284 }
285
jpegUTI()286 static inline CFStringRef jpegUTI()
287 {
288 #if PLATFORM(WIN)
289 static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
290 #endif
291 return kUTTypeJPEG;
292 }
293
utiFromMIMEType(const String & mimeType)294 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
295 {
296 #if PLATFORM(MAC)
297 RetainPtr<CFStringRef> mimeTypeCFString(AdoptCF, mimeType.createCFString());
298 return RetainPtr<CFStringRef>(AdoptCF, UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeTypeCFString.get(), 0));
299 #else
300 ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
301
302 // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
303 // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
304 static const CFStringRef kUTTypePNG = CFSTR("public.png");
305 static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
306
307 if (equalIgnoringCase(mimeType, "image/png"))
308 return kUTTypePNG;
309 if (equalIgnoringCase(mimeType, "image/jpeg"))
310 return jpegUTI();
311 if (equalIgnoringCase(mimeType, "image/gif"))
312 return kUTTypeGIF;
313
314 ASSERT_NOT_REACHED();
315 return kUTTypePNG;
316 #endif
317 }
318
CGImageToDataURL(CGImageRef image,const String & mimeType,const double * quality)319 static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality)
320 {
321 RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0));
322 if (!data)
323 return "data:,";
324
325 RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType);
326 ASSERT(uti);
327
328 RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0));
329 if (!destination)
330 return "data:,";
331
332 RetainPtr<CFDictionaryRef> imageProperties = 0;
333 if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
334 // Apply the compression quality to the image destination.
335 RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality));
336 const void* key = kCGImageDestinationLossyCompressionQuality;
337 const void* value = compressionQuality.get();
338 imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
339 }
340
341 CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
342 CGImageDestinationFinalize(destination.get());
343
344 Vector<char> out;
345 base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out);
346
347 return makeString("data:", mimeType, ";base64,", out);
348 }
349
toDataURL(const String & mimeType,const double * quality) const350 String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const
351 {
352 ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
353
354 RetainPtr<CGImageRef> image;
355 if (!m_accelerateRendering)
356 image.adoptCF(CGBitmapContextCreateImage(context()->platformContext()));
357 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
358 else
359 image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext()));
360 #endif
361
362 if (!image)
363 return "data:,";
364
365 return CGImageToDataURL(image.get(), mimeType, quality);
366 }
367
ImageDataToDataURL(const ImageData & source,const String & mimeType,const double * quality)368 String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality)
369 {
370 ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
371
372 RetainPtr<CGImageRef> image;
373 RetainPtr<CGDataProviderRef> dataProvider;
374
375 dataProvider.adoptCF(CGDataProviderCreateWithData(0, source.data()->data()->data(),
376 4 * source.width() * source.height(), 0));
377
378 if (!dataProvider)
379 return "data:,";
380
381 image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(),
382 CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast,
383 dataProvider.get(), 0, false, kCGRenderingIntentDefault));
384
385
386 if (!image)
387 return "data:,";
388
389 return CGImageToDataURL(image.get(), mimeType, quality);
390 }
391 } // namespace WebCore
392