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 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 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 "BitmapImage.h" 32#include "GraphicsContext.h" 33#include "GraphicsContextCG.h" 34#include "ImageData.h" 35#include "IntRect.h" 36#include "MIMETypeRegistry.h" 37#include <math.h> 38#include <CoreGraphics/CoreGraphics.h> 39#include <ImageIO/ImageIO.h> 40#include <wtf/Assertions.h> 41#include <wtf/CheckedArithmetic.h> 42#include <wtf/MainThread.h> 43#include <wtf/RetainPtr.h> 44#include <wtf/text/Base64.h> 45#include <wtf/text/WTFString.h> 46#if PLATFORM(COCOA) 47#include "WebCoreSystemInterface.h" 48#endif 49 50#if USE(IOSURFACE_CANVAS_BACKING_STORE) 51#include "IOSurface.h" 52#include <IOSurface/IOSurface.h> 53#endif 54 55// CA uses ARGB32 for textures and ARGB32 -> ARGB32 resampling is optimized. 56#define USE_ARGB32 PLATFORM(IOS) 57 58namespace WebCore { 59 60#if USE(IOSURFACE_CANVAS_BACKING_STORE) 61 62// FIXME: Adopt WebCore::IOSurface. 63static RetainPtr<IOSurfaceRef> createIOSurface(const IntSize& size) 64{ 65 unsigned pixelFormat = 'BGRA'; 66 unsigned bytesPerElement = 4; 67 int width = size.width(); 68 int height = size.height(); 69 70 unsigned long bytesPerRow = IOSurfaceAlignProperty(kIOSurfaceBytesPerRow, size.width() * bytesPerElement); 71 if (!bytesPerRow) 72 return 0; 73 74 unsigned long allocSize = IOSurfaceAlignProperty(kIOSurfaceAllocSize, size.height() * bytesPerRow); 75 if (!allocSize) 76 return 0; 77 78#if !PLATFORM(IOS) 79 const void* keys[6]; 80 const void* values[6]; 81#else 82 const void* keys[7]; 83 const void* values[7]; 84#endif 85 keys[0] = kIOSurfaceWidth; 86 values[0] = CFNumberCreate(0, kCFNumberIntType, &width); 87 keys[1] = kIOSurfaceHeight; 88 values[1] = CFNumberCreate(0, kCFNumberIntType, &height); 89 keys[2] = kIOSurfacePixelFormat; 90 values[2] = CFNumberCreate(0, kCFNumberIntType, &pixelFormat); 91 keys[3] = kIOSurfaceBytesPerElement; 92 values[3] = CFNumberCreate(0, kCFNumberIntType, &bytesPerElement); 93 keys[4] = kIOSurfaceBytesPerRow; 94 values[4] = CFNumberCreate(0, kCFNumberLongType, &bytesPerRow); 95 keys[5] = kIOSurfaceAllocSize; 96 values[5] = CFNumberCreate(0, kCFNumberLongType, &allocSize); 97#if PLATFORM(IOS) 98 keys[6] = kIOSurfaceCacheMode; 99 int cacheMode = kIOMapWriteCombineCache; 100 values[6] = CFNumberCreate(0, kCFNumberIntType, &cacheMode); 101#endif 102 103 RetainPtr<CFDictionaryRef> dict = adoptCF(CFDictionaryCreate(0, keys, values, WTF_ARRAY_LENGTH(values), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 104 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(values); ++i) 105 CFRelease(values[i]); 106 107 return adoptCF(IOSurfaceCreate(dict.get())); 108} 109#endif 110 111static void releaseImageData(void*, const void* data, size_t) 112{ 113 fastFree(const_cast<void*>(data)); 114} 115 116static FloatSize scaleSizeToUserSpace(const FloatSize& logicalSize, const IntSize& backingStoreSize, const IntSize& internalSize) 117{ 118 float xMagnification = static_cast<float>(backingStoreSize.width()) / internalSize.width(); 119 float yMagnification = static_cast<float>(backingStoreSize.height()) / internalSize.height(); 120 return FloatSize(logicalSize.width() * xMagnification, logicalSize.height() * yMagnification); 121} 122 123ImageBuffer::ImageBuffer(const FloatSize& size, float resolutionScale, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success) 124 : m_data(IntSize(size)) // NOTE: The input here isn't important as ImageBufferDataCG's constructor just ignores it. 125 , m_logicalSize(size) 126 , m_resolutionScale(resolutionScale) 127{ 128 float scaledWidth = ceilf(resolutionScale * size.width()); 129 float scaledHeight = ceilf(resolutionScale * size.height()); 130 131 // FIXME: Should we automatically use a lower resolution? 132 if (!FloatSize(scaledWidth, scaledHeight).isExpressibleAsIntSize()) 133 return; 134 135 m_size = IntSize(scaledWidth, scaledHeight); 136 m_data.m_backingStoreSize = m_size; 137 138 success = false; // Make early return mean failure. 139 bool accelerateRendering = renderingMode == Accelerated; 140 if (m_size.width() <= 0 || m_size.height() <= 0) 141 return; 142 143#if USE(IOSURFACE_CANVAS_BACKING_STORE) 144 Checked<int, RecordOverflow> width = m_size.width(); 145 Checked<int, RecordOverflow> height = m_size.height(); 146#endif 147 148 // Prevent integer overflows 149 m_data.m_bytesPerRow = 4 * Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.width()); 150 Checked<size_t, RecordOverflow> numBytes = Checked<unsigned, RecordOverflow>(m_data.m_backingStoreSize.height()) * m_data.m_bytesPerRow; 151 if (numBytes.hasOverflowed()) 152 return; 153 154#if USE(IOSURFACE_CANVAS_BACKING_STORE) 155 IntSize maxSize = IOSurface::maximumSize(); 156 if (width.unsafeGet() > maxSize.width() || height.unsafeGet() > maxSize.height()) 157 accelerateRendering = false; 158#else 159 ASSERT(renderingMode == Unaccelerated); 160#endif 161 162 switch (imageColorSpace) { 163 case ColorSpaceDeviceRGB: 164 m_data.m_colorSpace = deviceRGBColorSpaceRef(); 165 break; 166 case ColorSpaceSRGB: 167 m_data.m_colorSpace = sRGBColorSpaceRef(); 168 break; 169 case ColorSpaceLinearRGB: 170 m_data.m_colorSpace = linearRGBColorSpaceRef(); 171 break; 172 } 173 174 RetainPtr<CGContextRef> cgContext; 175 if (accelerateRendering) { 176#if USE(IOSURFACE_CANVAS_BACKING_STORE) 177 m_data.m_surface = createIOSurface(m_data.m_backingStoreSize); 178 FloatSize userBounds = scaleSizeToUserSpace(FloatSize(width.unsafeGet(), height.unsafeGet()), m_data.m_backingStoreSize, m_size); 179 cgContext = adoptCF(wkIOSurfaceContextCreate(m_data.m_surface.get(), userBounds.width(), userBounds.height(), m_data.m_colorSpace)); 180#endif 181 if (!cgContext) 182 accelerateRendering = false; // If allocation fails, fall back to non-accelerated path. 183 } 184 185 if (!accelerateRendering) { 186 if (!tryFastCalloc(m_data.m_backingStoreSize.height(), m_data.m_bytesPerRow.unsafeGet()).getValue(m_data.m_data)) 187 return; 188 ASSERT(!(reinterpret_cast<intptr_t>(m_data.m_data) & 3)); 189 190#if USE_ARGB32 191 m_data.m_bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host; 192#else 193 m_data.m_bitmapInfo = kCGImageAlphaPremultipliedLast; 194#endif 195 cgContext = adoptCF(CGBitmapContextCreate(m_data.m_data, m_data.m_backingStoreSize.width(), m_data.m_backingStoreSize.height(), 8, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo)); 196 // Create a live image that wraps the data. 197 m_data.m_dataProvider = adoptCF(CGDataProviderCreateWithData(0, m_data.m_data, numBytes.unsafeGet(), releaseImageData)); 198 } 199 200 if (!cgContext) 201 return; 202 203 m_context = adoptPtr(new GraphicsContext(cgContext.get())); 204 m_context->scale(FloatSize(1, -1)); 205 m_context->translate(0, -m_data.m_backingStoreSize.height()); 206 m_context->applyDeviceScaleFactor(m_resolutionScale); 207 m_context->setIsAcceleratedContext(accelerateRendering); 208 success = true; 209} 210 211ImageBuffer::~ImageBuffer() 212{ 213} 214 215GraphicsContext* ImageBuffer::context() const 216{ 217 return m_context.get(); 218} 219 220void ImageBuffer::flushContext() const 221{ 222 CGContextFlush(m_context->platformContext()); 223} 224 225static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds) 226{ 227 if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width()) 228 || CGImageGetHeight(image) != static_cast<size_t>(bounds.height()))) { 229 return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height()))); 230 } 231 return image; 232} 233 234PassRefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const 235{ 236 RetainPtr<CGImageRef> image; 237 if (m_resolutionScale == 1 || scaleBehavior == Unscaled) { 238 image = copyNativeImage(copyBehavior); 239 image = createCroppedImageIfNecessary(image.get(), internalSize()); 240 } else { 241 image = copyNativeImage(DontCopyBackingStore); 242 RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast)); 243 CGContextSetBlendMode(context.get(), kCGBlendModeCopy); 244 CGContextClipToRect(context.get(), FloatRect(FloatPoint::zero(), logicalSize())); 245 FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize(), m_data.m_backingStoreSize, internalSize()); 246 CGContextDrawImage(context.get(), FloatRect(FloatPoint::zero(), imageSizeInUserSpace), image.get()); 247 image = adoptCF(CGBitmapContextCreateImage(context.get())); 248 } 249 250 if (!image) 251 return nullptr; 252 253 RefPtr<BitmapImage> bitmapImage = BitmapImage::create(image.get()); 254 bitmapImage->setSpaceSize(spaceSize()); 255 256 return bitmapImage.release(); 257} 258 259BackingStoreCopy ImageBuffer::fastCopyImageMode() 260{ 261 return DontCopyBackingStore; 262} 263 264RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const 265{ 266 CGImageRef image = 0; 267 if (!m_context->isAcceleratedContext()) { 268 switch (copyBehavior) { 269 case DontCopyBackingStore: 270 image = CGImageCreate(m_data.m_backingStoreSize.width(), m_data.m_backingStoreSize.height(), 8, 32, m_data.m_bytesPerRow.unsafeGet(), m_data.m_colorSpace, m_data.m_bitmapInfo, m_data.m_dataProvider.get(), 0, true, kCGRenderingIntentDefault); 271 break; 272 case CopyBackingStore: 273 image = CGBitmapContextCreateImage(context()->platformContext()); 274 break; 275 default: 276 ASSERT_NOT_REACHED(); 277 break; 278 } 279 } 280#if USE(IOSURFACE_CANVAS_BACKING_STORE) 281 else 282 image = wkIOSurfaceContextCreateImage(context()->platformContext()); 283#endif 284 285 return adoptCF(image); 286} 287 288void ImageBuffer::draw(GraphicsContext* destContext, ColorSpace styleColorSpace, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode, bool useLowQualityScale) 289{ 290 UNUSED_PARAM(useLowQualityScale); 291 ColorSpace colorSpace = (destContext == m_context) ? ColorSpaceDeviceRGB : styleColorSpace; 292 293 RetainPtr<CGImageRef> image; 294 if (destContext == m_context || destContext->isAcceleratedContext()) 295 image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy. 296 else 297 image = copyNativeImage(DontCopyBackingStore); 298 299 FloatRect adjustedSrcRect = srcRect; 300 adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale); 301 destContext->drawNativeImage(image.get(), m_data.m_backingStoreSize, colorSpace, destRect, adjustedSrcRect, 1, op, blendMode); 302} 303 304void ImageBuffer::drawPattern(GraphicsContext* destContext, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, ColorSpace styleColorSpace, CompositeOperator op, const FloatRect& destRect, BlendMode blendMode) 305{ 306 FloatRect adjustedSrcRect = srcRect; 307 adjustedSrcRect.scale(m_resolutionScale, m_resolutionScale); 308 309 if (!m_context->isAcceleratedContext()) { 310 if (destContext == m_context || destContext->isAcceleratedContext()) { 311 RefPtr<Image> copy = copyImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy. 312 copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect, blendMode); 313 } else { 314 RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore); 315 imageForRendering->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect, blendMode); 316 } 317 } else { 318 RefPtr<Image> copy = copyImage(CopyBackingStore); 319 copy->drawPattern(destContext, adjustedSrcRect, patternTransform, phase, styleColorSpace, op, destRect, blendMode); 320 } 321} 322 323void ImageBuffer::clip(GraphicsContext* contextToClip, const FloatRect& rect) const 324{ 325 FloatSize backingStoreSizeInUserSpace = scaleSizeToUserSpace(rect.size(), m_data.m_backingStoreSize, internalSize()); 326 327 CGContextRef platformContextToClip = contextToClip->platformContext(); 328 // FIXME: This image needs to be grayscale to be used as an alpha mask here. 329 RetainPtr<CGImageRef> image = copyNativeImage(DontCopyBackingStore); 330 CGContextTranslateCTM(platformContextToClip, rect.x(), rect.y() + backingStoreSizeInUserSpace.height()); 331 CGContextScaleCTM(platformContextToClip, 1, -1); 332 CGContextClipToRect(platformContextToClip, FloatRect(FloatPoint(0, backingStoreSizeInUserSpace.height() - rect.height()), rect.size())); 333 CGContextClipToMask(platformContextToClip, FloatRect(FloatPoint(), backingStoreSizeInUserSpace), image.get()); 334 CGContextScaleCTM(platformContextToClip, 1, -1); 335 CGContextTranslateCTM(platformContextToClip, -rect.x(), -rect.y() - rect.height()); 336} 337 338PassRefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const 339{ 340 if (m_context->isAcceleratedContext()) 341 flushContext(); 342 343 IntRect srcRect = rect; 344 if (coordinateSystem == LogicalCoordinateSystem) 345 srcRect.scale(m_resolutionScale); 346 347 return m_data.getData(srcRect, internalSize(), m_context->isAcceleratedContext(), true, 1); 348} 349 350PassRefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const 351{ 352 if (m_context->isAcceleratedContext()) 353 flushContext(); 354 355 IntRect srcRect = rect; 356 if (coordinateSystem == LogicalCoordinateSystem) 357 srcRect.scale(m_resolutionScale); 358 359 return m_data.getData(srcRect, internalSize(), m_context->isAcceleratedContext(), false, 1); 360} 361 362void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem) 363{ 364 if (!m_context->isAcceleratedContext()) { 365 IntRect scaledSourceRect = sourceRect; 366 IntSize scaledSourceSize = sourceSize; 367 if (coordinateSystem == LogicalCoordinateSystem) { 368 scaledSourceRect.scale(m_resolutionScale); 369 scaledSourceSize.scale(m_resolutionScale); 370 } 371 372 m_data.putData(source, scaledSourceSize, scaledSourceRect, destPoint, internalSize(), false, multiplied == Unmultiplied, 1); 373 return; 374 } 375 376#if USE(IOSURFACE_CANVAS_BACKING_STORE) 377 // Make a copy of the source to ensure the bits don't change before being drawn 378 IntSize sourceCopySize(sourceRect.width(), sourceRect.height()); 379 std::unique_ptr<ImageBuffer> sourceCopy = ImageBuffer::create(sourceCopySize, 1, ColorSpaceDeviceRGB, Unaccelerated); 380 if (!sourceCopy) 381 return; 382 383 sourceCopy->m_data.putData(source, sourceSize, sourceRect, IntPoint(-sourceRect.x(), -sourceRect.y()), sourceCopy->internalSize(), sourceCopy->context()->isAcceleratedContext(), multiplied == Unmultiplied, 1); 384 385 // Set up context for using drawImage as a direct bit copy 386 CGContextRef destContext = context()->platformContext(); 387 CGContextSaveGState(destContext); 388 if (coordinateSystem == LogicalCoordinateSystem) 389 CGContextConcatCTM(destContext, AffineTransform(wkGetUserToBaseCTM(destContext)).inverse()); 390 else 391 CGContextConcatCTM(destContext, AffineTransform(CGContextGetCTM(destContext)).inverse()); 392 wkCGContextResetClip(destContext); 393 CGContextSetInterpolationQuality(destContext, kCGInterpolationNone); 394 CGContextSetAlpha(destContext, 1.0); 395 CGContextSetBlendMode(destContext, kCGBlendModeCopy); 396 CGContextSetShadowWithColor(destContext, CGSizeZero, 0, 0); 397 398 // Draw the image in CG coordinate space 399 FloatSize scaledDestSize = scaleSizeToUserSpace(coordinateSystem == LogicalCoordinateSystem ? logicalSize() : internalSize(), m_data.m_backingStoreSize, internalSize()); 400 IntPoint destPointInCGCoords(destPoint.x() + sourceRect.x(), scaledDestSize.height() - (destPoint.y() + sourceRect.y()) - sourceRect.height()); 401 IntRect destRectInCGCoords(destPointInCGCoords, sourceCopySize); 402 CGContextClipToRect(destContext, destRectInCGCoords); 403 404 RetainPtr<CGImageRef> sourceCopyImage = sourceCopy->copyNativeImage(); 405 FloatRect backingStoreInDestRect = FloatRect(FloatPoint(destPointInCGCoords.x(), destPointInCGCoords.y() + sourceCopySize.height() - (int)CGImageGetHeight(sourceCopyImage.get())), FloatSize(CGImageGetWidth(sourceCopyImage.get()), CGImageGetHeight(sourceCopyImage.get()))); 406 CGContextDrawImage(destContext, backingStoreInDestRect, sourceCopyImage.get()); 407 CGContextRestoreGState(destContext); 408#endif 409} 410 411static inline CFStringRef jpegUTI() 412{ 413#if PLATFORM(IOS) || PLATFORM(WIN) 414 static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg"); 415#endif 416 return kUTTypeJPEG; 417} 418 419static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType) 420{ 421#if PLATFORM(MAC) 422 return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0)); 423#else 424 ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe. 425 426 // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found. 427 // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>. 428 static const CFStringRef kUTTypePNG = CFSTR("public.png"); 429 static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif"); 430 431 if (equalIgnoringCase(mimeType, "image/png")) 432 return kUTTypePNG; 433 if (equalIgnoringCase(mimeType, "image/jpeg")) 434 return jpegUTI(); 435 if (equalIgnoringCase(mimeType, "image/gif")) 436 return kUTTypeGIF; 437 438 ASSERT_NOT_REACHED(); 439 return kUTTypePNG; 440#endif 441} 442 443static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data) 444{ 445 if (!image || !uti || !data) 446 return false; 447 448 RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0)); 449 if (!destination) 450 return false; 451 452 RetainPtr<CFDictionaryRef> imageProperties = 0; 453 if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) { 454 // Apply the compression quality to the JPEG image destination. 455 RetainPtr<CFNumberRef> compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality)); 456 const void* key = kCGImageDestinationLossyCompressionQuality; 457 const void* value = compressionQuality.get(); 458 imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); 459 } 460 461 // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math 462 // in the calling functions, but it doesn't seem to work. 463 464 CGImageDestinationAddImage(destination.get(), image, imageProperties.get()); 465 return CGImageDestinationFinalize(destination.get()); 466} 467 468static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality) 469{ 470 RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); 471 ASSERT(uti); 472 473 RetainPtr<CFMutableDataRef> data = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0)); 474 if (!CGImageEncodeToData(image, uti.get(), quality, data.get())) 475 return "data:,"; 476 477 Vector<char> base64Data; 478 base64Encode(CFDataGetBytePtr(data.get()), CFDataGetLength(data.get()), base64Data); 479 480 return "data:" + mimeType + ";base64," + base64Data; 481} 482 483String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const 484{ 485 ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); 486 487 if (m_context->isAcceleratedContext()) 488 flushContext(); 489 490 RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); 491 ASSERT(uti); 492 493 RefPtr<Uint8ClampedArray> premultipliedData; 494 RetainPtr<CGImageRef> image; 495 496 if (CFEqual(uti.get(), jpegUTI())) { 497 // JPEGs don't have an alpha channel, so we have to manually composite on top of black. 498 premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize())); 499 if (!premultipliedData) 500 return "data:,"; 501 502 RetainPtr<CGDataProviderRef> dataProvider; 503 dataProvider = adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0)); 504 if (!dataProvider) 505 return "data:,"; 506 507 image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(), 508 deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast, 509 dataProvider.get(), 0, false, kCGRenderingIntentDefault)); 510 } else if (m_resolutionScale == 1) { 511 image = copyNativeImage(CopyBackingStore); 512 image = createCroppedImageIfNecessary(image.get(), internalSize()); 513 } else { 514 image = copyNativeImage(DontCopyBackingStore); 515 RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast)); 516 CGContextSetBlendMode(context.get(), kCGBlendModeCopy); 517 CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height())); 518 FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize(), m_data.m_backingStoreSize, internalSize()); 519 CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.height()), image.get()); 520 image = adoptCF(CGBitmapContextCreateImage(context.get())); 521 } 522 523 return CGImageToDataURL(image.get(), mimeType, quality); 524} 525 526String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality) 527{ 528 ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); 529 530 RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); 531 ASSERT(uti); 532 533 CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast; 534 unsigned char* data = source.data()->data(); 535 Vector<uint8_t> premultipliedData; 536 537 if (CFEqual(uti.get(), jpegUTI())) { 538 // JPEGs don't have an alpha channel, so we have to manually composite on top of black. 539 size_t size = 4 * source.width() * source.height(); 540 if (!premultipliedData.tryReserveCapacity(size)) 541 return "data:,"; 542 543 unsigned char *buffer = premultipliedData.data(); 544 for (size_t i = 0; i < size; i += 4) { 545 unsigned alpha = data[i + 3]; 546 if (alpha != 255) { 547 buffer[i + 0] = data[i + 0] * alpha / 255; 548 buffer[i + 1] = data[i + 1] * alpha / 255; 549 buffer[i + 2] = data[i + 2] * alpha / 255; 550 } else { 551 buffer[i + 0] = data[i + 0]; 552 buffer[i + 1] = data[i + 1]; 553 buffer[i + 2] = data[i + 2]; 554 } 555 } 556 557 dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel. 558 data = premultipliedData.data(); 559 } 560 561 RetainPtr<CGDataProviderRef> dataProvider; 562 dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0)); 563 if (!dataProvider) 564 return "data:,"; 565 566 RetainPtr<CGImageRef> image; 567 image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(), 568 deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo, 569 dataProvider.get(), 0, false, kCGRenderingIntentDefault)); 570 571 return CGImageToDataURL(image.get(), mimeType, quality); 572} 573 574} // namespace WebCore 575