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/WebDocumentPrivate.h> 42#import <WebKit/WebHTMLViewPrivate.h> 43#import <WebKit/WebKit.h> 44#import <WebKit/WebViewPrivate.h> 45 46#if defined(BUILDING_ON_TIGER) 47#include <OpenGL/OpenGL.h> 48#include <OpenGL/CGLMacro.h> 49#endif 50 51// To ensure pixel tests consistency, we need to always render in the same colorspace. 52// Unfortunately, because of AppKit / WebKit constraints, we can't render directly in the colorspace of our choice. 53// This implies we have to temporarily change the profile of the main display to the colorspace we want to render into. 54// We also need to make sure the CGBitmapContext we return is in that same colorspace. 55 56#define PROFILE_PATH "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" // FIXME: This cannot be more than CS_MAX_PATH (256 characters) 57 58static CMProfileLocation sInitialProfileLocation; // The locType field is initialized to 0 which is the same as cmNoProfileBase 59 60void restoreMainDisplayColorProfile(int ignored) 61{ 62 // This is used as a signal handler, and thus the calls into ColorSync are unsafe 63 // But we might as well try to restore the user's color profile, we're going down anyway... 64 if (sInitialProfileLocation.locType != cmNoProfileBase) { 65 const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost }; 66 int error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &sInitialProfileLocation); 67 if (error) 68 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); 69 sInitialProfileLocation.locType = cmNoProfileBase; 70 } 71} 72 73void setupMainDisplayColorProfile() 74{ 75 const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost }; 76 int error; 77 78 CMProfileRef profile = 0; 79 error = CMGetProfileByAVID((CMDisplayIDType)kCGDirectMainDisplay, &profile); 80 if (!error) { 81 UInt32 size = sizeof(CMProfileLocation); 82 error = NCMGetProfileLocation(profile, &sInitialProfileLocation, &size); 83 CMCloseProfile(profile); 84 } 85 if (error) { 86 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); 87 sInitialProfileLocation.locType = cmNoProfileBase; 88 return; 89 } 90 91 CMProfileLocation location; 92 location.locType = cmPathBasedProfile; 93 strcpy(location.u.pathLoc.path, PROFILE_PATH); 94 error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &location); 95 if (error) { 96 fprintf(stderr, "Failed to set color profile for main display! Many pixel tests may fail as a result. (Error: %i)", error); 97 sInitialProfileLocation.locType = cmNoProfileBase; 98 return; 99 } 100 101 // Other signals are handled in installSignalHandlers() which also calls restoreMainDisplayColorProfile() 102 signal(SIGINT, restoreMainDisplayColorProfile); 103 signal(SIGHUP, restoreMainDisplayColorProfile); 104 signal(SIGTERM, restoreMainDisplayColorProfile); 105} 106 107PassRefPtr<BitmapContext> createBitmapContextFromWebView(bool onscreen, bool incrementalRepaint, bool sweepHorizontally, bool drawSelectionRect) 108{ 109 WebView* view = [mainFrame webView]; 110 111 // If the WebHTMLView uses accelerated compositing, we need for force the on-screen capture path 112 // and also force Core Animation to start its animations with -display since the DRT window has autodisplay disabled. 113 if ([view _isUsingAcceleratedCompositing]) 114 onscreen = YES; 115 116 NSSize webViewSize = [view frame].size; 117 size_t pixelsWide = static_cast<size_t>(webViewSize.width); 118 size_t pixelsHigh = static_cast<size_t>(webViewSize.height); 119 size_t rowBytes = (4 * pixelsWide + 63) & ~63; // Use a multiple of 64 bytes to improve CG performance 120 121 void *buffer = calloc(pixelsHigh, rowBytes); 122 if (!buffer) 123 return 0; 124 125 static CGColorSpaceRef colorSpace = 0; 126 if (!colorSpace) { 127 CMProfileLocation location; 128 location.locType = cmPathBasedProfile; 129 strcpy(location.u.pathLoc.path, PROFILE_PATH); 130 CMProfileRef profile; 131 if (CMOpenProfile(&profile, &location) == noErr) { 132 colorSpace = CGColorSpaceCreateWithPlatformColorSpace(profile); 133 CMCloseProfile(profile); 134 } 135 } 136 137 CGContextRef context = CGBitmapContextCreate(buffer, pixelsWide, pixelsHigh, 8, rowBytes, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); // Use ARGB8 on PPC or BGRA8 on X86 to improve CG performance 138 if (!context) { 139 free(buffer); 140 return 0; 141 } 142 143 // The BitmapContext keeps the CGContextRef and the pixel buffer alive 144 RefPtr<BitmapContext> bitmapContext = BitmapContext::createByAdoptingBitmapAndContext(buffer, context); 145 146 NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO]; 147 ASSERT(nsContext); 148 149 if (incrementalRepaint) { 150 if (sweepHorizontally) { 151 for (NSRect column = NSMakeRect(0, 0, 1, webViewSize.height); column.origin.x < webViewSize.width; column.origin.x++) 152 [view displayRectIgnoringOpacity:column inContext:nsContext]; 153 } else { 154 for (NSRect line = NSMakeRect(0, 0, webViewSize.width, 1); line.origin.y < webViewSize.height; line.origin.y++) 155 [view displayRectIgnoringOpacity:line inContext:nsContext]; 156 } 157 } else { 158 159 if (onscreen) { 160#if !defined(BUILDING_ON_TIGER) 161 // displayIfNeeded does not update the CA layers if the layer-hosting view was not marked as needing display, so 162 // 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 163 // to get AppKit to update the CA layers synchronously. 164 // FIXME: this will break repaint testing if we have compositing in repaint tests 165 // (displayWebView() painted gray over the webview, but we'll be making everything repaint again). 166 [view display]; 167 168 // Ask the window server to provide us a composited version of the *real* window content including surfaces (i.e. OpenGL content) 169 // Note that the returned image might differ very slightly from the window backing because of dithering artifacts in the window server compositor 170 CGImageRef image = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, [[view window] windowNumber], kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque); 171 CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image); 172 CGImageRelease(image); 173#else 174 // On 10.4 and earlier, we have to move the window temporarily "onscreen" and read directly from the display framebuffer using OpenGL 175 // In this code path, we need to ensure the window is above any other window or captured result will be corrupted 176 177 NSWindow *window = [view window]; 178 int oldLevel = [window level]; 179 NSRect oldFrame = [window frame]; 180 181 NSRect newFrame = [[[NSScreen screens] objectAtIndex:0] frame]; 182 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); 183 [window setLevel:NSScreenSaverWindowLevel]; 184 [window setFrame:newFrame display:NO animate:NO]; 185 186 CGRect rect = CGRectMake(newFrame.origin.x, newFrame.origin.y, webViewSize.width, webViewSize.height); 187 CGDirectDisplayID displayID; 188 CGDisplayCount count; 189 if (CGGetDisplaysWithRect(rect, 1, &displayID, &count) == kCGErrorSuccess) { 190 CGRect bounds = CGDisplayBounds(displayID); 191 rect.origin.x -= bounds.origin.x; 192 rect.origin.y -= bounds.origin.y; 193 194 CGLPixelFormatAttribute attributes[] = {kCGLPFAAccelerated, kCGLPFANoRecovery, kCGLPFAFullScreen, kCGLPFADisplayMask, (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(displayID), (CGLPixelFormatAttribute)0}; 195 CGLPixelFormatObj pixelFormat; 196 GLint num; 197 if (CGLChoosePixelFormat(attributes, &pixelFormat, &num) == kCGLNoError) { 198 CGLContextObj cgl_ctx; 199 if (CGLCreateContext(pixelFormat, 0, &cgl_ctx) == kCGLNoError) { 200 if (CGLSetFullScreen(cgl_ctx) == kCGLNoError) { 201 void *flipBuffer = calloc(pixelsHigh, rowBytes); 202 if (flipBuffer) { 203 glPixelStorei(GL_PACK_ROW_LENGTH, rowBytes / 4); 204 glPixelStorei(GL_PACK_ALIGNMENT, 4); 205#if __BIG_ENDIAN__ 206 glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, flipBuffer); 207#else 208 glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, flipBuffer); 209#endif 210 if (!glGetError()) { 211 for(size_t i = 0; i < pixelsHigh; ++i) 212 bcopy((char*)flipBuffer + rowBytes * i, (char*)buffer + rowBytes * (pixelsHigh - i - 1), pixelsWide * 4); 213 } 214 215 free(flipBuffer); 216 } 217 } 218 CGLDestroyContext(cgl_ctx); 219 } 220 CGLDestroyPixelFormat(pixelFormat); 221 } 222 } 223 224 [window setFrame:oldFrame display:NO animate:NO]; 225 [window setLevel:oldLevel]; 226#endif 227 } else { 228 // Make sure the view has been painted. 229 [view displayIfNeeded]; 230 231 // Grab directly the contents of the window backing buffer (this ignores any surfaces on the window) 232 // FIXME: This path is suboptimal: data is read from window backing store, converted to RGB8 then drawn again into an RGBA8 bitmap 233 [view lockFocus]; 234 NSBitmapImageRep *imageRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]] autorelease]; 235 [view unlockFocus]; 236 237 RetainPtr<NSGraphicsContext> savedContext = [NSGraphicsContext currentContext]; 238 [NSGraphicsContext setCurrentContext:nsContext]; 239 [imageRep draw]; 240 [NSGraphicsContext setCurrentContext:savedContext.get()]; 241 } 242 } 243 244 if (drawSelectionRect) { 245 NSView *documentView = [[mainFrame frameView] documentView]; 246 ASSERT([documentView conformsToProtocol:@protocol(WebDocumentSelection)]); 247 NSRect rect = [documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil]; 248 CGContextSaveGState(context); 249 CGContextSetLineWidth(context, 1.0); 250 CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); 251 CGContextStrokeRect(context, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height)); 252 CGContextRestoreGState(context); 253 } 254 255 return bitmapContext.release(); 256} 257