/*
 * 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_MODES 64
static screen_mode modes[MAX_MODES];
static screen_mode* max_mode = &modes[0];
static const int VduInfoBlock[3] = {148, 149, -1};
static const int VduSizeBlock[2] = {150, -1};
static int VduOutBlock[2];


static int display_getBankCount(player_t* player)
{
  if (player->vo_overlay)
    return OverlayBanksCount;
  else
    return ScreenBanksCount;
}

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);
}

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);
}

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;
}

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;
}

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];
}

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)
{
  // 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.
 */
void display_init(player_t* player)
{
  _kernel_swi_regs regs;

  // 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;
    glb_col_mask = 0;
    for (int i = 0; i < size; i++)
    {
      if (blk[i] == 3)
        glb_col_mask |= 1; // 256, 8bpp
      else if (blk[i] == 4)
        glb_col_mask |= 4; // 32K, 16bpp
      else if (blk[i] == 5)
        glb_col_mask |= 16; // 16M, 32bpp
      else if (blk[i] == 0x40000004)
        glb_col_mask |= 8; // 64K, 16bpp
    }

    ka_mem_free(blk);
  }
  else
  {
    glb_col_mask = 0;
    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) == 0)
            {
              glb_col_mask |= 1; // 256, 8bpp
              col_flags[0] = blk[1];
            }
          }
          else if (blk[2] == 4)
          {
            if (blk[0] == 4095)
            {
              if ((glb_col_mask & 2) == 0)
              {
                glb_col_mask |= 2; // 4K, 16bpp
                col_flags[1] = blk[1];
              }
            }
            else if (blk[0] == 65535)
            {
              if ((glb_col_mask & 8) == 0)
              {
                if (blk[1] & 0x80)
                {
                  glb_col_mask |= 8; // 64K, 16bpp
                  col_flags[3] = blk[1];
                }
              }
              else
              {
                if ((glb_col_mask & 4) == 0)
                {
                  glb_col_mask |= 4; // 32K, 16bpp
                  col_flags[2] = blk[1];
                }
              }
            }
          }
          else if (blk[2] == 5)
          {
            if ((glb_col_mask & 16) == 0)
            {
              glb_col_mask |= 16; // 16M, 32bpp
              col_flags[4] = 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 != modes[j].width) || (h != modes[j].height))
          {
            j++;
            if (j >= MAX_MODES) break;

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

          // add colour depth to current resolution
          if (l == 3) // 256
            modes[j].col_mask |= 1;
          else if (l == 4) // 4k, 32k or 64k
          {
            if (c == (1<<12)) // 4K
              modes[j].col_mask |= 2;
            else if ((c == (1<<16)) && (f & 0x80)) // 64K, OS 5
              modes[j].col_mask |= 8;
            else // 32K OS 4,5 or 32/64K (Select)
            {
              if (glb_col_mask & 0xc)
                modes[j].col_mask |= (glb_col_mask & 0xc);
              else
                modes[j].col_mask |= 4;
            }
          }
          else // 16m
            modes[j].col_mask |= 16;
        }
      }

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

    // Merge depths in global depth mask (for pre-GraphicsV/Select cases)
    for (i = 0; i <= j; i++)
      glb_col_mask |= modes[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 any resources.
 */
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_colours
 * --------------
 * Adjust number of colours according to an availability mask.
 */
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.
 */
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 >= &modes[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 >= &modes[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 >= &modes[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 that will give the best fit for the display.
 */
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);
}

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.
 */
static void savearea_saveDesktop(player_t* player)
{
  rgb_savearea* area = &player->rgb_area[SAVEAREA_DESKTOP];
  const uint8_t* src;
  uint8_t* dst;
  uint8_t* dst_end;
  int i;

  if (!player->vo_overlay)
    savearea_getInstance(area, "Desktop", player->plot.dst_byte_width, player->plot.dst_height, &player->screen_plot);
  if (!area->frame) return;

  src = display_getDriverScreenBank() + player->plot.dst_offset;
  dst = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
  dst_end = ((uint8_t*) area->frame) + area->buf_size;

  for (i = player->plot.dst_height; i; i--)
  {
    if (dst >= dst_end)
    {
      // (video buffer overflow)
      report_error(1, "error6: %s %d %d", "Desktop SaveArea"
                 , player->plot.dst_height * player->plot.dst_byte_width, area->buf_size);
    }

    memcpy(dst, src, player->plot.dst_byte_width);
    src += player->plot.dst_bpr;
    dst += player->plot.dst_byte_width;
  }
}

/**
 * Restores the content of a 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;
  int i;

  if (!area->frame) return;

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

  src = ((uint8_t*) area->frame) + area->frame->offset + area->frame->s_offset;
  dst = display_getDriverScreenBank() + player->plot.dst_offset;

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

/**
 * 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;
  }
}

/**
 * Restores what was beneath the video window.
 * Called when changing zoom, desktop mode/palette, full screen switch, and exit.
 */
void display_restoreDesktop(player_t* player, int newmode, int reason)
{
  int i, b;

  // 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);
    }

    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;
        }
      }
    }
  }
}

/**
 * Returns width and height of a sprite in OS units.
 */
static void get_sprite_size(char *name, int area, int *width, int *height)
{
  _kernel_swi_regs regs;
  int w, h, mode;

  regs.r[0] = 256+40;
  regs.r[1] = area;
  regs.r[2] = (int)name;
  _kernel_swi(OS_SpriteOp, &regs, &regs);
  w = regs.r[3];       // width
  h = regs.r[4];       // height
  mode = regs.r[6];

  regs.r[0] = mode;
  regs.r[1] = 4;       // xeig
  _kernel_swi(OS_ReadModeVariable, &regs, &regs);
  *width = w << regs.r[2];

  regs.r[0] = mode;
  regs.r[1] = 5;       // yeig
  _kernel_swi(OS_ReadModeVariable, &regs, &regs);
  *height = h << regs.r[2];
}

/**
 * Returns window tool border sizes in OS units.
 */
void get_border_sizes(int *top, int *right, int *bottom)
{
  int area;

  // get toolsprites area
  _swix(Wimp_ReadSysInfo, _IN(0)|_OUT(0), 9, &area);
  get_sprite_size("ticon22", area, right, top);
  get_sprite_size("sicon22", area, right, bottom);
}

/**
 * Reduce with/height to fit format.
 */
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;
  }
}

static int changemag(int mag, int mul, int div)
{
  int64_t cmag = mag;
  cmag *= mul;
  cmag /= div;
  return (int) cmag;
}

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);
  }
}

static void rounddown_framesize(int* pwidth, int* pheight, const ka_vinfo_t* vinfo, int rgb_zoom2, int bytes_per_pixel)
{
  // Round down source size to multiple of to chroma block size
  // Formula below works because shift is only 0 or 1
  *pheight &= ~vinfo->chroma.hshift;
  *pwidth &= ~vinfo->chroma.wshift;

  // Round down source size for colour space converter alignment requirements
  // Take into account interlaced replay
  {
    int pixels = 2;

    if (rgb_zoom2 == 1)
      pixels = 8/bytes_per_pixel;
    else if (bytes_per_pixel == 1)
    {
      if ((rgb_zoom2 == 2) || (rgb_zoom2 == 6))
        pixels = 4;
    }
    pixels -= 1;
    *pwidth &= ~pixels;
    *pheight &= ~3;
  }
}

static void screenFromVdu(ka_screen_t* screen, int* blk, int rgb, int glb_col_mask)
{
  rgb |= ((blk[7] & 0x7000) == 0x4000);
  int alpha = ((blk[7] & 0xb000) == 0x8000);

  screen->xeig = blk[1];
  screen->yeig = blk[2];
  screen->bytesperrow = blk[3];
  screen->log2Bpp = blk[4] - 3;
  screen->width   = blk[5] + 1;
  screen->height  = blk[6] + 1;
  screen->col_depth = (ka_col_depth) -1;

  if (blk[4] == 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 (blk[4] == 4)
  {
    // 4K/32K/64K colours
    // Note blk[0] incorrect on OS 4 (32K), so we check first if depth is supported
    if ((blk[0] == 4095) && (glb_col_mask & (1 << KA_COL_4K)))
    {
      screen->col_depth = ka_col_depth_TBGR12;
    }
    else if ((blk[0] == 65535) && (blk[7] & 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 (blk[4] == 5)
  {
    // 16M colours
    screen->col_depth = ka_col_depth_TBGR32;
//if (alpha)
//{
//  screen->col_depth = ka_col_depth_UYVY;
//  screen->log2Bpp = 1; // 32bpp words of &Y1.Cr.Y0.Cb, 2x1 sub-sampled
//}
  }
  else if (blk[4] == 7)
  {
    switch(blk[0])
    {
      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;
}

/*
 * Creates/updates the player's window once the plotting size is known.
 *
 * Note: Due to the necessity to attach overlays to a window, in non-multitasking mode
 * a window without title area, scolling areas nor borders is created
 * on top of the screen to position the overlay.
 */
static void display_setupwindow(player_t* player, int reason)
{
  static int initial = 1;
  int b[64], dw, dh, sw, sh;
  int top, right, bottom;

  // This code is executed when,
  //   zoom is changed by 'z' 'x' or menu.
  //   desktop mode/palette is changed.
  //   switching between full screen and desktop.

  if (player->mode == KA_MODE_MULTITASK)
    get_border_sizes(&top, &right, &bottom);
  else
  {
    top = right = bottom = 0;
  }

  // Displayed frame size in os units
  dw = player->pos.width;
  dh = player->pos.height;

  // Screen size in os units (leaving room for furniture)
  sw = player->screen_plot.width << player->screen_plot.xeig;
  sh = (player->screen_plot.height << player->screen_plot.yeig) - top;
  if (config.control & cfg_ctrl_scroll_bars)
  {
    sw -= right;
    sh -= bottom;
  }

  // If screen change, delete window has they don't have identical flags
  if ((reason == SCREEN_CHANGE)
  &&  (win_handle.player != -1))
  {
    b[0] = win_handle.player;
    _swix(Wimp_DeleteWindow, _IN(1), b);
    win_handle.player = -1;
  }

  // Create player window with work area equal to the screen size
  if (win_handle.player == -1)
  {
    b[0]  = (sw - dw) / 2;
    b[1]  = (sh - dh) / 2;
    b[2]  = b[0] + dw;
    b[3]  = b[1] + dh;
    b[4]  = b[5] = 0;      // scroll
    b[6]  = -1;            // on top
    // window flags
    if (player->mode != KA_MODE_MULTITASK)
    {
      b[7]  = (int) 0x80000002;  // window flags
      b[8]  = (int) 0xff0002ff;  // colours (no border + no backround)
    }
    else
    {
      if(config.control & cfg_ctrl_scroll_bars)
        b[7]  = (int) 0xff000002;  // window flags
      else
        b[7]  = (int) 0x8f000002;  // window flags
      b[8]  = (int) 0xff000207;    // colours (no background)
    }
    b[9]  = 0x020c0000;    // colours + extended scroll requests
    b[10] = 0;             // work area coords
    b[11] = -sh;
    b[12] = sw;
    b[13] = 0;
    b[14] = 0x00000109;    // title flags
    b[15] = 10<<12;        // workarea button type
    b[16] = 1;             // spritearea
    b[17] = 0x00100010;    // min size
    b[18] = (int)playlist_getcurrentfilm();
    b[19] = 0;             // no val str
    b[20] = 1024;
    b[21] = 0;             // no icons
    _swix(Wimp_CreateWindow, _IN(1)|_OUT(0), b, &win_handle.player);
  }
  else // resize work area in case screen size has been changed
  {
    b[0] = 0;
    b[1] = -sh;
    b[2] = sw;
    b[3] = 0;
    _swix(Wimp_SetExtent, _INR(0,1), win_handle.player, b);
  }

  if (player->vo_overlay)
  {
    _swix(VideoOverlay_SetWindow, _INR(0,1), player->vo_overlay, win_handle.player);
  }

  b[0] = win_handle.player;
  _swix(Wimp_GetWindowState, _IN(1), b);

  if (((b[8] & (1<<16)) == 0) // window closed ?
  ||  ((reason != SCREEN_CHANGE) && (reason != MODE_CHANGE)))
  {
    if (player->mode != KA_MODE_MULTITASK)
    {
      // center on screen
      b[1]  = (sw - dw) / 2;
      b[2]  = (sh - dh) / 2;
      b[3]  = b[1] + dw;
      b[4]  = b[2] + dh;
      b[5]  = b[6] = 0;      // scroll
      b[7]  = -1;            // on top
    }
    // Adjust window size to take account of scale factors
    b[3] = b[1] + dw;
    b[2] = b[4] - dh;
    b[5] = b[6] = 0; // disable scroll action
    // The title bar text, indirected from b[19] has already
    // been updated. But Wimp_OpenWindow will only update the title
    // if the window size has changed. We must therefore force a
    // redraw of the title bar area.
    _swix(Wimp_ForceRedraw, _INR(0,4), -1, b[1], b[4], b[3], b[4] + top);
// nested wimp version
//      _swix(Wimp_ForceRedraw, _INR(0,2), win_handle.player, *(int *)"TASK", 3);

    _swix(Wimp_OpenWindow, _IN(1), b);

    // Gain input focus, but not every time a new file is played
    // from a playlist.
    if (!(player->status_flags & player_status_new) || initial)
    {
      _swix(Wimp_SetCaretPosition, _INR(0,5), win_handle.player, -1, -1000, -1000, 40, 0);
    }

    open_controls(player, NULL, PANEL_TRACK);
  }

  initial = 0;
}

/**
 * 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};
  static int fs_scaled_width = 0;
  static int fs_scaled_height = 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;
  int blk[10];
  video_mode* vmode;

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

  if (reason == FRAC_CHANGE)
  {
    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;
    return;
  }

  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))
  {
    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);

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

      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
          frame_width  = (player->aspect.width * player->aspect.x_mag + ( 1 << (FRAC - 1))) >> FRAC;
          frame_height = (player->aspect.height * player->aspect.y_mag + ( 1 << (FRAC - 1))) >> FRAC;
          rounddown_framesize(&frame_width, &frame_height, vinfo
                            , 2 /* rgb_zoom2 = 100% */, overlay_modes[i][3] /* bytes per pixel */);
          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))
  {
    twidth = player->aspect.width;
    theight = player->aspect.height;

    if (config.control & cfg_ctrl_accel_anymodes)
    {
      // 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 = (player->vo_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);
          }
        }

        if (_swix(OS_ScreenMode, _IN(0)|_OUT(1), 7, &ScreenBanksCount) != NULL)
        {
          // SWI not supported, do it the old way
          _swi(OS_ReadVduVariables, _INR(0, 1), VduSizeBlock, VduOutBlock);
          dummy = fs_width * fs_height * col_bytes[fs_cols];
          ScreenBanksCount = VduOutBlock[0] / dummy;
        }
        if (ScreenBanksCount > MAX_SCREENBANKS) ScreenBanksCount = MAX_SCREENBANKS;

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

        display_showCursors(0);

        // 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
        fs_scaled_width = 0;
        fs_scaled_height = 0;
      }
    }
    else
    {
      ScreenBanksCount = 1;
      player->full_screen.width = 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;

  blk[0] = 3;   // Ncolour - 1
  blk[1] = 4;   // XEigFactor
  blk[2] = 5;   // YEigFactor
  blk[3] = 6;   // screen width in bytes
  blk[4] = 9;   // log2bpp
  blk[5] = 11;  // width
  blk[6] = 12;  // height
  blk[7] = 0;   // Mode flags
  blk[8] = -1;
  _swix(OS_ReadVduVariables, _INR(0,1), blk, blk);
  int rgb = (vmode->cfg & cfg_video_to_BGR) != 0;

  screenFromVdu(&player->screen_plot, blk, rgb, glb_col_mask);
  if (player->vo_overlay != 0)
  {
    // Patch in the values from the overlay desc
    blk[0] = player->vo_desc.ncolour;
    blk[4] = player->vo_desc.log2bpp;
 //   blk[5] = player->vo_desc.width;
 //   blk[6] = player->vo_desc.height;
    blk[7] = player->vo_desc.modeflags;
  }
  screenFromVdu(&player->screen_paint, blk, 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;

  if ((reason == INIT_DISPLAY)
  ||  (reason == SCREEN_CHANGE)
  ||  (reason == REDIM_CHANGE)
  ||  (reason == ZOOM_UP)
  ||  (reason == ZOOM_DOWN))
  {
    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_UP)
       &&  (reason != ZOOM_DOWN))
  {
    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->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_UP)
    ||  (reason == ZOOM_DOWN))
    {
      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);
    }
  }

  //
  // 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;
    rounddown_framesize(&frame_width, &frame_height, vinfo, rgb_zoom2, player->screen_sprite.bytes_per_pixel);

    // 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;
  }

  if (player->mode == KA_MODE_MULTITASK)
  {
    // display complete area
    frame_width  = (player->aspect.width * player->aspect.x_mag + ( 1 << (FRAC - 1))) >> FRAC;
    frame_height = (player->aspect.height * player->aspect.y_mag + ( 1 << (FRAC - 1))) >> FRAC;
  }
  else if (player->scaleconv.type != ka_scaler_none)
  {
    // 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 * rgb_width) > (sw * rgb_height))
        {
           // Lock height to width
           sh = (rgb_height * sw) / rgb_width;
        }
        else
        {
           // Lock width to height
           sw = (rgb_width * sh) / rgb_height;
        }
      }

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

    // Default scaled output size
    scaled_width = (rgb_width * player->aspect.x_mag) / player->x_mag;
    scaled_height = (rgb_height * player->aspect.y_mag) / player->y_mag;

    // Limit scaled output size to screen size
    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;

    frame_width  = (scaled_width * player->x_mag + ( 1 << (FRAC - 1))) >> FRAC;
    frame_height = (scaled_height * player->y_mag + ( 1 << (FRAC - 1))) >> FRAC;
  }
  else
  {
    // No scaling, limit colour space converter output size to screen size
    if (rgb_width > player->screen_plot.width)
      rgb_width = player->screen_plot.width;
    if (rgb_height > player->screen_plot.height)
      rgb_height = player->screen_plot.height;

    frame_width = (rgb_width << 1) / rgb_zoom2;
    frame_height = (rgb_height << 1) / rgb_zoom2;
  }

  rounddown_framesize(&frame_width, &frame_height, vinfo, rgb_zoom2, player->screen_paint.bytes_per_pixel);

  // 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;
  }
  else
  {
    scaled_width = rgb_width;
    scaled_height = rgb_height;
  }

  vo_setscale(player, &scaled_width, &scaled_height);

  if ((player->mode == KA_MODE_AUTO)
  ||  (player->mode == KA_MODE_MANUAL))
  {
    // If the covered size changes, clear screen banks
    // even for overlays has they may not cover the whole screen
    if ((fs_scaled_width != scaled_width) || (fs_scaled_height != scaled_height))
    {
      fs_scaled_width = scaled_width;
      fs_scaled_height = scaled_height;
      uint32_t bytes = fs_width * fs_height * col_bytes[fs_cols];

      for (int i = 0; i < ScreenBanksCount; i++)
      {
        memset(display_getScreenBank(i), 0, bytes);
      }
    }
  }

  // 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: 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;

  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;

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

  if (player->mode == KA_MODE_DESKTOP)
  {
    // 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;
    }
  }

  display_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;

  // 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)
    ||  (area->byte_width != player->paint.dst_byte_width)
    ||  (area->height != player->paint.dst_height))
    {
      // (video buffer overflow)
      report_error(1, "error6: %s %d <> %d or (%d x %d) <> (%d x %d)", "Banner SaveArea"
                 , area->byte_width*area->height + area->frame->offset + area->frame->s_offset
                 , area->buf_size
                 , area->byte_width, area->height
                 , player->paint.dst_byte_width, 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;
}
