Not logged inGosu Forums
Forum back to libgosu.org Help Search Register Login
Up Topic Gosu / Gosu Exchange / Using an image as an alpha channel for another image
- - By ericathegreat Date 2015-12-24 00:29
I have a situation where I want to alpha mask one image with another image.

My game has multiple tile images to represent different types of walls in my game. Any of these walls could potentially have a door or window added to it. I would like to have a set of greyscale 'alpha' images representing the shape and location of the 'door hole', and then apply them to the wall images as an alpha channel in-game, thus 'cutting out' door and window holes.

Is there a straightforward way to do this? I'd rather not have to have to create alternative png images for every wall/door/window combination if I can help it.
Parent - By jlnr (dev) Date 2015-12-24 07:49
Nope, not without additional OpenGL hackery. Or you can use Gosu::clip_to to render the background again, clipped to the window/door rectangle on a tile, but that's only efficient if the background is very simple (a static image, for example).

It should be easy to generate each tile/cutout combination using RMagick.
Parent - - By nietzschette Date 2016-01-24 20:34
It's very doable.

I developed this method for blending different tiles, but the principle can be applied to utilize an alpha mask.  It requires you build a class to store pixel data capable of being used as a parameter for Gosu::Image.new();

I called mine Surface because it is used in the same way as sdl's surface class.  basicly, it's like this:

class Surface
def initialize(pixels,w,h)
  @pixels = pixels
  @w = w
  @h =h
end
def to_blob
  @pixels
end
def to_img
  Image::Gosu.new(self, @w, @h)
end
end

#first, ensure the mask and the tile are the same width and height, than use gosu to load the images. 

tile = Gosu::Image.new(<tile filename>)
mask = Gosu::Image.new(<mask filename>)

#both gosu::image and our new surface class contain a method .to_blob which returns pixel data as 32 bit strings.  Here is a simple method to extract RGBA values from a given blob:

tile_pixel_array = tile.to_blob.unpack('L*').collect{|c| [c].pack('L*').unpack('C*')}
mask_pixel_array = mask.to_blob.unpack('L*').collect{|c| [c].pack('L*').unpack('C*')}

#this is inefficient, what with all this unpacking and re-packing and whatnot.  My actual method uses maths but the end result is the same.
#something like this will take the alpha channel of the mask (c[1]) and append it to the rgb of the tile (c[0]):

new_pixels = (tile_pixel_array.zip(mask_pixel_array)).collect{|c| (c[0][0,3] << c[1][3]) }

#you can also add any other color channel as the alpha channel:

new_pixels = (tile_pixel_array.zip(mask_pixel_array)).collect{|c| (c[0,3] << c[1][0]) } #red as alpha mask
new_pixels = (tile_pixel_array.zip(mask_pixel_array)).collect{|c| (c[0,3] << c[1][1]) } #blue as alpha mask
new_pixels = (tile_pixel_array.zip(mask_pixel_array)).collect{|c| (c[0,3] << c[1][2]) } #green as alpha mask

#you then re-pack these into a string, and use it as the blob for a new surface:

composite = Surface.new(new_pixels.flatten.pack('C*'), tile.width, tile.height))

#we can use our Surface class to manipulate these 'blobs' however we want, putting the above code into some custom method and use #to_img to return a new gosu image class.

img = Gosu::Image.new(composite)
  or
img = composite.to_img

nothing to it!
Parent - - By jlnr (dev) Date 2016-01-25 06:51
I think Surface also has to provide width/height as accessors, or columns/rows. But playing around with to_blob is fun.
My suggestion for improved performance is to avoid the packing/unpacking and work with binary strings instead, using the character values as the alpha channel. Or maybe I just like dirty solutions ;)
Parent - - By nietzschette Date 2016-01-25 21:53
You are absolutely right.  I demonstrated a method using pack and unpack for the simplicity of explaining the process.  I usually have my blending functions occuring before the main game loop begins or on whatever shabby excuse for a thread ruby implements ;p.  that said, it's better to consolodate the process to a single itterator:


newtile = ''
(0..width*height).each{|n|
newtile << tile[n*4,3] << mask[n*4+3]
}


on a side note, I find this method does not always work when using your mask's alpha channel.  I'm not sure what's going on under the hood, but I encountered a simmilar bug when using such methods with pure sdl - the SDL::Surface.new_from() method, which converts binary strings into display formatted objects, appear to clamp alpha values to either 0, 128, or 255; or ignore them completely.  I don't think this is a bug, though, most likely I'm operating with much ignorance :/

As a consequence i've been using one of the RGB channels for alpha masks, replace a line in the above code with:

newtile << tile[n*4,3] << mask[n*4]    #red
newtile << tile[n*4,3] << mask[(n*4)+1]  #blue
newtile << tile[n*4,3] << mask[(n*4)+2]  #green

as needed.
Parent - By jlnr (dev) Date 2016-01-26 21:29
When you you notice that values are being clipped at 127/128, it might be the case that Ruby does not treat it as a binary string. newtile.encoding should return #<Encoding:ASCII-8BIT>, which is what Gosu::Image#to_blob returns by default.
Does it work if, instead of creating a new string, you modify tile by merging the mask values in?
Up Topic Gosu / Gosu Exchange / Using an image as an alpha channel for another image

Powered by mwForum 2.29.7 © 1999-2015 Markus Wichitill