/* * Surface rotation, zooming, and flipping functions. * *-- * Rubygame -- Ruby code and bindings to SDL to facilitate game creation * Copyright (C) 2004-2005 John 'jacius' Croisant * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA *++ */ #include "rubygame.h" #include "rubygame_surface.h" #include "rubygame_transform.h" void Rubygame_Init_Transform(); VALUE mTrans; VALUE rbgm_transform_version(VALUE); VALUE rbgm_transform_flip(int, VALUE*, VALUE); /* * call-seq: * version -> [Integer, Integer, Integer] * * Return the major, minor, and micro numbers for the version of SDL_gfx * that Rubygame was compiled with. If Rubygame was not compiled with SDL_gfx, * all version numbers will be 0 (zero), and you should not attempt to use * the Transform module. * * This allows for more flexible detection of the capabilities of the * Transform module's base library, SDL_gfx. See also #usable?. */ VALUE rbgm_transform_version(VALUE module) { return rb_ary_new3(3, INT2NUM(SDL_GFXPRIMITIVES_MAJOR), INT2NUM(SDL_GFXPRIMITIVES_MINOR), INT2NUM(SDL_GFXPRIMITIVES_MICRO)); } #ifdef HAVE_SDL_ROTOZOOM_H VALUE rbgm_transform_rotozoom(int, VALUE*, VALUE); VALUE rbgm_transform_rotozoomsize(int, VALUE*, VALUE); VALUE rbgm_transform_zoom(int, VALUE*, VALUE); VALUE rbgm_transform_zoomsize(int, VALUE*, VALUE); /* * call-seq: * rotozoom( surface, angle, zoom, smooth ) -> Surface * * Return a rotated and/or zoomed version of the given surface. Note that * rotating a Surface anything other than a multiple of 90 degrees will * cause the new surface to be larger than the original to accomodate the * corners (which would otherwise extend beyond the surface). * * This function takes these arguments: * surface:: the source surface. * angle:: degrees to rotate counter-clockwise (negative for clockwise). * zoom:: scaling factor. If Rubygame was compiled with SDL_gfx >= * 2.0.13, this can be an Array of 2 Numerics for separate X and Y * scaling, and can be negative to indicate flipping horizontally * or vertically. * smooth:: whether to anti-alias the new surface. This option can be * omitted, in which case the surface will not be anti-aliased. * If true, the new surface will be 32bit RGBA. */ VALUE rbgm_transform_rotozoom(int argc, VALUE *argv, VALUE module) { SDL_Surface *src, *dst; double angle, zoomx, zoomy; int smooth = 0; if(argc < 3) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); /* argv[0], the source surface. */ Data_Get_Struct(argv[0],SDL_Surface,src); /* argv[1], the angle of rotation. */ angle = NUM2DBL(argv[1]); /* Parsing of argv[2] is delayed until below, because its type affects which function we call. */ /* argv[3] (optional), rotozoom smoothly? */ if(argc > 3) smooth = argv[3]; /* argv[2], the zoom factor(s) */ if(TYPE(argv[2])==T_ARRAY) { #ifdef HAVE_ROTOZOOMXY /* Do the real function. */ zoomx = NUM2DBL(rb_ary_entry(argv[2],0)); zoomy = NUM2DBL(rb_ary_entry(argv[2],1)); dst = rotozoomSurfaceXY(src, angle, zoomx, zoomy, smooth); if(dst == NULL) rb_raise(eSDLError,"Could not rotozoom surface: %s",SDL_GetError()); #else /* Warn and return nil. You should have checked first! */ rb_warn("Separate X/Y rotozoom scale factors is not supported by your version of SDL_gfx (%d,%d,%d). Please upgrade to 2.0.13 or later.", SDL_GFXPRIMITIVES_MAJOR, SDL_GFXPRIMITIVES_MINOR, SDL_GFXPRIMITIVES_MICRO); return Qnil; #endif } else if(FIXNUM_P(argv[2]) || TYPE(argv[2])==T_FLOAT) { zoomx = NUM2DBL(argv[2]); dst = rotozoomSurface(src, angle, zoomx, smooth); if(dst == NULL) rb_raise(eSDLError,"Could not rotozoom surface: %s",SDL_GetError()); } else rb_raise(rb_eArgError,"wrong zoom factor type (expected Array or Numeric)"); return Data_Wrap_Struct(cSurface,0,SDL_FreeSurface,dst); } /* * call-seq: * rotozoomsize( size, angle, zoom ) -> [width, height] * * Return the dimensions of the surface that would be returned if * Transform.rotozoom() were called with a surface of the given size, and * the same angle and zoom factors. * * size:: an Array with the hypothetical surface width and height (pixels) * angle:: degrees to rotate counter-clockwise (negative for clockwise). * zoom:: scaling factor. If Rubygame was compiled with SDL_gfx >= 2.0.13, * this can be an Array of 2 Numerics for separate X and Y scaling, * and can be negative to indicate flipping across Y and/or X axes. */ VALUE rbgm_transform_rzsize(int argc, VALUE *argv, VALUE module) { int w,h, dstw,dsth; double angle, zoomx, zoomy; if(argc < 3) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); w = NUM2INT(rb_ary_entry(argv[0],0)); h = NUM2INT(rb_ary_entry(argv[0],0)); angle = NUM2DBL(argv[1]); if(TYPE(argv[2])==T_ARRAY) { /* Separate X/Y rotozoom scaling was not supported prior to 2.0.13. */ /* Check if we have at least version 2.0.13 of SDL_gfxPrimitives */ #ifdef HAVE_ROTOZOOMXY /* Do the real function. */ zoomx = NUM2DBL(rb_ary_entry(argv[1],0)); zoomy = NUM2DBL(rb_ary_entry(argv[1],1)); rotozoomSurfaceSizeXY(w, h, angle, zoomx, zoomy, &dstw, &dsth); #else /* Warn and return nil. You should have checked first! */ rb_warn("Separate X/Y rotozoom scale factors is not supported by your version of SDL_gfx (%d,%d,%d). Please upgrade to 2.0.13 or later.", SDL_GFXPRIMITIVES_MAJOR, SDL_GFXPRIMITIVES_MINOR, SDL_GFXPRIMITIVES_MICRO); return Qnil; #endif } else if(FIXNUM_P(argv[1]) || TYPE(argv[1])==T_FLOAT) { zoomx = NUM2DBL(argv[1]); rotozoomSurfaceSize(w, h, angle, zoomx, &dstw, &dsth); } else rb_raise(rb_eArgError,"wrong zoom factor type (expected Array or Numeric)"); /* if(dstw == NULL || dsth == NULL) rb_raise(eSDLError,"Could not rotozoom surface: %s",SDL_GetError());*/ return rb_ary_new3(2,INT2NUM(dstw),INT2NUM(dsth)); } #if 0 VALUE rbgm_transform_rotozoomsize(int argc, VALUE *argv, VALUE module) { int w,h, dstw,dsth; double angle, zoom; if(argc < 3) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); w = NUM2INT(rb_ary_entry(argv[0],0)); h = NUM2INT(rb_ary_entry(argv[0],0)); angle = NUM2DBL(argv[1]); zoom = NUM2DBL(argv[2]); rotozoomSurfaceSize(w, h, angle, zoom, &dstw, &dsth); return rb_ary_new3(2,INT2NUM(dstw),INT2NUM(dsth)); } #endif /* * call-seq: * zoom(surface, zoom, smooth) -> Surface * * Return a zoomed version of the given Surface. * * This function takes these arguments: * surface:: the surface to zoom * zoom:: the factor to scale by in both x and y directions, or an Array * with separate x and y scale factors. * smooth:: whether to anti-alias the new surface. This option can be * omitted, in which case the surface will not be anti-aliased. * If true, the new surface will be 32bit RGBA. */ VALUE rbgm_transform_zoom(int argc, VALUE *argv, VALUE module) { SDL_Surface *src, *dst; double zoomx, zoomy; int smooth = 0; if(argc < 2) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); Data_Get_Struct(argv[0],SDL_Surface,src); if(TYPE(argv[1])==T_ARRAY) { zoomx = NUM2DBL(rb_ary_entry(argv[1],0)); zoomy = NUM2DBL(rb_ary_entry(argv[1],1)); } else if(FIXNUM_P(argv[1]) || TYPE(argv[1])==T_FLOAT) { zoomx = NUM2DBL(argv[1]); zoomy = zoomx; } else rb_raise(rb_eArgError,"wrong zoom factor type (expected Array or Numeric)"); if(argc > 2) smooth = argv[3]; dst = zoomSurface(src,zoomx,zoomy,smooth); if(dst == NULL) rb_raise(eSDLError,"Could not rotozoom surface: %s",SDL_GetError()); return Data_Wrap_Struct(cSurface,0,SDL_FreeSurface,dst); } /* * call-seq: * zoom_size(size, zoom) -> [width, height] * * Return the dimensions of the surface that would be returned if * Transform.zoom() were called with a surface of the given size, and * the same zoom factors. * * This function takes these arguments: * size:: an Array with the hypothetical surface width and height (pixels) * zoom:: the factor to scale by in both x and y directions, or an Array * with separate x and y scale factors. */ VALUE rbgm_transform_zoomsize(int argc, VALUE *argv, VALUE module) { int w,h, dstw,dsth; double zoomx, zoomy; if(argc < 3) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); w = NUM2INT(rb_ary_entry(argv[0],0)); h = NUM2INT(rb_ary_entry(argv[0],0)); if(TYPE(argv[1])==T_ARRAY) { zoomx = NUM2DBL(rb_ary_entry(argv[1],0)); zoomy = NUM2DBL(rb_ary_entry(argv[1],1)); } else if(FIXNUM_P(argv[1]) || TYPE(argv[1])==T_FLOAT) { zoomx = NUM2DBL(argv[1]); zoomy = zoomx; } else rb_raise(rb_eArgError,"wrong zoom factor type (expected Array or Numeric)"); zoomSurfaceSize(w, h, zoomx, zoomy, &dstw, &dsth); return rb_ary_new3(2,INT2NUM(dstw),INT2NUM(dsth)); } #endif /* HAVE_SDL_ROTOZOOM_H */ /* -- * Borrowed from Pygame: * ++ */ static SDL_Surface* newsurf_fromsurf(SDL_Surface* surf, int width, int height) { SDL_Surface* newsurf; if(surf->format->BytesPerPixel <= 0 || surf->format->BytesPerPixel > 4) rb_raise(eSDLError,"unsupported Surface bit depth for transform"); newsurf = SDL_CreateRGBSurface(surf->flags, width, height, surf->format->BitsPerPixel, surf->format->Rmask, surf->format->Gmask, surf->format->Bmask, surf->format->Amask); if(!newsurf) rb_raise(eSDLError,"%s",SDL_GetError()); /* Copy palette, colorkey, etc info */ if(surf->format->BytesPerPixel==1 && surf->format->palette) SDL_SetColors(newsurf, surf->format->palette->colors, 0, surf->format->palette->ncolors); if(surf->flags & SDL_SRCCOLORKEY) SDL_SetColorKey(newsurf, (surf->flags&SDL_RLEACCEL)|SDL_SRCCOLORKEY, surf->format->colorkey); return newsurf; } /* * call-seq: * flip(surface, flipXp, flipYp) -> Surface * * Flips the source surface horizontally (if +flipXp+ is true), vertically * (if +flipYp+ is true), or both (if both are true). This operation is * non-destructive; the original image can be perfectly reconstructed by * flipping the resultant image again. * * This operation does NOT require SDL_gfx. * * A similar effect can (supposedly) be achieved by giving X or Y zoom * factors of -1 to Transform.rotozoom (if compiled with SDL_gfx 2.0.13 or * greater). Your mileage may vary. */ VALUE rbgm_transform_flip(int argc, VALUE *argv, VALUE module) { SDL_Surface *surf, *newsurf; int xaxis, yaxis; int loopx, loopy; int pixsize, srcpitch, dstpitch; Uint8 *srcpix, *dstpix; xaxis = argv[1]; yaxis = argv[2]; if(argc < 2) rb_raise(rb_eArgError,"wrong number of arguments (%d for 3)",argc); Data_Get_Struct(argv[0],SDL_Surface,surf); /* Borrowed from Pygame: */ newsurf = newsurf_fromsurf(surf, surf->w, surf->h); if(!newsurf) return Qnil; pixsize = surf->format->BytesPerPixel; srcpitch = surf->pitch; dstpitch = newsurf->pitch; SDL_LockSurface(newsurf); srcpix = (Uint8*)surf->pixels; dstpix = (Uint8*)newsurf->pixels; if(!xaxis) { if(!yaxis) { for(loopy = 0; loopy < surf->h; ++loopy) memcpy(dstpix+loopy*dstpitch, srcpix+loopy*srcpitch, surf->w*surf->format->BytesPerPixel); } else { for(loopy = 0; loopy < surf->h; ++loopy) memcpy(dstpix+loopy*dstpitch, srcpix+(surf->h-1-loopy)*srcpitch, surf->w*surf->format->BytesPerPixel); } } else /*if (xaxis)*/ { if(yaxis) { switch(surf->format->BytesPerPixel) { case 1: for(loopy = 0; loopy < surf->h; ++loopy) { Uint8* dst = (Uint8*)(dstpix+loopy*dstpitch); Uint8* src = ((Uint8*)(srcpix+(surf->h-1-loopy)*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 2: for(loopy = 0; loopy < surf->h; ++loopy) { Uint16* dst = (Uint16*)(dstpix+loopy*dstpitch); Uint16* src = ((Uint16*)(srcpix+(surf->h-1-loopy)*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 4: for(loopy = 0; loopy < surf->h; ++loopy) { Uint32* dst = (Uint32*)(dstpix+loopy*dstpitch); Uint32* src = ((Uint32*)(srcpix+(surf->h-1-loopy)*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 3: for(loopy = 0; loopy < surf->h; ++loopy) { Uint8* dst = (Uint8*)(dstpix+loopy*dstpitch); Uint8* src = ((Uint8*)(srcpix+(surf->h-1-loopy)*srcpitch)) + surf->w*3 - 3; for(loopx = 0; loopx < surf->w; ++loopx) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst += 3; src -= 3; } }break; } } else { switch(surf->format->BytesPerPixel) { case 1: for(loopy = 0; loopy < surf->h; ++loopy) { Uint8* dst = (Uint8*)(dstpix+loopy*dstpitch); Uint8* src = ((Uint8*)(srcpix+loopy*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 2: for(loopy = 0; loopy < surf->h; ++loopy) { Uint16* dst = (Uint16*)(dstpix+loopy*dstpitch); Uint16* src = ((Uint16*)(srcpix+loopy*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 4: for(loopy = 0; loopy < surf->h; ++loopy) { Uint32* dst = (Uint32*)(dstpix+loopy*dstpitch); Uint32* src = ((Uint32*)(srcpix+loopy*srcpitch)) + surf->w - 1; for(loopx = 0; loopx < surf->w; ++loopx) *dst++ = *src--; }break; case 3: for(loopy = 0; loopy < surf->h; ++loopy) { Uint8* dst = (Uint8*)(dstpix+loopy*dstpitch); Uint8* src = ((Uint8*)(srcpix+loopy*srcpitch)) + surf->w*3 - 3; for(loopx = 0; loopx < surf->w; ++loopx) { dst[0] = src[0]; dst[1] = src[1]; dst[2] = src[2]; dst += 3; src -= 3; } }break; } } } SDL_UnlockSurface(newsurf); /* Thanks, Pygame :) */ if(newsurf == NULL) rb_raise(eSDLError,"Could not rotozoom surface: %s",SDL_GetError()); return Data_Wrap_Struct(cSurface,0,SDL_FreeSurface,newsurf); } /* * Document-module: Rubygame::Transform * * The Transform module provides several methods for rotating, zooming * (scaling), and flipping (mirroring) Surfaces. * * NOTICE: Except for #flip, all these methods require Rubygame to * be compiled against the SDL_gfx library! * Furthermore, #rotozoom and #rotozoom_size offer additional functionality if * Rubygame was compiled against SDL_gfx version 2.0.13 or greater. * * You can check if Rubygame was compiled against SDL_gfx (of any version) * with the #usable? method, or find the version of SDL_gfx that Rubygame was * compiled against with the #version method. * */ void Rubygame_Init_Transform() { #if 0 /* Pretend to define Rubygame module, so RDoc knows about it: */ mRubygame = rb_define_module("Rubygame"); #endif mTrans = rb_define_module_under(mRubygame,"Transform"); rb_define_module_function(mTrans,"flip",rbgm_transform_flip,-1); rb_define_module_function(mTrans,"version",rbgm_transform_version,0); #ifdef HAVE_SDL_ROTOZOOM_H rb_define_module_function(mTrans,"usable?",rbgm_usable,0); // in rubygame.c rb_define_module_function(mTrans,"rotozoom",rbgm_transform_rotozoom,-1); rb_define_module_function(mTrans,"rotozoom_size",rbgm_transform_rzsize,-1); rb_define_module_function(mTrans,"zoom",rbgm_transform_zoom,-1); rb_define_module_function(mTrans,"zoom_size",rbgm_transform_zoomsize,-1); #else /* HAVE_SDL_ROTOZOOM_H */ /* SDL_TTF is NOT available, so we will provide dummy functions. */ rb_define_module_function(mTrans,"usable?",rbgm_unusable,0); rb_define_module_function(mTrans,"rotozoom",rbgm_dummy,-1); rb_define_module_function(mTrans,"rotozoom_size",rbgm_dummy,-1); rb_define_module_function(mTrans,"zoom",rbgm_dummy,-1); rb_define_module_function(mTrans,"zoom_size",rbgm_dummy,-1); #endif /* HAVE_SDL_ROTOZOOM_H */ }