1 /* 2 * Copyright (C) 2006 Nikolas Zimmermann <zimmermann (at) 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 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 95 static void releaseImageData(void*, const void* data, size_t) 96 { 97 fastFree(const_cast<void*>(data)); 98 } 99 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 162 ImageBuffer::~ImageBuffer() 163 { 164 } 165 166 size_t ImageBuffer::dataSize() const 167 { 168 return m_size.height() * m_data.m_bytesPerRow; 169 } 170 171 GraphicsContext* ImageBuffer::context() const 172 { 173 return m_context.get(); 174 } 175 176 bool ImageBuffer::drawsUsingCopy() const 177 { 178 return false; 179 } 180 181 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 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 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 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 237 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 254 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 261 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 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 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 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 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 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 346 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 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