Modernize some aspects of text codecs, eliminate WebKit use of strcasecmp
[WebKit-https.git] / Tools / WebKitTestRunner / cg / TestInvocationCG.cpp
1 /*
2  * Copyright (C) 2011 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "TestInvocation.h"
28
29 #include "PixelDumpSupport.h"
30 #include "PlatformWebView.h"
31 #include "TestController.h"
32 #include <ImageIO/CGImageDestination.h>
33 #include <WebKit/WKImageCG.h>
34 #include <wtf/MD5.h>
35 #include <wtf/RetainPtr.h>
36
37 #if PLATFORM(MAC) && !PLATFORM(IOS)
38 #include <LaunchServices/UTCoreTypes.h>
39 #endif
40
41 #if PLATFORM(IOS)
42 // FIXME: get kUTTypePNG from MobileCoreServices on iOS
43 static const CFStringRef kUTTypePNG = CFSTR("public.png");
44 #endif
45
46 namespace WTR {
47
48 static CGContextRef createCGContextFromCGImage(CGImageRef image)
49 {
50     size_t pixelsWide = CGImageGetWidth(image);
51     size_t pixelsHigh = CGImageGetHeight(image);
52     size_t rowBytes = (4 * pixelsWide + 63) & ~63;
53
54     // Creating this bitmap in the device color space should prevent any color conversion when the image of the web view is drawn into it.
55     RetainPtr<CGColorSpaceRef> colorSpace = adoptCF(CGColorSpaceCreateDeviceRGB());
56     CGContextRef context = CGBitmapContextCreate(0, pixelsWide, pixelsHigh, 8, rowBytes, colorSpace.get(), kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
57     if (!context)
58         return nullptr;
59
60     CGContextDrawImage(context, CGRectMake(0, 0, pixelsWide, pixelsHigh), image);
61     return context;
62 }
63
64 static CGContextRef createCGContextFromImage(WKImageRef wkImage)
65 {
66     RetainPtr<CGImageRef> image = adoptCF(WKImageCreateCGImage(wkImage));
67     return createCGContextFromCGImage(image.get());
68 }
69
70 void computeMD5HashStringForContext(CGContextRef bitmapContext, char hashString[33])
71 {
72     if (!bitmapContext) {
73         WTFLogAlways("computeMD5HashStringForContext: context is null\n");
74         return;
75     }
76     ASSERT(CGBitmapContextGetBitsPerPixel(bitmapContext) == 32); // ImageDiff assumes 32 bit RGBA, we must as well.
77     size_t pixelsHigh = CGBitmapContextGetHeight(bitmapContext);
78     size_t pixelsWide = CGBitmapContextGetWidth(bitmapContext);
79     size_t bytesPerRow = CGBitmapContextGetBytesPerRow(bitmapContext);
80     
81     // We need to swap the bytes to ensure consistent hashes independently of endianness
82     MD5 md5;
83     unsigned char* bitmapData = static_cast<unsigned char*>(CGBitmapContextGetData(bitmapContext));
84 #if PLATFORM(COCOA)
85     if ((CGBitmapContextGetBitmapInfo(bitmapContext) & kCGBitmapByteOrderMask) == kCGBitmapByteOrder32Big) {
86         for (unsigned row = 0; row < pixelsHigh; row++) {
87             Vector<uint8_t> buffer(4 * pixelsWide);
88             for (unsigned column = 0; column < pixelsWide; column++)
89                 buffer[column] = OSReadLittleInt32(bitmapData, 4 * column);
90             md5.addBytes(buffer);
91             bitmapData += bytesPerRow;
92         }
93     } else
94 #endif
95     {
96         for (unsigned row = 0; row < pixelsHigh; row++) {
97             md5.addBytes(bitmapData, 4 * pixelsWide);
98             bitmapData += bytesPerRow;
99         }
100     }
101
102     MD5::Digest hash;
103     md5.checksum(hash);
104
105     hashString[0] = '\0';
106     for (size_t i = 0; i < MD5::hashSize; i++)
107         snprintf(hashString, 33, "%s%02x", hashString, hash[i]);
108 }
109
110 static void dumpBitmap(CGContextRef bitmapContext, const char* checksum)
111 {
112     RetainPtr<CGImageRef> image = adoptCF(CGBitmapContextCreateImage(bitmapContext));
113     RetainPtr<CFMutableDataRef> imageData = adoptCF(CFDataCreateMutable(0, 0));
114     RetainPtr<CGImageDestinationRef> imageDest = adoptCF(CGImageDestinationCreateWithData(imageData.get(), kUTTypePNG, 1, 0));
115     CGImageDestinationAddImage(imageDest.get(), image.get(), 0);
116     CGImageDestinationFinalize(imageDest.get());
117
118     const unsigned char* data = CFDataGetBytePtr(imageData.get());
119     const size_t dataLength = CFDataGetLength(imageData.get());
120
121     printPNG(data, dataLength, checksum);
122 }
123
124 static void paintRepaintRectOverlay(CGContextRef context, WKSize imageSize, WKArrayRef repaintRects)
125 {
126     CGContextSaveGState(context);
127
128     // Using a transparency layer is easier than futzing with clipping.
129     CGContextBeginTransparencyLayer(context, 0);
130     
131     // Flip the context.
132     CGContextScaleCTM(context, 1, -1);
133     CGContextTranslateCTM(context, 0, -imageSize.height);
134     
135     CGContextSetRGBFillColor(context, 0, 0, 0, static_cast<CGFloat>(0.66));
136     CGContextFillRect(context, CGRectMake(0, 0, imageSize.width, imageSize.height));
137
138     // Clear the repaint rects.
139     size_t count = WKArrayGetSize(repaintRects);
140     for (size_t i = 0; i < count; ++i) {
141         WKRect rect = WKRectGetValue(static_cast<WKRectRef>(WKArrayGetItemAtIndex(repaintRects, i)));
142         CGRect cgRect = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height);
143         CGContextClearRect(context, cgRect);
144     }
145     
146     CGContextEndTransparencyLayer(context);
147     CGContextRestoreGState(context);
148 }
149
150 void TestInvocation::dumpPixelsAndCompareWithExpected(SnapshotResultType snapshotType, WKArrayRef repaintRects, WKImageRef wkImage)
151 {
152     RetainPtr<CGContextRef> context;
153     WKSize imageSize;
154
155     switch (snapshotType) {
156     case SnapshotResultType::WebContents:
157         if (!wkImage) {
158             WTFLogAlways("dumpPixelsAndCompareWithExpected: image is null\n");
159             return;
160         }
161         context = adoptCF(createCGContextFromImage(wkImage));
162         imageSize = WKImageGetSize(wkImage);
163         break;
164     case SnapshotResultType::WebView:
165         auto image = TestController::singleton().mainWebView()->windowSnapshotImage();
166         if (!image) {
167             WTFLogAlways("dumpPixelsAndCompareWithExpected: image is null\n");
168             return;
169         }
170         context = adoptCF(createCGContextFromCGImage(image.get()));
171         imageSize = WKSizeMake(CGImageGetWidth(image.get()), CGImageGetHeight(image.get()));
172         break;
173     }
174
175     if (!context) {
176         WTFLogAlways("dumpPixelsAndCompareWithExpected: context is null\n");
177         return;
178     }
179
180     // A non-null repaintRects array means we're doing a repaint test.
181     if (repaintRects)
182         paintRepaintRectOverlay(context.get(), imageSize, repaintRects);
183
184     char actualHash[33];
185     computeMD5HashStringForContext(context.get(), actualHash);
186     if (!compareActualHashToExpectedAndDumpResults(actualHash))
187         dumpBitmap(context.get(), actualHash);
188 }
189
190 } // namespace WTR