summaryrefslogtreecommitdiffstats
path: root/WebKitTools/DumpRenderTree/mac/PixelDumpSupportMac.mm
blob: d17c11101a2e0c518bd9daaa622dffe55dc16b4c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
/*
 * Copyright (C) 2005, 2006, 2007 Apple Inc. All rights reserved.
 *           (C) 2007 Graham Dennis (graham.dennis@gmail.com)
 *           (C) 2007 Eric Seidel <eric@webkit.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer. 
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution. 
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission. 
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "DumpRenderTree.h" 
#include "PixelDumpSupport.h"
#include "PixelDumpSupportCG.h"

#include "LayoutTestController.h"
#include <CoreGraphics/CGBitmapContext.h>
#ifndef BUILDING_ON_LEOPARD
#include <OpenGL/OpenGL.h>
#include <OpenGL/CGLMacro.h>
#endif
#include <wtf/Assertions.h>
#include <wtf/RefPtr.h>

#import <WebKit/WebDocumentPrivate.h>
#import <WebKit/WebHTMLViewPrivate.h>
#import <WebKit/WebKit.h>
#import <WebKit/WebViewPrivate.h>

// To ensure pixel tests consistency, we need to always render in the same colorspace.
// Unfortunately, because of AppKit / WebKit constraints, we can't render directly in the colorspace of our choice.
// This implies we have to temporarily change the profile of the main display to the colorspace we want to render into.
// We also need to make sure the CGBitmapContext we return is in that same colorspace.

#define PROFILE_PATH "/System/Library/ColorSync/Profiles/Generic RGB Profile.icc" // FIXME: This cannot be more than CS_MAX_PATH (256 characters)

static CMProfileLocation sInitialProfileLocation; // The locType field is initialized to 0 which is the same as cmNoProfileBase

void restoreMainDisplayColorProfile(int ignored)
{
    // This is used as a signal handler, and thus the calls into ColorSync are unsafe
    // But we might as well try to restore the user's color profile, we're going down anyway...
    if (sInitialProfileLocation.locType != cmNoProfileBase) {
        const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost };
        int error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &sInitialProfileLocation);
        if (error)
            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);
        sInitialProfileLocation.locType = cmNoProfileBase;
    }
}

void setupMainDisplayColorProfile()
{
    const CMDeviceScope scope = { kCFPreferencesCurrentUser, kCFPreferencesCurrentHost };
    int error;
    
    CMProfileRef profile = 0;
    error = CMGetProfileByAVID((CMDisplayIDType)kCGDirectMainDisplay, &profile);
    if (!error) {
        UInt32 size = sizeof(CMProfileLocation);
        error = NCMGetProfileLocation(profile, &sInitialProfileLocation, &size);
        CMCloseProfile(profile);
    }
    if (error) {
        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);
        sInitialProfileLocation.locType = cmNoProfileBase;
        return;
    }
    
    CMProfileLocation location;
    location.locType = cmPathBasedProfile;
    strcpy(location.u.pathLoc.path, PROFILE_PATH);
    error = CMSetDeviceProfile(cmDisplayDeviceClass, (CMDeviceID)kCGDirectMainDisplay, &scope, cmDefaultProfileID, &location);
    if (error) {
        fprintf(stderr, "Failed to set color profile for main display!  Many pixel tests may fail as a result.  (Error: %i)", error);
        sInitialProfileLocation.locType = cmNoProfileBase;
        return;
    }
    
    // Other signals are handled in installSignalHandlers() which also calls restoreMainDisplayColorProfile()
    signal(SIGINT, restoreMainDisplayColorProfile);
    signal(SIGHUP, restoreMainDisplayColorProfile);
    signal(SIGTERM, restoreMainDisplayColorProfile);
}

PassRefPtr<BitmapContext> createBitmapContextFromWebView(bool onscreen, bool incrementalRepaint, bool sweepHorizontally, bool drawSelectionRect)
{
    WebView* view = [mainFrame webView];

    // If the WebHTMLView uses accelerated compositing, we need for force the on-screen capture path
    // and also force Core Animation to start its animations with -display since the DRT window has autodisplay disabled.
    if ([view _isUsingAcceleratedCompositing])
        onscreen = YES;

    NSSize webViewSize = [view frame].size;
    size_t pixelsWide = static_cast<size_t>(webViewSize.width);
    size_t pixelsHigh = static_cast<size_t>(webViewSize.height);
    size_t rowBytes = (4 * pixelsWide + 63) & ~63; // Use a multiple of 64 bytes to improve CG performance

    void *buffer = calloc(pixelsHigh, rowBytes);
    if (!buffer)
        return 0;
    
    static CGColorSpaceRef colorSpace = 0;
    if (!colorSpace) {
        CMProfileLocation location;
        location.locType = cmPathBasedProfile;
        strcpy(location.u.pathLoc.path, PROFILE_PATH);
        CMProfileRef profile;
        if (CMOpenProfile(&profile, &location) == noErr) {
            colorSpace = CGColorSpaceCreateWithPlatformColorSpace(profile);
            CMCloseProfile(profile);
        }
    }
    
    CGContextRef context = CGBitmapContextCreate(buffer, pixelsWide, pixelsHigh, 8, rowBytes, colorSpace, kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host); // Use ARGB8 on PPC or BGRA8 on X86 to improve CG performance
    if (!context) {
        free(buffer);
        return 0;
    }

    // The BitmapContext keeps the CGContextRef and the pixel buffer alive
    RefPtr<BitmapContext> bitmapContext = BitmapContext::createByAdoptingBitmapAndContext(buffer, context);
    
    NSGraphicsContext *nsContext = [NSGraphicsContext graphicsContextWithGraphicsPort:context flipped:NO];
    ASSERT(nsContext);
    
    if (incrementalRepaint) {
        if (sweepHorizontally) {
            for (NSRect column = NSMakeRect(0, 0, 1, webViewSize.height); column.origin.x < webViewSize.width; column.origin.x++)
                [view displayRectIgnoringOpacity:column inContext:nsContext];
        } else {
            for (NSRect line = NSMakeRect(0, 0, webViewSize.width, 1); line.origin.y < webViewSize.height; line.origin.y++)
                [view displayRectIgnoringOpacity:line inContext:nsContext];
        }
    } else {
        if (onscreen) {
#ifdef BUILDING_ON_LEOPARD
            // Ask the window server to provide us a composited version of the *real* window content including surfaces (i.e. OpenGL content)
            // Note that the returned image might differ very slightly from the window backing because of dithering artifacts in the window server compositor
            
            CGImageRef image = CGWindowListCreateImage(CGRectNull, kCGWindowListOptionIncludingWindow, [[view window] windowNumber], kCGWindowImageBoundsIgnoreFraming | kCGWindowImageShouldBeOpaque);
            CGContextDrawImage(context, CGRectMake(0, 0, CGImageGetWidth(image), CGImageGetHeight(image)), image);
            CGImageRelease(image);
#else
            // On 10.4 and earlier, we have to move the window temporarily "onscreen" and read directly from the display framebuffer using OpenGL
            // In this code path, we need to ensure the window is above any other window or captured result will be corrupted
            
            NSWindow *window = [view window];
            int oldLevel = [window level];
            NSRect oldFrame = [window frame];
            
            NSRect newFrame = [[[NSScreen screens] objectAtIndex:0] frame];
            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);
            [window setLevel:NSScreenSaverWindowLevel];
            [window setFrame:newFrame display:NO animate:NO];
            
            CGRect rect = CGRectMake(newFrame.origin.x, newFrame.origin.y, webViewSize.width, webViewSize.height);
            CGDirectDisplayID displayID;
            CGDisplayCount count;
            if (CGGetDisplaysWithRect(rect, 1, &displayID, &count) == kCGErrorSuccess) {
                CGRect bounds = CGDisplayBounds(displayID);
                rect.origin.x -= bounds.origin.x;
                rect.origin.y -= bounds.origin.y;
                
                CGLPixelFormatAttribute attributes[] = {kCGLPFAAccelerated, kCGLPFANoRecovery, kCGLPFAFullScreen, kCGLPFADisplayMask, (CGLPixelFormatAttribute)CGDisplayIDToOpenGLDisplayMask(displayID), (CGLPixelFormatAttribute)0};
                CGLPixelFormatObj pixelFormat;
                GLint num;
                if (CGLChoosePixelFormat(attributes, &pixelFormat, &num) == kCGLNoError) {
                    CGLContextObj cgl_ctx;
                    if (CGLCreateContext(pixelFormat, 0, &cgl_ctx) == kCGLNoError) {
                        if (CGLSetFullScreen(cgl_ctx) == kCGLNoError) {
                            void *flipBuffer = calloc(pixelsHigh, rowBytes);
                            if (flipBuffer) {
                                glPixelStorei(GL_PACK_ROW_LENGTH, rowBytes / 4);
                                glPixelStorei(GL_PACK_ALIGNMENT, 4);
#if __BIG_ENDIAN__
                                glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8, flipBuffer);
#else
                                glReadPixels(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height, GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV, flipBuffer);
#endif
                                if (!glGetError()) {
                                    for(size_t i = 0; i < pixelsHigh; ++i)
                                    bcopy((char*)flipBuffer + rowBytes * i, (char*)buffer + rowBytes * (pixelsHigh - i - 1), pixelsWide * 4);
                                }
                                
                                free(flipBuffer);
                            }
                        }
                        CGLDestroyContext(cgl_ctx);
                    }
                    CGLDestroyPixelFormat(pixelFormat);
                }
            }
            
            [window setFrame:oldFrame display:NO animate:NO];
            [window setLevel:oldLevel];
#endif
        } else {
            // Grab directly the contents of the window backing buffer (this ignores any surfaces on the window)
            // FIXME: This path is suboptimal: data is read from window backing store, converted to RGB8 then drawn again into an RGBA8 bitmap
            
            [view displayIfNeeded];
            [view lockFocus];
            NSBitmapImageRep *imageRep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:[view frame]] autorelease];
            [view unlockFocus];

            RetainPtr<NSGraphicsContext> savedContext = [NSGraphicsContext currentContext];
            [NSGraphicsContext setCurrentContext:nsContext];
            [imageRep draw];
            [NSGraphicsContext setCurrentContext:savedContext.get()];
        }
    }

    if (drawSelectionRect) {
        NSView *documentView = [[mainFrame frameView] documentView];
        ASSERT([documentView conformsToProtocol:@protocol(WebDocumentSelection)]);
        NSRect rect = [documentView convertRect:[(id <WebDocumentSelection>)documentView selectionRect] fromView:nil];
        CGContextSaveGState(context);
        CGContextSetLineWidth(context, 1.0);
        CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0);
        CGContextStrokeRect(context, CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height));
        CGContextRestoreGState(context);
    }
    
    return bitmapContext.release();
}