| /* |
| * Copyright (C) 2018-2020 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' |
| * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS |
| * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
| * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
| * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
| * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
| * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF |
| * THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "ImageBufferUtilitiesCG.h" |
| |
| #if USE(CG) |
| |
| #include "GraphicsContextCG.h" |
| #include "MIMETypeRegistry.h" |
| #include "PixelBuffer.h" |
| #include <ImageIO/ImageIO.h> |
| #include <wtf/CheckedArithmetic.h> |
| #include <wtf/text/Base64.h> |
| |
| #if PLATFORM(COCOA) |
| #include "UTIUtilities.h" |
| #endif |
| |
| namespace WebCore { |
| |
| uint8_t verifyImageBufferIsBigEnough(const void* buffer, size_t bufferSize) |
| { |
| RELEASE_ASSERT(bufferSize); |
| |
| uintptr_t lastByte; |
| bool isSafe = WTF::safeAdd((uintptr_t)buffer, bufferSize - 1, lastByte); |
| RELEASE_ASSERT(isSafe); |
| |
| return *(uint8_t*)lastByte; |
| } |
| |
| CFStringRef jpegUTI() |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| #if PLATFORM(IOS_FAMILY) || PLATFORM(WIN) |
| static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg"); |
| #endif |
| return kUTTypeJPEG; |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| |
| RetainPtr<CFStringRef> utiFromImageBufferMIMEType(const String& mimeType) |
| { |
| ALLOW_DEPRECATED_DECLARATIONS_BEGIN |
| // FIXME: Why doesn't iOS use the CoreServices version? |
| #if PLATFORM(MAC) |
| return UTIFromMIMEType(mimeType).createCFString(); |
| #else |
| // 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 CFStringRef kUTTypePNG; |
| static CFStringRef kUTTypeGIF; |
| |
| static std::once_flag onceKey; |
| std::call_once(onceKey, [&] { |
| kUTTypePNG = CFSTR("public.png"); |
| kUTTypeGIF = CFSTR("com.compuserve.gif"); |
| }); |
| |
| if (equalLettersIgnoringASCIICase(mimeType, "image/png")) |
| return kUTTypePNG; |
| if (equalLettersIgnoringASCIICase(mimeType, "image/jpeg")) |
| return jpegUTI(); |
| if (equalLettersIgnoringASCIICase(mimeType, "image/gif")) |
| return kUTTypeGIF; |
| |
| ASSERT_NOT_REACHED(); |
| return kUTTypePNG; |
| #endif |
| ALLOW_DEPRECATED_DECLARATIONS_END |
| } |
| |
| bool encodeImage(CGImageRef image, CFStringRef uti, Optional<double> quality, CFMutableDataRef data) |
| { |
| if (!image || !uti || !data) |
| return false; |
| |
| auto destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0)); |
| if (!destination) |
| return false; |
| |
| RetainPtr<CFDictionaryRef> imageProperties; |
| if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) { |
| // Apply the compression quality to the JPEG image destination. |
| auto 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()); |
| } |
| |
| static RetainPtr<CFDataRef> cfData(const PixelBuffer& source, const String& mimeType, Optional<double> quality) |
| { |
| ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType)); |
| |
| auto uti = utiFromImageBufferMIMEType(mimeType); |
| ASSERT(uti); |
| |
| CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast; |
| |
| auto data = source.data().data(); |
| auto dataSize = source.data().byteLength(); |
| |
| 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. |
| if (!premultipliedData.tryReserveCapacity(dataSize)) |
| return nullptr; |
| |
| premultipliedData.grow(dataSize); |
| unsigned char* buffer = premultipliedData.data(); |
| for (size_t i = 0; i < dataSize; 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(); |
| } |
| |
| verifyImageBufferIsBigEnough(data, dataSize); |
| |
| auto dataProvider = adoptCF(CGDataProviderCreateWithData(nullptr, data, dataSize, nullptr)); |
| if (!dataProvider) |
| return nullptr; |
| |
| auto imageSize = source.size(); |
| auto image = adoptCF(CGImageCreate(imageSize.width(), imageSize.height(), 8, 32, 4 * imageSize.width(), cachedCGColorSpace(source.format().colorSpace), kCGBitmapByteOrderDefault | dataAlphaInfo, dataProvider.get(), 0, false, kCGRenderingIntentDefault)); |
| |
| auto cfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0)); |
| if (!encodeImage(image.get(), uti.get(), quality, cfData.get())) |
| return nullptr; |
| |
| return WTFMove(cfData); |
| } |
| |
| String dataURL(CFDataRef data, const String& mimeType) |
| { |
| return makeString("data:", mimeType, ";base64,", base64Encoded(CFDataGetBytePtr(data), CFDataGetLength(data))); |
| } |
| |
| String dataURL(const PixelBuffer& source, const String& mimeType, Optional<double> quality) |
| { |
| if (auto data = cfData(source, mimeType, quality)) |
| return dataURL(data.get(), mimeType); |
| return "data:,"_s; |
| } |
| |
| Vector<uint8_t> dataVector(CFDataRef cfData) |
| { |
| Vector<uint8_t> data; |
| data.append(CFDataGetBytePtr(cfData), CFDataGetLength(cfData)); |
| return data; |
| } |
| |
| Vector<uint8_t> data(const PixelBuffer& source, const String& mimeType, Optional<double> quality) |
| { |
| if (auto data = cfData(source, mimeType, quality)) |
| return dataVector(data.get()); |
| return { }; |
| } |
| |
| } // namespace WebCore |
| |
| #endif // USE(CG) |