Implementors of memoryCost() need to be thread-safe.
[WebKit-https.git] / Source / WebCore / platform / graphics / cg / ImageBufferCG.cpp
1 /*
2  * Copyright (C) 2006 Nikolas Zimmermann <zimmermann@kde.org>
3  * Copyright (C) 2008-2017 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     // memoryCost() may be invoked concurrently from a GC thread, and we need to be careful about what data we access here and how.
216     // It's safe to access internalSize() because it doesn't do any pointer chasing.
217     // It's safe to access m_data.surface because the surface can only be assigned during construction of this ImageBuffer.
218     // It's safe to access m_data.surface->totalBytes() because totalBytes() doesn't chase pointers.
219     if (m_data.surface)
220         return m_data.surface->totalBytes();
221     return 4 * internalSize().width() * internalSize().height();
222 }
223
224 size_t ImageBuffer::externalMemoryCost() const
225 {
226     // externalMemoryCost() may be invoked concurrently from a GC thread, and we need to be careful about what data we access here and how.
227     // It's safe to access m_data.surface because the surface can only be assigned during construction of this ImageBuffer.
228     // It's safe to access m_data.surface->totalBytes() because totalBytes() doesn't chase pointers.
229     if (m_data.surface)
230         return m_data.surface->totalBytes();
231     return 0;
232 }
233 #endif
234
235 GraphicsContext& ImageBuffer::context() const
236 {
237 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
238     if (m_data.surface)
239         return m_data.surface->ensureGraphicsContext();
240 #endif
241     return *m_data.context;
242 }
243
244 void ImageBuffer::flushContext() const
245 {
246     CGContextFlush(context().platformContext());
247 }
248
249 static RetainPtr<CGImageRef> createCroppedImageIfNecessary(CGImageRef image, const IntSize& bounds)
250 {
251     if (image && (CGImageGetWidth(image) != static_cast<size_t>(bounds.width()) || CGImageGetHeight(image) != static_cast<size_t>(bounds.height())))
252         return adoptCF(CGImageCreateWithImageInRect(image, CGRectMake(0, 0, bounds.width(), bounds.height())));
253     return image;
254 }
255
256 static RefPtr<Image> createBitmapImageAfterScalingIfNeeded(RetainPtr<CGImageRef>&& image, IntSize internalSize, IntSize logicalSize, IntSize backingStoreSize, float resolutionScale, ScaleBehavior scaleBehavior)
257 {
258     if (resolutionScale == 1 || scaleBehavior == Unscaled)
259         image = createCroppedImageIfNecessary(image.get(), internalSize);
260     else {
261         auto context = adoptCF(CGBitmapContextCreate(0, logicalSize.width(), logicalSize.height(), 8, 4 * logicalSize.width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
262         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
263         CGContextClipToRect(context.get(), FloatRect(FloatPoint::zero(), logicalSize));
264         FloatSize imageSizeInUserSpace = scaleSizeToUserSpace(logicalSize, backingStoreSize, internalSize);
265         CGContextDrawImage(context.get(), FloatRect(FloatPoint::zero(), imageSizeInUserSpace), image.get());
266         image = adoptCF(CGBitmapContextCreateImage(context.get()));
267     }
268
269     if (!image)
270         return nullptr;
271
272     return BitmapImage::create(WTFMove(image));
273 }
274
275 RefPtr<Image> ImageBuffer::copyImage(BackingStoreCopy copyBehavior, ScaleBehavior scaleBehavior) const
276 {
277     RetainPtr<CGImageRef> image;
278     if (m_resolutionScale == 1 || scaleBehavior == Unscaled)
279         image = copyNativeImage(copyBehavior);
280     else
281         image = copyNativeImage(DontCopyBackingStore);
282
283     return createBitmapImageAfterScalingIfNeeded(WTFMove(image), internalSize(), logicalSize(), m_data.backingStoreSize, m_resolutionScale, scaleBehavior);
284 }
285
286 RefPtr<Image> ImageBuffer::sinkIntoImage(std::unique_ptr<ImageBuffer> imageBuffer, ScaleBehavior scaleBehavior)
287 {
288     IntSize internalSize = imageBuffer->internalSize();
289     IntSize logicalSize = imageBuffer->logicalSize();
290     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
291     float resolutionScale = imageBuffer->m_resolutionScale;
292
293     return createBitmapImageAfterScalingIfNeeded(sinkIntoNativeImage(WTFMove(imageBuffer)), internalSize, logicalSize, backingStoreSize, resolutionScale, scaleBehavior);
294 }
295
296 BackingStoreCopy ImageBuffer::fastCopyImageMode()
297 {
298     return DontCopyBackingStore;
299 }
300
301 RetainPtr<CGImageRef> ImageBuffer::sinkIntoNativeImage(std::unique_ptr<ImageBuffer> imageBuffer)
302 {
303 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
304     if (!imageBuffer->m_data.surface)
305         return imageBuffer->copyNativeImage(DontCopyBackingStore);
306
307     return IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
308 #else
309     return imageBuffer->copyNativeImage(DontCopyBackingStore);
310 #endif
311 }
312
313 RetainPtr<CGImageRef> ImageBuffer::copyNativeImage(BackingStoreCopy copyBehavior) const
314 {
315     RetainPtr<CGImageRef> image;
316     if (!context().isAcceleratedContext()) {
317         switch (copyBehavior) {
318         case DontCopyBackingStore:
319             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));
320             break;
321         case CopyBackingStore:
322             image = adoptCF(CGBitmapContextCreateImage(context().platformContext()));
323             break;
324         default:
325             ASSERT_NOT_REACHED();
326             break;
327         }
328     }
329 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
330     else
331         image = m_data.surface->createImage();
332 #endif
333
334     return image;
335 }
336
337 void ImageBuffer::drawConsuming(std::unique_ptr<ImageBuffer> imageBuffer, GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
338 {
339 #if USE(IOSURFACE_CANVAS_BACKING_STORE)
340     if (!imageBuffer->m_data.surface) {
341         imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
342         return;
343     }
344     
345     ASSERT(destContext.isAcceleratedContext());
346     
347     float resolutionScale = imageBuffer->m_resolutionScale;
348     IntSize backingStoreSize = imageBuffer->m_data.backingStoreSize;
349
350     RetainPtr<CGImageRef> image = IOSurface::sinkIntoImage(IOSurface::createFromImageBuffer(WTFMove(imageBuffer)));
351     
352     FloatRect adjustedSrcRect = srcRect;
353     adjustedSrcRect.scale(resolutionScale);
354     destContext.drawNativeImage(image.get(), backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
355 #else
356     imageBuffer->draw(destContext, destRect, srcRect, op, blendMode);
357 #endif
358 }
359
360 void ImageBuffer::draw(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator op, BlendMode blendMode)
361 {
362     RetainPtr<CGImageRef> image;
363     if (&destContext == &context() || destContext.isAcceleratedContext())
364         image = copyNativeImage(CopyBackingStore); // Drawing into our own buffer, need to deep copy.
365     else
366         image = copyNativeImage(DontCopyBackingStore);
367
368     FloatRect adjustedSrcRect = srcRect;
369     adjustedSrcRect.scale(m_resolutionScale);
370     destContext.drawNativeImage(image.get(), m_data.backingStoreSize, destRect, adjustedSrcRect, op, blendMode);
371 }
372
373 void ImageBuffer::drawPattern(GraphicsContext& destContext, const FloatRect& destRect, const FloatRect& srcRect, const AffineTransform& patternTransform, const FloatPoint& phase, const FloatSize& spacing, CompositeOperator op, BlendMode blendMode)
374 {
375     FloatRect adjustedSrcRect = srcRect;
376     adjustedSrcRect.scale(m_resolutionScale);
377
378     if (!context().isAcceleratedContext()) {
379         if (&destContext == &context() || destContext.isAcceleratedContext()) {
380             if (RefPtr<Image> copy = copyImage(CopyBackingStore)) // Drawing into our own buffer, need to deep copy.
381                 copy->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
382         } else {
383             if (RefPtr<Image> imageForRendering = copyImage(DontCopyBackingStore))
384                 imageForRendering->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
385         }
386     } else {
387         if (RefPtr<Image> copy = copyImage(CopyBackingStore))
388             copy->drawPattern(destContext, destRect, adjustedSrcRect, patternTransform, phase, spacing, op, blendMode);
389     }
390 }
391
392 RefPtr<Uint8ClampedArray> ImageBuffer::getUnmultipliedImageData(const IntRect& rect, IntSize* pixelArrayDimensions, CoordinateSystem coordinateSystem) const
393 {
394     if (context().isAcceleratedContext())
395         flushContext();
396
397     IntRect srcRect = rect;
398     if (coordinateSystem == LogicalCoordinateSystem)
399         srcRect.scale(m_resolutionScale);
400
401     if (pixelArrayDimensions)
402         *pixelArrayDimensions = srcRect.size();
403
404     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), true, 1);
405 }
406
407 RefPtr<Uint8ClampedArray> ImageBuffer::getPremultipliedImageData(const IntRect& rect, IntSize* pixelArrayDimensions, CoordinateSystem coordinateSystem) const
408 {
409     if (context().isAcceleratedContext())
410         flushContext();
411
412     IntRect srcRect = rect;
413     if (coordinateSystem == LogicalCoordinateSystem)
414         srcRect.scale(m_resolutionScale);
415
416     if (pixelArrayDimensions)
417         *pixelArrayDimensions = srcRect.size();
418
419     return m_data.getData(srcRect, internalSize(), context().isAcceleratedContext(), false, 1);
420 }
421
422 void ImageBuffer::putByteArray(Multiply multiplied, Uint8ClampedArray* source, const IntSize& sourceSize, const IntRect& sourceRect, const IntPoint& destPoint, CoordinateSystem coordinateSystem)
423 {
424     if (context().isAcceleratedContext())
425         flushContext();
426
427     IntRect scaledSourceRect = sourceRect;
428     IntSize scaledSourceSize = sourceSize;
429     if (coordinateSystem == LogicalCoordinateSystem) {
430         scaledSourceRect.scale(m_resolutionScale);
431         scaledSourceSize.scale(m_resolutionScale);
432     }
433
434     m_data.putData(source, scaledSourceSize, scaledSourceRect, destPoint, internalSize(), context().isAcceleratedContext(), multiplied == Unmultiplied, 1);
435     
436     // Force recreating the IOSurface cached image if it is requested through CGIOSurfaceContextCreateImage().
437     // See https://bugs.webkit.org/show_bug.cgi?id=157966 for explaining why this is necessary.
438     if (context().isAcceleratedContext())
439         context().fillRect(FloatRect(1, 1, 0, 0));
440 }
441
442 static inline CFStringRef jpegUTI()
443 {
444 #if PLATFORM(IOS) || PLATFORM(WIN)
445     static const CFStringRef kUTTypeJPEG = CFSTR("public.jpeg");
446 #endif
447     return kUTTypeJPEG;
448 }
449     
450 static RetainPtr<CFStringRef> utiFromMIMEType(const String& mimeType)
451 {
452 #if PLATFORM(MAC)
453     return adoptCF(UTTypeCreatePreferredIdentifierForTag(kUTTagClassMIMEType, mimeType.createCFString().get(), 0));
454 #else
455     ASSERT(isMainThread()); // It is unclear if CFSTR is threadsafe.
456
457     // FIXME: Add Windows support for all the supported UTIs when a way to convert from MIMEType to UTI reliably is found.
458     // For now, only support PNG, JPEG, and GIF. See <rdar://problem/6095286>.
459     static const CFStringRef kUTTypePNG = CFSTR("public.png");
460     static const CFStringRef kUTTypeGIF = CFSTR("com.compuserve.gif");
461
462     if (equalLettersIgnoringASCIICase(mimeType, "image/png"))
463         return kUTTypePNG;
464     if (equalLettersIgnoringASCIICase(mimeType, "image/jpeg"))
465         return jpegUTI();
466     if (equalLettersIgnoringASCIICase(mimeType, "image/gif"))
467         return kUTTypeGIF;
468
469     ASSERT_NOT_REACHED();
470     return kUTTypePNG;
471 #endif
472 }
473
474 static bool encodeImage(CGImageRef image, CFStringRef uti, std::optional<double> quality, CFMutableDataRef data)
475 {
476     if (!image || !uti || !data)
477         return false;
478
479     auto destination = adoptCF(CGImageDestinationCreateWithData(data, uti, 1, 0));
480     if (!destination)
481         return false;
482
483     RetainPtr<CFDictionaryRef> imageProperties;
484     if (CFEqual(uti, jpegUTI()) && quality && *quality >= 0.0 && *quality <= 1.0) {
485         // Apply the compression quality to the JPEG image destination.
486         auto compressionQuality = adoptCF(CFNumberCreate(kCFAllocatorDefault, kCFNumberDoubleType, &*quality));
487         const void* key = kCGImageDestinationLossyCompressionQuality;
488         const void* value = compressionQuality.get();
489         imageProperties = adoptCF(CFDictionaryCreate(0, &key, &value, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
490     }
491
492     // Setting kCGImageDestinationBackgroundColor to black for JPEG images in imageProperties would save some math
493     // in the calling functions, but it doesn't seem to work.
494
495     CGImageDestinationAddImage(destination.get(), image, imageProperties.get());
496     return CGImageDestinationFinalize(destination.get());
497 }
498
499 static String dataURL(CFDataRef data, const String& mimeType)
500 {
501     Vector<char> base64Data;
502     base64Encode(CFDataGetBytePtr(data), CFDataGetLength(data), base64Data);
503
504     return "data:" + mimeType + ";base64," + base64Data;
505 }
506
507 static Vector<uint8_t> dataVector(CFDataRef cfData)
508 {
509     Vector<uint8_t> data;
510     data.append(CFDataGetBytePtr(cfData), CFDataGetLength(cfData));
511     return data;
512 }
513
514 String ImageBuffer::toDataURL(const String& mimeType, std::optional<double> quality, CoordinateSystem) const
515 {
516     if (auto data = toCFData(mimeType, quality))
517         return dataURL(data.get(), mimeType);
518     return ASCIILiteral("data:,");
519 }
520
521 Vector<uint8_t> ImageBuffer::toData(const String& mimeType, std::optional<double> quality) const
522 {
523     if (auto data = toCFData(mimeType, quality))
524         return dataVector(data.get());
525     return { };
526 }
527
528 RetainPtr<CFDataRef> ImageBuffer::toCFData(const String& mimeType, std::optional<double> quality) const
529 {
530     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
531
532     if (context().isAcceleratedContext())
533         flushContext();
534
535     auto uti = utiFromMIMEType(mimeType);
536     ASSERT(uti);
537
538     RetainPtr<CGImageRef> image;
539     RefPtr<Uint8ClampedArray> premultipliedData;
540
541     if (CFEqual(uti.get(), jpegUTI())) {
542         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
543         premultipliedData = getPremultipliedImageData(IntRect(IntPoint(), logicalSize()));
544         if (!premultipliedData)
545             return nullptr;
546
547         size_t dataSize = 4 * logicalSize().width() * logicalSize().height();
548         auto dataProvider = adoptCF(CGDataProviderCreateWithData(nullptr, premultipliedData->data(), dataSize, nullptr));
549         if (!dataProvider)
550             return nullptr;
551
552         image = adoptCF(CGImageCreate(logicalSize().width(), logicalSize().height(), 8, 32, 4 * logicalSize().width(), sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
553     } else if (m_resolutionScale == 1) {
554         image = copyNativeImage(CopyBackingStore);
555         image = createCroppedImageIfNecessary(image.get(), internalSize());
556     } else {
557         image = copyNativeImage(DontCopyBackingStore);
558         auto context = adoptCF(CGBitmapContextCreate(0, logicalSize().width(), logicalSize().height(), 8, 4 * logicalSize().width(), sRGBColorSpaceRef(), kCGImageAlphaPremultipliedLast));
559         CGContextSetBlendMode(context.get(), kCGBlendModeCopy);
560         CGContextClipToRect(context.get(), CGRectMake(0, 0, logicalSize().width(), logicalSize().height()));
561         FloatSize imageSizeInUserSpace = sizeForDestinationSize(logicalSize());
562         CGContextDrawImage(context.get(), CGRectMake(0, 0, imageSizeInUserSpace.width(), imageSizeInUserSpace.height()), image.get());
563         image = adoptCF(CGBitmapContextCreateImage(context.get()));
564     }
565
566     auto cfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
567     if (!encodeImage(image.get(), uti.get(), quality, cfData.get()))
568         return nullptr;
569
570     return WTFMove(cfData);
571 }
572
573 static RetainPtr<CFDataRef> cfData(const ImageData& source, const String& mimeType, std::optional<double> quality)
574 {
575     ASSERT(MIMETypeRegistry::isSupportedImageMIMETypeForEncoding(mimeType));
576
577     auto uti = utiFromMIMEType(mimeType);
578     ASSERT(uti);
579
580     CGImageAlphaInfo dataAlphaInfo = kCGImageAlphaLast;
581     unsigned char* data = source.data()->data();
582     Vector<uint8_t> premultipliedData;
583
584     if (CFEqual(uti.get(), jpegUTI())) {
585         // JPEGs don't have an alpha channel, so we have to manually composite on top of black.
586         size_t size = 4 * source.width() * source.height();
587         if (!premultipliedData.tryReserveCapacity(size))
588             return nullptr;
589
590         premultipliedData.resize(size);
591         unsigned char *buffer = premultipliedData.data();
592         for (size_t i = 0; i < size; i += 4) {
593             unsigned alpha = data[i + 3];
594             if (alpha != 255) {
595                 buffer[i + 0] = data[i + 0] * alpha / 255;
596                 buffer[i + 1] = data[i + 1] * alpha / 255;
597                 buffer[i + 2] = data[i + 2] * alpha / 255;
598             } else {
599                 buffer[i + 0] = data[i + 0];
600                 buffer[i + 1] = data[i + 1];
601                 buffer[i + 2] = data[i + 2];
602             }
603         }
604
605         dataAlphaInfo = kCGImageAlphaNoneSkipLast; // Ignore the alpha channel.
606         data = premultipliedData.data();
607     }
608
609     auto dataProvider = adoptCF(CGDataProviderCreateWithData(0, data, 4 * source.width() * source.height(), 0));
610     if (!dataProvider)
611         return nullptr;
612
613     auto image = adoptCF(CGImageCreate(source.width(), source.height(), 8, 32, 4 * source.width(), sRGBColorSpaceRef(), kCGBitmapByteOrderDefault | dataAlphaInfo, dataProvider.get(), 0, false, kCGRenderingIntentDefault));
614
615     auto cfData = adoptCF(CFDataCreateMutable(kCFAllocatorDefault, 0));
616     if (!encodeImage(image.get(), uti.get(), quality, cfData.get()))
617         return nullptr;
618
619     return WTFMove(cfData);
620 }
621
622 String dataURL(const ImageData& source, const String& mimeType, std::optional<double> quality)
623 {
624     if (auto data = cfData(source, mimeType, quality))
625         return dataURL(data.get(), mimeType);
626     return ASCIILiteral("data:,");
627 }
628
629 Vector<uint8_t> data(const ImageData& source, const String& mimeType, std::optional<double> quality)
630 {
631     if (auto data = cfData(source, mimeType, quality))
632         return dataVector(data.get());
633     return { };
634 }
635
636 } // namespace WebCore
637
638 #endif