Codebase list pangzero / upstream/1.4.1+git20121103 lib / Games / PangZero / Graphics.pm
upstream/1.4.1+git20121103

Tree @upstream/1.4.1+git20121103 (Download .tar.gz)

Graphics.pm @upstream/1.4.1+git20121103raw · history · blame

package Games::PangZero::Graphics;

use strict;
use warnings;

use SDL;
use SDL::Surface;
use SDL::Palette;
use SDL::PixelFormat;
use SDL::Video;
use SDL::Event;
use SDL::Events;
use SDL::Color;
use SDL::Config;
use SDL::Cursor;
use SDL::GFX::Rotozoom;
use SDL::Mixer;
use SDL::Mixer::Samples;
use SDL::Mixer::Channels;
use SDL::Mixer::Music;
use SDL::Mixer::MixChunk;
use SDL::Mixer::MixMusic;
use SDL::Joystick;
use SDL::Mouse;
use SDL::Image;
use SDLx::SFont;

sub LoadSurfaces {
  my ($i, $transparentColor);

  my %balls = qw (
  ball0 Balls-Red128.png ball1 Balls-Red96.png ball2 Balls-Red64.png ball3 Balls-Red32.png ball4 Balls-Red16.png
  xmas Balls-XMAS128.png
  ball4 Balls-Red16.png ball3 Balls-Red32.png
  bouncy2 Balls-Bouncy64.png bouncy3 Balls-Bouncy32.png bouncy4 Balls-Bouncy16.png
  hexa0 Hexa-64.png hexa1 Hexa-32.png hexa2 Hexa-16.png
  blue1 Balls-Water96.png blue2 Balls-Water64.png blue3 Balls-Water32.png blue4 Balls-Water16.png
  frag0 Balls-Fragile128.png frag1 Balls-Fragile96.png frag2 Balls-Fragile64.png frag3 Balls-Fragile32.png frag4 Balls-Fragile16.png
  green1 Balls-SuperClock96.png green2 Balls-SuperClock64.png gold1 Balls-SuperStar96.png gold2 Balls-SuperStar64.png
  death2 Balls-Death64.png
  white2 Balls-Seeker64.png white3 Balls-Seeker32.png
  quake2 Balls-EarthQ64.png quake3 Balls-EarthQ32.png quake4 Balls-EarthQ16.png
  upside0 Balls-Upside128.png upside1 Balls-Upside96.png upside2 Balls-Upside64.png upside3 Balls-Upside32.png upside4 Balls-Upside16.png
  );

  foreach (sort keys %balls) {
    $Games::PangZero::BallSurfaces{$_} = SDL::Image::load("$Games::PangZero::DataDir/$balls{$_}");
    $Games::PangZero::BallSurfaces{$_} = SDL::Video::display_format($Games::PangZero::BallSurfaces{$_});
    $transparentColor = $Games::PangZero::BallSurfaces{$_}->get_pixel(0);
    SDL::Video::set_color_key($Games::PangZero::BallSurfaces{$_}, SDL_SRCCOLORKEY, $transparentColor );
    $Games::PangZero::BallSurfaces{"dark$_"} = SDL::Image::load( "$Games::PangZero::DataDir/$balls{$_}");
    $Games::PangZero::BallSurfaces{"dark$_"} = SDL::Video::display_format($Games::PangZero::BallSurfaces{"dark$_"});
    SDL::Video::set_color_key($Games::PangZero::BallSurfaces{"dark$_"}, SDL_SRCCOLORKEY, $Games::PangZero::BallSurfaces{"dark$_"}->get_pixel(0) );
    SDL::Video::set_alpha($Games::PangZero::BallSurfaces{"dark$_"}, SDL_SRCALPHA, 128);
  }

  $Games::PangZero::BorderSurface          = SDL::Image::load("$Games::PangZero::DataDir/border.png");
  $Games::PangZero::RedBorderSurface       = SDL::Image::load("$Games::PangZero::DataDir/border.png");
  $Games::PangZero::WhiteBorderSurface     = SDL::Image::load("$Games::PangZero::DataDir/border.png");
  $Games::PangZero::BonusSurface           = SDL::Image::load("$Games::PangZero::DataDir/bonus.png");
  $Games::PangZero::LevelIndicatorSurface  = SDL::Image::load("$Games::PangZero::DataDir/level.png");
  $Games::PangZero::LevelIndicatorSurface2 = SDL::Image::load("$Games::PangZero::DataDir/level_empty.png");

  AlterPalette( $Games::PangZero::RedBorderSurface, sub { 1; },
    sub { shift @_; my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
    return Games::PangZero::Palette::HsiToRgb( $h - 30, $s, $i * 0.75 + 63); } );
  AlterPalette( $Games::PangZero::WhiteBorderSurface, sub { 1; },
    sub { shift @_; my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
    return Games::PangZero::Palette::HsiToRgb( 0, 0, $i*0.25 + 191 ); } );

  MakeGuySurfaces();
}

sub MakeGuySurface {
  my ($player) = @_;
  my ($guySurfaceFile, $guySurface, $whiteGuySurface, $harpoonSurface);

  $guySurfaceFile       = $Games::PangZero::DataDir . '/' . $Games::PangZero::GuyImageFiles[ $player->{imagefileindex} % scalar(@Games::PangZero::GuyImageFiles) ];
  $guySurface           = SDL::Image::load($guySurfaceFile);
  $whiteGuySurface      = SDL::Image::load($guySurfaceFile);
  $harpoonSurface       = SDL::Image::load("$Games::PangZero::DataDir/harpoon.png");
  $player->{hue}        = $Games::PangZero::GuyColors[$player->{colorindex}]->[0];
  $player->{saturation} = $Games::PangZero::GuyColors[$player->{colorindex}]->[1];

  AlterPalette($whiteGuySurface, sub {1;}, sub { return (255, 255, 255); } );
  AlterPalette( $guySurface, sub { $_[3] > $_[2] and $_[3] > $_[1]; },
    sub {
      shift @_;
      my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
      return Games::PangZero::Palette::HsiToRgb($player->{hue}, $player->{saturation}, $i); }
  );
  AlterPalette( $harpoonSurface, sub { 1; },
    sub {
      shift @_;
      my ($h, $s, $i) = Games::PangZero::Palette::RgbToHsi(@_);
      return Games::PangZero::Palette::HsiToRgb($player->{hue}, $player->{saturation} * $s / 256, $i); }
  );
  $player->{guySurface}      = $guySurface;
  $player->{whiteGuySurface} = $whiteGuySurface;
  $player->{harpoonSurface}  = $harpoonSurface;
}

sub MakeGuySurfaces {
  foreach my $player (@Games::PangZero::Players) {
    MakeGuySurface($player);
  }

  $Games::PangZero::WhiteHarpoonSurface = SDL::Image::load("$Games::PangZero::DataDir/harpoon.png");
  AlterPalette($Games::PangZero::WhiteHarpoonSurface, sub {1;}, sub { return (255, 255, 255); } );
}

sub AlterPalette {
  my ($surface, $filterSub, $alterSub) = @_;
  my ($r, $g, $b);
  my ($palette, $numColors, $n, $color);

  $palette   = $surface->format->palette();
  $numColors = ($surface->format->BytesPerPixel == 1) ? $palette->ncolors() : -1;
  for ($n = 0; $n < $numColors; $n++) {
    $color       = $palette->color_index($n);
    ($r, $g, $b) = ( $color->r, $color->g, $color->b );

    next unless $filterSub->($n, $r, $g, $b);
    ($r, $g, $b) = $alterSub->($n, $r, $g, $b);
    $r = $g = $b = 4 if ($r == 0 and $g == 0 and $b == 0);

    $color->r($r);
    $color->g($g);
    $color->b($b);
    SDL::Video::set_colors($surface, $n, $color);
  }
  $surface = SDL::Video::display_format($surface);
}

sub RenderBorder {
  my ($borderSurface, $targetSurface) = @_;
  my ($dstrect, $srcrect1, $srcrect2, $xpos, $ypos, $width, $height);

  $width  = $Games::PangZero::ScreenWidth  + 2 * $Games::PangZero::ScreenMargin;
  $height = $Games::PangZero::ScreenHeight + 2 * $Games::PangZero::ScreenMargin;

  # Draw the corners
  $dstrect  = SDL::Rect->new(0, 0, 16, 16);
  $srcrect1 = SDL::Rect->new(0, 0, 16, 16);
  SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
  $dstrect->x($width - 16); $srcrect1->x(144);
  SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
  $dstrect->y($height - 16); $srcrect1->y(144);
  SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
  $dstrect->x(0); $srcrect1->x(0);
  SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);

  if(SDL::Config->has('SDL_gfx_rotozoom')) {
    # Top border
    my $zoom = SDL::Surface->new(SDL_SWSURFACE(), 128, 16, 32);
    $srcrect1->x(16); $srcrect1->y(0); $srcrect1->w(128); $srcrect1->h(16);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
    $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, $Games::PangZero::ScreenWidth / 128, 1, SDL::GFX::Rotozoom::SMOOTHING_OFF());
    $dstrect->x(16); $dstrect->y(0);
    SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );

    # Left border
    $zoom = SDL::Surface->new(SDL_SWSURFACE(), 16, 128, 32);
    $srcrect1->x(0); $srcrect1->y(16); $srcrect1->h(128); $srcrect1->w(16);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
    $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, 1, $Games::PangZero::ScreenHeight / 128, SDL::GFX::Rotozoom::SMOOTHING_OFF());
    $dstrect->x(0); $dstrect->y(16);
    SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
  }

  # Draw top and bottom border

  $srcrect1->w(128); $srcrect1->x(16); $srcrect1->y(0);
  $srcrect2 = SDL::Rect->new( 16, 144, 128, 16 );
  for ($xpos = 16; $xpos < $width-16; ) {
    $dstrect->x($xpos);
    $dstrect->y(0);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
    $dstrect->y($height - 16);
    SDL::Video::blit_surface($borderSurface, $srcrect2, $targetSurface, $dstrect);
    $xpos += $srcrect1->w();
    $srcrect1->w(16); $srcrect1->x(128);
    $srcrect2->w(16); $srcrect2->x(128);
  }

  # Draw left and right border

  $srcrect1->h(128); $srcrect1->y(16); $srcrect1->x(0);
  $srcrect2->h(128); $srcrect2->y(16); $srcrect2->x(144);
  for ($ypos = 16; $ypos < $height-16; ) {
    $dstrect->x(0);
    $dstrect->y($ypos);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $targetSurface, $dstrect);
    $dstrect->x($width - 16);
    SDL::Video::blit_surface($borderSurface, $srcrect2, $targetSurface, $dstrect);
    $ypos += $srcrect1->h();
    $srcrect1->h(16); $srcrect1->y(128);
    $srcrect2->h(16); $srcrect2->y(128);
  }

  if(SDL::Config->has('SDL_gfx_rotozoom')) {
    # Top border
    my $zoom = SDL::Surface->new(SDL::Video::SDL_SWSURFACE(), 128, 16, 32);
    $srcrect1->x(16); $srcrect1->y(0); $srcrect1->w(128); $srcrect1->h(16);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
    $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, $Games::PangZero::ScreenWidth / 128, 1, SDL::GFX::Rotozoom::SMOOTHING_OFF());
    $dstrect->x(16); $dstrect->y(0);
    SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );

    # Left border
    $zoom = SDL::Surface->new( SDL_SWSURFACE(), 16, 128, 32);
    $srcrect1->x(0); $srcrect1->y(16); $srcrect1->h(128); $srcrect1->w(16);
    SDL::Video::blit_surface($borderSurface, $srcrect1, $zoom, SDL::Rect->new(0, 0, $srcrect1->w, $srcrect1->h) );
    $zoom = SDL::GFX::Rotozoom::zoom_surface($zoom, 1, $Games::PangZero::ScreenHeight / 128, SDL::GFX::Rotozoom::SMOOTHING_OFF());
    $dstrect->x(0); $dstrect->y(16);
    SDL::Video::blit_surface($zoom, SDL::Rect->new(0, 0, $zoom->w, $zoom->h), $targetSurface, $dstrect );
  }
}

sub LoadBackground {
  my $filename = shift;

  SDL::Video::fill_rect($Games::PangZero::Background, SDL::Rect->new(0, 0, $Games::PangZero::PhysicalScreenWidth, $Games::PangZero::PhysicalScreenHeight), SDL::Video::map_RGB($Games::PangZero::Background->format(), 0, 0, 0));
  my $backgroundImage = SDL::Image::load("$Games::PangZero::DataDir/$filename");
  my $dstrect         = SDL::Rect->new($Games::PangZero::ScreenMargin, $Games::PangZero::ScreenMargin, 0, 0);
  my $srcrect         = SDL::Rect->new(0, 0, $Games::PangZero::ScreenWidth, $Games::PangZero::ScreenHeight);
  if ($Games::PangZero::ScreenWidth != $backgroundImage->w() or $Games::PangZero::ScreenHeight != $backgroundImage->h()) {
    if (SDL::Config->has('SDL_gfx_rotozoom')) {
      my $zoomX        = $Games::PangZero::ScreenWidth  / $backgroundImage->w(); # $zoomX = 1.0 if $zoomX < 1.0;
      my $zoomY        = $Games::PangZero::ScreenHeight / $backgroundImage->h(); # $zoomY = 1.0 if $zoomY < 1.0;
      $backgroundImage = SDL::GFX::Rotozoom::zoom_surface($backgroundImage, $zoomX, $zoomY, SDL::GFX::Rotozoom::SMOOTHING_OFF());
    }
  }
  SDL::Video::blit_surface($backgroundImage, $srcrect, $Games::PangZero::Background, $dstrect);

  RenderBorder($Games::PangZero::BorderSurface, $Games::PangZero::Background);
}

sub TextWidth {
  SDLx::SFont::SDL_TEXTWIDTH(@_); # perl-sdl-2.x
}

sub FindVideoMode {
  if ($Games::PangZero::FullScreen < 2) {
    return (800, 600);
  }

  # Find a suitable widescreen mode
  # One native resolution:   1680 x 1050 => 1.6  : 1
  # Which could translate to: 840 x 525  => 1.6  : 1
  # Some adapters have:       848 x 480  => 1.76 : 1
  #                           720 x 480  => 1.5  : 1
  #                           800 x 512  => 1.56 : 1
  # Conclusion: Any resolution where w in [800,900], h > 480 and r in [1.5, 1.8] is good

  my ($modes, $mode, @goodModes, $w, $h, $ratio);
  $modes = SDL::Video::list_modes( 0, SDL_HWSURFACE ); #add back fullscreen
  foreach $mode (@{$modes}) {
    $w     = $mode->w;
    $h     = $mode->h;
    $ratio = $w / $h;
    warn sprintf( "%4d x %4d => %0.3f\n", $w, $h, $ratio );
    next if $w < 800 or $w > 900;
    next if $h < 480;
    next if $ratio < 1.5 or $ratio > 1.8;
    push @goodModes, ( { -w => $w, -h => $h, -score => abs($ratio - 1.6) * 1000 + abs($w - 800) } );
  }
  @goodModes = sort { $a->{-score} <=> $b->{-score} } @goodModes;
  return (800, 600) unless @goodModes;
  foreach $mode (@goodModes) {
    print sprintf( '%d x %d => %0.3f (score %d)', $mode->{-w}, $mode->{-h}, $mode->{-w} / $mode->{-h}, $mode->{-score} ), "\n";
  }
  return ($goodModes[0]->{-w}, $goodModes[0]->{-h});
}

1;