String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const { ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); RetainPtr<CGImageRef> image; RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); RefPtr<ByteArray> arr; if (CFEqual(uti.get(), jpegUTI())) { // JPEGs don't have an alpha channel, so we have to manually composite on top of black. arr = getPremultipliedImageData(IntRect(IntPoint(0, 0), internalSize())); unsigned char *data = arr->data(); for (int i = 0; i < internalSize().width() * internalSize().height(); i++) data[i * 4 + 3] = 255; // The image is already premultiplied, so we just need to make it opaque. RetainPtr<CGDataProviderRef> dataProvider; dataProvider.adoptCF(CGDataProviderCreateWithData(0, data, 4 * internalSize().width() * internalSize().height(), 0)); if (!dataProvider) return "data:,"; image.adoptCF(CGImageCreate(internalSize().width(), internalSize().height(), 8, 32, 4 * internalSize().width(), CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider.get(), 0, false, kCGRenderingIntentDefault)); } else image.adoptCF(copyNativeImage(CopyBackingStore)); if (!image) return "data:,"; return CGImageToDataURL(image.get(), mimeType, quality); }
static String CGImageToDataURL(CGImageRef image, const String& mimeType, const double* quality) { RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0)); if (!data) return "data:,"; RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0)); if (!destination) return "data:,"; RetainPtr<CFDictionaryRef> imageProperties = 0; if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) { // Apply the compression quality to the image destination. RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality)); const void* key = kCGImageDestinationLossyCompressionQuality; const void* value = compressionQuality.get(); imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); } // Setting kCGImageDestinationBackgroundColor to black in imageProperties would allow saving some math in the // calling functions, but it doesn't seem to work. CGImageDestinationAddImage(destination.get(), image, imageProperties.get()); CGImageDestinationFinalize(destination.get()); Vector<char> out; base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out); return "data:" + mimeType + ";base64," + out; }
String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality) { ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); RetainPtr<CGImageRef> image; RetainPtr<CGDataProviderRef> dataProvider; unsigned char* data = source.data()->data()->data(); RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); Vector<uint8_t> dataVector; if (CFEqual(uti.get(), jpegUTI())) { // JPEGs don't have an alpha channel, so we have to manually composite on top of black. dataVector.resize(4 * source.width() * source.height()); unsigned char *out = dataVector.data(); for (int i = 0; i < source.width() * source.height(); i++) { // Multiply color data by alpha, and set alpha to 255. int alpha = data[4 * i + 3]; if (alpha != 255) { out[4 * i + 0] = data[4 * i + 0] * alpha / 255; out[4 * i + 1] = data[4 * i + 1] * alpha / 255; out[4 * i + 2] = data[4 * i + 2] * alpha / 255; } else { out[4 * i + 0] = data[4 * i + 0]; out[4 * i + 1] = data[4 * i + 1]; out[4 * i + 2] = data[4 * i + 2]; } out[4 * i + 3] = 255; } data = out; } dataProvider.adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0)); if (!dataProvider) return "data:,"; image.adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(), CGColorSpaceCreateDeviceRGB(), kCGBitmapByteOrderDefault | kCGImageAlphaLast, dataProvider.get(), 0, false, kCGRenderingIntentDefault)); if (!image) return "data:,"; return CGImageToDataURL(image.get(), mimeType, quality); }
String ImageDataToDataURL(const ImageData& source, const String& mimeType, const double* quality) { ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast; unsigned char* data = source.data()->data(); Vector<uint8_t> premultipliedData; if (CFEqual(uti.get(), jpegUTI())) { // JPEGs don't have an alpha channel, so we have to manually composite on top of black. size_t size = 4 * source.width() * source.height(); if (!premultipliedData.tryReserveCapacity(size)) return "data:,"; premultipliedData.resize(size); unsigned char *buffer = premultipliedData.data(); for (size_t i = 0; i < size; i += 4) { unsigned alpha = data[i + 3]; if (alpha != 255) { buffer[i + 0] = data[i + 0] * alpha / 255; buffer[i + 1] = data[i + 1] * alpha / 255; buffer[i + 2] = data[i + 2] * alpha / 255; } else { buffer[i + 0] = data[i + 0]; buffer[i + 1] = data[i + 1]; buffer[i + 2] = data[i + 2]; } } dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel. data = premultipliedData.data(); } RetainPtr<CGDataProviderRef> dataProvider; dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0)); if (!dataProvider) return "data:,"; RetainPtr<CGImageRef> image; image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(), deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo, dataProvider.get(), 0, false, kCGRenderingIntentDefault)); return CGImageToDataURL(image.get(), mimeType, quality); }
String ImageBuffer::toDataURL(const String& mimeType, const double* quality) const { ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); RetainPtr<CGImageRef> image; if (!m_accelerateRendering) image.adoptCF(CGBitmapContextCreateImage(context()->platformContext())); #if USE(IOSURFACE_CANVAS_BACKING_STORE) else image.adoptCF(wkIOSurfaceContextCreateImage(context()->platformContext())); #endif if (!image) return "data:,"; RetainPtr<CFMutableDataRef> data(AdoptCF, CFDataCreateMutable(kCFAllocatorDefault, 0)); if (!data) return "data:,"; RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); RetainPtr<CGImageDestinationRef> destination(AdoptCF, CGImageDestinationCreateWithData(data.get(), uti.get(), 1, 0)); if (!destination) return "data:,"; RetainPtr<CFDictionaryRef> imageProperties = 0; if (CFEqual(uti.get(), jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) { // Apply the compression quality to the image destination. RetainPtr<CFNumberRef> compressionQuality(AdoptCF, CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality)); const void* key = kCGImageDestinationLossyCompressionQuality; const void* value = compressionQuality.get(); imageProperties.adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); } CGImageDestinationAddImage(destination.get(), image.get(), imageProperties.get()); CGImageDestinationFinalize(destination.get()); Vector<char> out; base64Encode(reinterpret_cast<const char*>(CFDataGetBytePtr(data.get())), CFDataGetLength(data.get()), out); return makeString("data:", mimeType, ";base64,", out); }
static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType) { ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe. // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found. // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>. static const CFStringRef kUTTypePNG = CFSTR("public.png"); static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif"); if (equalIgnoringCase(mimeType, "image/png")) return kUTTypePNG; if (equalIgnoringCase(mimeType, "image/jpeg")) return jpegUTI(); if (equalIgnoringCase(mimeType, "image/gif")) return kUTTypeGIF; ASSERT_NOT_REACHED(); return kUTTypePNG; }
String ImageBuffer::toDataURL(const String& mimeType, const double* quality, CoordinateSystem) const { ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); if (context().isAcceleratedContext()) flushContext(); RetainPtr<CFStringRef> uti = utiFromMIMEType(mimeType); ASSERT(uti); RefPtr<Uint8ClampedArray> premultipliedData; RetainPtr<CGImageRef> image; if (CFEqual(uti.get(), jpegUTI())) { // JPEGs don't have an alpha channel, so we have to manually composite on top of black. premultipliedData = getPremultipliedImageData(IntRect(IntPoint(0, 0), logicalSize())); if (!premultipliedData) return "data:,"; RetainPtr<CGDataProviderRef> dataProvider; dataProvider = adoptCF(CGDataProviderCreateWithData(0, premultipliedData->data(), 4 * logicalSize().width() * logicalSize().height(), 0)); if (!dataProvider) return "data:,"; image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast, dataProvider.get(), 0, false, kCGRenderingIntentDefault)); } else if (m_resolutionScale == 1) { image = copyNativeImage(CopyBackingStore); image = createCroppedImageIfNecessary(image.get(), internalSize()); } else { image = copyNativeImage(DontCopyBackingStore); RetainPtr<CGContextRef> context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), deviceRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast)); CGContextSetBlendMode(context.get(), kCGBlendModeCopy); CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height())); FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize(), m_data.backingStoreSize, internalSize()); CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.height()), image.get()); image = adoptCF(CGBitmapContextCreateImage(context.get())); } return CGImageToDataURL(image.get(), mimeType, quality); }
static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType) { #if PLATFORM(MAC) return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0)); #else ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe. // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found. // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>. static const CFStringRef kUTTypePNG = CFSTR("public.png"); static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif"); if (equalIgnoringCase(mimeType, "image/png")) return kUTTypePNG; if (equalIgnoringCase(mimeType, "image/jpeg")) return jpegUTI(); if (equalIgnoringCase(mimeType, "image/gif")) return kUTTypeGIF; ASSERT_NOT_REACHED(); return kUTTypePNG; #endif }
static bool CGImageEncodeToData(CGImageRef image, CFStringRef uti, const double* quality, CFMutableDataRef data) { if (!image || !uti || !data) return false; RetainPtr<CGImageDestinationRef> destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0)); if (!destination) return false; RetainPtr<CFDictionaryRef> imageProperties = 0; if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) { // Apply the compression quality to the JPEG image destination. RetainPtr<CFNumberRef> compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, quality)); const void* key = kCGImageDestinationLossyCompressionQuality; const void* value = compressionQuality.get(); imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)); } // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math // in the calling functions, but it doesn't seem to work. CGImageDestinationAddImage(destination.get(), image, imageProperties.get()); return CGImageDestinationFinalize(destination.get()); }