REGRESSION (r213412): JPEG premultiplication tests fail under GuardMalloc
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferCG.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2008, 2015 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 #if USE(CG)
32
33 #include "BitmapImage.h"
34 #include "GraphicsContext.h"
35 #include "GraphicsContextCG.h"
36 #include "ImageData.h"
37 #include "IntRect.h"
38 #include "MIMETypeRegistry.h"
39 #include <math.h>
40 #include <CoreGraphics/CoreGraphics.h>
41 #include <ImageIO/ImageIO.h>
42 #include <wtf/Assertions.h>
43 #include <wtf/CheckedArithmetic.h>
44 #include <wtf/MainThread.h>
45 #include <wtf/RetainPtr.h>
46 #include <wtf/text/Base64.h>
47 #include <wtf/text/WTFString.h>
48
49 #if PLATFORM(COCOA)
50 #include "WebCoreSystemInterface.h"
51 #endif
52
53 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
54 #include "IOSurface.h"
55 #include "IOSurfaceSPI.h"
56 #endif
57
58 // CA uses ARGB32 for textures and ARGB32 -> ARGB32 resampling is optimized.
59 #define USE_ARGB32 PLATFORM(IOS)
60
61 namespace WebCore {
62
63 static void releaseImageData(void*, const void* data, size_t)
64 {
65     fastFree(const_cast<void*>(data));
66 }
67
68 static FloatSize scaleSizeToUserSpace(const FloatSize& logicalSize, const IntSize& backingStoreSize, const IntSize& internalSize)
69 {
70     float xMagnification = static_cast<float>(backingStoreSize.width()) / internalSize.width();
71     float yMagnification = static_cast<float>(backingStoreSize.height()) / internalSize.height();
72     return FloatSize(logicalSize.width() * xMagnification, logicalSize.height() * yMagnification);
73 }
74
75 std::unique_ptr<ImageBuffer> ImageBuffer::createCompatibleBuffer(const FloatSize& size, const GraphicsContext& context)
76 {
77     if (size.isEmpty())
78         return nullptr;
79
80     RetainPtr<CGColorSpaceRef> colorSpace;
81 #if PLATFORM(COCOA)
82     CGContextRef cgContext = context.platformContext();
83     switch (CGContextGetType(cgContext)) {
84     case kCGContextTypeBitmap:
85         colorSpace = CGBitmapContextGetColorSpace(cgContext);
86         break;
87 #if USE(IOSURFACE)
88     case kCGContextTypeIOSurface:
89         colorSpace = CGIOSurfaceContextGetColorSpace(cgContext);
90         break;
91 #endif
92     default:
93         colorSpace = adoptCF(CGContextCopyDeviceColorSpace(cgContext));
94     }
95
96     if (!colorSpace)
97         colorSpace = sRGBColorSpaceRef();
98 #else
99     colorSpace = sRGBColorSpaceRef();
100 #endif
101     RenderingMode renderingMode = context.renderingMode();
102     IntSize scaledSize = ImageBuffer::compatibleBufferSize(size, context);
103     bool success = false;
104     std::unique_ptr<ImageBuffer> buffer(new ImageBuffer(scaledSize, 1, colorSpace.get(), renderingMode, success));
105
106     if (!success)
107         return nullptr;
108
109     // Set up a corresponding scale factor on the graphics context.
110     buffer->context().scale(FloatSize(scaledSize.width() / size.width(), scaledSize.height() / size.height()));
111     return buffer;
112 }
113
114 ImageBuffer::ImageBuffer(const FloatSize& size, float resolutionScale, CGColorSpaceRef colorSpace, RenderingMode renderingMode, bool& success)
115     : m_logicalSize(size)
116     , m_resolutionScale(resolutionScale)
117 {
118     success = false; // Make early return mean failure.
119     float scaledWidth = ceilf(resolutionScale * size.width());
120     float scaledHeight = ceilf(resolutionScale * size.height());
121
122     // FIXME: Should we automatically use a lower resolution?
123     if (!FloatSize(scaledWidth, scaledHeight).isExpressibleAsIntSize())
124         return;
125
126     m_size = IntSize(scaledWidth, scaledHeight);
127     m_data.backingStoreSize = m_size;
128
129     bool accelerateRendering = renderingMode == Accelerated;
130     if (m_size.width() <= 0 || m_size.height() <= 0)
131         return;
132
133 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
134     Checked<int, RecordOverflow> width = m_size.width();
135     Checked<int, RecordOverflow> height = m_size.height();
136 #endif
137
138     // Prevent integer overflows
139     m_data.bytesPerRow = 4 * Checked<unsigned, RecordOverflow>(m_data.backingStoreSize.width());
140     Checked<size_t, RecordOverflow> numBytes = Checked<unsigned, RecordOverflow>(m_data.backingStoreSize.height()) * m_data.bytesPerRow;
141     if (numBytes.hasOverflowed())
142         return;
143
144 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
145     IntSize maxSize = IOSurface::maximumSize();
146     if (width.unsafeGet() > maxSize.width() || height.unsafeGet() > maxSize.height())
147         accelerateRendering = false;
148 #else
149     ASSERT(renderingMode == Unaccelerated);
150 #endif
151
152     m_data.colorSpace = colorSpace;
153
154     RetainPtr<CGContextRef> cgContext;
155     if (accelerateRendering) {
156 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
157         FloatSize userBounds = sizeForDestinationSize(FloatSize(width.unsafeGet(), height.unsafeGet()));
158         m_data.surface = IOSurface::create(m_data.backingStoreSize, IntSize(userBounds), colorSpace);
159         if (m_data.surface) {
160             cgContext = m_data.surface->ensurePlatformContext();
161             if (cgContext)
162                 CGContextClearRect(cgContext.get(), FloatRect(FloatPoint(), userBounds));
163             else
164                 m_data.surface = nullptr;
165         }
166 #endif
167         if (!cgContext)
168             accelerateRendering = false; // If allocation fails, fall back to non-accelerated path.
169     }
170
171     if (!accelerateRendering) {
172         if (!tryFastCalloc(m_data.backingStoreSize.height(), m_data.bytesPerRow.unsafeGet()).getValue(m_data.data))
173             return;
174         ASSERT(!(reinterpret_cast<intptr_t>(m_data.data) & 3));
175
176 #if USE_ARGB32
177         m_data.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host;
178 #else
179         m_data.bitmapInfo = kCGImageAlphaPremultipliedLast;
180 #endif
181         cgContext = adoptCF(CGBitmapContextCreate(m_data.data, m_data.backingStoreSize.width(), m_data.backingStoreSize.height(), 8, m_data.bytesPerRow.unsafeGet(), m_data.colorSpace, m_data.bitmapInfo));
182         // Create a live image that wraps the data.
183         m_data.dataProvider = adoptCF(CGDataProviderCreateWithData(0, m_data.data, numBytes.unsafeGet(), releaseImageData));
184
185         if (!cgContext)
186             return;
187
188         m_data.context = std::make_unique<GraphicsContext>(cgContext.get());
189     }
190
191     context().scale(FloatSize(1, -1));
192     context().translate(0, -m_data.backingStoreSize.height());
193     context().applyDeviceScaleFactor(m_resolutionScale);
194
195     success = true;
196 }
197
198 ImageBuffer::ImageBuffer(const FloatSize& size, float resolutionScale, ColorSpace imageColorSpace, RenderingMode renderingMode, bool& success)
199     : ImageBuffer(size, resolutionScale, cachedCGColorSpace(imageColorSpace), renderingMode, success)
200 {
201 }
202
203 ImageBuffer::~ImageBuffer()
204 {
205 }
206
207 FloatSize ImageBuffer::sizeForDestinationSize(FloatSize destinationSize) const
208 {
209     return scaleSizeToUserSpace(destinationSize, m_data.backingStoreSize, internalSize());
210 }
211
212 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
213 size_t ImageBuffer::memoryCost() const
214 {
215     if (m_data.surface)
216         return m_data.surface->totalBytes();
217     return 4 * internalSize().width() * internalSize().height();
218 }
219
220 size_t ImageBuffer::externalMemoryCost() const
221 {
222     if (m_data.surface)
223         return m_data.surface->totalBytes();
224     return 0;
225 }
226 #endif
227
228 GraphicsContext& ImageBuffer::context() const
229 {
230 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
231     if (m_data.surface)
232         return m_data.surface->ensureGraphicsContext();
233 #endif
234     return *m_data.context;
235 }
236
237 void ImageBuffer::flushContext() const
238 {
239     CGContextFlush(context().platformContext());
240 }
241
242 static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds)
243 {
244     if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width()) || CGImageGetHeight(image) != static_cast<size_t>(bounds.height())))
245         return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height())));
246     return image;
247 }
248
249 static RefPtr<Image> createBitmapImageAfterScalingIfNeeded(RetainPtr<CGImageRef>&& image, IntSize internalSize, IntSize logicalSize, IntSize backingStoreSize, float resolutionScale, ScaleBehavior scaleBehavior)
250 {
251     if (resolutionScale == 1 || scaleBehavior == Unscaled)
252         image = createCroppedImageIfNecessary(image.get(), internalSize);
253     else {
254         auto context = adoptCF(CGBitmapContextCreate(0, logicalSize.width(), logicalSize.height(), 8, 4 * logicalSize.width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
255         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
256         CGContextClipToRect(context.get(), FloatRect(FloatPoint::zero(), logicalSize));
257         FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize, backingStoreSize, internalSize);
258         CGContextDrawImage(context.get(), FloatRect(FloatPoint::zero(), imageSizeInUserSpace), image.get());
259         image = adoptCF(CGBitmapContextCreateImage(context.get()));
260     }
261
262     if (!image)
263         return nullptr;
264
265     return BitmapImage::create(WTFMove(image));
266 }
267
268 RefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
269 {
270     RetainPtr<CGImageRef> image;
271     if (m_resolutionScale == 1 || scaleBehavior == Unscaled)
272         image = copyNativeImage(copyBehavior);
273     else
274         image = copyNativeImage(DontCopyBackingStore);
275
276     return createBitmapImageAfterScalingIfNeeded(WTFMove(image), internalSize(), logicalSize(), m_data.backingStoreSize, m_resolutionScale, scaleBehavior);
277 }
278
279 RefPtr<Image> ImageBuffer::sinkIntoImage(std::unique_ptr<ImageBuffer> imageBuffer, ScaleBehavior scaleBehavior)
280 {
281     IntSize internalSize = imageBuffer->internalSize();
282     IntSize logicalSize = imageBuffer->logicalSize();
283     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
284     float resolutionScale = imageBuffer->m_resolutionScale;
285
286     return createBitmapImageAfterScalingIfNeeded(sinkIntoNativeImage(WTFMove(imageBuffer)), internalSize, logicalSize, backingStoreSize, resolutionScale, scaleBehavior);
287 }
288
289 BackingStoreCopy ImageBuffer::fastCopyImageMode()
290 {
291     return DontCopyBackingStore;
292 }
293
294 RetainPtr<CGImageRef> ImageBuffer::sinkIntoNativeImage(std::unique_ptr<ImageBuffer> imageBuffer)
295 {
296 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
297     if (!imageBuffer->m_data.surface)
298         return imageBuffer->copyNativeImage(DontCopyBackingStore);
299
300     return IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
301 #else
302     return imageBuffer->copyNativeImage(DontCopyBackingStore);
303 #endif
304 }
305
306 RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
307 {
308     RetainPtr<CGImageRef> image;
309     if (!context().isAcceleratedContext()) {
310         switch (copyBehavior) {
311         case DontCopyBackingStore:
312             image = adoptCF(CGImageCreate(m_data.backingStoreSize.width(), m_data.backingStoreSize.height(), 8, 32, m_data.bytesPerRow.unsafeGet(), m_data.colorSpace, m_data.bitmapInfo, m_data.dataProvider.get(), 0, true, kCGRenderingIntentDefault));
313             break;
314         case CopyBackingStore:
315             image = adoptCF(CGBitmapContextCreateImage(context().platformContext()));
316             break;
317         default:
318             ASSERT_NOT_REACHED();
319             break;
320         }
321     }
322 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
323     else
324         image = m_data.surface->createImage();
325 #endif
326
327     return image;
328 }
329
330 void ImageBuffer::drawConsuming(std::unique_ptr<ImageBuffer> imageBuffer, GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
331 {
332 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
333     if (!imageBuffer->m_data.surface) {
334         imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
335         return;
336     }
337     
338     ASSERT(destContext.isAcceleratedContext());
339     
340     float resolutionScale = imageBuffer->m_resolutionScale;
341     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
342
343     RetainPtr<CGImageRef> image = IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
344     
345     FloatRect adjustedSrcRect = srcRect;
346     adjustedSrcRect.scale(resolutionScale);
347     destContext.drawNativeImage(image.get(), backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
348 #else
349     imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
350 #endif
351 }
352
353 void ImageBuffer::draw(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
354 {
355     RetainPtr<CGImageRef> image;
356     if (&destContext == &context() || destContext.isAcceleratedContext())
357         image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
358     else
359         image = copyNativeImage(DontCopyBackingStore);
360
361     FloatRect adjustedSrcRect = srcRect;
362     adjustedSrcRect.scale(m_resolutionScale);
363     destContext.drawNativeImage(image.get(), m_data.backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
364 }
365
366 void ImageBuffer::drawPattern(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode)
367 {
368     FloatRect adjustedSrcRect = srcRect;
369     adjustedSrcRect.scale(m_resolutionScale);
370
371     if (!context().isAcceleratedContext()) {
372         if (&destContext == &context() || destContext.isAcceleratedContext()) {
373             if (RefPtr<Image> copy = copyImage(CopyBackingStore)) // Drawing into our own buffer, need to deep copy.
374                 copy->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
375         } else {
376             if (RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore))
377                 imageForRendering->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
378         }
379     } else {
380         if (RefPtr<Image> copy = copyImage(CopyBackingStore))
381             copy->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
382     }
383 }
384
385 RefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
386 {
387     if (context().isAcceleratedContext())
388         flushContext();
389
390     IntRect srcRect = rect;
391     if (coordinateSystem == LogicalCoordinateSystem)
392         srcRect.scale(m_resolutionScale);
393
394     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), true, 1);
395 }
396
397 RefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, CoordinateSystem coordinateSystem) const
398 {
399     if (context().isAcceleratedContext())
400         flushContext();
401
402     IntRect srcRect = rect;
403     if (coordinateSystem == LogicalCoordinateSystem)
404         srcRect.scale(m_resolutionScale);
405
406     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), false, 1);
407 }
408
409 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
410 {
411     if (context().isAcceleratedContext())
412         flushContext();
413
414     IntRect scaledSourceRect = sourceRect;
415     IntSize scaledSourceSize = sourceSize;
416     if (coordinateSystem == LogicalCoordinateSystem) {
417         scaledSourceRect.scale(m_resolutionScale);
418         scaledSourceSize.scale(m_resolutionScale);
419     }
420
421     m_data.putData(source, scaledSourceSize, scaledSourceRect, destPoint, internalSize(), context().isAcceleratedContext(), multiplied == Unmultiplied, 1);
422     
423     // Force recreating the IOSurface cached image if it is requested through CGIOSurfaceContextCreateImage().
424     // See https://bugs.webkit.org/show_bug.cgi?id=157966 for explaining why this is necessary.
425     if (context().isAcceleratedContext())
426         context().fillRect(FloatRect(1, 1, 0, 0));
427 }
428
429 static inline CFStringRef jpegUTI()
430 {
431 #if PLATFORM(IOS) || PLATFORM(WIN)
432     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
433 #endif
434     return kUTTypeJPEG;
435 }
436     
437 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
438 {
439 #if PLATFORM(MAC)
440     return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
441 #else
442     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
443
444     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
445     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
446     static const CFStringRef kUTTypePNG = CFSTR("public.png");
447     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
448
449     if (equalLettersIgnoringASCIICase(mimeType, "image/png"))
450         return kUTTypePNG;
451     if (equalLettersIgnoringASCIICase(mimeType, "image/jpeg"))
452         return jpegUTI();
453     if (equalLettersIgnoringASCIICase(mimeType, "image/gif"))
454         return kUTTypeGIF;
455
456     ASSERT_NOT_REACHED();
457     return kUTTypePNG;
458 #endif
459 }
460
461 static bool encodeImage(CGImageRef image, CFStringRef uti, std::optional<double> quality, CFMutableDataRef data)
462 {
463     if (!image || !uti || !data)
464         return false;
465
466     auto destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0));
467     if (!destination)
468         return false;
469
470     RetainPtr<CFDictionaryRef> imageProperties;
471     if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
472         // Apply the compression quality to the JPEG image destination.
473         auto compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &*quality));
474         const void* key = kCGImageDestinationLossyCompressionQuality;
475         const void* value = compressionQuality.get();
476         imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
477     }
478
479     // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
480     // in the calling functions, but it doesn't seem to work.
481
482     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
483     return CGImageDestinationFinalize(destination.get());
484 }
485
486 static String dataURL(CFDataRef data, const String& mimeType)
487 {
488     Vector<char> base64Data;
489     base64Encode(CFDataGetBytePtr(data), CFDataGetLength(data), base64Data);
490
491     return "data:" + mimeType + ";base64," + base64Data;
492 }
493
494 static Vector<uint8_t> dataVector(CFDataRef cfData)
495 {
496     Vector<uint8_t> data;
497     data.append(CFDataGetBytePtr(cfData), CFDataGetLength(cfData));
498     return data;
499 }
500
501 String ImageBuffer::toDataURL(const String& mimeType, std::optional<double> quality, CoordinateSystem) const
502 {
503     if (auto data = toCFData(mimeType, quality))
504         return dataURL(data.get(), mimeType);
505     return ASCIILiteral("data:,");
506 }
507
508 Vector<uint8_t> ImageBuffer::toData(const String& mimeType, std::optional<double> quality) const
509 {
510     if (auto data = toCFData(mimeType, quality))
511         return dataVector(data.get());
512     return { };
513 }
514
515 RetainPtr<CFDataRef> ImageBuffer::toCFData(const String& mimeType, std::optional<double> quality) const
516 {
517     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
518
519     if (context().isAcceleratedContext())
520         flushContext();
521
522     auto uti = utiFromMIMEType(mimeType);
523     ASSERT(uti);
524
525     RetainPtr<CGImageRef> image;
526     RefPtr<Uint8ClampedArray> premultipliedData;
527
528     if (CFEqual(uti.get(), jpegUTI())) {
529         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
530         premultipliedData = getPremultipliedImageData(IntRect(IntPoint(), logicalSize()));
531         if (!premultipliedData)
532             return nullptr;
533
534         size_t dataSize = 4 * logicalSize().width() * logicalSize().height();
535         auto dataProvider = adoptCF(CGDataProviderCreateWithData(nullptr, premultipliedData->data(), dataSize, nullptr));
536         if (!dataProvider)
537             return nullptr;
538
539         image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(), sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
540     } else if (m_resolutionScale == 1) {
541         image = copyNativeImage(CopyBackingStore);
542         image = createCroppedImageIfNecessary(image.get(), internalSize());
543     } else {
544         image = copyNativeImage(DontCopyBackingStore);
545         auto context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
546         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
547         CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()));
548         FloatSize imageSizeInUserSpace = sizeForDestinationSize(logicalSize());
549         CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.height()), image.get());
550         image = adoptCF(CGBitmapContextCreateImage(context.get()));
551     }
552
553     auto cfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
554     if (!encodeImage(image.get(), uti.get(), quality, cfData.get()))
555         return nullptr;
556
557     return WTFMove(cfData);
558 }
559
560 static RetainPtr<CFDataRef> cfData(const ImageData& source, const String& mimeType, std::optional<double> quality)
561 {
562     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
563
564     auto uti = utiFromMIMEType(mimeType);
565     ASSERT(uti);
566
567     CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast;
568     unsigned char* data = source.data()->data();
569     Vector<uint8_t> premultipliedData;
570
571     if (CFEqual(uti.get(), jpegUTI())) {
572         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
573         size_t size = 4 * source.width() * source.height();
574         if (!premultipliedData.tryReserveCapacity(size))
575             return nullptr;
576
577         premultipliedData.resize(size);
578         unsigned char *buffer = premultipliedData.data();
579         for (size_t i = 0; i < size; i += 4) {
580             unsigned alpha = data[i + 3];
581             if (alpha != 255) {
582                 buffer[i + 0] = data[i + 0] * alpha / 255;
583                 buffer[i + 1] = data[i + 1] * alpha / 255;
584                 buffer[i + 2] = data[i + 2] * alpha / 255;
585             } else {
586                 buffer[i + 0] = data[i + 0];
587                 buffer[i + 1] = data[i + 1];
588                 buffer[i + 2] = data[i + 2];
589             }
590         }
591
592         dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel.
593         data = premultipliedData.data();
594     }
595
596     auto dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0));
597     if (!dataProvider)
598         return nullptr;
599
600     auto image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(), sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
601
602     auto cfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
603     if (!encodeImage(image.get(), uti.get(), quality, cfData.get()))
604         return nullptr;
605
606     return WTFMove(cfData);
607 }
608
609 String dataURL(const ImageData& source, const String& mimeType, std::optional<double> quality)
610 {
611     if (auto data = cfData(source, mimeType, quality))
612         return dataURL(data.get(), mimeType);
613     return ASCIILiteral("data:,");
614 }
615
616 Vector<uint8_t> data(const ImageData& source, const String& mimeType, std::optional<double> quality)
617 {
618     if (auto data = cfData(source, mimeType, quality))
619         return dataVector(data.get());
620     return { };
621 }
622
623 } // namespace WebCore
624
625 #endif