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 59static CMProfileLocation sInitialProfileLocation; // The locType field is initialized to 0 which is the same as cmNoProfileBase 60 61void 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 74void 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 108static 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 137PassRefPtr<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 268PassRefPtr<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} 280