1 /* |
|
2 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved. |
|
3 * (C) 2007 Graham Dennis (graham.dennis@gmail.com) |
|
4 * (C) 2007 Eric Seidel <eric@webkit.org> |
|
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 * |
|
10 * 1. Redistributions of source code must retain the above copyright |
|
11 * notice, this list of conditions and the following disclaimer. |
|
12 * 2. Redistributions in binary form must reproduce the above copyright |
|
13 * notice, this list of conditions and the following disclaimer in the |
|
14 * documentation and/or other materials provided with the distribution. |
|
15 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
|
16 * its contributors may be used to endorse or promote products derived |
|
17 * from this software without specific prior written permission. |
|
18 * |
|
19 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
|
20 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
|
21 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
|
22 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
|
23 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
|
24 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
|
25 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
|
26 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|
28 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|
29 */ |
|
30 |
|
31 #include "config.h" |
|
32 #include "PixelDumpSupport.h" |
|
33 #include "PixelDumpSupportCG.h" |
|
34 |
|
35 #include "DumpRenderTree.h" |
|
36 #include "LayoutTestController.h" |
|
37 #include <CoreGraphics/CGBitmapContext.h> |
|
38 #include <wtf/Assertions.h> |
|
39 #include <wtf/RefPtr.h> |
|
40 |
|
41 #import <WebKit/WebCoreStatistics.h> |
|
42 #import <WebKit/WebDocumentPrivate.h> |
|
43 #import <WebKit/WebHTMLViewPrivate.h> |
|
44 #import <WebKit/WebKit.h> |
|
45 #import <WebKit/WebViewPrivate.h> |
|
46 |
|
47 #if defined(BUILDING_ON_TIGER) |
|
48 #include <OpenGL/OpenGL.h> |
|
49 #include <OpenGL/CGLMacro.h> |
|
50 #endif |
|
51 |
|
52 // To ensure pixel tests consistency, we need to always render in the same colorspace. |
|
53 // Unfortunately, because of AppKit / WebKit constraints, we can't render directly in the colorspace of our choice. |
|
54 // This implies we have to temporarily change the profile of the main display to the colorspace we want to render into. |
|
55 // We also need to make sure the CGBitmapContext we return is in that same colorspace. |
|
56 |
|
57 #define PROFILE_PATH "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" // FIXME: This cannot be more than CS_MAX_PATH (256 characters) |
|
58 |
|
59 static CMProfileLocation sInitialProfileLocation; // The locType field is initialized to 0 which is the same as cmNoProfileBase |
|
60 |
|
61 void restoreMainDisplayColorProfile(int ignored) |
|
62 { |
|
63 // This is used as a signal handler, and thus the calls into ColorSync are unsafe |
|
64 // But we might as well try to restore the user's color profile, we're going down anyway... |
|
65 if (sInitialProfileLocation.locType != cmNoProfileBase) { |
|
66 const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost }; |
|
67 int error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &sInitialProfileLocation); |
|
68 if (error) |
|
69 fprintf(stderr, "Failed to restore initial color profile for main display! Open System Preferences > Displays > Color and manually re-select the profile. (Error: %i)", error); |
|
70 sInitialProfileLocation.locType = cmNoProfileBase; |
|
71 } |
|
72 } |
|
73 |
|
74 void setupMainDisplayColorProfile() |
|
75 { |
|
76 const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost }; |
|
77 int error; |
|
78 |
|
79 CMProfileRef profile = 0; |
|
80 error = CMGetProfileByAVID((CMDisplayIDType)kCGDirectMainDisplay, &profile); |
|
81 if (!error) { |
|
82 UInt32 size = sizeof(CMProfileLocation); |
|
83 error = NCMGetProfileLocation(profile, &sInitialProfileLocation, &size); |
|
84 CMCloseProfile(profile); |
|
85 } |
|
86 if (error) { |
|
87 fprintf(stderr, "Failed to retrieve current color profile for main display, thus it won't be changed. Many pixel tests may fail as a result. (Error: %i)", error); |
|
88 sInitialProfileLocation.locType = cmNoProfileBase; |
|
89 return; |
|
90 } |
|
91 |
|
92 CMProfileLocation location; |
|
93 location.locType = cmPathBasedProfile; |
|
94 strcpy(location.u.pathLoc.path, PROFILE_PATH); |
|
95 error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &location); |
|
96 if (error) { |
|
97 fprintf(stderr, "Failed to set color profile for main display! Many pixel tests may fail as a result. (Error: %i)", error); |
|
98 sInitialProfileLocation.locType = cmNoProfileBase; |
|
99 return; |
|
100 } |
|
101 |
|
102 // Other signals are handled in installSignalHandlers() which also calls restoreMainDisplayColorProfile() |
|
103 signal(SIGINT, restoreMainDisplayColorProfile); |
|
104 signal(SIGHUP, restoreMainDisplayColorProfile); |
|
105 signal(SIGTERM, restoreMainDisplayColorProfile); |
|
106 } |
|
107 |
|
108 static PassRefPtr<BitmapContext> createBitmapContext(size_t pixelsWide, size_t pixelsHigh, size_t& rowBytes, void*& buffer) |
|
109 { |
|
110 rowBytes = (4 * pixelsWide + 63) & ~63; // Use a multiple of 64 bytes to improve CG performance |
|
111 |
|
112 buffer = calloc(pixelsHigh, rowBytes); |
|
113 if (!buffer) |
|
114 return 0; |
|
115 |
|
116 static CGColorSpaceRef colorSpace = 0; |
|
117 if (!colorSpace) { |
|
118 CMProfileLocation location; |
|
119 location.locType = cmPathBasedProfile; |
|
120 strcpy(location.u.pathLoc.path, PROFILE_PATH); |
|
121 CMProfileRef profile; |
|
122 if (CMOpenProfile(&profile, &location) == noErr) { |
|
123 colorSpace = CGColorSpaceCreateWithPlatformColorSpace(profile); |
|
124 CMCloseProfile(profile); |
|
125 } |
|
126 } |
|
127 |
|
128 CGContextRef context = CGBitmapContextCreate(buffer, pixelsWide, pixelsHigh, 8, rowBytes, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); // Use ARGB8 on PPC or BGRA8 on X86 to improve CG performance |
|
129 if (!context) { |
|
130 free(buffer); |
|
131 return 0; |
|
132 } |
|
133 |
|
134 return BitmapContext::createByAdoptingBitmapAndContext(buffer, context); |
|
135 } |
|
136 |
|
137 PassRefPtr<BitmapContext> createBitmapContextFromWebView(bool onscreen, bool incrementalRepaint, bool sweepHorizontally, bool drawSelectionRect) |
|
138 { |
|
139 WebView* view = [mainFrame webView]; |
|
140 |
|
141 // If the WebHTMLView uses accelerated compositing, we need for force the on-screen capture path |
|
142 // and also force Core Animation to start its animations with -display since the DRT window has autodisplay disabled. |
|
143 if ([view _isUsingAcceleratedCompositing]) |
|
144 onscreen = YES; |
|
145 |
|
146 NSSize webViewSize = [view frame].size; |
|
147 size_t pixelsWide = static_cast<size_t>(webViewSize.width); |
|
148 size_t pixelsHigh = static_cast<size_t>(webViewSize.height); |
|
149 size_t rowBytes = 0; |
|
150 void* buffer = 0; |
|
151 RefPtr<BitmapContext> bitmapContext = createBitmapContext(pixelsWide, pixelsHigh, rowBytes, buffer); |
|
152 if (!bitmapContext) |
|
153 return 0; |
|
154 CGContextRef context = bitmapContext->cgContext(); |
|
155 |
|
156 NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; |
|
157 ASSERT(nsContext); |
|
158 |
|
159 if (incrementalRepaint) { |
|
160 if (sweepHorizontally) { |
|
161 for (NSRect column = NSMakeRect(0, 0, 1, webViewSize.height); column.origin.x < webViewSize.width; column.origin.x++) |
|
162 [view displayRectIgnoringOpacity:column inContext:nsContext]; |
|
163 } else { |
|
164 for (NSRect line = NSMakeRect(0, 0, webViewSize.width, 1); line.origin.y < webViewSize.height; line.origin.y++) |
|
165 [view displayRectIgnoringOpacity:line inContext:nsContext]; |
|
166 } |
|
167 } else { |
|
168 |
|
169 if (onscreen) { |
|
170 #if !defined(BUILDING_ON_TIGER) |
|
171 // displayIfNeeded does not update the CA layers if the layer-hosting view was not marked as needing display, so |
|
172 // we're at the mercy of CA's display-link callback to update layers in time. So we need to force a display of the view |
|
173 // to get AppKit to update the CA layers synchronously. |
|
174 // FIXME: this will break repaint testing if we have compositing in repaint tests |
|
175 // (displayWebView() painted gray over the webview, but we'll be making everything repaint again). |
|
176 [view display]; |
|
177 |
|
178 // Ask the window server to provide us a composited version of the *real* window content including surfaces (i.e. OpenGL content) |
|
179 // Note that the returned image might differ very slightly from the window backing because of dithering artifacts in the window server compositor |
|
180 CGImageRef image = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, [[view window] windowNumber], kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque); |
|
181 CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); |
|
182 CGImageRelease(image); |
|
183 #else |
|
184 // On 10.4 and earlier, we have to move the window temporarily "onscreen" and read directly from the display framebuffer using OpenGL |
|
185 // In this code path, we need to ensure the window is above any other window or captured result will be corrupted |
|
186 |
|
187 NSWindow *window = [view window]; |
|
188 int oldLevel = [window level]; |
|
189 NSRect oldFrame = [window frame]; |
|
190 |
|
191 NSRect newFrame = [[[NSScreen screens] objectAtIndex:0] frame]; |
|
192 newFrame = NSMakeRect(newFrame.origin.x + (newFrame.size.width - oldFrame.size.width) / 2, newFrame.origin.y + (newFrame.size.height - oldFrame.size.height) / 2, oldFrame.size.width, oldFrame.size.height); |
|
193 [window setLevel:NSScreenSaverWindowLevel]; |
|
194 [window setFrame:newFrame display:NO animate:NO]; |
|
195 |
|
196 CGRect rect = CGRectMake(newFrame.origin.x, newFrame.origin.y, webViewSize.width, webViewSize.height); |
|
197 CGDirectDisplayID displayID; |
|
198 CGDisplayCount count; |
|
199 if (CGGetDisplaysWithRect(rect, 1, &displayID, &count) == kCGErrorSuccess) { |
|
200 CGRect bounds = CGDisplayBounds(displayID); |
|
201 rect.origin.x -= bounds.origin.x; |
|
202 rect.origin.y -= bounds.origin.y; |
|
203 |
|
204 CGLPixelFormatAttribute attributes[] = {kCGLPFAAccelerated, kCGLPFANoRecovery, kCGLPFAFullScreen, kCGLPFADisplayMask, (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(displayID), (CGLPixelFormatAttribute)0}; |
|
205 CGLPixelFormatObj pixelFormat; |
|
206 GLint num; |
|
207 if (CGLChoosePixelFormat(attributes, &pixelFormat, &num) == kCGLNoError) { |
|
208 CGLContextObj cgl_ctx; |
|
209 if (CGLCreateContext(pixelFormat, 0, &cgl_ctx) == kCGLNoError) { |
|
210 if (CGLSetFullScreen(cgl_ctx) == kCGLNoError) { |
|
211 void *flipBuffer = calloc(pixelsHigh, rowBytes); |
|
212 if (flipBuffer) { |
|
213 glPixelStorei(GL_PACK_ROW_LENGTH, rowBytes / 4); |
|
214 glPixelStorei(GL_PACK_ALIGNMENT, 4); |
|
215 #if __BIG_ENDIAN__ |
|
216 glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, flipBuffer); |
|
217 #else |
|
218 glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, flipBuffer); |
|
219 #endif |
|
220 if (!glGetError()) { |
|
221 for(size_t i = 0; i < pixelsHigh; ++i) |
|
222 bcopy((char*)flipBuffer + rowBytes * i, (char*)buffer + rowBytes * (pixelsHigh - i - 1), pixelsWide * 4); |
|
223 } |
|
224 |
|
225 free(flipBuffer); |
|
226 } |
|
227 } |
|
228 CGLDestroyContext(cgl_ctx); |
|
229 } |
|
230 CGLDestroyPixelFormat(pixelFormat); |
|
231 } |
|
232 } |
|
233 |
|
234 [window setFrame:oldFrame display:NO animate:NO]; |
|
235 [window setLevel:oldLevel]; |
|
236 #endif |
|
237 } else { |
|
238 // Make sure the view has been painted. |
|
239 [view displayIfNeeded]; |
|
240 |
|
241 // Grab directly the contents of the window backing buffer (this ignores any surfaces on the window) |
|
242 // FIXME: This path is suboptimal: data is read from window backing store, converted to RGB8 then drawn again into an RGBA8 bitmap |
|
243 [view lockFocus]; |
|
244 NSBitmapImageRep *imageRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]] autorelease]; |
|
245 [view unlockFocus]; |
|
246 |
|
247 RetainPtr<NSGraphicsContext> savedContext = [NSGraphicsContext currentContext]; |
|
248 [NSGraphicsContext setCurrentContext:nsContext]; |
|
249 [imageRep draw]; |
|
250 [NSGraphicsContext setCurrentContext:savedContext.get()]; |
|
251 } |
|
252 } |
|
253 |
|
254 if (drawSelectionRect) { |
|
255 NSView *documentView = [[mainFrame frameView] documentView]; |
|
256 ASSERT([documentView conformsToProtocol:@protocol(WebDocumentSelection)]); |
|
257 NSRect rect = [documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]; |
|
258 CGContextSaveGState(context); |
|
259 CGContextSetLineWidth(context, 1.0); |
|
260 CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); |
|
261 CGContextStrokeRect(context, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); |
|
262 CGContextRestoreGState(context); |
|
263 } |
|
264 |
|
265 return bitmapContext.release(); |
|
266 } |
|
267 |
|
268 PassRefPtr<BitmapContext> createPagedBitmapContext() |
|
269 { |
|
270 int pageWidthInPixels = LayoutTestController::maxViewWidth; |
|
271 int pageHeightInPixels = LayoutTestController::maxViewHeight; |
|
272 int numberOfPages = [mainFrame numberOfPages:pageWidthInPixels:pageHeightInPixels]; |
|
273 size_t rowBytes = 0; |
|
274 void* buffer = 0; |
|
275 |
|
276 RefPtr<BitmapContext> bitmapContext = createBitmapContext(pageWidthInPixels, numberOfPages * (pageHeightInPixels + 1) - 1, rowBytes, buffer); |
|
277 [mainFrame printToCGContext:bitmapContext->cgContext():pageWidthInPixels:pageHeightInPixels]; |
|
278 return bitmapContext.release(); |
|
279 } |
|