function PanelFitterDemo
% PanelFitterDemo - Demonstrate use of the panel fitter.
%
% This demo shows how to use Screen()'s panel fitter function by using the
% high-level setup code in PsychImaging().
%
% The panelfitter allows to implement "rotated framebuffers", which have a
% shape (width and height in pixels) that corresponds to a display device
% that is rotated by increments of 90 degrees with respect to its "natural"
% upright orientation (typically landscape). E.g., assume you have a flat
% panel with a natural landscape orientation - more wide than high - and a
% resolution of 1920 x 1080 pixels. Now for some reason you want to turn
% your panel into "portrait" orientation - more high than wide - by
% rotating it by 90 degrees. Drawing stimulus images for such a rotated
% display would be inconvenient, as everything would be shown tilted by 90
% degrees to your subject. Here the panelfitter can help by providing a
% framebuffer that is rotated by 90 degrees wrt. its normal shape, ie., the
% framebuffer is 1080 x 1920 pixels in size instead of its natural 1920 x
% 1080 pixels. Drawing into such a framebuffer becomes natural and easy
% again, as the shape and orientation of the framebuffer corresponds to the
% effective shape and orientation of your display device.
%
% Such rotations can be requested from the panelfitter by the
% PsychImaging('AddTask', 'General', 'UseDisplayRotation', angle); command.
%
% Why not simply use the "Rotate display" buttons or settings in the
% display control GUI of your operating system? Because the method by which
% operating systems and graphics cards do implement that display rotation
% will usually severely interfere with visual stimulus onset timing and/or
% stimulus onset timestamping -- you lose control over your stimulus
% presentation timing. Our panelfitter makes sure timing stays under
% control.
%
% Another application of the panelfitter is if you want to operate your
% flat panel or video projector at a different resolution or image size
% than its native resolution. Good old CRT monitors didn't have a native
% resolution, they were happy to display at a wide range of different
% resolutions and refresh rates. Modern flat panels and projectors do have
% only one true native resolution, which is their maximum resolution, due
% to their fixed pixel grid. If you want to set such a display to a
% different resolution, your graphics hardware or display device will use
% digital image processing to scale and resize the input video signal from
% its set resolution to the native resolution of the device. This
% introduces lag due to processing delays, and possibly non-deterministic
% timing variance in visual stimulus onset, therefore you should avoid such
% non-native resolutions and run your displays at native (maximum)
% resolution. If you still want to display stimulus images at a different
% size than the panels native resolution, the panelfitter can help you. It
% provides you with a framebuffer of selectable size and then performs the
% scaling and resizing of the stimulus image in that framebuffer to the
% true size of the panels framebuffer resolution. In other words, it fits
% the content onto the panel, hence the name "panelfitter". You can use the
% PsychImaging('AddTask', 'General', 'UsePanelFitter', fitSize, 'Centered');
% command to define the size of your framebuffer ('fitSize') and the
% strategy for placing and scaling the image onto the panel.
%
% Again, operating systems allow you to select different resolutions and
% fitting modes in their display GUI controls, but the methods by which
% non-native resolutions and fittings are implemented by current operating
% systems and graphics cards can severely interfere with stimulus onset
% timing and timestamping, or with low level stimulus properties, due to
% the digital filtering, resampling and blurring involved. Our panelfitter
% prevents timing artifacts and gives you more control over other
% properties.
%
% Please note that use of our panelfitter is also not totally free:
%
% a) Currently display rotation and image scaling don't go well together.
%    One or the other works well, but both combined can exhibit bugs and
%    artifacts. These are limitations of our current high-level setup code.
%    You could probably use the Screen('PanelFitter') low-level setup
%    command directly to get a well working solution for your specific
%    needs, should you require scaling and rotation at the same time. Or
%    you could fix and extend our high-level setup code in PsychImaging.m.
%
% b) Multisample anti-aliasing does not work together with display rotation
%    on older graphics hardware, or with Apple OSX versions up to and
%    including 10.9 "Mavericks". It will work on modern GPU's under Linux
%    or Windows if they at least support OpenGL 3.2, but not on the same
%    GPU's under OSX, as this is a purely political restriction introduced
%    by Apple, making OSX less useful than it could be.
%
% c) There is a small performance impact on the order of < 1 millisecond
%    per Screen('Flip') with modern graphics cards due to the processing
%    involved.
%

% History:
% 29-Sep-2013 mk   Written.

% Set unified keymappings and normalized color range:
PsychDefaultSetup(2);

% Display on external screen, if any:
screenId = max(Screen('Screens'));

% Disable sync tests for this simple demo to speed up the whole thing:
oldsynctest = Screen('Preference','SkipSyncTests', 2);

try
    % First show display rotation:
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Rotate framebuffer clockwise for portrait mode:
    PsychImaging('AddTask', 'General', 'UseDisplayRotation', 90);
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 48);
    DrawFormattedText(w, '<--- Hello World! --->\nTilt me counter-clockwise.\nPress key to continue.', 'center', 'center', [0,0,1]);
    
    % Wait for a key press:
    ShowUntilKey(w);

    % Close down:
    sca;
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Rotate framebuffer counter-clockwise for portrait mode:
    PsychImaging('AddTask', 'General', 'UseDisplayRotation', -90);
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 48);
    DrawFormattedText(w, '<--- Hello World! --->\nTilt me clockwise.\nPress key to continue.', 'center', 'center', [0,0,1]);

    % Wait for a key press:
    ShowUntilKey(w);
    
    % Close down:
    sca;
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Rotate framebuffer 180 degrees for upside down landscape mode:
    PsychImaging('AddTask', 'General', 'UseDisplayRotation', 180);
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 48);
    DrawFormattedText(w, '<--- Hello World! --->\nYou spin me right round baby right round!\nPress key to continue.', 'center', 'center', [0,0,1]);

    % Wait for a key press:
    ShowUntilKey(w);
    
    % Close down:
    sca;
    
    % Show some scaling. Define a framebuffer one quarter the size of the
    % real screen:
    fitSize = [RectWidth(Screen('Rect', screenId)), RectHeight(Screen('Rect', screenId))] * 0.25;
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Center small framebuffer inside big framebuffer:
    PsychImaging('AddTask', 'General', 'UsePanelFitter', fitSize, 'Centered');
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 24);
    DrawFormattedText(w, '<--- Hello World! --->\nI am feeling small but\ncentered today!\nPress key to continue.', 'center', 'center', [0,0,1]);

    % Wait for a key press:
    ShowUntilKey(w);

    % Close down:
    sca;
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Center small framebuffer inside big framebuffer. Scale it up to
    % maximum size while preserving aspect ration of the original
    % framebuffer:
    PsychImaging('AddTask', 'General', 'UsePanelFitter', fitSize, 'Aspect');
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 24);
    DrawFormattedText(w, '<--- Hello World! --->\nI am feeling big and blurry.\nA nice new aspect!\nPress key to continue.', 'center', 'center', [0,0,1]);

    % Wait for a key press:
    ShowUntilKey(w);
    
    % Close down:
    sca;
    
    % Prepare imaging pipeline for some fitting work:
    PsychImaging('PrepareConfiguration');
    
    % Center small framebuffer inside big framebuffer. Scale it up to
    % maximum size, even if it would not preserve aspect ratio:
    PsychImaging('AddTask', 'General', 'UsePanelFitter', fitSize, 'Full');
    
    % Open a fullscreen window, with a 50% gray background:
    w = PsychImaging('OpenWindow', screenId, 0.5, [], [], [], [], 8);
    
    % Scribble something:
    Screen('FrameRect', w, [0, 1, 0], [], 5);
    Screen('FillOval', w, [1, 1, 0]);
    Screen('TextSize', w, 24);
    DrawFormattedText(w, '<--- Hello World! --->\nI am feeling big and soft.\nThat''s the way i like it!\nPress key to continue.', 'center', 'center', [0,0,1]);

    % Wait for a key press:
    ShowUntilKey(w);
    
    % Close down:
    sca;
    
    Screen('Preference','SkipSyncTests', oldsynctest);
    return;
    
catch %#ok<*CTCH>
    sca;
    Screen('Preference','SkipSyncTests', oldsynctest);
    psychrethrow(psychlasterror);
end

% End of function.
end

function ShowUntilKey(w)
    KbReleaseWait;
    
    while ~KbCheck
        [x, y] = GetMouse(w);
        [xm, ym] = RemapMouse(w, 'AllViews', x, y);
        Screen('DrawDots', w, [xm ; ym], 5, [1, 0, 0], [], 1);
        Screen('Flip', w, [], 1);
    end
end
