/*
 * display.c
 * Copyright (C) 2002 P.Everett <peter@everett9981.freeserve.co.uk>
 *
 * This file is part of KinoAMP, a free RISCOS MPEG program stream decoder.
 *
 * KinoAMP is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * KinoAMP is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * This software is based on Kino v0.3 by eQ R&D <http://www.eqrd.net>
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"
#include "inttypes.h"
#include "config.h"
#include "display.h"
#include "keyboard.h"
#include "mt.h"
#include "player.h"
#include "wimp.h"
#include "ka_mem.h"
#include "ka_log.h"
#include "playlist.h"
#include "sprite.h"
#include "timer1.h"

static const char col_depth[7] = {3, 4, 4, 4, 5, 7, 7};
static const char col_bytes[7] = {1, 2, 2, 2, 4, 4, 4};
static const int col_ncolours[7] = {255, 4095, 65535, 65535, -1, 0x32595559, 0x3231564e};
static int col_flags[7] = {0x80, 0, 0, 0x80, 0, 0x6000, 0x6000};

#define MAX_SCREENBANKS 3
static int ScreenBanksCount = MAX_SCREENBANKS;
static int ScreenBank = 0;
static int OverlayBanksCount = MAX_SCREENBANKS;
static int OverlayBank = 0;
static int* wimp_mode; // desktop mode id/ptr
static int wimp_mode_blk[20] = {0, 0 /* x res */};
// full screen parameters, used when switching to full screen.
static int fs_height, fs_width, fs_cols, fs_zoom = -1;
static int glb_col_mask = 0;
static int vsync_counter = 0;

typedef struct
{
  int width;
  int height;
  int col_mask;
} screen_mode;
#define MAX_RES 64
static screen_mode res[MAX_RES];
static screen_mode* max_mode = &res[0];
static const int VduInfoBlock[3] = {148, 149, -1};
static const int VduSizeBlock[2] = {150, -1};
static int VduOutBlock[2];


/**
 * Returns number of overlay/screen banks.
 *
 * @param  player  in: vo_overlay.
 */
static int display_getBankCount(player_t* player)
{
  if (player->vo_overlay)
    return OverlayBanksCount;
  else
    return ScreenBanksCount;
}

/**
 * Returns the planes of the overlay bank to write to.
 *
 * @param  player  in: vo_overlay.
 * @param  planes  pointer to a list of overlay planes to fill in.
 */
const _kernel_oserror* display_getDriverOverlayBank(player_t* player, const gvoverlayplane_t** planes)
{
  return _swix(VideoOverlay_MapBuffer, _INR(0,1) | _OUT(0), player->vo_overlay, OverlayBank, planes);
}

/**
 * Returns the planes of the overlay bank to display.
 *
 * @param  player  in: vo_overlay.
 * @param  planes  pointer to a list of overlay planes to fill in.
 */
static const _kernel_oserror* display_getOverlayBank(player_t* player, int index, const gvoverlayplane_t** planes)
{
  return _swix(VideoOverlay_MapBuffer, _INR(0,1) | _OUT(0), player->vo_overlay, index, planes);
}

/**
 * Rotates on the overlay/screen banks and notes down current vsync counter.
 *
 * @param  player  in: vo_overlay.
 */
void display_swapBanks(player_t* player)
{
  if (player->vo_overlay)
  {
    // Update even if single bank to force drawing
    _swix(VideoOverlay_DisplayBuffer, _INR(0, 1), player->vo_overlay, OverlayBank);
    _swix(VideoOverlay_UnmapBuffer, _INR(0, 1), player->vo_overlay, OverlayBank);
    OverlayBank = (OverlayBank + 1) % OverlayBanksCount;
  }
  else if (((player->mode == KA_MODE_AUTO) || (player->mode == KA_MODE_MANUAL))
  &&  (ScreenBanksCount > 1))
  {
    // Clean screen buffering, if available
    _swix(OS_ScreenMode, _IN(0), 6);
    // Swap screen banks, OS_Bytes 112 and 113
    ScreenBank = (ScreenBank + 1) % ScreenBanksCount;
    _swix(OS_Byte, _INR(0, 1), 112, 1 + ScreenBank);
    _swix(OS_Byte, _INR(0, 1), 113, 1 + ((ScreenBank + ScreenBanksCount - 1) % ScreenBanksCount));
  }
  // Read VSync counter
  if (_swix(OS_Byte, _INR(0,2)|_OUT(1), 176, 0, 255, &vsync_counter) != NULL)
    vsync_counter = 0;
}

/**
 * Waits for the vsync counter to differ from its value at screen bank rotation time.
 *
 * @param  player  in: mode out: cputime spent waiting.
 */
void display_waitsync(player_t* player)
{
  uint32_t ts = timer_gettime();

  if (config.debug & cfg_displayall)
    return;

  if (((player->mode == KA_MODE_AUTO) || (player->mode == KA_MODE_MANUAL))
  && (display_getBankCount(player) > 1))
  {
#if 1
    int counter = vsync_counter;
    while (counter == vsync_counter)
    {
      // Read VSync counter
      if (_swix(OS_Byte, _INR(0,2)|_OUT(1), 176, 0, 255, &counter) != NULL)
        break;
    }
#else
    // Wait for VSync
    _swix(OS_Byte, _IN(0), 19);
#endif
  }

  player->cputime.sync += timer_gettime() - ts;
}

/**
 * Returns the pointer to the start of a given screen bank.
 *
 * @param  i  screen bank number.
 */
static uint8_t* display_getScreenBank(int i)
{
  _swix(OS_Byte, _INR(0, 1), 112, 1 + i);
  _swi(OS_ReadVduVariables, _INR(0, 1), &VduInfoBlock[0], &VduOutBlock[0]);
  _swix(OS_Byte, _INR(0, 1), 112, 1 + ScreenBank);

  return (uint8_t*) VduOutBlock[0];
}

/**
 * Returns the pointer to the screen bank to write to.
 */
uint8_t* display_getDriverScreenBank(void)
{
  _swi(OS_ReadVduVariables, _INR(0, 1), &VduInfoBlock[0], &VduOutBlock[0]);

  return (uint8_t*) VduOutBlock[0];
}

/**
 * Saves information regarding the current wimp mode.
 */
static void desktop_saveMode(void)
{
  // Read current wimp mode if not saved yet (x res set to 0)
  if (wimp_mode_blk[1] == 0)
  {
    _swix(OS_ScreenMode, _IN(0)|_OUT(1), 1, &wimp_mode);
    wimp_mode_blk[0] = wimp_mode[0];
    wimp_mode_blk[1] = wimp_mode[1];
    wimp_mode_blk[2] = wimp_mode[2];
    wimp_mode_blk[3] = wimp_mode[3];
    wimp_mode_blk[4] = wimp_mode[4];
    int done = 0;
    for (int i = 5; !done; i += 2)
    {
       if (wimp_mode[i] == -1)
       {
         wimp_mode_blk[i] = wimp_mode[i];
         done = 1;
       }
       else
       {
         wimp_mode_blk[i] = wimp_mode[i];
         wimp_mode_blk[i+1] = wimp_mode[i+1];
       }
    }
  }
}

/**
 * Restores the current wimp mode.
 */
static void desktop_restoreMode(void)
{
  if (wimp_mode_blk[1] != 0)
  {
    // Set current wimp mode
    _swix(Wimp_SetMode, _IN(0), wimp_mode_blk);
    // Reset x res to zero to forget saved wimp mode
    wimp_mode_blk[1] = 0;
  }
}

/**
 * Initializes one time stuff.
 *
 * @param  player
 */
void display_init(player_t* player)
{
  _kernel_swi_regs regs;

  glb_col_mask = 0;

  // Try to obtain a list of supported colour depths, RISC OS Select
  regs.r[0] = 12; // get display driver decriptor
  regs.r[1] = -1; // current driver
  regs.r[2] = 3;  // list of colour depths
  regs.r[3] = 0;  // request descriptor size
  regs.r[4] = 0;
  if (_kernel_swi(OS_ScreenMode, &regs, &regs) == NULL)
  {
    int* blk;
    int size = regs.r[4];

    if ((blk = ka_mem_alloc(size)) == 0)
      report_error(1, "error7"); // (Cannot allocate mode block)

    regs.r[0] = 12; // get display driver decriptor
    regs.r[1] = -1; // current driver
    regs.r[2] = 3;  // list of colour depths
    regs.r[3] = (int) blk;
    regs.r[4] = size;
    _kernel_swi(OS_ScreenMode, &regs, &regs);
    size = size / 4;
    for (int i = 0; i < size; i++)
    {
      if (blk[i] == 3)
        glb_col_mask |= (1 << KA_COL_256); // 256, 8bpp
      else if (blk[i] == 4)
        glb_col_mask |= (1 << KA_COL_32K); // 32K, 16bpp
      else if (blk[i] == 5)
        glb_col_mask |= (1 << KA_COL_16M); // 16M, 32bpp
      else if (blk[i] == 0x40000004)
        glb_col_mask |= (1 << KA_COL_64K); // 64K, 16bpp
    }

    ka_mem_free(blk);
  }
  else
  {
    // Try to obtain a list of supported colour depths, RISC OS 5
    regs.r[0] = 11; // set/read current driver number
    regs.r[1] = -1; // read
    if (_kernel_swi(OS_ScreenMode, &regs, &regs) == NULL)
    {
      regs.r[4] = 17 + (regs.r[1] << 24);
      regs.r[9] = 42;
      if (_kernel_swi(OS_CallAVector, &regs, &regs) == NULL)
      {
        int* blk = (int*) regs.r[0];

        for (int i = regs.r[1]; i > 0; i--, blk += 3)
        {
          if ((blk[2] == 3) && (blk[1] & 0x80))
          {
            if ((glb_col_mask & (1 << KA_COL_256)) == 0)
            {
              glb_col_mask |= (1 << KA_COL_256); // 256, 8bpp
              col_flags[KA_COL_256] = blk[1];
            }
          }
          else if (blk[2] == 4)
          {
            if (blk[0] == 4095)
            {
              if ((glb_col_mask & (1 << KA_COL_4K)) == 0)
              {
                glb_col_mask |= (1 << KA_COL_4K); // 4K, 16bpp
                col_flags[KA_COL_4K] = blk[1];
              }
            }
            else if (blk[0] == 65535)
            {
              if (blk[1] & 0x80)
              {
                if ((glb_col_mask & (1 << KA_COL_64K)) == 0)
                {
                  glb_col_mask |= (1 << KA_COL_64K); // 64K, 16bpp
                  col_flags[KA_COL_64K] = blk[1];
                }
              }
              else
              {
                if ((glb_col_mask & (1 << KA_COL_32K)) == 0)
                {
                  glb_col_mask |= (1 << KA_COL_32K); // 32K, 16bpp
                  col_flags[KA_COL_32K] = blk[1];
                }
              }
            }
          }
          else if (blk[2] == 5)
          {
            if ((glb_col_mask & (1 << KA_COL_16M)) == 0)
            {
              glb_col_mask |= (1 << KA_COL_16M); // 16M, 32bpp
              col_flags[KA_COL_16M] = blk[1];
            }
          }
        }
      }
    }
  }

  // Build a list of screen modes
  {
    int i, j, w, h, c, l, f, size;
    int *blk, *ptr, *blk_end;

    regs.r[0] = 2; // enumerate screen modes
    regs.r[2] = 0; // skip none
    regs.r[6] = 0; // request required block size
    regs.r[7] = 0;
    _kernel_swi(OS_ScreenMode, &regs, &regs);
    size = -regs.r[7];
    if ((blk = ka_mem_alloc(size)) == 0)
      report_error(1, "error7"); // (Cannot allocate mode block)

    regs.r[0] = 2;
    regs.r[2] = 0;
    regs.r[6] = (int)blk;
    regs.r[7] = size;
    _kernel_swi(OS_ScreenMode, &regs, &regs);

    // We now have a list of the available modes
    blk_end = blk + (size / 4);
    ptr = blk;
    j = -1;

    // Build a list of modes to choose from.
    while(ptr < blk_end)
    {
      switch (ptr[1] & 0xff)
      {
        case 1:
        {
          w = ptr[2];
          h = ptr[3];
          l = ptr[4];
          c = 1 << (1 << l);
          f = 0;
        }
        break;
        case 3:
        {
          w = ptr[2];
          h = ptr[3];
          c = ptr[4] + 1;
          f = ptr[5];
          l = ptr[6];
        }
        break;
        default:
          ptr += ptr[0] / 4;
          continue;
      }

      // Keep only modes with at least 256 colours
      if ((l >= 3) && (l <= 5))
      {
        // Only use approximately square pixel modes,
        // i.e. modes with aspect ratios between 6:5 and 16:9.
        if (((6 * h) <= (5 * w)) && ((16 * h) >= (9 * w)))
        {
          // new resolution ?
          if ((j < 0) || (w != res[j].width) || (h != res[j].height))
          {
            j++;
            if (j >= MAX_RES) break;

            res[j].width = w;
            res[j].height = h;
            res[j].col_mask = 0;
          }

          // add colour depth to current resolution
          if (l == 3) // 256
            res[j].col_mask |= (1 << KA_COL_256);
          else if (l == 4) // 4k, 32k or 64k
          {
            if (c == (1<<12)) // 4K
              res[j].col_mask |= (1 << KA_COL_4K);
            else if ((c == (1<<16)) && (f & 0x80)) // 64K, OS 5
              res[j].col_mask |= (1 << KA_COL_64K);
            else // 32K OS 4,5 or 32/64K (Select)
            {
              const int mask = (1 << KA_COL_64K) | (1 << KA_COL_32K);
              if (glb_col_mask & mask)
                res[j].col_mask |= (glb_col_mask & mask);
              else
                res[j].col_mask |= (1 << KA_COL_32K);
            }
          }
          else // 16m
            res[j].col_mask |= (1 << KA_COL_16M);
        }
      }

      ptr += ptr[0] / 4;
    }
    max_mode = &res[j+1];

    // Merge depths in global depth mask (for pre-GraphicsV/Select cases)
    for (i = 0; i <= j; i++)
      glb_col_mask |= res[i].col_mask;

    ka_mem_free(blk);
  }

  // Screen DMA buffer management for Iyonix
  {
    player->acceleration_mode = ACCEL_MODE_NONE;

    // Check for presence of VideoOverlay module
    if (player->acceleration_mode == ACCEL_MODE_NONE)
    {
      if (config.control & cfg_ctrl_accel_videooverlay)
      {
        if (_swix(OS_Module, _INR(0,1), 18, "VideoOverlay") == NULL)
          player->acceleration_mode = ACCEL_MODE_VIDEOOVERLAY;
      }
    }

    // Check for presence of IntelDMA module
    if ((player->acceleration_mode == ACCEL_MODE_NONE)
    &&  (config.control & cfg_ctrl_accel_inteldma)
    &&  player->idma)
      player->acceleration_mode = ACCEL_MODE_INTELDMA;

    // Check for presence of Geminus module
    if (player->acceleration_mode == ACCEL_MODE_NONE)
    {
      if (config.control & cfg_ctrl_accel_geminus)
      {
        if (_swix(OS_Module, _INR(0,1), 18, "Geminus") == NULL)
          player->acceleration_mode = ACCEL_MODE_GEMINUS;
      }
    }
  }

  // Zoom
  player->x_mag = player->y_mag = (100 << FRAC) / config.video.modes[KA_MODE_MULTITASK].zoom;
  player->aspect.width = player->aspect.height = 1;

  // Full screen manual, use requested mode
  fs_width   = config.video.modes[KA_MODE_MANUAL].width;
  fs_height  = config.video.modes[KA_MODE_MANUAL].height;
  fs_cols    = config.video.modes[KA_MODE_MANUAL].cols;
  fs_zoom    = config.video.modes[KA_MODE_MANUAL].zoom;

  player->mode = config.video.mode;
}

/**
 * Releases used resources.
 *
 * @param  player
 */
void display_final(player_t* player)
{
  int i;

  // Release existing DMA
  inteldma_cleanupTransfer(player->idma);

  if (player->vo_overlay)
  {
    _swix(VideoOverlay_Destroy,_IN(0),player->vo_overlay);
    player->vo_overlay = 0;
  }

  for (i = 0; i < SAVEAREA_COUNT; i++)
  {
    if (player->rgb_area[i].frame)
    {
      ka_mem_free(player->rgb_area[i].frame);
      player->rgb_area[i].frame = NULL;
    }
  }
}

/**
 * Adjust number of colours according to an availability mask.
 *
 * @param  cols  initial colour depth
 * @param  mask  mask with 1 bit per available colour depth.
 */
static int adjust_colours(int cols, int mask)
{
  int i = 1 << cols;

  if (!(i & mask))
  {
    if (mask & (i - 1))
    {
      // Use less colours if possible
      while (!(i & mask))
      {
        cols--;
        i >>= 1;
      }
    }
    else if (mask & ~(i - 1))
    {
      // Use more colours
      while (!(i & mask))
      {
        cols++;
        i <<= 1;
      }
    }
  }

  return cols;
}

/**
 * Selects the available mode that will give the largest display.
 * It returns the width/height/depth of the selected mode.
 *
 * @param  screen_width   pointer where to return the selected mode width.
 * @param  screen_height  pointer where to return the selected mode heigh.
 * @param  screen_cols    pointer where to return the selected mode colour depth.
 * @param  width          width of the image to fit in selected mode.
 * @param  height         height of the image to fit in selected mode.
 * @param  zoom           zoom to apply to the image, in percents
 */
static void select_mode
  ( int *screen_width
  , int *screen_height
  , int *screen_cols
  , int width
  , int height
  , int zoom
  )
{
  screen_mode* mode;
  screen_mode* sel_mode = NULL;
  video_mode* vmode = &config.video.modes[KA_MODE_AUTO];
  int mwidth = width * zoom / 100;
  int mheight = height * zoom / 100;

  // Limit the screen width and height to the requested minimum
  if (mwidth < vmode->width)
    mwidth = vmode->width;
  if (mheight < vmode->height)
    mheight = vmode->height;

  // Assumes mode list is sorted by ascending width, height
  for (mode = max_mode - 1; mode >= &res[0]; mode--)
  {
    if (mode->width < mwidth)
      break;

    if (mode->height >= mheight)
      sel_mode = mode;
  }

  if (!sel_mode)
  {
    if (config.debug & (cfg_printwarnings | cfg_printdrawstats))
    {
      ka_logs(ka_log_error, "Failed to auto-select a mode for %d x %d (zoom = %d%%)\n", mwidth, mheight, zoom);
      ka_logn("mode list\n");
      for (mode = max_mode - 1; mode >= &res[0]; mode--)
      {
        ka_logn("%d x %d col_mask %01x\n", mode->width, mode->height, mode->col_mask);
      }
    }

    // try at mode fitting width
    for (mode = max_mode - 1; mode >= &res[0]; mode--)
    {
      if (mode->width < mwidth)
      break;

      sel_mode = mode;
    }

    if (!sel_mode)
    {
      // use configured minimal resolution
      *screen_width  = vmode->width;
      *screen_height  = vmode->height;
      *screen_cols = adjust_colours(5, ((2 << vmode->cols) - 1));
      return;
    }
  }

  *screen_width = sel_mode->width;
  *screen_height = sel_mode->height;

  // adjust colour depth to take count of available colours and automatic limit
  *screen_cols = adjust_colours(5, sel_mode->col_mask & ((2 << vmode->cols) - 1));
}

/**
 * Selects the zoom [50, 100, 200, 300, 400] that will give the best fit for the display.
 *
 * @param  screen_width   width of the screen.
 * @param  screen_height  height of the screen.
 * @param  width          width of the image.
 * @param  height         height of the image.
 */
static int select_zoom
  ( int screen_width
  , int screen_height
  , int width
  , int height
  )
{
  int zoom;

  if ((width > screen_width)
  ||  (height > screen_height))
    return 50;

  for (zoom = 2; zoom < 5; zoom += 1)
  {
    if (((zoom * width) > screen_width)
    ||  ((zoom * height) > screen_height))
      break;
  }

  return 100 * (zoom - 1);
}

/**
 * Constructs a sprite save area, reserving the required memory.
 *
 * @param  area       pointer to the area to construct.
 * @param  name       name of the area (for error reporting).
 * @param  byte_with  width of the sprite.
 * @param  height     height of the sprite.
 * @param  screen     pointer to screen description to match for sprite mode and palette.
 */
static int savearea_getInstance(rgb_savearea* area, const char* name, int byte_width, int height, const ka_screen_t* screen)
{
  sprite_header_t* frame = area->frame;
  int size = byte_width * height + sizeof(*frame);
  int palette_size = (screen->col_depth == ka_col_depth_256) ? 256 : 0;

  size += palette_size<<3;

  // Allocated area is too small ? Release it
  if (frame && (size > area->buf_size))
  {
    ka_mem_free(frame);
    area->frame = frame = NULL;
    area->byte_width = 0;
    area->height = 0;
  }

  if (!frame)
  {
    frame = ka_mem_calloc(size);
    if(!frame)
    {
      // Cannot allocate buffer
      report_error(1, "error4:%s of %d bytes", name, size);
      return 1;
    }
    area->frame = frame;
    area->buf_size = size;

    frame->area_size = size;
    frame->num = 1;
    frame->offset = 16;
    frame->size_1 = frame->area_size;
    frame->size_2 = frame->size_1 - 16;
    strcpy(frame->name, "sprite");
  }

  // Write spritearea header. There is no real need for the rgb_frame
  // to be a valid sprite area but it may be required in the future.
  // Being a sprite area, it is possible to use OS_SpriteOp to plot the
  // sprite but it is a bit slower than the functions in s.plot and
  // doesn't support variable scaling in the same way.

  frame->width = (byte_width >> 2) - 1;
  frame->height = height - 1;
  frame->first = 0;
  frame->last = 31;
  frame->s_offset = 44 + (palette_size<<3);
  frame->m_offset = frame->s_offset;
  frame->mode = sprite_getmode(screen->col_depth);

  if (palette_size > 0)
  {
    int* pal = (int*) (frame + 1);
    const int* ref = (int*) screen->palette;
    int i;

    for (i = palette_size; i; i--)
    {
      *pal++ = *ref;
      *pal++ = *ref++;
    }
  }

  area->byte_width = byte_width;
  area->height = height;

  return 0;
}

/**
 * Saves the desktop area that will be painted over.
 *
 * @param  player  in: overlay, desktop save area.
 */
static void savearea_saveDesktop(player_t* player)
{
  rgb_savearea* area = &player->rgb_area[SAVEAREA_DESKTOP];
  const uint8_t* src;
  uint8_t* dst;

  if (!player->vo_overlay)
    savearea_getInstance(area, "Desktop", player->screen_plot.bytesperrow, player->screen_plot.height, &player->screen_plot);
  if (!area->frame) return;

  src = display_getDriverScreenBank();
  dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
  memcpy(dst, src, player->screen_plot.bytesperrow * player->screen_plot.height);
}

/**
 * Restores the content of the desktop save area.
 *
 * @param  player  in: desktop save area.
 */
static void savearea_plotDesktop(player_t* player)
{
  const rgb_savearea* area = &player->rgb_area[SAVEAREA_DESKTOP];
  const uint8_t* src;
  uint8_t* dst;

  if (!area->frame) return;

  src = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
  dst = display_getDriverScreenBank();
  memcpy(dst, src, player->screen_plot.bytesperrow * player->screen_plot.height);
}

/**
 * Restores a part of the background (either the desktop or a black background).
 *
 * @param  player  in: mode, screen_plot, desktop save area.
 * @param  r       rectangle in pixels with (0, 0) as bottom left corner of the screen.
 */
static void background_plot(player_t* player, const wimp_rect_t* r)
{
  const ka_screen_t* screen = &player->screen_plot;
  uint8_t* dst;
  int i;

  if (player->mode == KA_MODE_DESKTOP)
  {
    const rgb_savearea* area = &player->rgb_area[SAVEAREA_DESKTOP];
    const uint8_t* src;

    if (!area->frame) return;

    src = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
    dst = display_getDriverScreenBank();
    src += screen->bytesperrow * (screen->height - r->y0 - 1);
    dst += screen->bytesperrow * (screen->height - r->y0 - 1);
    src += r->x0 * screen->bytes_per_pixel;
    dst += r->x0 * screen->bytes_per_pixel;

    for (i = r->y1 - r->y0; i > 0; i--)
    {
      memcpy(dst, src, (r->x1 - r->x0) * screen->bytes_per_pixel);
      src -= screen->bytesperrow;
      dst -= screen->bytesperrow;
    }
  }
  else if (player->mode != KA_MODE_MULTITASK)
  {
    // Clear area in all screen banks
    for (int s = 0; s < ScreenBanksCount; s++)
    {
      dst = display_getScreenBank(s);
      dst += screen->bytesperrow * (screen->height - r->y0 - 1);
      dst += r->x0 * screen->bytes_per_pixel;

      for (i = r->y1 - r->y0; i > 0; i--)
      {
        memset(dst, 0, (r->x1 - r->x0) * screen->bytes_per_pixel);
        dst -= screen->bytesperrow;
      }
    }
  }
}

/**
 * Restores the background parts uncovered when changing zoom.
 *
 * @param  player  in: mode, screen_plot, desktop save area.
 * @param  old     old video position and size in os coordinates.
 * @param  new     new video position and size in os coordinates.
 */
static void background_fix(player_t* player, const scr_pos_t* old, const scr_pos_t* new)
{
  wimp_rect_t r;

  r.x0 =  old->x               >> player->screen_plot.xeig;
  r.x1 =  new->x               >> player->screen_plot.xeig;
  r.y0 =  old->y                >> player->screen_plot.yeig;
  r.y1 = (old->y + old->height) >> player->screen_plot.yeig;
  if ((r.x0 < r.x1) && (r.y0 < r.y1))
    background_plot(player, &r);

  r.x0 = (new->x + new->width) >> player->screen_plot.xeig;
  r.x1 = (old->x + old->width) >> player->screen_plot.xeig;
  r.y0 =  old->y                >> player->screen_plot.yeig;
  r.y1 = (old->y + old->height) >> player->screen_plot.yeig;
  if ((r.x0 < r.x1) && (r.y0 < r.y1))
    background_plot(player, &r);

  r.x0 =  old->x               >> player->screen_plot.xeig;
  r.x1 = (old->x + old->width) >> player->screen_plot.xeig;
  r.y0 =  old->y                >> player->screen_plot.yeig;
  r.y1 =  new->y                >> player->screen_plot.yeig;
  if ((r.x0 < r.x1) && (r.y0 < r.y1))
    background_plot(player, &r);

  r.x0 =  old->x               >> player->screen_plot.xeig;
  r.x1 = (old->x + old->width) >> player->screen_plot.xeig;
  r.y0 = (new->y + new->height) >> player->screen_plot.yeig;
  r.y1 = (old->y + old->height) >> player->screen_plot.yeig;
  if ((r.x0 < r.x1) && (r.y0 < r.y1))
    background_plot(player, &r);
}

/**
 * Hide/Restore cursors.
 * Hidden cursor must always be restored.
 */
void display_showCursors(int on)
{
  static int saved_cursors = 0;

  if (saved_cursors)
  {
    _swix(OS_RestoreCursors, 0);
    saved_cursors = 0;
  }

  if (!on)
  {
    _swix(OS_RemoveCursors, 0);
    saved_cursors = 1;
  }
}

/**
 * Hide/Restore pointer.
 * Hidden pointer must always be restored.
 */
void display_showPointer(int on)
{
  static int show_pointer = 0;

  if (on && !show_pointer)
  {
    _kernel_oscli("pointer 1");
    show_pointer = 1;
  }

  if (!on && show_pointer)
  {
    _kernel_oscli("pointer 0");
    show_pointer = 0;
  }
}

/**
 * Completely restores the background (the desktop or a black background) and drops all save areas.
 * If nessesary: restores cursor, pointer and screen mode.
 * Called when changing desktop mode/palette, full screen switch, and exit.
 *
 * @param  player  in: mode, screen_plot, desktop save area.
 */
void display_restoreDesktop(player_t* player, int newmode, int reason)
{
  int i, b;

  if ((reason == ZOOM_CHANGE)  // Use another method
  ||  (reason == FRAC_CHANGE)) // Multitasking
    return;

  // Restore old desktop contents
  if (player->mode == KA_MODE_DESKTOP)
    savearea_plotDesktop(player);

  // Drop all save areas
  for (i = 0; i < SAVEAREA_COUNT; i++)
  {
    if (player->rgb_area[i].frame)
    {
      ka_mem_free(player->rgb_area[i].frame);
      player->rgb_area[i].frame = NULL;
    }
  }

  if ((reason == SCREEN_CHANGE)
  ||  (reason == FINAL_RESTORE))
  {
    if ((reason == FINAL_RESTORE)
    ||  (newmode == KA_MODE_MULTITASK))
    {
      display_showCursors(1);
      display_showPointer(0);
    }

    if ((player->mode == KA_MODE_AUTO)
    ||  (player->mode == KA_MODE_MANUAL))
    {
      if ((reason == FINAL_RESTORE)
      ||  (newmode == KA_MODE_MULTITASK)
      ||  (newmode == KA_MODE_DESKTOP))
      {
        desktop_restoreMode();
      }
    }
  }
  else if ((player->mode == KA_MODE_AUTO)
       ||  (player->mode == KA_MODE_MANUAL))
  {
    {
      // Changing full screen zoom, clear full screen frame display for all display banks
      for (b = ScreenBanksCount - 1; b >= 0; b--)
      {
        uint8_t* dst = display_getScreenBank(b) + player->plot.dst_offset;

        for(i = player->plot.dst_height; i; i--)
        {
          memset(dst, 0, player->plot.dst_byte_width);
          dst += player->plot.dst_bpr;
        }
      }
    }
  }
}

/**
 * Reduce with/height to fit pan and scan format.
 *
 * @param  player  in: scaler type.
 * @param  twidth  pointer to with to adjust.
 * @param  theight pointer to height to adjust.
 */
static void panscan(player_t* player, int* twidth, int* theight)
{
  int x, y;

  // Stretching is not possible with internal scaler
  if ((player->scaleconv.type == ka_scaler_none)
  &&  (config.video.resizemode == KA_RESIZE_STRETCH))
    return;

  config_aspect_ratio(&config, &x, &y);

  if (x && y)
  {
    if ((*twidth * y) < (*theight * x))
      *theight  = *twidth * y / x;
    else if ((*twidth * y) > (*theight * x))
      *twidth = *theight * x / y;
  }
}

/**
 * Returns value scale by a given ratio.
 *
 * @param  mag  value to adjust.
 * @param  mul  scale numerator.
 * @param  div  scale denominator.
 */
static int changemag(int mag, int mul, int div)
{
  int64_t cmag = mag;
  cmag *= mul;
  cmag /= div;
  return (int) cmag;
}

/**
 * Sets the overlays output size (in os coordinates).
 *
 * @param  player  in: vo_overlay, vo_aspect.
 * @param  width   pointer to proposed width, updated on exit with actual value.
 * @param  height  pointer to proposed height, updated on exit with actual value.
 */
static void vo_setscale(player_t* player, int* width, int* height)
{
  if (player->vo_overlay)
  {
//    ka_log(ka_log_info | ka_log_draw, "size in: %d %d\n", *width, *height);
    _swix(VideoOverlay_SetScale, _INR(0,3)|_OUTR(1,2)
        , player->vo_overlay, *width, *height
        , (config.video.cfg & cfg_video_lock_aspect) ? player->vo_aspect : (((*width)<<16) | (*height))
        , width, height
        );
//    ka_log(ka_log_info | ka_log_draw, "size out: %d %d\n", *width, *height);
  }
}

/**
 * Calculate source area to use.
 * If multitasking, from scaled sprite, else from scaled display area.
 * If fit screen, x_mag anf y_mag are updated.
 *
 * @param player     in : mode, aspect, screen_plot, x_mag, y_mag
 *                   out: x_mag, y_mag
 * @param vmode
 * @param rgb_zoom2  1 = 50%, 2 = 100%, 4 = 200%, 6 = 300%, 8 = 400%
 * @param pwidth     rgb_width  on imput, frame_width  on exit
 * @param pheight    rgb_height on imput, frame_height on exit
 */
static void frameArea(player_t* player, const video_mode* vmode, int rgb_zoom2, int* pwidth, int* pheight)
{
  if (player->mode == KA_MODE_MULTITASK)
  {
    // display complete area
    *pwidth  = (player->aspect.width  * player->aspect.x_mag + ( 1 << (FRAC - 1))) >> FRAC;
    *pheight = (player->aspect.height * player->aspect.y_mag + ( 1 << (FRAC - 1))) >> FRAC;
  }
  else if (player->scaleconv.type != ka_scaler_none)
  {
    int wmax, hmax;
    int width  = *pwidth;
    int height = *pheight;

    // Limit colour space converter output size
    // so that scaled output size fits the screen size

    // Recalculate scaler x and y magnifications ?
    if (vmode->cfg & cfg_video_fit_screen)
    {
      // Compute from screen dimension
      int sw, sh;

      sh = player->screen_plot.height;
      sw = player->screen_plot.width;

      if (config.video.cfg & cfg_video_lock_aspect)
      {
        // Will the picture be too high or too wide for the screen when enlarged ?
        if((sh * width) > (sw * height))
        {
           // Lock height to width
           sh = (height * sw) / width;
        }
        else
        {
           // Lock width to height
           sw = (width * sh) / height;
        }
      }

      player->x_mag = changemag(player->aspect.x_mag, width , sw);
      player->y_mag = changemag(player->aspect.y_mag, height, sh);
    }

    // Default scaled output size
    wmax = (player->screen_plot.width  * player->x_mag + player->aspect.x_mag - 1) / player->aspect.x_mag;
    hmax = (player->screen_plot.height * player->y_mag + player->aspect.y_mag - 1) / player->aspect.y_mag;

    // Limit scaled output size to screen size
    width  = (width  < wmax) ? width  : wmax;
    height = (height < hmax) ? height : hmax;

    *pwidth  = (width  * player->aspect.x_mag + (1 << (FRAC - 1))) >> FRAC;
    *pheight = (height * player->aspect.y_mag + (1 << (FRAC - 1))) >> FRAC;
  }
  else
  {
    // No scaling, limit colour space converter output size to screen size
    if (*pwidth > player->screen_plot.width)
      *pwidth = player->screen_plot.width;
    if (*pheight > player->screen_plot.height)
      *pheight = player->screen_plot.height;

    *pwidth  = (*pwidth  << 1) / rgb_zoom2;
    *pheight = (*pheight << 1) / rgb_zoom2;
  }
}

/**
 * Rounds down width and height used in source to cope with drawer/scaler limitations.
 *
 * @param pwidth           pointer to width to adjust.
 * @param pheight          pointer to height to adjust.
 * @param vinfo            video info (chroma).
 * @param rgb_zoom2        colour converter zoom (2 = 100%).
 * @param bytes_per_pixel  number of bytes per pixel of output.
 */
static void roundFrameSize(int* pwidth, int* pheight, const ka_vinfo_t* vinfo, int rgb_zoom2, int bytes_per_pixel, int up)
{
  int wpixels = 2;
  int hpixels = 4-1;

  if (rgb_zoom2 == 1)
    wpixels = 8/bytes_per_pixel;
  else if (bytes_per_pixel == 1)
  {
    if ((rgb_zoom2 == 2) || (rgb_zoom2 == 6))
      wpixels = 4;
  }
  wpixels -= 1;

  if (up)
  {
    // Round up source size to multiple of to chroma block size
    // Formula below works because shift is only 0 or 1
    *pwidth  += *pwidth  & vinfo->chroma.wshift;
    *pheight += *pheight & vinfo->chroma.hshift;

    // Round up source size for colour space converter alignment requirements
    // Take into account interlaced replay
    {
      *pwidth  += (1 + wpixels - (*pwidth  & wpixels)) & wpixels;
      *pheight += (1 + hpixels - (*pheight & hpixels)) & hpixels;
    }

    // Limit to picture width/height which possibly do not respect alignment requirements
    // and so we need to round down afterward to correct
    if (*pwidth  > vinfo->picture.width)
      *pwidth  = vinfo->picture.width;
    if (*pheight > vinfo->picture.height)
      *pheight = vinfo->picture.height;
  }

  // Round down source size to multiple of to chroma block size
  // Formula below works because shift is only 0 or 1
  *pwidth &= ~vinfo->chroma.wshift;
  *pheight &= ~vinfo->chroma.hshift;

  // Round down source size for colour space converter alignment requirements
  // Take into account interlaced replay
  {
    *pwidth &= ~wpixels;
    *pheight &= ~hpixels;
  }
}

typedef struct
{
  int ncolour;
  int xeig;
  int yeig;
  int bytesperrow;
  int log2bpp;
  int width;
  int height;
  int modeflags;
  int eol;
} vdu_vars_t;

/**
 * Fill in VDU variables.
 *
 * @param  vdu_vars      pointer to VDU variables.
 */
static void fillVduList(vdu_vars_t* vdu)
{
  // On entry fill with variables numbers, on exit we have the corresponding values
  vdu->ncolour     = 3;   // Ncolour - 1
  vdu->xeig        = 4;   // XEigFactor
  vdu->yeig        = 5;   // YEigFactor
  vdu->bytesperrow = 6;   // screen width in bytes
  vdu->log2bpp     = 9;   // log2bpp
  vdu->width       = 11;  // width
  vdu->height      = 12;  // height
  vdu->modeflags   = 0;   // Mode flags
  vdu->eol         = -1;  // End of list marker
  _swix(OS_ReadVduVariables, _INR(0,1), vdu, vdu);
}

/**
 * Translate list of VDU variables into a screen structure.
 *
 * @param  screen        pointer to structure to fill in.
 * @param  blk           pointer to VDU variables.
 * @param  rgb           1 signals rgb instead of gbr.
 * @param  glb_col_mask  mask with 1 bit per available colour depths.
 */
static void screenFromVdu(ka_screen_t* screen, vdu_vars_t* vdu, int rgb, int glb_col_mask)
{
  rgb |= ((vdu->modeflags & 0x7000) == 0x4000);
  int alpha = ((vdu->modeflags & 0xb000) == 0x8000);

  screen->xeig = vdu->xeig;
  screen->yeig = vdu->yeig;
  screen->bytesperrow = vdu->bytesperrow;
  screen->log2Bpp = vdu->log2bpp - 3;
  screen->width   = vdu->width + 1;
  screen->height  = vdu->height + 1;
  screen->col_depth = (ka_col_depth) -1;

  if (vdu->log2bpp == 3)
  {
    // 256 colours
    uint32_t* palette = screen->palette;
    unsigned int y;

    screen->col_depth = ka_col_depth_256;

    // Read 8bpp palette
    _swix(ColourTrans_ReadPalette, _INR(0, 4), -1, -1, palette, 1024, 0);

    // Check if its a grey palette
    screen->greyscale = 1;
    for (y = 0; y < 256; y++)
    {
      if (palette[y] != (uint32_t)(y * 0x01010100))
      {
        screen->greyscale = 0;
        break;
      }
    }
  }
  else if (vdu->log2bpp == 4)
  {
    // 4K/32K/64K colours
    // Note blk[0] incorrect on OS 4 (32K), so we check first if depth is supported
    if ((vdu->ncolour == 4095) && (glb_col_mask & (1 << KA_COL_4K)))
    {
      screen->col_depth = ka_col_depth_TBGR12;
    }
    else if ((vdu->ncolour == 65535) && (vdu->modeflags & 0x80) && (glb_col_mask & (1 << KA_COL_64K)))
    {
      screen->col_depth = ka_col_depth_TBGR16;
    }
    else
    {
      screen->col_depth = ka_col_depth_TBGR15;
    }
  }
  else if (vdu->log2bpp == 5)
  {
    // 16M colours
    screen->col_depth = ka_col_depth_TBGR32;
  }
  else if (vdu->log2bpp == 7)
  {
    switch(vdu->ncolour)
    {
      case 0x3231564e:
      {
        screen->col_depth = ka_col_depth_NV12;
        screen->log2Bpp = 0; // Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cr.Cb 2x2, sub-sampled
      }
      break;
      case 0x3132564e:
      {
        screen->col_depth = ka_col_depth_NV21;
        screen->log2Bpp = 0; // Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cb.Cr 2x2, sub-sampled
      }
      break;
      case 0x59565955:
      {
        screen->col_depth = ka_col_depth_UYVY;
        screen->log2Bpp = 1; // 32bpp words of &Y1.Cr.Y0.Cb, 2x1 sub-sampled
      }
      break;
      case 0x32595559:
      {
        screen->col_depth = ka_col_depth_YUY2;
        screen->log2Bpp = 1; // 32bpp words of &Cr.Y1.Cb.Y0, 2x1 sub-sampled
      }
      break;
      case 0x32315659:
      {
        screen->col_depth = ka_col_depth_YV12;
        screen->log2Bpp = 0; // Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x2 sub-sampled
      }
      break;
      case 0x36315659:
      {
        screen->col_depth = ka_col_depth_YV16;
        screen->log2Bpp = 0; // Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x1 sub-sampled
      }
      break;
    }
  }

  if (screen->col_depth >= ka_col_depth_TBGR12)
  {
    if (rgb)
      screen->col_depth += (ka_col_depth_TRGB12 - ka_col_depth_TBGR12);
    if (alpha)
      screen->col_depth += (ka_col_depth_ABGR12 - ka_col_depth_TBGR12);
  }

  screen->bytes_per_pixel = 1 << screen->log2Bpp;
}

/**
 * Setup screen mode if needed.
 *
 * @param  player   in : mode, aspect, full_screen
 *                  out: x_mag, y_mag, screen_paint.palette, full_screen, screen_plot
 * @param  vmode    vmode config.
 * @param  reason   reason for the change in display.
 * @param  overlay  1 if we will attempt to use an overlay, 0 otherwise.
 */
static void setup_mode(player_t* player, const video_mode* vmode, int reason, int overlay)
{
  int twidth = player->aspect.width;
  int theight = player->aspect.height;
  int blk[10];

  if ((reason == INIT_DISPLAY)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE))
  {
    if ((config.control & cfg_ctrl_accel_anymodes)
    &&  (!overlay))
    {
      // If any mode base screen size on image size

      // Selection of number of colours
      fs_cols = vmode->cols;
      if (player->mode == KA_MODE_AUTO)
      {
        select_mode( &fs_width, &fs_height, &fs_cols
                   , twidth, theight, 100);
      }

      // limitation of AnyMode
      if (fs_cols == KA_COL_256)
      {
        if (twidth & 0x0f)
          twidth = 32 + (twidth &~0x1f);
      }
      else
      {
        if (twidth & 0xf)
          twidth = 16 + (twidth &~0xf);
      }

      if (theight & 1)
        theight++;

      fs_width = twidth;
      fs_height = theight;
      fs_zoom = 100;
      player->x_mag = player->aspect.x_mag;
      player->y_mag = player->aspect.y_mag;
    }
    else
    {
      // Recheck auto mode for each new file
      // It takes no account of the zoom setting.
      if (player->mode == KA_MODE_AUTO)
      {
        select_mode( &fs_width, &fs_height, &fs_cols
                   , twidth, theight
                   , (vmode->cfg & cfg_video_fit_screen) ? 100 : vmode->zoom);
      }
      else if (player->mode == KA_MODE_MANUAL)
      {
        fs_width  = vmode->width;
        fs_height = vmode->height;
        fs_cols   = vmode->cols;
      }

      if (player->mode != KA_MODE_MULTITASK)
      {
        fs_zoom = select_zoom( fs_width, fs_height
                             , twidth, theight);
        if (vmode->cfg & cfg_video_fit_screen)
        {
          player->x_mag = (100 * player->aspect.x_mag) / fs_zoom;
          player->y_mag = (100 * player->aspect.y_mag) / fs_zoom;
        }
        else
        {
          if (reason != INIT_DISPLAY)
          {
            player->x_mag = (100 * player->aspect.x_mag) / vmode->zoom;
            player->y_mag = (100 * player->aspect.x_mag) / vmode->zoom;
          }
        }
      }
      else
      {
        if (reason != INIT_DISPLAY)
        {
          player->x_mag = (100 * player->aspect.x_mag) / vmode->zoom;
          player->y_mag = (100 * player->aspect.x_mag) / vmode->zoom;
        }
      }
    }

    // Setup full screen mode?
    if ((player->mode == KA_MODE_AUTO)
    ||  (player->mode == KA_MODE_MANUAL))
    {
      if ((player->full_screen.width != fs_width)
      ||  (player->full_screen.height != fs_height)
      ||  (player->full_screen.cols != fs_cols))
      {
        ScreenBanksCount = 1;

        // Restore cursors before mode change
        display_showCursors(1);

        // Save current desktop screen mode (if not done yet)
        desktop_saveMode();

        // Extend screen memory if required
        _swi(OS_ReadVduVariables, _INR(0, 1), VduSizeBlock, VduOutBlock);

        ScreenBanksCount = overlay ? 1 : MAX_SCREENBANKS;
        int32_t dummy = (ScreenBanksCount * fs_width * fs_height * col_bytes[fs_cols]) - VduOutBlock[0];
        if (dummy > 0)
        {
          // OS_ChangeDynamicArea, screen memory
          _swix(OS_ChangeDynamicArea, _INR(0, 1), 2, dummy);

          // If fails try with 1 bank only
          _swi(OS_ReadVduVariables, _INR(0, 1), VduSizeBlock, VduOutBlock);
          dummy = (fs_width * fs_height * col_bytes[fs_cols]) - VduOutBlock[0];
          if (dummy > 0)
          {
            // OS_ChangeDynamicArea, screen memory
            _swix(OS_ChangeDynamicArea, _INR(0, 1), 2, dummy);
          }
        }

        // Set up screen mode
        blk[0] = 1;          // flags, graphic mode
        blk[1] = fs_width;   // display width in pixels
        blk[2] = fs_height;  // display height in pixels
        blk[3] = col_depth[fs_cols];   // log2bpp
        blk[4] = -1;         // frame rate
        if (col_flags[fs_cols] || (fs_cols == KA_COL_4K) || (fs_cols == KA_COL_64K))
        {
          // Mode variables 0 and 3
          blk[5] = 0;
          blk[6] = col_flags[fs_cols];
          blk[7] = 3;
          blk[8] = col_ncolours[fs_cols]; // Ncolours
          blk[9] = -1;
        }
        else
        {
          blk[5] = -1;
        }

        const _kernel_oserror* err = _swix(Wimp_SetMode, _IN(0), blk); // So that Overlay maps correctly (cf. linked to a window)
        err = _swix(OS_ScreenMode, _INR(0,1), 0, blk); // Still required to be able to hide pointer
        if (err)
        {
          if (config.debug & (cfg_printwarnings | cfg_printdrawstats))
          {
            ka_logs(ka_log_error | ka_log_draw, "Mode (%d, %d) log2bpp %d", blk[1], blk[2], blk[3]);
            if (blk[5] != -1)
              ka_logn(", flags %x, ncolours %d", blk[6], blk[8]);
            ka_logn(", change failed: %s\n", err->errmess);
            report_error(1, "Mode change failed: %s\n", err->errmess);
          }
        }

        // Hide cursor before doing anything else
        display_showCursors(0);

        {
          const int VduSizesBlock[3] = {150, 7, -1}; // screen memory size, screen bank size
          int sizes[2];

          _swi(OS_ReadVduVariables, _INR(0, 1), VduSizesBlock, sizes);

          if (_swix(OS_ScreenMode, _IN(0)|_OUT(1), 7, &ScreenBanksCount) != NULL)
          {
            ScreenBanksCount = sizes[0] / sizes[1];
          }
          if (ScreenBanksCount > MAX_SCREENBANKS) ScreenBanksCount = MAX_SCREENBANKS;

          // Clear area in all screen banks
          for (int s = 0; s < ScreenBanksCount; s++)
          {
            uint8_t* dst = display_getScreenBank(s);
            memset(dst, 0, sizes[1]);
          }
        }

        if (config.debug & cfg_printdrawstats)
          ka_log(ka_log_info | ka_log_draw, "Screen banks: %d", ScreenBanksCount);

        // Rewrite palette in 8bpp with monochrome option
        if ((fs_cols == KA_COL_256) && (vmode->cfg & cfg_video_mono))
        {
          uint32_t* palette = player->screen_paint.palette;
          unsigned int y;

          for (y = 0; y < 256; y++)
            palette[y] = (uint32_t)(y * 0x01010100);

          // Set palette
          _swix(ColourTrans_WritePalette, _INR(0, 4), -1, -1, palette, 0, 0);
        }

        player->full_screen.width = fs_width;
        player->full_screen.height = fs_height;
        player->full_screen.cols = fs_cols;

        // Enforce clear of screen banks
        player->pos.width = 0;
      }
    }
    else
    {
      ScreenBanksCount = 1;
      player->full_screen.width = 0;
    }
  }

  // Required for overlay
  vdu_vars_t* vdu = (vdu_vars_t*) blk;
  fillVduList(vdu);
  int rgb = (vmode->cfg & cfg_video_to_BGR) != 0;

  screenFromVdu(&player->screen_plot, vdu, rgb, glb_col_mask);

  if ((reason == INIT_DISPLAY)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE)
  ||  (reason == ZOOM_CHANGE))
  {
    if ((config.video.cfg & cfg_video_lock_aspect)
    &&  (config.video.cfg & cfg_video_pixel_aspect)
    &&  (player->scaleconv.type != ka_scaler_none))
      player->y_mag = changemag(player->x_mag, player->aspect.y_mag, player->aspect.x_mag);
    else
      player->y_mag = player->x_mag;
  }
}

/**
 * Reinitializes display parameters after window resizing.
 */
void display_fracChange(player_t* player)
{
  player->osd_mag.x = player->x_mag;
  player->osd_mag.y = player->y_mag;

  player->pos.width = ((player->paint.dst_pix_width << FRAC) / player->x_mag);
  player->pos.height = ((player->paint.dst_height << FRAC) / player->y_mag);
  vo_setscale(player, &player->pos.width, &player->pos.height);
  player->pos.width <<= player->screen_plot.xeig;
  player->pos.height <<= player->screen_plot.yeig;
}

/**
 * Reinitializes display parameters according to video info
 * and reason call code.
 */
void display_setup(player_t* player, const ka_vinfo_t* vinfo, int reason)
{
  static const ka_vinfo_t dummy_vinfo =
    {"None", {320, 240}, {0, 0, ka_chroma_BGR}, {320, 240}, 0, {0, 0}, 0};
  int plot_x, plot_y;
  int frame_width, frame_height;
  int rgb_width, rgb_height;
  int scaled_width, scaled_height;
  int twidth, theight;
  int rgb_zoom2;
  vdu_vars_t vdu;
  video_mode* vmode;

  if (!vinfo || !vinfo->luminance.width || !vinfo->luminance.height)
    vinfo = &dummy_vinfo;

  if (reason == INIT_DISPLAY)
  {
    // Restore possible old desktop contents
    // to erase last image of previous video (cf. playlists)
    if (player->mode == KA_MODE_DESKTOP)
      savearea_plotDesktop(player);

    // Realloc buffers for draw.frame (deinterlace & subtitles)
    player->draw.frame.valid = 0;

    if (player->draw.frame.planes.y != NULL)
    {
      ka_mem_free(player->draw.frame.planes.y);
      player->draw.frame.planes.y = NULL;
    }
    if (player->draw.frame.planes.cb != NULL)
    {
      ka_mem_free(player->draw.frame.planes.cb);
      player->draw.frame.planes.cb = NULL;
    }
    if (player->draw.frame.planes.cr != NULL)
    {
      ka_mem_free(player->draw.frame.planes.cr);
      player->draw.frame.planes.cr = NULL;
    }
    int size = vinfo->luminance.width * vinfo->luminance.height;
    player->draw.frame.planes.y = ka_mem_alloc(size);
    if (player->draw.frame.planes.y == NULL)
    {
      // (Cannot allocate rgb buffer)
      report_error(1, "error4:%s of %d bytes", "YUV", size);
    }
    size = (size >> vinfo->chroma.wshift) >> vinfo->chroma.hshift;
    player->draw.frame.planes.cb = ka_mem_alloc(size);
    if (player->draw.frame.planes.cb == NULL)
    {
      // (Cannot allocate rgb buffer)
      report_error(1, "error4:%s of %d bytes", "YUV", size);
    }
    player->draw.frame.planes.cr = ka_mem_alloc(size);
    if (player->draw.frame.planes.cr == NULL)
    {
      // (Cannot allocate rgb buffer)
      report_error(1, "error4:%s of %d bytes", "YUV", size);
    }
  }

  //
  // Part 0: Get picture size after aspect correction and pan & scan.
  //

  vmode = &config.video.modes[player->mode];

  // Select default scaler
  if (player->acceleration_mode == ACCEL_MODE_GEMINUS)
    player->scaleconv.type = ka_scaler_system; // SpriteOp is intercepted by Geminus
  else if (player->acceleration_mode == ACCEL_MODE_VIDEOOVERLAY)
  {
    if ((player->mode != KA_MODE_MULTITASK)
    ||  ((config.control & cfg_ctrl_accel_videooverlay_not_multitasking) == 0))
      player->scaleconv.type = ka_scaler_hardware;
    else
      player->scaleconv.type = (ka_scaler_type) vmode->scaler;
  }
  else if ((player->mode == KA_MODE_MULTITASK)
  ||  (vmode->cfg & cfg_video_force_scaler))
    player->scaleconv.type = (ka_scaler_type) vmode->scaler;
  else
    player->scaleconv.type = ka_scaler_none;

  // Try to accelerate fit screen by not zooming but using of same size as picture
  config.control &= ~cfg_ctrl_accel_anymodes;
  if ((player->mode == KA_MODE_AUTO)
  ||  (player->mode == KA_MODE_MANUAL))
  {
    if ((vmode->cfg & cfg_video_fit_screen)
    &&  (rm_version("AnyMode") >= 0))
    {
        config.control |= cfg_ctrl_accel_anymodes;
    }
  }

  // Temporary set to signal not-stretched
  player->aspect.width = 0;

  // Strech image ?
  if ((config.video.cfg & cfg_video_lock_aspect)
  && (player->scaleconv.type != ka_scaler_none))
  {
    // Because asked to stretch ?
    if  (config.video.resizemode == KA_RESIZE_STRETCH)
    {
      int x, y;
      config_aspect_ratio(&config, &x, &y);

      int resize_width = ((x * vinfo->picture.height) > (y * vinfo->picture.width));
      if (resize_width)
      {
        player->aspect.width = (x * vinfo->picture.height) / y;
        player->aspect.height = vinfo->picture.height;
        player->aspect.x_mag = (vinfo->picture.width << FRAC) / player->aspect.width;
        player->aspect.y_mag = 1 << FRAC;
      }
      else
      {
        player->aspect.width = vinfo->picture.width;
        player->aspect.height = (y * vinfo->picture.width) / x;
        player->aspect.x_mag = 1 << FRAC;
        player->aspect.y_mag = (vinfo->picture.height << FRAC) / player->aspect.height;
      }
    }
    // Because of pixel aspect ?
    else if ((config.video.cfg & cfg_video_pixel_aspect)
    &&  (vinfo->aspect_ratio.width > 0)
    &&  (vinfo->aspect_ratio.height > 0))
    {
      // if fit screen and mode size controlled by picture size, don't compress height (beter visual quality)
      int resize_width = (vinfo->aspect_ratio.width > vinfo->aspect_ratio.height);
      resize_width &= ((vmode->cfg & cfg_video_fit_screen) != 0);
      resize_width &= (((config.control & cfg_ctrl_accel_anymodes) != 0) || (player->mode == KA_MODE_AUTO));
      if (resize_width)
      {
        player->aspect.width = (vinfo->aspect_ratio.width * vinfo->picture.width) / vinfo->aspect_ratio.height;
        player->aspect.height = vinfo->picture.height;
        player->aspect.x_mag = (vinfo->aspect_ratio.height << FRAC) / vinfo->aspect_ratio.width;
        player->aspect.y_mag = 1 << FRAC;
      }
      else
      {
        player->aspect.width = vinfo->picture.width;
        player->aspect.height = (vinfo->aspect_ratio.height * vinfo->picture.height) / vinfo->aspect_ratio.width;
        player->aspect.x_mag = 1 << FRAC;
        player->aspect.y_mag = (vinfo->aspect_ratio.width << FRAC) / vinfo->aspect_ratio.height;
      }
    }
  }

  // Not stretched ?
  if (player->aspect.width == 0)
  {
    player->aspect.width = vinfo->picture.width;
    player->aspect.height = vinfo->picture.height;
    player->aspect.x_mag = 1 << FRAC;
    player->aspect.y_mag = 1 << FRAC;
  }

  // Pan & scan ?
  if (config.video.resizemode == KA_RESIZE_PANSCAN)
    panscan(player, &player->aspect.width, &player->aspect.height);

  //
  // Part 1: Setup screen mode and colour depth info
  //
  if ((reason == INIT_DISPLAY)
  ||  (reason == MODE_CHANGE)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE)
  ||  (player->vo_overlay && (reason == ZOOM_CHANGE)))
  {
    static const uint32_t overlay_modes[][4] =
    // YUV first, by increasing subsampling, RGB by decreasing resolution
    // NColour ModeFlags Log2BPP BPP
    { {0x36315659, 0x6000, 7, 1} // YV16, Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x1 sub-sampled
    , {0x59565955, 0x6000, 7, 1} // UYVY, 32bpp words of &Y1.Cr.Y0.Cb, 2x1 sub-sampled
    , {0x32595559, 0x6000, 7, 1} // YUY2, 32bpp words of &Cr.Y1.Cb.Y0, 2x1 sub-sampled
    , {0x32315659, 0x6000, 7, 1} // YV12, Plane 0 is 8bpp Y, Plane 1 Cb, Plane 2 Cr, 2x2 sub-sampled
    , {0x3231564e, 0x6000, 7, 1} // NV12, Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cr.Cb, 2x2 sub-sampled
    , {0x3132564e, 0x6000, 7, 1} // NV21, Plane 0 is 8bpp Y, Plane 1 is 16bpp &Cb.Cr, 2x2 sub-sampled
    , {-1,         0x0000, 5, 4} // TBGR32
    , {-1,         0x4000, 5, 4} // TRGB32
    , {-1,         0x8000, 5, 4} // ABGR32
    , {-1,         0xC000, 5, 4} // ARGB32
    , {65535,      0x0080, 4, 2} // BGR16
    , {65535,      0x4080, 4, 2} // RGB16
    , {65535,      0x0000, 4, 2} // TBGR15
    , {65535,      0x4000, 4, 2} // TRGB15
    , {65535,      0x0000, 4, 2} // ABGR15
    , {65535,      0x4000, 4, 2} // ARGB15
    , {4095,       0x8000, 4, 2} // TBGR12
    , {4095,       0xC000, 4, 2} // TRGB12
    , {4095,       0x8000, 4, 2} // ABGR12
    , {4095,       0xC000, 4, 2} // ARGB12
    };

    // Release existing DMA
    inteldma_cleanupTransfer(player->idma);

    // For overlays we need to setup screen mode first, to avoid overlays larger than the screen
    if ((player->acceleration_mode == ACCEL_MODE_VIDEOOVERLAY)
    &&  (player->scaleconv.type == ka_scaler_hardware))
    {
      setup_mode(player, vmode, reason, 1);
    }

    // Release existing overlay ?
    if (player->vo_overlay)
    {
      rgb_zoom2 = 2; // 100%
      frame_width  = player->aspect.width;
      frame_height = player->aspect.height;
      frameArea(player, vmode, rgb_zoom2, &frame_width, &frame_height);
      roundFrameSize(&frame_width, &frame_height, vinfo
                   , rgb_zoom2, overlay_modes[player->vo_mode_index][3] /* bytes per pixel */, 1);

      if ((player->acceleration_mode != ACCEL_MODE_VIDEOOVERLAY)
      ||  (player->scaleconv.type != ka_scaler_hardware)
      ||  (player->vo_aspect != ((vinfo->aspect_ratio.width<<16)|vinfo->aspect_ratio.height))
      ||  (player->vo_desc.width != frame_width)
      ||  (player->vo_desc.height != frame_height))
      {
        _swix(VideoOverlay_Destroy, _IN(0), player->vo_overlay);
        player->vo_overlay = 0;
      }
    }

    if ((player->acceleration_mode == ACCEL_MODE_VIDEOOVERLAY)
    &&  (player->scaleconv.type == ka_scaler_hardware)
    &&  (!player->vo_overlay))
    {
      if (vinfo != &dummy_vinfo)
      {
        player->vo_desc.flags = 1;
        player->vo_desc.framerate = -1;
        player->vo_desc.modeflags_varnum = 0;
        player->vo_desc.ncolour_varnum = 3;
        player->vo_desc.nbuffers_varnum = 13;
        player->vo_desc.nbuffers = MAX_SCREENBANKS;
        player->vo_desc.terminator = -1;
        // Ensure buffer is large enough for the full frame
        player->vo_aspect = (vinfo->aspect_ratio.width<<16)|vinfo->aspect_ratio.height;

        for(int i= 0; i < (sizeof(overlay_modes) / sizeof(overlay_modes[0])); i++)
        {
          // ignore YUV overlays for RGB type
          if ((vinfo->chroma.type == ka_chroma_BGR) && (overlay_modes[i][2] > 4))
            continue;
          // display complete area
          rgb_zoom2 = 2; // 100%
          frame_width  = player->aspect.width;
          frame_height = player->aspect.height;
          frameArea(player, vmode, rgb_zoom2, &frame_width, &frame_height);
          roundFrameSize(&frame_width, &frame_height, vinfo
                       , rgb_zoom2, overlay_modes[i][3] /* bytes per pixel */, 1);
          player->vo_desc.width = frame_width;
          player->vo_desc.height = frame_height;

          player->vo_desc.ncolour = overlay_modes[i][0];
          player->vo_desc.modeflags = overlay_modes[i][1];
          player->vo_desc.log2bpp = overlay_modes[i][2];
          const _kernel_oserror * err;
          err = _swix(VideoOverlay_Create, _INR(0,3)|_OUT(0)
                    , &player->vo_desc, player->vo_aspect, 1, task_handle
                    , &player->vo_overlay);
          if (!err)
          {
            player->vo_mode_index = i;
/*            ka_log( ka_log_info | ka_log_draw
                  , "VO overlay: %08x %d x %d ncolour %x modeflags %x log2bpp %d: OK"
                  , player->vo_overlay
                  , player->vo_desc.width
                  , player->vo_desc.height
                  , player->vo_desc.ncolour
                  , player->vo_desc.modeflags
                  , player->vo_desc.log2bpp);
*/
            // clear all overlay banks
            for (int b = 0; b < OverlayBanksCount; b++)
            {
              const gvoverlayplane_t* planes;
              display_getOverlayBank(player, b, &planes);
              uint32_t size = planes[0].stride * player->vo_desc.height;
              memset(planes[0].ba, 0, size);

              if (player->vo_desc.ncolour == 0x36315659) // YV16
              {
                size = planes[1].stride * player->vo_desc.height;
                memset(planes[1].ba, 0x80, size);
                size = planes[2].stride * player->vo_desc.height;
                memset(planes[2].ba, 0x80, size);
              }
              else if (player->vo_desc.ncolour == 0x32315659)  // YV12
              {
                size = planes[1].stride * (player->vo_desc.height >> 1);
                memset(planes[1].ba, 0x80, size);
                size = planes[2].stride * (player->vo_desc.height >> 1);
                memset(planes[2].ba, 0x80, size);
              }
              else if ((player->vo_desc.ncolour == 0x3231564e)  // NV12
                   ||  (player->vo_desc.ncolour == 0x3132564e)) // NV12
              {
                size = planes[1].stride * (player->vo_desc.height >> 1);
                memset(planes[1].ba, 0x80, size);
              }
              _swix(VideoOverlay_UnmapBuffer, _INR(0, 1), player->vo_overlay, b);
            }
            // needed so change is taken into account
            for (int b = 0; b < OverlayBanksCount; b++)
              display_swapBanks(player);

            break;
          }
/*          else
          {
            ka_log( ka_log_error | ka_log_draw
                  , "VO overlay: %08x %d x %d ncolour %x modeflags %x log2bpp %d: %s"
                  , player->vo_overlay
                  , player->vo_desc.width
                  , player->vo_desc.height
                  , player->vo_desc.ncolour
                  , player->vo_desc.modeflags
                  , player->vo_desc.log2bpp
                  , err->errmess);
          }
*/
        }
      }

      if (player->vo_overlay)
      {
        // Assume overlay will be faster than anymodes
        config.control &= ~cfg_ctrl_accel_anymodes;
      }
      else
      {
        // Can't create overlay, give up
        player->scaleconv.type = (ka_scaler_type) vmode->scaler;
      }
    }
  }

  if ((reason == INIT_DISPLAY)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE))
  {
    // Only if not done yet for overley
    if (!player->vo_overlay)
      setup_mode(player, vmode, reason, 0);
  }


  //
  // Part 2: Read screen mode info, select scaler and acceleration mode
  //

  // Read current screen variables.
  // These will either be for the full screen mode we have just setup
  // or the current desktop mode.
  player->scaleconv.config &= ~ka_scaleconv_config_monochrome;

  fillVduList(&vdu);
  int rgb = (vmode->cfg & cfg_video_to_BGR) != 0;

  screenFromVdu(&player->screen_plot, &vdu, rgb, glb_col_mask);
  if (player->vo_overlay)
  {
    // Patch in the values from the overlay desc
    vdu.ncolour = player->vo_desc.ncolour;
    vdu.log2bpp = player->vo_desc.log2bpp;
 //   vdu.width = player->vo_desc.width;
 //   vdu.height = player->vo_desc.height;
    vdu.modeflags = player->vo_desc.modeflags;
  }
  screenFromVdu(&player->screen_paint, &vdu, rgb, glb_col_mask);
  player->ptrans = NULL;

  if (player->screen_paint.col_depth == -1)
  {
    // Unsupported colour depth (either less than 256 colours or an unkown new one)
    // We will switch output to 256 greys rgbarea and use the system scaler to plot it
    // (assuming that OS_SpriteOp with translation table works)
    uint32_t* palette = player->screen_paint.palette;
    unsigned int y;

    for (y = 0; y < 256; y++)
      palette[y] = (uint32_t)(y * 0x01010100);

    player->ptrans = &player->trans[0];

    player->screen_paint.col_depth = ka_col_depth_256;
    player->screen_paint.bytesperrow = player->screen_paint.width;
    player->screen_paint.log2Bpp = 0;
    player->screen_paint.bytes_per_pixel = 1;
    player->screen_paint.greyscale = 1;
    if (player->screen_plot.col_depth == -1)
      player->screen_plot = player->screen_paint;
    // Cannot use direct plotting
    player->scaleconv.type = ka_scaler_system;
  }

  if (player->screen_paint.greyscale)
    player->scaleconv.config |= ka_scaleconv_config_monochrome;

  player->screen_sprite = player->screen_plot;

  // Force sprite mode for tests:
  //   must also modify sprite.c "if (player->paint.dst_base)" -> "if (0)"
  // player->screen_sprite.col_depth = ka_col_depth_ARGB32;
  // player->screen_sprite.log2Bpp = 2;
  // player->screen_sprite.bytes_per_pixel = 1 << player->screen_sprite.log2Bpp;
  // player->screen_sprite.bytesperrow = player->screen_sprite.width * player->screen_sprite.bytes_per_pixel;

  if ((reason == INIT_DISPLAY)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE)
  ||  (reason == ZOOM_CHANGE))
  {
    if ((config.video.cfg & cfg_video_lock_aspect)
    &&  (config.video.cfg & cfg_video_pixel_aspect)
    &&  (player->scaleconv.type != ka_scaler_none))
      player->y_mag = changemag(player->x_mag, player->aspect.y_mag, player->aspect.x_mag);
    else
      player->y_mag = player->x_mag;
  }

  //
  // Part 3: Select colour space converter
  //

  // Zoom is handled differently when converting into an rgb area
  // and when converting directly to screen. Direct conversion
  // to screen can be only be done in a fixed set of zooms, while
  // conversion into an rgb area is always done at 100%
  // and it is the scaler function that plots this area to the screen
  // which performs the scaling.

  if (player->scaleconv.type != ka_scaler_none)
    player->zoom = 100;
  else if (vinfo == &dummy_vinfo)
    player->zoom = 100;
  else if (config.control & cfg_ctrl_accel_anymodes)
    player->zoom = 100;
  else if (reason != ZOOM_CHANGE)
  {
    if (vmode->cfg & cfg_video_fit_screen)
      player->zoom = fs_zoom;
    else
      player->zoom = vmode->zoom;
  }
  else
    player->zoom = vmode->zoom;

  // Setup color space conversion
  {
    ka_colourspace_t* colour = &player->colourspace;

    colour->brightness = config.video.brightness;
    colour->contrast = config.video.contrast;
    colour->colour = config.video.colour;
    colour->config = vmode->cfg;
    colour->hardware = player->hardware;
    colour->chroma_type = vinfo->chroma.type;

    if (player->scaleconv.type != ka_scaler_none)
    {
      colour->zoom = ka_zoom_1;
      rgb_zoom2 = 2;
    }
    else
    {
      switch (player->zoom)
      {
        case  50: colour->zoom = ka_zoom_05; break;
        case 200: colour->zoom = ka_zoom_2; break;
        case 300: colour->zoom = ka_zoom_3; break;
        case 400: colour->zoom = ka_zoom_4; break;
        default:  colour->zoom = ka_zoom_1;
      }
      rgb_zoom2 = player->zoom / 50;
    }

    if (reason == ZOOM_CHANGE)
    {
      player->drawer = ColourConverter_fastsetup(&player->error_block, &player->screen_paint, colour);
      player->drawer_sprite = ColourConverter_fastsetup(&player->error_block, &player->screen_sprite, colour);
    }
    else
    {
      player->drawer = ColourConverter_setup(&player->error_block, &player->screen_paint, colour);
      player->drawer_sprite = ColourConverter_setup(&player->error_block, &player->screen_sprite, colour);
    }
    if (!player->drawer || !player->drawer_sprite)
    {
      report_error(1, player->error_block.errmess);
    }
    player->paint.params = &colour->params;
    player->paint_sprite.params = &colour->params;
  }

  //
  // Part 4: Determine the sizes of source and output areas for the colour space converter
  //         in regard of limiting factors like screen size and conversion alignments.
  //

  // Default colour space converter source size
  if ((vinfo == &dummy_vinfo)
  &&  (player->mode != KA_MODE_MULTITASK))
  {
    // Fit "novideo" banner to screen size if not too large (cf. memory)
    rgb_width = player->screen_paint.width;
    rgb_height = player->screen_paint.height;

    while ((vinfo->picture.width << 1) < rgb_width)
      rgb_width >>= 1;
    while ((vinfo->picture.height << 1) < rgb_height)
      rgb_height >>= 1;

    player->aspect.width = (rgb_width << 1) / rgb_zoom2;
    player->aspect.height = (rgb_height << 1) / rgb_zoom2;
    player->aspect.x_mag = 1 << FRAC;
    player->aspect.y_mag = 1 << FRAC;
  }
  else
  {
    // Default colour space converter output size
    rgb_width = (player->aspect.width * rgb_zoom2) >> 1;
    rgb_height = (player->aspect.height * rgb_zoom2) >> 1;
  }

  // Setup area for sprite saving
  {
    rgb_savearea* area = &player->rgb_area[SAVEAREA_SPRITE];
    int width, height;

    frame_width = (vinfo->picture.width << 1) / rgb_zoom2;
    frame_height = (vinfo->picture.height << 1) / rgb_zoom2;
    roundFrameSize(&frame_width, &frame_height, vinfo, rgb_zoom2, player->screen_sprite.bytes_per_pixel, 0);

    // Final colour space converter output sizes
    width = (frame_width * rgb_zoom2) >> 1;
    height = (frame_height * rgb_zoom2) >> 1;

    // Request a buffer of exact colour space converter output size
    savearea_getInstance( area
                        , "Sprite"
                        , width * player->screen_sprite.bytes_per_pixel
                        , height
                        , &player->screen_sprite);

    player->paint_sprite.yc_bpr = vinfo->luminance.width;
    player->paint_sprite.yc_width = width;
    player->paint_sprite.yc_height = height;
    player->paint_sprite.yc_skip = player->paint_sprite.yc_bpr - player->paint_sprite.yc_width;
    player->paint_sprite.uv_skip = player->paint_sprite.yc_skip >> vinfo->chroma.wshift;
    player->paint_sprite.yc_offset = 0;
    player->paint_sprite.uv_offset = 0;
    if (vinfo->chroma.type == ka_chroma_BGR)
    {
      player->paint_sprite.yc_bpr *= 4;
      player->paint_sprite.yc_skip *= 4;
      player->paint_sprite.yc_offset *= 4;
    }
    player->paint_sprite.dst_byte_width = width * player->screen_sprite.bytes_per_pixel;
    player->paint_sprite.dst_pix_width = width;
    player->paint_sprite.dst_height = height;
    player->paint_sprite.dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
    player->paint_sprite.dst_bpr = player->paint_sprite.dst_byte_width;
    player->paint_sprite.dst_skip = 0;
    player->paint_sprite.dst_base = 0;
    player->paint_sprite.dst_offset = 0;
  }

  // Frame width/height: if multitasking, from scaled sprite, else from display area.
  // If fit screen, x_mag anf y_mag are updated.
  frame_width  = rgb_width;
  frame_height = rgb_height;
  frameArea(player, vmode, rgb_zoom2, &frame_width, &frame_height);
  roundFrameSize(&frame_width, &frame_height, vinfo, rgb_zoom2
               , player->screen_paint.bytes_per_pixel
               , (player->scaleconv.type != ka_scaler_none));

  // for overlay, use overlay dimensions
  if (player->vo_overlay)
  {
    frame_width = player->vo_desc.width;
    frame_height = player->vo_desc.height;
  }

  // We now have the final colour space converter source size

  switch(player->acceleration_mode)
  {
    case ACCEL_MODE_INTELDMA:
    {
      if ((player->x_mag == (1 << FRAC))
      &&  (player->y_mag == (1 << FRAC)))
      {
        player->acceleration_mode_used = ACCEL_MODE_INTELDMA;
      }
      else
      {
        player->acceleration_mode_used = ACCEL_MODE_NONE;
      }
    }
    break;
    case ACCEL_MODE_GEMINUS:
    {
      player->acceleration_mode_used = ACCEL_MODE_GEMINUS;
    }
    break;
    case ACCEL_MODE_VIDEOOVERLAY:
    {
      player->acceleration_mode_used = (player->vo_overlay) ? ACCEL_MODE_VIDEOOVERLAY : ACCEL_MODE_NONE;
    }
    break;
    default:
    {
      player->acceleration_mode_used = ACCEL_MODE_NONE;
    }
  }

  //
  // Part 5: Setup colour space converter source area parameters.
  //

  // Set paint source parameters
  player->paint.yc_bpr = vinfo->luminance.width;
  player->paint.yc_width = frame_width;
  player->paint.yc_height = frame_height;
  player->paint.yc_skip = player->paint.yc_bpr - player->paint.yc_width;
  player->paint.uv_skip = player->paint.yc_skip >> vinfo->chroma.wshift;

  // Show center of source frame if output doesn't cover everything
  // Align with the smaller chroma tables
  twidth  = (vinfo->picture.width - frame_width) / 2;
  theight = (vinfo->picture.height - frame_height) / 2;
  twidth  >>= vinfo->chroma.wshift;
  theight >>= vinfo->chroma.hshift;
  if (vinfo->chroma.hshift && !vinfo->progressive)
    theight &= ~1; // ensure alignement of luma and chroma on interlaced YUV420
  twidth  &= ~3; // converter requires word alignment
  player->paint.uv_offset = twidth + theight * (player->paint.yc_bpr >> vinfo->chroma.wshift);
  twidth  <<= vinfo->chroma.wshift;
  theight <<= vinfo->chroma.hshift;
  player->paint.yc_offset = twidth + theight * player->paint.yc_bpr;
  if (vinfo->chroma.type == ka_chroma_BGR)
  {
    player->paint.yc_bpr *= 4;
    player->paint.yc_skip *= 4;
    player->paint.yc_offset *= 4;
  }

  //
  // Part 6: Setup colour space converter output area parameters.
  //         The output is either directly to screen or into an rgb buffer.
  //

  // Final colour space converter output sizes
  rgb_width = (frame_width * rgb_zoom2) >> 1;
  rgb_height = (frame_height * rgb_zoom2) >> 1;

  player->paint.dst_byte_width = rgb_width * player->screen_paint.bytes_per_pixel;
  player->paint.dst_pix_width = rgb_width;
  player->paint.dst_height = rgb_height;

  // Final scaled output size
  if (player->scaleconv.type != ka_scaler_none)
  {
    scaled_width = (rgb_width << FRAC) / player->x_mag;
    scaled_height = (rgb_height << FRAC) / player->y_mag;
    if (player->mode != KA_MODE_MULTITASK)
    {
      if (scaled_width  > player->screen_plot.width)
        scaled_width  = player->screen_plot.width;
      if (scaled_height > player->screen_plot.height)
        scaled_height = player->screen_plot.height;
    }
    player->osd_mag.x = player->x_mag;
    player->osd_mag.y = player->y_mag;
  }
  else
  {
    scaled_width = rgb_width;
    scaled_height = rgb_height;
    player->osd_mag.x = (1 << FRAC);
    player->osd_mag.y = (1 << FRAC);
  }

  vo_setscale(player, &scaled_width, &scaled_height);

  // Center on screen ...
  plot_x = (player->screen_plot.width - scaled_width) / 2;  // pixels
  plot_y = (player->screen_plot.height - scaled_height) / 2; // pixels

  // ... unless explicit position is used
  // or scaled ouput size is larger than screen size (possible in multitasking mode)
  if ((player->mode == KA_MODE_MULTITASK)
  ||  (player->mode == KA_MODE_DESKTOP))
  {
    if (player->mode == config.video.mode)
    {
      if (config.video.position_x != -1)
        plot_x = config.video.position_x;
      if (config.video.position_y != -1)
        plot_y = player->screen_plot.height - config.video.position_y - scaled_height;
    }
    if (plot_x < 0)
      plot_x = 0;
    if (plot_x + scaled_width >player->screen_plot.width)
      plot_x = player->screen_plot.width - scaled_width;
    if (plot_y < 0)
      plot_y = 0;
    if (plot_y + scaled_height > player->screen_plot.height)
      plot_y = player->screen_plot.height - scaled_height;
  }

  // Convert to bytes and word align (cf. internal scaler)
  plot_x = (plot_x * player->screen_plot.bytes_per_pixel) & ~3;

  if (player->acceleration_mode_used == ACCEL_MODE_INTELDMA)
  {
    //
    // Setup colour space converter for output to an intermediate buffer.
    // Actual screen redraw will be performed by DMA transfer.
    //
    rgb_savearea* area = &player->rgb_area[SAVEAREA_PLOTTER];

    if ((player->mode == KA_MODE_AUTO)
    ||  (player->mode == KA_MODE_MANUAL))
    {
      // Request a buffer of the same size as the screen
      savearea_getInstance( area
                          , "RGB"
                          , player->screen_paint.bytesperrow
                          , player->screen_paint.height
                          , &player->screen_paint);
      player->paint.dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
      player->paint.dst += (plot_y * player->screen_paint.bytesperrow) + plot_x;
      player->paint.dst_bpr = player->screen_paint.bytesperrow;
      player->paint.dst_base = player->paint.dst;
      player->paint.dst_offset = 0;
    }
    else
    {
      // Request a buffer of exact colour space converter output size
      savearea_getInstance( area
                          , "RGB"
                          , player->paint.dst_byte_width
                          , scaled_height
                          , &player->screen_paint);
      player->paint.dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
      player->paint.dst_bpr = player->paint.dst_byte_width;
      player->paint.dst_base = player->paint.dst;
      player->paint.dst_offset = 0;
    }
  }
  else if (player->vo_overlay)
  {
    //
    // Setup colour space converter for plotting to overlay
    //
    player->paint.dst = 0; // screen banks
    player->paint.dst_bpr = player->vo_desc.width << player->screen_paint.log2Bpp;
    player->paint.dst_base = 0; // screen banks
    player->paint.dst_offset = 0;
  }
  else if (player->scaleconv.type != ka_scaler_none)
  {
    //
    // Setup colour space converter for output an intermediate buffer.
    // The scaler will perform the actual screen redraw from this buffer.
    //
    rgb_savearea* area = &player->rgb_area[SAVEAREA_PLOTTER];

    // Request a buffer of exact colour space converter output size
    savearea_getInstance( area
                        , "RGB"
                        , player->paint.dst_byte_width
                        , player->paint.dst_height
                        , &player->screen_paint);
    // Direct colour space converter output to start of buffer
    player->paint.dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
    player->paint.dst_bpr = player->paint.dst_byte_width;
    player->paint.dst_base = player->paint.dst;
    player->paint.dst_offset = 0;
  }
  else
  {
    //
    // Setup colour space converter for direct to screen plotting.
    //
    player->paint.dst = 0; // screen banks
    player->paint.dst_bpr = player->screen_paint.bytesperrow;
    player->paint.dst_base = 0; // screen banks
    player->paint.dst_offset = plot_y * player->screen_paint.bytesperrow + plot_x;
  }
  player->paint.dst_skip = player->paint.dst_bpr - player->paint.dst_byte_width;

  // Now that with have the sprite (i.e. the rgb area)
  // we can setup the colour translation table if required.
  // Note: it's because I never managed to get it right by using a mode number and palette.
  if (player->ptrans != NULL)
  {
    sprite_header_t* sp_area = player->rgb_area[SAVEAREA_PLOTTER].frame;

    _swi(ColourTrans_SelectTable, _INR(0,5)
        , sp_area
        , (uint32_t) sp_area + sp_area->offset
        , -1
        , -1
        , player->ptrans
        , 0x1);
  }

  //
  // Part 7: Define new area occupied on screen and restored old uncovered area.
  //
  {
    scr_pos_t old = player->pos;

    player->pos.x = plot_x / player->screen_plot.bytes_per_pixel;
    player->pos.y = player->screen_plot.height - plot_y - scaled_height;
    player->pos.width = scaled_width;
    player->pos.height = scaled_height;

    if (player->mode == KA_MODE_MULTITASK)
    {
      // Save for next video
      config.video.position_x = player->pos.x;
      config.video.position_y = player->pos.y;

      // Is window relative
      player->pos.x = 0;
      player->pos.y = 0;
    }

    // Transform plotter parameters into OS coordinates
    player->pos.x <<= player->screen_plot.xeig;
    player->pos.y <<= player->screen_plot.yeig;
    player->pos.width <<= player->screen_plot.xeig;
    player->pos.height <<= player->screen_plot.yeig;

    // Restore uncovered parts when zooming
    if (!player->vo_overlay)
      background_fix(player, &old, &player->pos);
  }

  //
  // Part 8: Setup scaler input and output parameters.
  //

  player->plot.src = player->paint.dst;
  player->plot.src_bpr = player->paint.dst_bpr;
  player->plot.src_byte_width = player->paint.dst_byte_width;
  player->plot.src_pix_width = player->paint.dst_pix_width;
  player->plot.src_height = player->paint.dst_height;

  player->plot.dst_offset = plot_y * player->screen_plot.bytesperrow + plot_x;
  player->plot.dst_bpr = player->screen_plot.bytesperrow;
  player->plot.dst_byte_width = scaled_width * player->screen_plot.bytes_per_pixel;
  player->plot.dst_pix_width = scaled_width;
  player->plot.dst_height = scaled_height;

  //
  // Part 9: Setup remaining areas and window.
  //

  if ((player->mode == KA_MODE_DESKTOP)
  &&  ((reason == INIT_DISPLAY) || (reason == SCREEN_CHANGE)))
  {
    // Save desktop area that will be plotted over
    savearea_saveDesktop(player);
  }

  if (vinfo == &dummy_vinfo)
  {
    // Reserve rgb frame buffer for banner
    savearea_getInstance( &player->rgb_area[SAVEAREA_BANNER]
                        , "NoVideo"
                        , player->paint.dst_byte_width
                        , player->paint.dst_height
                        , &player->screen_paint);

    player->status_flags &= ~player_status_plot_isbanner;
    player->status_flags |= player_status_redrawframe;
  }
  else
  {
    rgb_savearea* area = &player->rgb_area[SAVEAREA_BANNER];
    if (area->frame)
    {
      ka_mem_free(area->frame);
      area->frame = NULL;
    }
  }

  mt_setupWindow(player, reason);

  if (player->mode != KA_MODE_MULTITASK)
    display_showCursors(0);
}

/**
 * Plots a banner to the rgb/screen buffer.
 */
void display_paintbanner(player_t* player)
{
  // Plot banner to screen/buffer
  static int* banner = NULL;
  static int bannersize = 0;
  const rgb_savearea* area = &player->rgb_area[SAVEAREA_BANNER];
  const ka_screen_t* screen = &player->screen_paint;

  if (!player->paint.dst)
    return;

  // Load banner if required
  if (!banner && bannersize == 0)
  {
    FILE* fh;
    bannersize = -1;
    fh = fopen("<Kino$Dir>.NoVideo", "rb");
    if (fh)
    {
      fseek(fh, 0, SEEK_END);
      bannersize = (int)ftell(fh);
      fseek(fh, 0, SEEK_SET);
      banner = ka_mem_alloc(bannersize);
      if (!banner)
        bannersize = -1;
      else
        fread(banner, 1, bannersize, fh);
      fclose(fh);
    }
  }

  // Recalculate banner in save area?
  if (area->frame
  && !(player->status_flags & player_status_plot_isbanner))
  {
    _kernel_swi_regs saveregs;
    void* SaveArea = NULL;

    // OS_SpriteOp: read save area size
    saveregs.r[0] = 0x200 + 62;
    saveregs.r[1] = (int) area->frame;
    saveregs.r[2] = (int) &area->frame->size_2;
    _kernel_swi(OS_SpriteOp, &saveregs, &saveregs);
    SaveArea = ka_mem_calloc(saveregs.r[3]);
    if (SaveArea)
    {
      // Render in sprite

      // OS_SpriteOp: redirect output to sprite
      saveregs.r[0] = 0x200 + 60;
      saveregs.r[1] = (int) area->frame;
      saveregs.r[2] = (int) &area->frame->size_2;
      saveregs.r[3] = (int) SaveArea;
      _kernel_swi(OS_SpriteOp, &saveregs, &saveregs);

      render_banner(banner, bannersize, 0, 0
                  , (area->byte_width >> screen->log2Bpp) << screen->xeig
                  , area->height << screen->yeig);

      // OS_SpriteOp: restore old output
      _kernel_swi(OS_SpriteOp, &saveregs, &saveregs);
      ka_mem_free(SaveArea);
      player->status_flags |= player_status_plot_isbanner;
    }
  }

  // Plot banner or clear rgb screen/buffer
  if (area->frame
  &&  (player->status_flags & player_status_plot_isbanner))
  {
    uint8_t *src = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
    uint8_t *dst = player->paint.dst;
    int i;

    if (area->byte_width*area->height + area->frame->offset + area->frame->s_offset > area->buf_size)
    {
      // (buffer overflow)
      report_error(1, "error6: %s %d %d", "Banner SaveArea"
                 , area->byte_width*area->height + area->frame->offset + area->frame->s_offset
                 , area->buf_size);
    }
    else if (area->byte_width != player->paint.dst_byte_width)
    {
      report_error(1, "error6: %s %d %d", "Banner SaveArea X", area->byte_width, player->paint.dst_byte_width);
    }
    else if (area->height != player->paint.dst_height)
    {
      report_error(1, "error6: %s %d %d", "Banner SaveArea Y", area->height, player->paint.dst_height);
    }

    for (i = player->paint.dst_height; i; i--)
    {
      memcpy(dst, src, player->paint.dst_byte_width);
      src += player->paint.dst_byte_width;
      dst += player->paint.dst_bpr;
    }
  }
  else
  {
    uint8_t *dst = player->paint.dst;
    int i;

    for(i = player->paint.dst_height; i; i--)
    {
      memset(dst, 0, player->paint.dst_byte_width);
      dst += player->paint.dst_bpr;
    }
  }
}

/**
 * Sets/Read the brightness of the display.
 *
 * @param  player    Player instance.
 * @param  brightness  New brighness [0, 200], 100 is default, -1 for read only.
 *
 * @returns Current brightness.
 */
int display_brightness(player_t* player, int brightness)
{
  if (brightness != -1)
  {
    if (brightness < 0) brightness = 0;
    if (brightness > 200) brightness = 200;

    player->colourspace.brightness = config.video.brightness = brightness;
    player->drawer = ColourConverter_fastsetup(&player->error_block, &player->screen_paint, &player->colourspace);
    player->drawer_sprite = ColourConverter_fastsetup(&player->error_block, &player->screen_sprite, &player->colourspace);
    if (!player->drawer || !player->drawer_sprite)
      report_error(1, player->error_block.errmess);
  }
  return config.video.brightness;
}

/**
 * Sets/Read the contrasts of the display.
 *
 * @param  player  Player instance.
 * @param  contrast  New contrast [0 ... 100 ... 200], or -1 to read.
 *
 * @returns Current contrast.
 */
int display_contrast(player_t* player, int contrast)
{
  if (contrast != -1)
  {
    if (contrast < 0) contrast = 0;
    if (contrast > 200) contrast = 200;

    player->colourspace.contrast = config.video.contrast = contrast;
    player->drawer = ColourConverter_fastsetup(&player->error_block, &player->screen_paint, &player->colourspace);
    player->drawer_sprite = ColourConverter_fastsetup(&player->error_block, &player->screen_sprite, &player->colourspace);
    if (!player->drawer || !player->drawer_sprite)
      report_error(1, player->error_block.errmess);
  }

  return config.video.contrast;
}

/**
 * Sets/Read the colour saturation of the display.
 *
 * @param  player  Player instance.
 * @param  colour    New colour saturation [0 ... 100 ...  200], or -1 to read.
 *
 * @returns Current colour saturation.
 */
int display_colour(player_t* player, int colour)
{
  if (colour != -1)
  {
    if (colour < 0) colour = 0;
    if (colour > 200) colour = 200;

    player->colourspace.colour = config.video.colour = colour;
    player->drawer = ColourConverter_fastsetup(&player->error_block, &player->screen_paint, &player->colourspace);
    player->drawer_sprite = ColourConverter_fastsetup(&player->error_block, &player->screen_sprite, &player->colourspace);
    if (!player->drawer || !player->drawer_sprite)
      report_error(1, player->error_block.errmess);
  }

  return config.video.colour;
}
