Not logged inGosu Forums
Forum back to libgosu.org Help Search Register Login
Up Topic Gosu / Extending Gosu / Drawing only a portion of a Texture using rmagick
- - By JosephAustin Date 2012-09-21 23:22
Hey everyone. I am trying to make a game that implements multiple views of a single image at the same time. Clearly this is hard in Gosu but I found a way. Because I used a large texture (you'll need to supply your own panorama.jpg for this snippet to work) I could not get TexPlay to work, however rmagick did a fine job. It's a little bit of a dirty process: you keep a Gosu::Image canvas the size of the image in the view and a Magick::Image version of the background itself loaded into memory. Then you use rmagick's cropping ability to paste it onto your canvas, before using it. Here it is... sorry if the comments aren't right, I'm still getting used to Ruby and this is just a bit I wanted to share.


require 'rubygems'
require 'gosu'
require 'RMagick'

# Extension to the image class so we can easily convert to rmagick
class Gosu::Image
  def to_rmagick
    this = self
    Magick::Image.from_blob(to_blob) do
      self.format = 'RGBA'
      self.depth = 8
      self.size = "#{this.width}x#{this.height}"
    end.first
  end
end

# A class that holds a single background image and handles cropping it
class Scene
    #  rmbg - rmagick image representing the background of the scene
    def initialize(rmbg)
        @rmbg = rmbg
    end
   
    # Pass in a Gosu::Image for a canvas and describe the section of the background to copy into it
    def crop(canvas,  x, y, w, h)
        canvas.insert(@rmbg.excerpt(x, y, w, h),  0, 0)
    end
end

# A class which views an image at some position without using up the entire screen
class View
    # Define the geometry of the view within the main window. It must be given a Scene object to show
    def initialize(win,  scene,  x,  y,  w,  h)
        @scene = scene
        @canvas = Gosu::Image.new(win,  Magick::Image.new(640, 480),   false)
        @x = x
        @y = y
        @w = w
        @h = h
        @camera_x = 0
        @camera_y = 0
    end
    # Draws the camera position of the scene into the view space
    def draw
        @scene.crop(@canvas, @camera_x, @camera_y, @w, @h)
        @canvas.draw(@x,  @y,  1.0,  1.0)
    end
   
    # Use these to set the camera position
    def cameraX(x)
        @camera_x = x
    end
    def cameraY(y)
        @camera_y = y
    end
end

class GameWindow < Gosu::Window
  def initialize
super 640, 480, false
    self.caption = "Multiview test"
    @i = 0
    @scene = Scene.new(Gosu::Image.new(self,  'panorama.jpg', false).to_rmagick)
    @viewa = View.new(self,  @scene,  0, 0, 319, 480)
    @viewb = View.new(self,  @scene,  321, 0, 319, 480)
  end
 
  # Move the two views on the scene
  def update
    @i = @i + 1
    @viewa.cameraX(@i)
    @viewa.cameraY(@i)
    @viewb.cameraY(@i)
  end
 
  # Just draw the views
  def draw
    @viewa.draw
    @viewb.draw
  end
end

window = GameWindow.new
window.show
Parent - - By jlnr (dev) Date 2012-09-22 04:27
As long as the portion you want to draw is rectangular, you can use Window#clip_to and it'll be a gazillion times faster :)
Parent - By JosephAustin Date 2012-09-22 04:40
o.o Oh goodness I didnt spot that in the API!
Parent - - By JosephAustin Date 2012-09-22 04:44
... and that would be because i was looking inside the image class and didnt think about the window. Dagnabbit xD
Parent - - By jlnr (dev) Date 2012-09-22 04:48
Some time in the future (maybe to celebrate the colonization of Mars), I will move it into the Gosu module as a free function. Would that have helped? It doesn't fit into Image because it also affects Font and draw_quad etc...
Parent - By JosephAustin Date 2012-09-22 04:52
No, actually you're right to put it where you did. It makes logical sense for the window to control windowing, it just didn't hit me. I was thinking in terms of having an image draw a portion of itself to a rect on the main window, but that is in fact backwards. Thank you so much for your response, the speed of this is lightning fast.
Parent - - By RavensKrag Date 2012-09-26 00:33 Edited 2012-09-26 00:51
Is there any way to use load part of a Gosu::Image as another one? I know you can load blob data into a Gosu::Image, and then specify source x,y, and dimensions, but it seems like there should be simple enough to create an Image from another one, without having to go through the #to_blob interface.  Parsing the blob data is rather slow, but it seems like all that needs to be done is change a few properties on the glTextureInfo thing.

Though from the current train of discussion, it seems that's not the case.

Would doing it that way be any better than just macroing a clip_to?
Parent - - By Spooner Date 2012-09-26 01:44
But #clip_to works fine already...

You shouldn't really need to use a blob within Gosu code since there is no reason to duplicate an image in vanilla Gosu (there is no way to modify it).

To efficiently copy an image, what should happen is that the original sprite should just be drawn onto the new one in replace mode (How I do it in Gosu::Image#to_texture and Ashton::Texture#dup), but since the only way to create an image is by giving it a blob, you don't gain anything doing it that way!
Parent - - By RavensKrag Date 2012-09-26 03:40
I was just worried about efficiency, but I think it's actually a non-issue.  It takes more time to clip than not, but the additional time is probably so small, that in ruby, for a 2D game, it doesn't matter.
Parent - - By jlnr (dev) Date 2012-09-26 04:34
FWIW, I could never get the FPS to drop when using clipping, even on the old iPhone 3G. Seems like OpenGL is always clipping (to the window) anyway and doesn't care what the exact boundaries are.
Setting/unsetting the clipping will take some time, of course.
Parent - - By Spooner Date 2012-09-26 14:36
Don't worry about anything happening inside Gosu. In a real game, your Ruby code will be using 90% of the CPU and Gosu will be using 10% of the potential of your graphics card. What you do, not what Gosu does, is the limit in real games (assuming you use stuff like Window#record and don't do daft things).
Parent - By jlnr (dev) Date 2012-09-27 08:31
Right. And I couldn't even get the FPS to drop in pure C++. :) Clipping is basically free.

One thing where Gosu can still bring a computer to its silicon knees is Font#draw. The implementation of <b> and other codes is really inefficient for long strings. That's the only thing a Ruby coder might have to be careful about.
Parent - By jlnr (dev) Date 2012-09-26 02:57
clip_to does not work inside of record. It would be easy to add Image#sub_image(x, y, w, h) that returns an Image by itself. My main worry about it is that it messes with the whole tileability thing. Gosu usually adds a pixel of padding around images, duplicating pixels for tileable things, adding transparent pixels for everything else. This prevents bleeding when rotating or stretching, and sub_image couldn't do this (or would have to make a copy).
Parent - - By Spooner Date 2012-09-22 15:49
RMagick/TexPlay really are too slow to use per-frame in a game, although they are fine for setting up graphics when loading the game or a new level. Please avoid RMagick if you can, because it has external dependencies which are fiddly for the end-user to install for a game; it is more intended to be used on Rails servers where the client end doesn't need to install it ;)

You might want to look at my Ashton gem, which allows supports rendering to large Textures and stencilling (so you don't have to be limited to clipping to a rectangle :D ). Needs a bit more work to be properly released, but will hopefully obsolete most of what TexPlay does and is certainly fast enough for complex graphical manipulations every frame.
Parent - By JosephAustin Date 2012-09-22 21:14
Ooh that does sound neat. Of course, jlnr's approach helped big time, but per-frame manipulations are a good feature to have.
Up Topic Gosu / Extending Gosu / Drawing only a portion of a Texture using rmagick

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill